File: shared.rb

package info (click to toggle)
ruby-fog-google 1.19.0-3
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 2,568 kB
  • sloc: ruby: 16,775; makefile: 3
file content (253 lines) | stat: -rw-r--r-- 10,644 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
require "google-cloud-env"

module Fog
  module Google
    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/"
        # google-cloud-env allows us to figure out which GCP runtime we're running in and query metadata
        # e.g. whether we're running in GCE/GKE/AppEngine or what region the instance is running in
        @google_cloud_env = ::Google::Cloud::Env.get
      end

      ##
      # Initializes the Google API Client
      #
      # @param [Hash] options Google API options
      # @option options [Bool]   :google_application_default Explicitly use application default credentials
      # @option options [Google::Auth|Signet] :google_auth Manually created authorization to use
      # @option options [String] :google_json_key_location The location of a JSON key file
      # @option options [String] :google_json_key_string The content of the JSON 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 [Hash] :google_client_options A hash to send additional options to 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
          # TODO: google-api-client is in gemspec now, re-assess if this initialization logic is still needed
          require "google/apis/monitoring_#{Fog::Google::Monitoring::GOOGLE_MONITORING_API_VERSION}"
          require "google/apis/compute_#{Fog::Compute::Google::GOOGLE_COMPUTE_API_VERSION}"
          require "google/apis/dns_#{Fog::DNS::Google::GOOGLE_DNS_API_VERSION}"
          require "google/apis/pubsub_#{Fog::Google::Pubsub::GOOGLE_PUBSUB_API_VERSION}"
          require "google/apis/sqladmin_#{Fog::Google::SQL::GOOGLE_SQL_API_VERSION}"
          require "google/apis/storage_#{Fog::Storage::GoogleJSON::GOOGLE_STORAGE_JSON_API_VERSION}"
          require "google/apis/iamcredentials_#{Fog::Storage::GoogleJSON::GOOGLE_STORAGE_JSON_IAM_API_VERSION}"
          require "googleauth"
        rescue LoadError => error
          Fog::Errors::Error.new("Please install the google-api-client (>= 0.9) gem before using this provider")
          raise error
        end

        validate_client_options(options)

        application_name = "fog"
        unless options[:app_name].nil?
          application_name = "#{options[:app_name]}/#{options[:app_version] || '0.0.0'} fog"
        end

        ::Google::Apis::ClientOptions.default.application_name = application_name
        ::Google::Apis::ClientOptions.default.application_version = Fog::Google::VERSION

        if ENV["DEBUG"]
          ::Google::Apis.logger = ::Logger.new(::STDERR)
          ::Google::Apis.logger.level = ::Logger::DEBUG
        end

        auth = nil

        if options[:google_json_key_location] || options[:google_json_key_string]
          auth = process_key_auth(options)
        elsif options[:google_auth]
          auth = options[:google_auth]
        elsif options[:google_application_default]
          auth = process_application_default_auth(options)
        else
          auth = process_fallback_auth(options)
        end

        ::Google::Apis::RequestOptions.default.authorization = auth
        auth
      end

      ##
      # Applies given options to the client instance
      #
      # @param [Google::Apis::Core::BaseService] service API service client instance
      # @param [Hash] options (all ignored a.t.m., except :google_client_options)
      # @return [void]
      def apply_client_options(service, options = {})
        google_client_options = options[:google_client_options]
        return if google_client_options.nil? || google_client_options.empty?
        (service.client_options.members & google_client_options.keys).each do |option|
          service.client_options[option] = google_client_options[option]
        end
      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, media = nil)
        client_parms = {
          :api_method => api_method,
          :parameters => parameters
        }
        # The Google API complains when given null values for enums, so just don't pass it any null fields
        # XXX It may still balk if we have a nested object, e.g.:
        #   {:a_field => "string", :a_nested_field => { :an_empty_nested_field => nil } }
        client_parms[:body_object] = body_object.reject { |_k, v| v.nil? } if body_object
        client_parms[:media] = media if media

        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.key?("error")
          msg = "Google Cloud did not return an error message"

          if body["error"].is_a?(Hash)
            response.status = body["error"]["code"]
            if body["error"].key?("errors")
              msg = body["error"]["errors"].map { |error| error["message"] }.join(", ")
            elsif body["error"].key?("message")
              msg = body["error"]["message"]
            end
          elsif body["error"].is_a?(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

      private

      # Helper method to process application default authentication
      #
      # @param [Hash]  options - client options hash
      # @return [Google::Auth::DefaultCredentials] - google auth object
      def process_application_default_auth(options)
        ::Google::Auth.get_application_default(options[:google_api_scope_url])
      end

      # Helper method to process fallback authentication
      # Current fallback is application default authentication
      #
      # @param [Hash]  options - client options hash
      # @return [Google::Auth::DefaultCredentials] - google auth object
      def process_fallback_auth(options)
        Fog::Logger.warning(
          "Didn't detect any client auth settings, " \
          "trying to fall back to application default credentials..."
        )
        begin
          return process_application_default_auth(options)
        rescue
          raise Fog::Errors::Error.new(
            "Fallback auth failed, could not configure authentication for Fog client.\n" \
              "Check your auth options, must be one of:\n" \
              "- :google_json_key_location,\n" \
              "- :google_json_key_string,\n" \
              "- :google_auth,\n" \
              "- :google_application_default,\n" \
              "If credentials are valid - please, file a bug to fog-google." \
          )
        end
      end

      # Helper method to process key authentication
      #
      # @param [Hash]  options - client options hash
      # @return [Google::Auth::ServiceAccountCredentials] - google auth object
      def process_key_auth(options)
        if options[:google_json_key_location]
          json_key = File.read(File.expand_path(options[:google_json_key_location]))
        elsif options[:google_json_key_string]
          json_key = options[:google_json_key_string]
        end

        validate_json_credentials(json_key)

        ::Google::Auth::ServiceAccountCredentials.make_creds(
          :json_key_io => StringIO.new(json_key),
          :scope => options[:google_api_scope_url]
        )
      end

      # Helper method to sort out deprecated and missing auth options
      #
      # @param [Hash]  options - client options hash
      def validate_client_options(options)
        # Users can no longer provide their own clients due to rewrite of auth
        # in https://github.com/google/google-api-ruby-client/ version 0.9.
        if options[:google_client]
          raise ArgumentError.new("Deprecated argument no longer works: google_client")
        end

        # They can also no longer use pkcs12 files, because Google's new auth
        # library doesn't support them either.
        if options[:google_key_location]
          raise ArgumentError.new("Deprecated auth method no longer works: google_key_location")
        end
        if options[:google_key_string]
          raise ArgumentError.new("Deprecated auth method no longer works: google_key_string")
        end

        # Google client email option is no longer needed
        if options[:google_client_email]
          Fog::Logger.deprecation("Argument no longer needed for auth: google_client_email")
        end

        # Validate required arguments
        unless options[:google_api_scope_url]
          raise ArgumentError.new("Missing required arguments: google_api_scope_url")
        end
      end

      # Helper method to checks whether the necessary fields are present in
      # JSON key credentials
      #
      # @param [String]  json_key - Google json auth key string
      def validate_json_credentials(json_key)
        json_key_hash = Fog::JSON.decode(json_key)

        unless json_key_hash.key?("client_email") || json_key_hash.key?("private_key")
          raise ArgumentError.new("Invalid Google JSON key")
        end
      end
    end
  end
end