defmodule GCloudex.CloudSQL.Impl do @moduledoc """ """ defmacro __using__(:cloud_sql) do quote do use GCloudex.CloudSQL.Request @project_id GCloudex.get_project_id @instance_ep "https://www.googleapis.com/sql/v1beta4/projects/#{@project_id}/instances" @flag_ep "https://www.googleapis.com/sql/v1beta4/flags" @operation_ep "https://www.googleapis.com/sql/v1beta4/projects/#{@project_id}/operations" @tiers_ep "https://www.googleapis.com/sql/v1beta4/projects/#{@project_id}/tiers" ################# ### Instances ### ################# @doc """ List instances from the project. """ @spec list_instances() :: HTTPResponse.t def list_instances do request :get, @instance_ep, [], "" end @doc """ Retrieves a resource containing information about the given Cloud SQL 'instance'. """ @spec get_instance(instance :: binary) :: HTTPResponse.t def get_instance(instance) do request_query :get, @instance_ep, [], "", instance end @doc """ Creates a new Cloud SQL instance with the specified 'name', 'settings', 'optional_properties' and with the given 'tier' unless already passed through 'settings'. The 'settings' parameter is meant to have the settings JSON nested object while 'optional_properties' is meant to have other non-required request fields like the 'replicaConfiguration' JSON nested object field. The 'settings' map will be nested into 'optional_properties' to match the API's request structure like in the following example: optional_properties = %{ name: 'name', region: someRegion, settings: %{ tier: 'tier', other_key_1: someValue, other_key_2: someValue }, optional_prop_1: someValue, optional_prop_2: someValue } TODO: Re-evaluate how the optional fields should be passed. """ @spec insert_instance(name :: binary, optional_properties :: map, settings :: map, tier :: binary) :: HTTPResponse.t def insert_instance(name, optional_properties, settings, tier) do settings = settings |> Map.put_new(:tier, tier) body = %{ name: name, settings: settings } |> Map.merge(optional_properties) |> Poison.encode! request :post, @instance_ep, [{"Content-Type", "application/json"}], body end @doc """ Deletes the given 'instance' from the project. """ @spec delete_instance(instance :: binary) :: HTTPResponse.t def delete_instance(instance) do request_query :delete, @instance_ep, [], "", instance end @doc """ Clones the given 'instance' and gives the new instance the chosen 'dest_name' and the given 'bin_log_file' and 'bin_log_pos'. """ @spec clone_instance(instance :: binary, dest_name :: binary, bin_log_file :: binary, bin_log_pos :: binary) :: HTTPResponse.t def clone_instance(instance, dest_name, bin_log_file, bin_log_pos) do bin_log_coords = %{ "kind" => "sql#binLogCoordinates", "binLogFileName" => bin_log_file, "binLogPosition" => bin_log_pos } clone_context = %{ "kind" => "sql#cloneContext", "destinationInstanceName" => dest_name, "binLogCoordinates" => bin_log_coords } body = %{ "cloneContext" => clone_context } |> Poison.encode! request_query( :post, @instance_ep, [{"Content-Type", "application/json"}], body, instance <> "/clone" ) end @doc """ Restarts the given 'instance'. """ @spec restart_instance(instance :: binary) :: HTTPResponse.t def restart_instance(instance) do request_query( :post, @instance_ep, [{"Content-Type", "application/json"}], "", instance <> "/" <> "restart" ) end @doc """ Starts the replication in the read replica 'instance'. """ @spec start_replica(instance :: binary) :: HTTPResponse.t def start_replica(instance) do request_query( :post, @instance_ep, [{"Content-Type", "application/json"}], "", instance <> "/" <> "startReplica" ) end @doc """ Stops the replication in the read replica 'instance'. """ @spec stop_replica(instance :: binary) :: HTTPResponse.t def stop_replica(instance) do request_query( :post, @instance_ep, [{"Content-Type", "application/json"}], "", instance <> "/" <> "stopReplica" ) end @doc """ Promotes the read replica 'instance' to be a stand-alone Cloud SQL instance. """ @spec promote_replica(instance :: binary) :: HTTPResponse.t def promote_replica(instance) do request_query( :post, @instance_ep, [{"Content-Type", "application/json"}], "", instance <> "/" <> "promoteReplica" ) end @doc """ Failover the 'instance' to its failover replica instance with the specified 'settings_version'. """ @spec failover_instance(instance :: binary, settings_version :: number) :: HTTPResponse.t def failover_instance(instance, settings_version) do failover = %{ "kind" => "sql#failoverContext", "settingsVersion" => settings_version } body = %{ "failoverContext" => failover } |> Poison.encode! request_query( :post, @instance_ep, [{"Content-Type", "application/json"}], body, instance <> "/" <> "failover" ) end @doc """ Deletes all client certificates and generates a new server SSL certificate for the 'instance'. For First Generation instances, the changes do not take effect until the instance is restarted. For Second Generation instances, the changes are immediate; all existing connections to the instance are broken. """ @spec reset_ssl_config(instance :: binary) :: HTTPResponse.t def reset_ssl_config(instance) do request_query( :post, @instance_ep, [{"Content-Type", "application/json"}], "", instance <> "/" <> "resetSslConfig" ) end ################# ### Databases ### ################# @doc """ Lists databases in the specified Cloud SQL 'instance'. """ @spec list_databases(instance :: binary) :: HTTPResponse.t def list_databases(instance) do request_query :get, @instance_ep, [], "", instance <> "/databases" end @doc """ Creates a new database inside the specified Cloud SQL 'instance' with the given 'name'. """ @spec insert_database(instance :: binary, name :: binary) :: HTTPResponse.t def insert_database(instance, name) do body = %{ "instance" => instance, "name" => name, "project" => @project_id } |> Poison.encode! request_query( :post, @instance_ep, [{"Content-Type", "application/json"}], body, instance <> "/databases" ) end @doc """ Retrieves a resource containing information about the 'database' inside a Cloud SQL 'instance'. """ @spec get_database(instance :: binary, database :: binary) :: HTTPResponse.t def get_database(instance, database) do request_query( :get, @instance_ep, [], "", instance <> "/databases" <> "/" <> database ) end @doc """ Deletes the 'database' from the Cloud SQL 'instance'. """ @spec delete_database(instance :: binary, database :: binary) :: HTTPResponse.t def delete_database(instance, database) do request_query( :delete, @instance_ep, [], "", instance <> "/databases" <> "/" <> database ) end @doc """ Updates a resource containing information about a 'database' inside a Cloud SQL 'instance' using patch semantics. The 'db_resource' must follow the description of Database Resources in: https://cloud.google.com/sql/docs/admin-api/v1beta4/databases#resource """ @spec patch_database(instance :: binary, database :: binary, db_resource :: Map.t) :: HTTPResponse.t def patch_database(instance, database, db_resource) do body = db_resource |> Poison.encode! request_query( :patch, @instance_ep, [{"Content-Type", "application/json"}], body, instance <> "/databases" <> "/" <> database ) end @doc """ Updates a resource containing information about a 'database' inside a Cloud SQL 'instance'. The 'update_map' must be a Map. """ @spec update_database(instance :: binary, database :: binary, db_resource :: Map.t) :: HTTPResponse.t def update_database(instance, database, db_resource) do body = db_resource |> Poison.encode! request_query( :put, @instance_ep, [{"Content-Type", "application/json"}], body, instance <> "/databases" <> "/" <> database ) end ############# ### Flags ### ############# @doc """ List all available database flags for Google the Cloud SQL 'instance'. """ @spec list_flags :: HTTPResponse.t def list_flags do request :get, @flag_ep, [], "" end ################## ### Operations ### ################## @doc """ Lists all instance operations that have been performed on the given Cloud SQL 'instance' in the reverse chronological order of the start time. """ @spec list_operations(instance :: binary) :: HTTPResponse.t def list_operations(instance) do request( :get, @operation_ep <> "?" <> "instance=#{instance}", [], "" ) end @doc """ Retrieves the instance operation with 'operation_id' that has been performed on an instance. """ @spec get_operation(operation_id :: binary) :: HTTPResponse.t def get_operation(operation_id) do request_query( :get, @operation_ep, [], "", operation_id ) end ############# ### Tiers ### ############# @doc """ Lists all available service tiers for Google Cloud SQL, for example D1, D2. """ @spec list_tiers :: HTTPResponse.t def list_tiers do request :get, @tiers_ep, [], "" end ############# ### Users ### ############# @doc """ Lists users in the specified Cloud SQL 'instance'. """ @spec list_users(instance :: binary) :: HTTPResponse.t def list_users(instance) do request_query( :get, @instance_ep, [], "", instance <> "/" <> "users" ) end @doc """ Creates a new user in a Cloud SQL 'instance' with the given 'name and 'password'. The authorized host to connect can be set through 'host' (defaults to '% (any host)'). """ @spec insert_user(instance :: binary, name :: binary, password :: binary, host :: binary) :: HTTPResponse.t def insert_user(instance, name, password, host \\ "%") do body = %{ "name" => name, "password" => password, "host" => host, "project" => @project_id, "instance" => instance } |> Poison.encode! request_query( :post, @instance_ep, [{"Content-Type", "application/json"}], body, instance <> "/" <> "users" ) end @doc """ Updates an existing user in a Cloud SQL 'instance' with the given 'host', 'name' and 'password'. """ @spec update_user(instance :: binary, host :: binary, name :: binary, password :: binary) :: HTTPResponse.t def update_user(instance, host, name, password) do body = %{"password" => password} |> Poison.encode! query = "#{instance}/users?host=#{host}&name=#{name}" request_query( :put, @instance_ep, [{"Content-Type", "application/json"}], body, query ) end @doc """ Deletes a user with the given 'host' and 'name' from the Cloud SQL 'instance'. """ @spec delete_user(instance :: binary, host :: binary, name :: binary) :: HTTPResponse.t def delete_user(instance, host, name) do query = "#{instance}/users?host=#{host}&name=#{name}" request_query( :delete, @instance_ep, [], "", query ) end ################### ### Backup Runs ### ################### @doc """ Lists all backup runs associated with a given 'instance' and configuration in the reverse chronological order of the backup initiation time. """ @spec list_backup_runs(instance :: binary) :: HTTPResponse.t def list_backup_runs(instance) do request_query( :get, @instance_ep, [], "", instance <> "/" <> "backupRuns" ) end @doc """ Retrieves a resource containing information about a backup run with the ID of 'run_id' and belonging to the given 'instance'. """ @spec get_backup_run(instance :: binary, run_id :: binary | number) :: HTTPResponse.t def get_backup_run(instance, run_id) do gbr instance, run_id end defp gbr(instance, run_id) when is_integer(run_id) do request_query( :get, @instance_ep, [], "", instance <> "/" <> "backupRuns" <> "/" <> Integer.to_string(run_id) ) end defp gbr(instance, run_id) do request_query( :get, @instance_ep, [], "", instance <> "/" <> "backupRuns" <> "/" <> run_id ) end @doc """ Deletes the backup taken by a backup run with ID 'run_id' and belonging to the given 'instance'. """ @spec delete_backup_run(instance :: binary, run_id :: binary | number) :: HTTPResponse.t def delete_backup_run(instance, run_id) do dbr instance, run_id end defp dbr(instance, run_id) when is_integer(run_id) do request_query( :delete, @instance_ep, [], "", instance <> "/" <> "backupRuns" <> "/" <> Integer.to_string(run_id) ) end defp dbr(instance, run_id) do request_query( :delete, @instance_ep, [], "", instance <> "/" <> "backupRuns" <> "/" <> run_id ) end ######################## ### SSL Certificates ### ######################## @doc """ Lists all of the current SSL certificates for the 'instance'. """ @spec list_ssl_certs(instance :: binary) :: HTTPResponse.t def list_ssl_certs(instance) do request_query( :get, @instance_ep, [], "", instance <> "/" <> "sslCerts" ) end @doc """ Retrieves a particular SSL certificate belonging to the given 'instance' and with the provided 'sh1_fingerprint'. Does not include the private key (required for usage). The private key must be saved from the response to initial creation. """ @spec get_ssl_cert(instance :: binary, sh1_fingerprint :: binary) :: HTTPResponse.t def get_ssl_cert(instance, sha1_fingerprint) do request_query( :get, @instance_ep, [], "", instance <> "/" <> "sslCerts" <> "/" <> sha1_fingerprint ) end @doc """ Creates an SSL certificate for the given 'instance' and gives it the name specified in 'common_name' and returns it along with the private key and server certificate authority. For First Generation instances, the new certificate does not take effect until the instance is restarted. """ @spec insert_ssl_cert(instance :: binary, common_name :: binary) :: HTTPResponse.t def insert_ssl_cert(instance, common_name) do body = %{"commonName" => common_name} |> Poison.encode! request_query( :post, @instance_ep, [{"Content-Type", "application/json"}], body, instance <> "/" <> "sslCerts" ) end @doc """ Deletes the SSL certificate with the given 'sha1_fingerprint' from the specified 'instance'. """ @spec delete_ssl_cert(instance :: binary, sha1_fingerprint :: binary) :: HTTPResponse.t def delete_ssl_cert(instance, sha1_fingerprint) do request_query( :delete, @instance_ep, [], "", instance <> "/" <> "sslCerts" <> "/" <> sha1_fingerprint ) end @doc """ Generates a short-lived X509 certificate containing the provided 'public_key' and signed by a private key specific to the target 'instance'. Users may use the certificate to authenticate as themselves when connecting to the database. """ @spec create_ephemeral_ssl_cert(instance :: binary, public_key :: binary) :: HTTPResponse.t def create_ephemeral_ssl_cert(instance, public_key) do body = %{"public_key" => public_key} |> Poison.encode! request_query( :post, @instance_ep, [{"Content-Type", "application/json"}], body, instance <> "/" <> "createEphemeral" ) end end end end