fog-google 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/README.md +7 -3
  4. data/examples/pubsub/subscriptions.rb +54 -0
  5. data/examples/pubsub/topics.rb +33 -0
  6. data/fog-google.gemspec +2 -2
  7. data/lib/fog/compute/google.rb +4 -884
  8. data/lib/fog/compute/google/mock.rb +871 -0
  9. data/lib/fog/compute/google/real.rb +22 -0
  10. data/lib/fog/dns/google.rb +3 -42
  11. data/lib/fog/dns/google/mock.rb +33 -0
  12. data/lib/fog/dns/google/real.rb +19 -0
  13. data/lib/fog/google.rb +14 -208
  14. data/lib/fog/google/mock.rb +14 -0
  15. data/lib/fog/google/models/pubsub/received_message.rb +40 -0
  16. data/lib/fog/google/models/pubsub/subscription.rb +86 -0
  17. data/lib/fog/google/models/pubsub/subscriptions.rb +32 -0
  18. data/lib/fog/google/models/pubsub/topic.rb +72 -0
  19. data/lib/fog/google/models/pubsub/topics.rb +31 -0
  20. data/lib/fog/google/monitoring.rb +3 -45
  21. data/lib/fog/google/monitoring/mock.rb +35 -0
  22. data/lib/fog/google/monitoring/real.rb +20 -0
  23. data/lib/fog/google/pubsub.rb +59 -0
  24. data/lib/fog/google/pubsub/mock.rb +34 -0
  25. data/lib/fog/google/pubsub/real.rb +20 -0
  26. data/lib/fog/google/requests/pubsub/acknowledge_subscription.rb +46 -0
  27. data/lib/fog/google/requests/pubsub/create_subscription.rb +57 -0
  28. data/lib/fog/google/requests/pubsub/create_topic.rb +36 -0
  29. data/lib/fog/google/requests/pubsub/delete_subscription.rb +28 -0
  30. data/lib/fog/google/requests/pubsub/delete_topic.rb +29 -0
  31. data/lib/fog/google/requests/pubsub/get_subscription.rb +44 -0
  32. data/lib/fog/google/requests/pubsub/get_topic.rb +41 -0
  33. data/lib/fog/google/requests/pubsub/list_subscriptions.rb +39 -0
  34. data/lib/fog/google/requests/pubsub/list_topics.rb +33 -0
  35. data/lib/fog/google/requests/pubsub/publish_topic.rb +61 -0
  36. data/lib/fog/google/requests/pubsub/pull_subscription.rb +77 -0
  37. data/lib/fog/google/shared.rb +191 -0
  38. data/lib/fog/google/sql.rb +3 -50
  39. data/lib/fog/google/sql/mock.rb +40 -0
  40. data/lib/fog/google/sql/real.rb +20 -0
  41. data/lib/fog/google/version.rb +1 -1
  42. data/lib/fog/storage/google_json.rb +4 -99
  43. data/lib/fog/storage/google_json/mock.rb +18 -0
  44. data/lib/fog/storage/google_json/real.rb +64 -0
  45. data/lib/fog/storage/google_json/utils.rb +32 -0
  46. data/lib/fog/storage/google_xml.rb +4 -260
  47. data/lib/fog/storage/google_xml/mock.rb +102 -0
  48. data/lib/fog/storage/google_xml/models/file.rb +14 -4
  49. data/lib/fog/storage/google_xml/real.rb +106 -0
  50. data/lib/fog/storage/google_xml/utils.rb +66 -0
  51. data/tests/models/pubsub/received_message_tests.rb +18 -0
  52. data/tests/models/pubsub/subscription_tests.rb +26 -0
  53. data/tests/models/pubsub/subscriptions_tests.rb +33 -0
  54. data/tests/models/pubsub/topic_tests.rb +18 -0
  55. data/tests/models/pubsub/topics_tests.rb +27 -0
  56. metadata +50 -14
