diff --git a/lib/fog/bin/google.rb b/lib/fog/bin/google.rb index 61eb50d28..8ce339016 100644 --- a/lib/fog/bin/google.rb +++ b/lib/fog/bin/google.rb @@ -6,6 +6,8 @@ module Google # deviates from other bin stuff to accomodate gem Fog::Compute::Google when :storage Fog::Storage::Google + when :sql + Fog::Google::SQL else raise ArgumentError, "Unsupported #{self} service: #{key}" end @@ -20,6 +22,8 @@ module Google # deviates from other bin stuff to accomodate gem when :compute Fog::Logger.warning("Google[:compute] is not recommended, use Compute[:google] for portability") Fog::Compute.new(:provider => 'Google') + when :sql + Fog::Google::SQL.new else raise ArgumentError, "Unrecognized service: #{key.inspect}" end diff --git a/lib/fog/google.rb b/lib/fog/google.rb index 1001adea0..c9f29b69a 100644 --- a/lib/fog/google.rb +++ b/lib/fog/google.rb @@ -1,2 +1,3 @@ require 'fog/google/compute' require 'fog/google/storage' +require 'fog/google/sql' diff --git a/lib/fog/google/core.rb b/lib/fog/google/core.rb index 17a1b35e6..d1dd93b40 100644 --- a/lib/fog/google/core.rb +++ b/lib/fog/google/core.rb @@ -7,6 +7,7 @@ module Fog service(:compute, 'Compute') service(:storage, 'Storage') + service(:sql, 'SQL') class Mock def self.etag @@ -18,5 +19,160 @@ module Fog rand(max).to_s(16) end end + + module Shared + attr_reader :project, :api_version, :api_url + + ## + # Initializes shared attributes + # + # @param [String] project Google Cloud Project + # @param [String] api_version Google API version + # @param [String] base_url Google API base url + # @return [void] + def shared_initialize(project, api_version, base_url) + @project = project + @api_version = api_version + @api_url = base_url + api_version + '/projects/' + end + + ## + # Initializes the Google API Client + # + # @param [Hash] options Google API options + # @option options [String] :google_client_email A @developer.gserviceaccount.com email address to use + # @option options [String] :google_key_location The location of a pkcs12 key file + # @option options [String] :google_key_string The content of the pkcs12 key file + # @option options [String] :google_api_scope_url The access scope URLs + # @option options [String] :app_name The app name to set in the user agent + # @option options [String] :app_version The app version to set in the user agent + # @option options [Google::APIClient] :google_client Existing Google API Client + # @return [Google::APIClient] Google API Client + # @raises [ArgumentError] If there is any missing argument + def initialize_google_client(options) + # NOTE: loaded here to avoid requiring this as a core Fog dependency + begin + require 'google/api_client' + rescue LoadError => error + Fog::Logger.warning('Please install the google-api-client gem before using this provider') + raise error + end + + # User can provide an existing Google API Client + client = options[:google_client] + return client unless client.nil? + + # Validate required arguments + unless options[:google_client_email] + raise ArgumentError.new('Missing required arguments: google_client_email') + end + + if options[:google_key_location] + google_key = File.expand_path(options[:google_key_location]) + elsif options[:google_key_string] + google_key = options[:google_key_string] + else + raise ArgumentError.new('Missing required arguments: google_key_location or google_key_string') + end + + unless options[:google_api_scope_url] + raise ArgumentError.new('Missing required arguments: google_api_scope_url') + end + + # Create a new Google API Client + self.new_pk12_google_client( + options[:google_client_email], + google_key, + options[:google_api_scope_url], + options[:app_name], + options[:app_version] + ) + end + + ## + # Create a Google API Client with a user email and a pkcs12 key + # + # @param [String] google_client_email A @developer.gserviceaccount.com email address to use + # @param [String] google_key An absolute location to a pkcs12 key file or the content of the file itself + # @param [String] google_api_scope_url Access scope URLs + # @param [String] app_name The app name to set in the user agent + # @param [String] app_version The app version to set in the user agent + # @return [Google::APIClient] Google API Client + def new_pk12_google_client(google_client_email, google_key, google_api_scope_url, app_name = nil, app_version = nil) + application_name = app_name.nil? ? 'fog' : "#{app_name}/#{app_version || '0.0.0'} fog" + api_client_options = { + :application_name => application_name, + :application_version => Fog::VERSION, + } + client = ::Google::APIClient.new(api_client_options) + + client.authorization = Signet::OAuth2::Client.new( + { + :audience => 'https://accounts.google.com/o/oauth2/token', + :auth_provider_x509_cert_url => 'https://www.googleapis.com/oauth2/v1/certs', + :client_x509_cert_url => "https://www.googleapis.com/robot/v1/metadata/x509/#{google_client_email}", + :issuer => google_client_email, + :scope => google_api_scope_url, + :signing_key => ::Google::APIClient::KeyUtils.load_from_pkcs12(google_key, 'notasecret'), + :token_credential_uri => 'https://accounts.google.com/o/oauth2/token', + } + ) + client.authorization.fetch_access_token! + + client + end + + ## + # Executes a request and wraps it in a result object + # + # @param [Google::APIClient::Method] api_method The method object or the RPC name of the method being executed + # @param [Hash] parameters The parameters to send to the method + # @param [Hash] body_object The body object of the request + # @return [Excon::Response] The result from the API + def request(api_method, parameters, body_object = nil) + client_parms = { + :api_method => api_method, + :parameters => parameters, + } + client_parms[:body_object] = body_object if body_object + + result = @client.execute(client_parms) + + build_excon_response(result.body.nil? || result.body.empty? ? nil : Fog::JSON.decode(result.body), result.status) + end + + ## + # Builds an Excon response + # + # @param [Hash] Response body + # @param [Integer] Response status + # @return [Excon::Response] Excon response + def build_excon_response(body, status = 200) + response = Excon::Response.new(:body => body, :status => status) + if body && body.has_key?('error') + msg = 'Google Cloud did not return an error message' + + if body['error'].kind_of?(Hash) + response.status = body['error']['code'] + if body['error'].has_key?('errors') + msg = body['error']['errors'].map{ |error| error['message'] }.join(', ') + elsif body['error'].has_key?('message') + msg = body['error']['message'] + end + elsif body['error'].kind_of?(Array) + msg = body['error'].map{ |error| error['code'] }.join(', ') + end + + case response.status + when 404 + raise Fog::Errors::NotFound.new(msg) + else + raise Fog::Errors::Error.new(msg) + end + end + + response + end + end end end diff --git a/lib/fog/google/models/sql/tier.rb b/lib/fog/google/models/sql/tier.rb new file mode 100644 index 000000000..2374b3150 --- /dev/null +++ b/lib/fog/google/models/sql/tier.rb @@ -0,0 +1,20 @@ +require 'fog/core/model' + +module Fog + module Google + class SQL + ## + # A Google Cloud SQL service tier resource + # + # @see https://developers.google.com/cloud-sql/docs/admin-api/v1beta3/tiers + class Tier < Fog::Model + identity :tier + + attribute :disk_quota, :aliases => 'DiskQuota' + attribute :kind + attribute :ram, :aliases => 'RAM' + attribute :region + end + end + end +end diff --git a/lib/fog/google/models/sql/tiers.rb b/lib/fog/google/models/sql/tiers.rb new file mode 100644 index 000000000..d94e1c190 --- /dev/null +++ b/lib/fog/google/models/sql/tiers.rb @@ -0,0 +1,21 @@ +require 'fog/core/collection' +require 'fog/google/models/sql/tier' + +module Fog + module Google + class SQL + class Tiers < Fog::Collection + model Fog::Google::SQL::Tier + + ## + # Lists all available service tiers + # + # @return [Array] List of tiers + def all + data = service.list_tiers.body['items'] || [] + load(data) + end + end + end + end +end diff --git a/lib/fog/google/requests/sql/list_tiers.rb b/lib/fog/google/requests/sql/list_tiers.rb new file mode 100644 index 000000000..cd6d9a9f6 --- /dev/null +++ b/lib/fog/google/requests/sql/list_tiers.rb @@ -0,0 +1,82 @@ +module Fog + module Google + class SQL + ## + # Lists all available service tiers for Google Cloud SQL + # + # @see https://developers.google.com/cloud-sql/docs/admin-api/v1beta3/tiers/list + + class Real + def list_tiers + api_method = @sql.tiers.list + parameters = { + 'project' => @project, + } + + request(api_method, parameters) + end + end + + class Mock + def list_tiers + body = { + 'kind' => 'sql#tiersList', + 'items' => [ + { + 'kind' => 'sql#tier', + 'tier' => 'D0', + 'RAM' => '134217728', + 'DiskQuota' => '268435456000', + 'region' => ['us-central', 'europe-west1', 'asia-east1'], + }, + { + 'kind' => 'sql#tier', + 'tier' => 'D1', + 'RAM' => '536870912', + 'DiskQuota' => '268435456000', + 'region' => ['us-central', 'europe-west1', 'asia-east1'], + }, + { + 'kind' => 'sql#tier', + 'tier' => 'D2', + 'RAM' => '1073741824', + 'DiskQuota' => '268435456000', + 'region' => ['us-central', 'europe-west1', 'asia-east1'], + }, + { + 'kind' => 'sql#tier', + 'tier' => 'D4', + 'RAM' => '2147483648', + 'DiskQuota' => '268435456000', + 'region' => ['us-central', 'europe-west1', 'asia-east1'], + }, + { + 'kind' => 'sql#tier', + 'tier' => 'D8', + 'RAM' => '4294967296', + 'DiskQuota' => '268435456000', + 'region' => ['us-central', 'europe-west1', 'asia-east1'], + }, + { + 'kind' => 'sql#tier', + 'tier' => 'D16', + 'RAM' => '8589934592', + 'DiskQuota' => '268435456000', + 'region' => ['us-central', 'europe-west1', 'asia-east1'], + }, + { + 'kind' => 'sql#tier', + 'tier' => 'D32', + 'RAM' => '17179869184', + 'DiskQuota' => '268435456000', + 'region' => ['us-central'], + }, + ] + } + + build_excon_response(body) + end + end + end + end +end diff --git a/lib/fog/google/sql.rb b/lib/fog/google/sql.rb new file mode 100644 index 000000000..57860bc66 --- /dev/null +++ b/lib/fog/google/sql.rb @@ -0,0 +1,81 @@ +require 'fog/google/core' + +module Fog + module Google + class SQL < Fog::Service + requires :google_project + recognizes :google_client_email, :google_key_location, :google_key_string, :google_client, + :app_name, :app_version + + GOOGLE_SQL_API_VERSION = 'v1beta3' + GOOGLE_SQL_BASE_URL = 'https://www.googleapis.com/sql/' + GOOGLE_SQL_API_SCOPE_URLS = %w(https://www.googleapis.com/auth/sqlservice.admin + https://www.googleapis.com/auth/cloud-platform) + + ## + # MODELS + model_path 'fog/google/models/sql' + + # Tier + model :tier + collection :tiers + + ## + # REQUESTS + request_path 'fog/google/requests/sql' + + # Tier + request :list_tiers + + class Mock + include Fog::Google::Shared + + def initialize(options) + shared_initialize(options[:google_project], GOOGLE_SQL_API_VERSION, GOOGLE_SQL_BASE_URL) + end + + def self.data + @data ||= Hash.new do |hash, key| + hash[key] = { + :backup_runs => {}, + :instances => {}, + :operations => {}, + :ssl_certs => {}, + } + end + end + + def self.reset + @data = nil + end + + def data + self.class.data[project] + end + + def reset_data + self.class.data.delete(project) + end + + def random_operation + "operation-#{Fog::Mock.random_numbers(13)}-#{Fog::Mock.random_hex(13)}-#{Fog::Mock.random_hex(8)}" + end + end + + class Real + include Fog::Google::Shared + + attr_accessor :client + attr_reader :sql + + def initialize(options) + shared_initialize(options[:google_project], GOOGLE_SQL_API_VERSION, GOOGLE_SQL_BASE_URL) + options.merge!(:google_api_scope_url => GOOGLE_SQL_API_SCOPE_URLS.join(' ')) + + @client = initialize_google_client(options) + @sql = @client.discovered_api('sqladmin', api_version) + end + end + end + end +end diff --git a/tests/google/models/sql/tiers_tests.rb b/tests/google/models/sql/tiers_tests.rb new file mode 100644 index 000000000..c129930e4 --- /dev/null +++ b/tests/google/models/sql/tiers_tests.rb @@ -0,0 +1,12 @@ +Shindo.tests('Fog::Google[:sql] | tiers model', ['google']) do + @tiers = Fog::Google[:sql].tiers + + tests('success') do + + tests('#all').succeeds do + @tiers.all + end + + end + +end diff --git a/tests/google/requests/sql/tier_tests.rb b/tests/google/requests/sql/tier_tests.rb new file mode 100644 index 000000000..af83ae2ed --- /dev/null +++ b/tests/google/requests/sql/tier_tests.rb @@ -0,0 +1,25 @@ +Shindo.tests('Fog::Google[:sql] | tier requests', ['google']) do + @sql = Fog::Google[:sql] + + @get_tier_format = { + 'tier' => String, + 'DiskQuota' => String, + 'kind' => String, + 'RAM' => String, + 'region' => Array, + } + + @list_tiers_format = { + 'kind' => String, + 'items' => [@get_tier_format], + } + + tests('success') do + + tests('#list_tiers').formats(@list_tiers_format) do + @sql.list_tiers.body + end + + end + +end