gcloud 0.1.1 → 0.2.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.
@@ -0,0 +1,284 @@
1
+ #--
2
+ # Copyright 2015 Google Inc. All rights reserved.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ require "gcloud/version"
17
+ require "google/api_client"
18
+
19
+ module Gcloud
20
+ module Pubsub
21
+ ##
22
+ # Represents the connection to Pub/Sub,
23
+ # as well as expose the API calls.
24
+ class Connection #:nodoc:
25
+ API_VERSION = "v1"
26
+
27
+ attr_accessor :project
28
+ attr_accessor :credentials #:nodoc:
29
+
30
+ ##
31
+ # Creates a new Connection instance.
32
+ def initialize project, credentials
33
+ @project = project
34
+ @credentials = credentials
35
+ @client = Google::APIClient.new application_name: "gcloud-ruby",
36
+ application_version: Gcloud::VERSION
37
+ @client.authorization = @credentials.client
38
+ @pubsub = @client.discovered_api "pubsub", API_VERSION
39
+ end
40
+
41
+ ##
42
+ # Gets the configuration of a topic.
43
+ # Since the topic only has the name attribute,
44
+ # this method is only useful to check the existence of a topic.
45
+ # If other attributes are added in the future,
46
+ # they will be returned here.
47
+ def get_topic topic_name, options = {}
48
+ @client.execute(
49
+ api_method: @pubsub.projects.topics.get,
50
+ parameters: { topic: topic_path(topic_name, options) }
51
+ )
52
+ end
53
+
54
+ ##
55
+ # Creates the given topic with the given name.
56
+ def create_topic topic_name, options = {}
57
+ @client.execute(
58
+ api_method: @pubsub.projects.topics.create,
59
+ parameters: { name: topic_path(topic_name, options) }
60
+ )
61
+ end
62
+
63
+ ##
64
+ # Lists matching topics.
65
+ def list_topics options = {}
66
+ params = { project: project_path(options),
67
+ pageToken: options.delete(:token),
68
+ pageSize: options.delete(:max)
69
+ }.delete_if { |_, v| v.nil? }
70
+
71
+ @client.execute(
72
+ api_method: @pubsub.projects.topics.list,
73
+ parameters: params
74
+ )
75
+ end
76
+
77
+ ##
78
+ # Deletes the topic with the given name.
79
+ # All subscriptions to this topic are also deleted.
80
+ # Returns NOT_FOUND if the topic does not exist.
81
+ # After a topic is deleted, a new topic may be created with the same name.
82
+ def delete_topic topic
83
+ @client.execute(
84
+ api_method: @pubsub.projects.topics.delete,
85
+ parameters: { topic: topic }
86
+ )
87
+ end
88
+
89
+ def get_topic_policy topic_name, options = {}
90
+ @client.execute(
91
+ api_method: @pubsub.projects.topics.get_iam_policy,
92
+ parameters: { resource: topic_path(topic_name, options) }
93
+ )
94
+ end
95
+
96
+ def set_topic_policy topic_name, new_policy, options = {}
97
+ @client.execute(
98
+ api_method: @pubsub.projects.topics.set_iam_policy,
99
+ parameters: { resource: topic_path(topic_name, options) },
100
+ body_object: { policy: new_policy }
101
+ )
102
+ end
103
+
104
+ ##
105
+ # Creates a subscription on a given topic for a given subscriber.
106
+ def create_subscription topic, subscription_name, options = {}
107
+ data = subscription_data topic, options
108
+ @client.execute(
109
+ api_method: @pubsub.projects.subscriptions.create,
110
+ parameters: { name: subscription_path(subscription_name, options) },
111
+ body_object: data
112
+ )
113
+ end
114
+
115
+ ##
116
+ # Gets the details of a subscription.
117
+ def get_subscription subscription_name, options = {}
118
+ @client.execute(
119
+ api_method: @pubsub.projects.subscriptions.get,
120
+ parameters: {
121
+ subscription: subscription_path(subscription_name, options) }
122
+ )
123
+ end
124
+
125
+ ##
126
+ # Lists matching subscriptions by project.
127
+ def list_subscriptions options = {}
128
+ params = { project: project_path(options),
129
+ pageToken: options.delete(:token),
130
+ pageSize: options.delete(:max)
131
+ }.delete_if { |_, v| v.nil? }
132
+
133
+ @client.execute(
134
+ api_method: @pubsub.projects.subscriptions.list,
135
+ parameters: params
136
+ )
137
+ end
138
+
139
+ ##
140
+ # Lists matching subscriptions by project and topic.
141
+ def list_topics_subscriptions topic, options = {}
142
+ params = { topic: topic,
143
+ pageToken: options.delete(:token),
144
+ pageSize: options.delete(:max)
145
+ }.delete_if { |_, v| v.nil? }
146
+
147
+ @client.execute(
148
+ api_method: @pubsub.projects.topics.subscriptions.list,
149
+ parameters: params
150
+ )
151
+ end
152
+
153
+ ##
154
+ # Deletes an existing subscription.
155
+ # All pending messages in the subscription are immediately dropped.
156
+ def delete_subscription subscription
157
+ @client.execute(
158
+ api_method: @pubsub.projects.subscriptions.delete,
159
+ parameters: { subscription: subscription }
160
+ )
161
+ end
162
+
163
+ def get_subscription_policy subscription_name, options = {}
164
+ @client.execute(
165
+ api_method: @pubsub.projects.subscriptions.get_iam_policy,
166
+ parameters: {
167
+ resource: subscription_path(subscription_name, options) }
168
+ )
169
+ end
170
+
171
+ def set_subscription_policy subscription_name, new_policy, options = {}
172
+ @client.execute(
173
+ api_method: @pubsub.projects.subscriptions.set_iam_policy,
174
+ parameters: {
175
+ resource: subscription_path(subscription_name, options) },
176
+ body_object: { policy: new_policy }
177
+ )
178
+ end
179
+
180
+ ##
181
+ # Adds one or more messages to the topic.
182
+ # Returns NOT_FOUND if the topic does not exist.
183
+ # The messages parameter is an array of arrays.
184
+ # The first element is the data, second is attributes hash.
185
+ def publish topic, messages
186
+ gapi_msgs = messages.map do |data, attributes|
187
+ { data: [data].pack("m"), attributes: attributes }
188
+ end
189
+ @client.execute(
190
+ api_method: @pubsub.projects.topics.publish,
191
+ parameters: { topic: topic },
192
+ body_object: { messages: gapi_msgs }
193
+ )
194
+ end
195
+
196
+ ##
197
+ # Pulls a single message from the server.
198
+ def pull subscription, options = {}
199
+ body = { returnImmediately: !(!options.fetch(:immediate, true)),
200
+ maxMessages: options.fetch(:max, 100).to_i }
201
+
202
+ @client.execute(
203
+ api_method: @pubsub.projects.subscriptions.pull,
204
+ parameters: { subscription: subscription },
205
+ body_object: body
206
+ )
207
+ end
208
+
209
+ ##
210
+ # Acknowledges receipt of a message.
211
+ def acknowledge subscription, *ack_ids
212
+ @client.execute(
213
+ api_method: @pubsub.projects.subscriptions.acknowledge,
214
+ parameters: { subscription: subscription },
215
+ body_object: { ackIds: ack_ids }
216
+ )
217
+ end
218
+
219
+ ##
220
+ # Modifies the PushConfig for a specified subscription.
221
+ def modify_push_config subscription, endpoint, attributes
222
+ @client.execute(
223
+ api_method: @pubsub.projects.subscriptions.modify_push_config,
224
+ parameters: { subscription: subscription },
225
+ body_object: { pushConfig: { pushEndpoint: endpoint,
226
+ attributes: attributes } }
227
+ )
228
+ end
229
+
230
+ ##
231
+ # Modifies the ack deadline for a specific message.
232
+ def modify_ack_deadline subscription, ids, deadline
233
+ ids = Array ids
234
+ @client.execute(
235
+ api_method: @pubsub.projects.subscriptions.modify_ack_deadline,
236
+ parameters: { subscription: subscription },
237
+ body_object: { ackIds: ids, ackDeadlineSeconds: deadline }
238
+ )
239
+ end
240
+
241
+ def project_path options = {}
242
+ project_name = options[:project] || project
243
+ "projects/#{project_name}"
244
+ end
245
+
246
+ def topic_path topic_name, options = {}
247
+ return topic_name if topic_name.to_s.include? "/"
248
+ "#{project_path(options)}/topics/#{topic_name}"
249
+ end
250
+
251
+ def subscription_path subscription_name, options = {}
252
+ return subscription_name if subscription_name.to_s.include? "/"
253
+ "#{project_path(options)}/subscriptions/#{subscription_name}"
254
+ end
255
+
256
+ protected
257
+
258
+ def subscription_data topic, options = {}
259
+ deadline = options[:deadline]
260
+ endpoint = options[:endpoint]
261
+ attributes = hashify options[:attributes]
262
+
263
+ data = { topic: topic }
264
+ data[:ackDeadlineSeconds] = deadline if deadline
265
+ if endpoint
266
+ data[:pushConfig] = { pushEndpoint: endpoint,
267
+ attributes: attributes }
268
+ end
269
+ data
270
+ end
271
+
272
+ ##
273
+ # Make sure the object is converted to a hash
274
+ # Ruby 1.9.3 doesn't support to_h, so here we are.
275
+ def hashify hash
276
+ if hash.respond_to? :to_h
277
+ hash.to_h
278
+ else
279
+ Hash.try_convert(hash) || {}
280
+ end
281
+ end
282
+ end
283
+ end
284
+ end
@@ -0,0 +1,29 @@
1
+ #--
2
+ # Copyright 2015 Google Inc. All rights reserved.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ require "gcloud/credentials"
17
+
18
+ module Gcloud
19
+ module Pubsub
20
+ ##
21
+ # Represents the OAuth 2.0 signing logic for Pub/Sub.
22
+ class Credentials < Gcloud::Credentials #:nodoc:
23
+ SCOPE = ["https://www.googleapis.com/auth/pubsub",
24
+ "https://www.googleapis.com/auth/cloud-platform"]
25
+ PATH_ENV_VARS = %w(PUBSUB_KEYFILE GOOGLE_CLOUD_KEYFILE)
26
+ JSON_ENV_VARS = %w(PUBSUB_KEYFILE_JSON GOOGLE_CLOUD_KEYFILE_JSON)
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,91 @@
1
+ #--
2
+ # Copyright 2015 Google Inc. All rights reserved.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ module Gcloud
17
+ module Pubsub
18
+ ##
19
+ # = Storage Error
20
+ #
21
+ # Base Pub/Sub exception class.
22
+ class Error < Gcloud::Error
23
+ ##
24
+ # The response object of the failed HTTP request.
25
+ attr_reader :response
26
+
27
+ def self.from_response resp #:nodoc:
28
+ new.tap do |e|
29
+ e.response = resp
30
+ end
31
+ end
32
+ end
33
+
34
+ ##
35
+ # = ApiError
36
+ #
37
+ # Raised when an API call is not successful.
38
+ class ApiError < Error
39
+ ##
40
+ # The code of the error.
41
+ def code
42
+ response.data["error"]["code"]
43
+ rescue
44
+ nil
45
+ end
46
+
47
+ ##
48
+ # The errors encountered.
49
+ def errors
50
+ response.data["error"]["errors"]
51
+ rescue
52
+ []
53
+ end
54
+
55
+ def initialize message, response
56
+ super message
57
+ @response = response
58
+ end
59
+
60
+ def self.from_response resp #:nodoc:
61
+ klass = klass_for resp.data["error"]["status"]
62
+ klass.new resp.data["error"]["message"], resp
63
+ rescue
64
+ Gcloud::Pubsub::Error.from_response resp
65
+ end
66
+
67
+ def self.klass_for status
68
+ if status == "ALREADY_EXISTS"
69
+ return AlreadyExistsError
70
+ elsif status == "NOT_FOUND"
71
+ return NotFoundError
72
+ end
73
+ self
74
+ end
75
+ end
76
+
77
+ ##
78
+ # = AlreadyExistsError
79
+ #
80
+ # Raised when Pub/Sub returns an +ALREADY_EXISTS+ error.
81
+ class AlreadyExistsError < ApiError
82
+ end
83
+
84
+ ##
85
+ # = NotFoundError
86
+ #
87
+ # Raised when Pub/Sub returns a +NOT_FOUND+ error.
88
+ class NotFoundError < ApiError
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,90 @@
1
+ #--
2
+ # Copyright 2015 Google Inc. All rights reserved.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ require "gcloud/pubsub/errors"
17
+
18
+ module Gcloud
19
+ module Pubsub
20
+ ##
21
+ # = Message
22
+ #
23
+ # Represents a Pub/Sub Message.
24
+ #
25
+ # Message objects are created by Topic#publish.
26
+ # Subscription#pull returns an array of ReceivedMessage objects, each of
27
+ # which contains a Message object. Each ReceivedMessage object can be
28
+ # acknowledged and/or delayed.
29
+ #
30
+ # require "gcloud"
31
+ #
32
+ # gcloud = Gcloud.new
33
+ # pubsub = gcloud.pubsub
34
+ #
35
+ # # Publish a message
36
+ # topic = pubsub.topic "my-topic"
37
+ # message = topic.publish "new-message"
38
+ # puts message.data #=> "new-message"
39
+ #
40
+ # # Pull a message
41
+ # sub = pubsub.subscription "my-topic-sub"
42
+ # received_message = sub.pull.first
43
+ # puts received_message.message.data #=> "new-message"
44
+ #
45
+ class Message
46
+ ##
47
+ # The Google API Client object.
48
+ attr_accessor :gapi #:nodoc:
49
+
50
+ ##
51
+ # Create an empty Message object.
52
+ # This can be used to publish several messages in bulk.
53
+ def initialize data = nil, attributes = {}
54
+ @gapi = {}
55
+ @gapi["data"] = data
56
+ @gapi["attributes"] = attributes
57
+ end
58
+
59
+ ##
60
+ # The received data.
61
+ def data
62
+ @gapi["data"]
63
+ end
64
+
65
+ ##
66
+ # The received attributes.
67
+ def attributes
68
+ attrs = @gapi["attributes"]
69
+ attrs = attrs.to_hash if attrs.respond_to? :to_hash
70
+ attrs
71
+ end
72
+
73
+ ##
74
+ # The ID of this message, assigned by the server at publication time.
75
+ # Guaranteed to be unique within the topic.
76
+ def message_id
77
+ @gapi["messageId"]
78
+ end
79
+ alias_method :msg_id, :message_id
80
+
81
+ ##
82
+ # New Topic from a Google API Client object.
83
+ def self.from_gapi gapi #:nodoc:
84
+ new.tap do |f|
85
+ f.gapi = gapi
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end