@@ -0,0 +1,36 @@
1
+ module Fog
2
+ module Google
3
+ class Pubsub
4
+ class Real
5
+ # Create a topic on the remote service.
6
+ #
7
+ # @param topic_name [#to_s] name of topic to create; note that it must
8
+ # obey the naming rules for a topic (e.g.
9
+ # 'projects/myProject/topics/my_topic')
10
+ # @see https://cloud.google.com/pubsub/reference/rest/v1/projects.topics/create
11
+ def create_topic(topic_name)
12
+ api_method = @pubsub.projects.topics.create
13
+ parameters = {
14
+ "name" => topic_name.to_s
15
+ }
16
+
17
+ request(api_method, parameters)
18
+ end
19
+ end
20
+
21
+ class Mock
22
+ def create_topic(topic_name)
23
+ data = {
24
+ "name" => topic_name
25
+ }
26
+ self.data[:topics][topic_name] = data
27
+
28
+ body = data.clone
29
+ status = 200
30
+
31
+ build_excon_response(body, status)
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,28 @@
1
+ module Fog
2
+ module Google
3
+ class Pubsub
4
+ class Real
5
+ # Delete a subscription on the remote service.
6
+ #
7
+ # @param subscription_name [#to_s] name of subscription to delete
8
+ # @see https://cloud.google.com/pubsub/reference/rest/v1/projects.subscriptions/delete
9
+ def delete_subscription(subscription_name)
10
+ api_method = @pubsub.projects.subscriptions.delete
11
+ parameters = {
12
+ "subscription" => subscription_name.to_s
13
+ }
14
+
15
+ request(api_method, parameters)
16
+ end
17
+ end
18
+
19
+ class Mock
20
+ def delete_subscription(subscription_name)
21
+ data[:subscriptions].delete(subscription_name)
22
+
23
+ build_excon_response(nil, 200)
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,29 @@
1
+ module Fog
2
+ module Google
3
+ class Pubsub
4
+ class Real
5
+ # Delete a topic on the remote service.
6
+ #
7
+ # @param topic_name [#to_s] name of topic to delete
8
+ # @see https://cloud.google.com/pubsub/reference/rest/v1/projects.topics/delete
9
+ def delete_topic(topic_name)
10
+ api_method = @pubsub.projects.topics.delete
11
+ parameters = {
12
+ "topic" => topic_name.to_s
13
+ }
14
+
15
+ request(api_method, parameters)
16
+ end
17
+ end
18
+
19
+ class Mock
20
+ def delete_topic(topic_name)
21
+ data[:topics].delete(topic_name)
22
+
23
+ status = 200
24
+ build_excon_response(nil, status)
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,44 @@
1
+ module Fog
2
+ module Google
3
+ class Pubsub
4
+ class Real
5
+ # Retrieves a subscription by name from the remote service.
6
+ #
7
+ # @param subscription_name [#to_s] name of subscription to retrieve
8
+ # @see https://cloud.google.com/pubsub/reference/rest/v1/projects.topics/get
9
+ def get_subscription(subscription_name)
10
+ api_method = @pubsub.projects.subscriptions.get
11
+ parameters = {
12
+ "subscription" => subscription_name.to_s
13
+ }
14
+
15
+ request(api_method, parameters)
16
+ end
17
+ end
18
+
19
+ class Mock
20
+ def get_subscription(subscription_name)
21
+ sub = data[:subscriptions][subscription_name]
22
+ if sub.nil?
23
+ subscription_resource = subscription_name.split("/")[-1]
24
+ body = {
25
+ "error" => {
26
+ "code" => 404,
27
+ "message" => "Resource not found (resource=#{subscription_resource}).",
28
+ "status" => "NOT_FOUND"
29
+ }
30
+ }
31
+ return build_excon_response(body, 404)
32
+ end
33
+
34
+ body = sub.select do |k, _|
35
+ %w(name topic pushConfig ackDeadlineSeconds).include?(k)
36
+ end
37
+ status = 200
38
+
39
+ build_excon_response(body, status)
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,41 @@
1
+ module Fog
2
+ module Google
3
+ class Pubsub
4
+ class Real
5
+ # Retrieves a resource describing a topic.
6
+ #
7
+ # @param topic_name [#to_s] name of topic to retrieve
8
+ # @see https://cloud.google.com/pubsub/reference/rest/v1/projects.topics/get
9
+ def get_topic(topic_name)
10
+ api_method = @pubsub.projects.topics.get
11
+ parameters = {
12
+ "topic" => topic_name.to_s
13
+ }
14
+
15
+ request(api_method, parameters)
16
+ end
17
+ end
18
+
19
+ class Mock
20
+ def get_topic(topic_name)
21
+ if !data[:topics].key?(topic_name)
22
+ topic_resource = topic_name.split("/")[-1]
23
+ body = {
24
+ "error" => {
25
+ "code" => 404,
26
+ "message" => "Resource not found (resource=#{topic_resource}).",
27
+ "status" => "NOT_FOUND"
28
+ }
29
+ }
30
+ status = 404
31
+ else
32
+ body = data[:topics][topic_name].clone
33
+ status = 200
34
+ end
35
+
36
+ build_excon_response(body, status)
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,39 @@
1
+ module Fog
2
+ module Google
3
+ class Pubsub
4
+ class Real
5
+ # Gets a list of all subscriptions for a given project.
6
+ #
7
+ # @param_name project [#to_s] Project path to list subscriptions under;
8
+ # must be a project url prefix (e.g. 'projects/my-project'). If nil,
9
+ # the project configured on the client is used.
10
+ # @see https://cloud.google.com/pubsub/reference/rest/v1/projects.topics/list
11
+ def list_subscriptions(project = nil)
12
+ api_method = @pubsub.projects.subscriptions.list
13
+ parameters = {
14
+ "project" => (project.nil? ? "projects/#{@project}" : project.to_s)
15
+ }
16
+
17
+ request(api_method, parameters)
18
+ end
19
+ end
20
+
21
+ class Mock
22
+ def list_subscriptions(_project = nil)
23
+ subs = data[:subscriptions].values.map do |sub|
24
+ # Filter out any keys that aren't part of the response object
25
+ sub.select do |k, _|
26
+ %w(name topic pushConfig ackDeadlineSeconds).include?(k)
27
+ end
28
+ end
29
+
30
+ body = {
31
+ "subscriptions" => subs
32
+ }
33
+ status = 200
34
+ build_excon_response(body, status)
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,33 @@
1
+ module Fog
2
+ module Google
3
+ class Pubsub
4
+ class Real
5
+ # Gets a list of all topics for a given project.
6
+ #
7
+ # @param_name project [#to_s] Project path to list topics under; must
8
+ # be a project url prefix (e.g. 'projects/my-project'). If nil, the
9
+ # project configured on the client is used.
10
+ # @see https://cloud.google.com/pubsub/reference/rest/v1/projects.topics/list
11
+ def list_topics(project = nil)
12
+ api_method = @pubsub.projects.topics.list
13
+ parameters = {
14
+ "project" => (project.nil? ? "projects/#{@project}" : project.to_s)
15
+ }
16
+
17
+ request(api_method, parameters)
18
+ end
19
+ end
20
+
21
+ class Mock
22
+ def list_topics(_project = nil)
23
+ body = {
24
+ "topics" => data[:topics].values
25
+ }
26
+ status = 200
27
+
28
+ build_excon_response(body, status)
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,61 @@
1
+ module Fog
2
+ module Google
3
+ class Pubsub
4
+ class Real
5
+ # Publish a list of messages to a topic.
6
+ #
7
+ # @param messages [Array<Hash>] List of messages to be published to a
8
+ # topic; each hash should have a value defined for 'data' or for
9
+ # 'attributes' (or both). Note that the value associated with 'data'
10
+ # must be base64 encoded.
11
+ # @see https://cloud.google.com/pubsub/reference/rest/v1/projects.topics/publish
12
+ def publish_topic(topic, messages)
13
+ api_method = @pubsub.projects.topics.publish
14
+
15
+ parameters = {
16
+ "topic" => topic
17
+ }
18
+
19
+ body = {
20
+ "messages" => messages
21
+ }
22
+
23
+ request(api_method, parameters, body)
24
+ end
25
+ end
26
+
27
+ class Mock
28
+ def publish_topic(topic, messages)
29
+ if data[:topics].key?(topic)
30
+ published_messages = messages.map do |msg|
31
+ msg.merge("messageId" => Fog::Mock.random_letters(16), "publishTime" => Time.now.iso8601)
32
+ end
33
+
34
+ # Gather the subscriptions and publish
35
+ data[:subscriptions].values.each do |sub|
36
+ next unless sub["topic"] == topic
37
+ sub[:messages] += published_messages
38
+ end
39
+
40
+ body = {
41
+ "messageIds" => published_messages.map { |msg| msg["messageId"] }
42
+ }
43
+ status = 200
44
+ else
45
+ topic_resource = topic_name.split("/")[-1]
46
+ body = {
47
+ "error" => {
48
+ "code" => 404,
49
+ "message" => "Resource not found (resource=#{topic_resource}).",
50
+ "status" => "NOT_FOUND"
51
+ }
52
+ }
53
+ status = 404
54
+ end
55
+
56
+ build_excon_response(body, status)
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,77 @@
1
+ module Fog
2
+ module Google
3
+ class Pubsub
4
+ class Real
5
+ # Pulls from a subscription. If option 'return_immediately' is
6
+ # false, then this method blocks until one or more messages is
7
+ # available or the remote server closes the connection.
8
+ #
9
+ # @param subscription [Subscription, #to_s] subscription instance or
10
+ # name of subscription to pull from
11
+ # @param options [Hash] options to modify the pull request
12
+ # @option options [Boolean] :return_immediately if true, method returns
13
+ # after API call; otherwise the connection is held open until
14
+ # messages are available or the remote server closes the connection
15
+ # (defaults to true)
16
+ # @option options [Number] :max_messages maximum number of messages to
17
+ # retrieve (defaults to 10)
18
+ # @see https://cloud.google.com/pubsub/reference/rest/v1/projects.subscriptions/pull
19
+ def pull_subscription(subscription, options = { :return_immediately => true, :max_messages => 10 })
20
+ api_method = @pubsub.projects.subscriptions.pull
21
+
22
+ parameters = {
23
+ "subscription" => Fog::Google::Pubsub.subscription_name(subscription)
24
+ }
25
+
26
+ body = {
27
+ "returnImmediately" => options[:return_immediately],
28
+ "maxMessages" => options[:max_messages]
29
+ }
30
+
31
+ request(api_method, parameters, body)
32
+ end
33
+ end
34
+
35
+ class Mock
36
+ def pull_subscription(subscription, options = { :return_immediately => true, :max_messages => 10 })
37
+ # We're going to ignore return_immediately; feel free to add support
38
+ # if you need it for testing
39
+ subscription_name = Fog::Google::Pubsub.subscription_name(subscription)
40
+ sub = data[:subscriptions][subscription_name]
41
+
42
+ if sub.nil?
43
+ subscription_resource = subscription_name.split("/")[-1]
44
+ body = {
45
+ "error" => {
46
+ "code" => 404,
47
+ "message" => "Resource not found (resource=#{subscription_resource}).",
48
+ "status" => "NOT_FOUND"
49
+ }
50
+ }
51
+ return build_excon_response(body, 404)
52
+ end
53
+
54
+ # This implementation is a bit weak; instead of "hiding" messages for
55
+ # some period of time after they are pulled, instead we always return
56
+ # them until acknowledged. This might cause issues with clients that
57
+ # refuse to acknowledge.
58
+ #
59
+ # Also, note that here we use the message id as the ack id - again,
60
+ # this might cause problems with some strange-behaving clients.
61
+ msgs = sub[:messages].take(options[:max_messages]).map do |msg|
62
+ {
63
+ "ackId" => msg["messageId"],
64
+ "message" => msg
65
+ }
66
+ end
67
+
68
+ body = {
69
+ "receivedMessages" => msgs
70
+ }
71
+ status = 200
72
+ build_excon_response(body, status)
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,191 @@
1
+ module Fog
2
+ module Google
3
+ module Shared
4
+ attr_reader :project, :api_version, :api_url
5
+
6
+ ##
7
+ # Initializes shared attributes
8
+ #
9
+ # @param [String] project Google Cloud Project
10
+ # @param [String] api_version Google API version
11
+ # @param [String] base_url Google API base url
12
+ # @return [void]
13
+ def shared_initialize(project, api_version, base_url)
14
+ @project = project
15
+ @api_version = api_version
16
+ @api_url = base_url + api_version + "/projects/"
17
+ end
18
+
19
+ ##
20
+ # Initializes the Google API Client
21
+ #
22
+ # @param [Hash] options Google API options
23
+ # @option options [String] :google_client_email A @developer.gserviceaccount.com email address to use
24
+ # @option options [String] :google_key_location The location of a pkcs12 key file
25
+ # @option options [String] :google_key_string The content of the pkcs12 key file
26
+ # @option options [String] :google_json_key_location The location of a JSON key file
27
+ # @option options [String] :google_json_key_string The content of the JSON key file
28
+ # @option options [String] :google_api_scope_url The access scope URLs
29
+ # @option options [String] :app_name The app name to set in the user agent
30
+ # @option options [String] :app_version The app version to set in the user agent
31
+ # @option options [Google::APIClient] :google_client Existing Google API Client
32
+ # @return [Google::APIClient] Google API Client
33
+ # @raises [ArgumentError] If there is any missing argument
34
+ def initialize_google_client(options)
35
+ # NOTE: loaded here to avoid requiring this as a core Fog dependency
36
+ begin
37
+ require "google/api_client"
38
+ rescue LoadError => error
39
+ Fog::Logger.warning("Please install the google-api-client gem before using this provider")
40
+ raise error
41
+ end
42
+
43
+ # User can provide an existing Google API Client
44
+ client = options[:google_client]
45
+ return client unless client.nil?
46
+
47
+ # Create a signing key
48
+ signing_key = create_signing_key(options)
49
+
50
+ # Validate required arguments
51
+ unless options[:google_client_email]
52
+ raise ArgumentError.new("Missing required arguments: google_client_email")
53
+ end
54
+
55
+ unless options[:google_api_scope_url]
56
+ raise ArgumentError.new("Missing required arguments: google_api_scope_url")
57
+ end
58
+
59
+ # Create a new Google API Client
60
+ new_pk12_google_client(
61
+ options[:google_client_email],
62
+ signing_key,
63
+ options[:google_api_scope_url],
64
+ options[:app_name],
65
+ options[:app_version]
66
+ )
67
+ end
68
+
69
+ ##
70
+ # Creates a Google signing key
71
+ #
72
+ def create_signing_key(options)
73
+ if options[:google_json_key_location] || options[:google_json_key_string]
74
+ if options[:google_json_key_location]
75
+ json_key_location = File.expand_path(options[:google_json_key_location])
76
+ json_key = File.open(json_key_location, "r", &:read)
77
+ else
78
+ json_key = options[:google_json_key_string]
79
+ end
80
+
81
+ json_key_hash = Fog::JSON.decode(json_key)
82
+ unless json_key_hash.key?("client_email") || json_key_hash.key?("private_key")
83
+ raise ArgumentError.new("Invalid Google JSON key")
84
+ end
85
+
86
+ options[:google_client_email] = json_key_hash["client_email"]
87
+ ::Google::APIClient::KeyUtils.load_from_pem(json_key_hash["private_key"], "notasecret")
88
+ elsif options[:google_key_location] || options[:google_key_string]
89
+ google_key =
90
+ if options[:google_key_location]
91
+ File.expand_path(options[:google_key_location])
92
+ else
93
+ options[:google_key_string]
94
+ end
95
+
96
+ ::Google::APIClient::KeyUtils.load_from_pkcs12(google_key, "notasecret")
97
+ else
98
+ raise ArgumentError.new("Missing required arguments: google_key_location, google_key_string, " \
99
+ "google_json_key_location or google_json_key_string")
100
+ end
101
+ end
102
+
103
+ ##
104
+ # Create a Google API Client with a user email and a pkcs12 key
105
+ #
106
+ # @param [String] google_client_email A @developer.gserviceaccount.com email address to use
107
+ # @param [OpenSSL::PKey] signing_key The private key for signing
108
+ # @param [String] google_api_scope_url Access scope URLs
109
+ # @param [String] app_name The app name to set in the user agent
110
+ # @param [String] app_version The app version to set in the user agent
111
+ # @return [Google::APIClient] Google API Client
112
+ def new_pk12_google_client(google_client_email, signing_key, google_api_scope_url, app_name = nil, app_version = nil)
113
+ application_name = app_name.nil? ? "fog" : "#{app_name}/#{app_version || '0.0.0'} fog"
114
+ api_client_options = {
115
+ :application_name => application_name,
116
+ :application_version => Fog::Google::VERSION
117
+ }
118
+ client = ::Google::APIClient.new(api_client_options)
119
+
120
+ client.authorization = Signet::OAuth2::Client.new(
121
+ :audience => "https://accounts.google.com/o/oauth2/token",
122
+ :auth_provider_x509_cert_url => "https://www.googleapis.com/oauth2/v1/certs",
123
+ :client_x509_cert_url => "https://www.googleapis.com/robot/v1/metadata/x509/#{google_client_email}",
124
+ :issuer => google_client_email,
125
+ :scope => google_api_scope_url,
126
+ :signing_key => signing_key,
127
+ :token_credential_uri => "https://accounts.google.com/o/oauth2/token"
128
+ )
129
+ client.authorization.fetch_access_token!
130
+
131
+ client
132
+ end
133
+
134
+ ##
135
+ # Executes a request and wraps it in a result object
136
+ #
137
+ # @param [Google::APIClient::Method] api_method The method object or the RPC name of the method being executed
138
+ # @param [Hash] parameters The parameters to send to the method
139
+ # @param [Hash] body_object The body object of the request
140
+ # @return [Excon::Response] The result from the API
141
+ def request(api_method, parameters, body_object = nil, media = nil)
142
+ client_parms = {
143
+ :api_method => api_method,
144
+ :parameters => parameters
145
+ }
146
+ # The Google API complains when given null values for enums, so just don't pass it any null fields
147
+ # XXX It may still balk if we have a nested object, e.g.:
148
+ # {:a_field => "string", :a_nested_field => { :an_empty_nested_field => nil } }
149
+ client_parms[:body_object] = body_object.reject { |_k, v| v.nil? } if body_object
150
+ client_parms[:media] = media if media
151
+
152
+ result = @client.execute(client_parms)
153
+
154
+ build_excon_response(result.body.nil? || result.body.empty? ? nil : Fog::JSON.decode(result.body), result.status)
155
+ end
156
+
157
+ ##
158
+ # Builds an Excon response
159
+ #
160
+ # @param [Hash] Response body
161
+ # @param [Integer] Response status
162
+ # @return [Excon::Response] Excon response
163
+ def build_excon_response(body, status = 200)
164
+ response = Excon::Response.new(:body => body, :status => status)
165
+ if body && body.key?("error")
166
+ msg = "Google Cloud did not return an error message"
167
+
168
+ if body["error"].is_a?(Hash)
169
+ response.status = body["error"]["code"]
170
+ if body["error"].key?("errors")
171
+ msg = body["error"]["errors"].map { |error| error["message"] }.join(", ")
172
+ elsif body["error"].key?("message")
173
+ msg = body["error"]["message"]
174
+ end
175
+ elsif body["error"].is_a?(Array)
176
+ msg = body["error"].map { |error| error["code"] }.join(", ")
177
+ end
178
+
179
+ case response.status
180
+ when 404
181
+ raise Fog::Errors::NotFound.new(msg)
182
+ else
183
+ raise Fog::Errors::Error.new(msg)
184
+ end
185
+ end
186
+
187
+ response
188
+ end
189
+ end
190
+ end
191
+ end