fog-google 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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