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,50 @@
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
+ class Subscription
19
+ ##
20
+ # Subscription::List is a special case Array with additional values.
21
+ class List < DelegateClass(::Array)
22
+ ##
23
+ # If not empty, indicates that there are more subscriptions
24
+ # that match the request and this value should be passed to
25
+ # the next Gcloud::PubSub::Topic#subscriptions to continue.
26
+ attr_accessor :token
27
+
28
+ ##
29
+ # Create a new Subscription::List with an array of values.
30
+ def initialize arr = [], token = nil
31
+ super arr
32
+ @token = token
33
+ end
34
+
35
+ ##
36
+ # New Subscription::List from a response object.
37
+ def self.from_resp resp, conn #:nodoc:
38
+ subs = Array(resp.data["subscriptions"]).map do |gapi_object|
39
+ if gapi_object.is_a? String
40
+ Subscription.new_lazy gapi_object, conn
41
+ else
42
+ Subscription.from_gapi gapi_object, conn
43
+ end
44
+ end
45
+ new subs, resp.data["nextPageToken"]
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,640 @@
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 "json"
17
+ require "gcloud/pubsub/errors"
18
+ require "gcloud/pubsub/topic/list"
19
+ require "gcloud/pubsub/subscription"
20
+
21
+ module Gcloud
22
+ module Pubsub
23
+ ##
24
+ # = Topic
25
+ #
26
+ # A named resource to which messages are published.
27
+ #
28
+ # require "gcloud"
29
+ #
30
+ # gcloud = Gcloud.new
31
+ # pubsub = gcloud.pubsub
32
+ #
33
+ # topic = pubsub.topic "my-topic"
34
+ # topic.publish "task completed"
35
+ #
36
+ class Topic
37
+ ##
38
+ # The Connection object.
39
+ attr_accessor :connection #:nodoc:
40
+
41
+ ##
42
+ # The Google API Client object.
43
+ attr_accessor :gapi #:nodoc:
44
+
45
+ ##
46
+ # Create an empty Topic object.
47
+ def initialize #:nodoc:
48
+ @connection = nil
49
+ @gapi = {}
50
+ @name = nil
51
+ @autocreate = nil
52
+ @exists = nil
53
+ end
54
+
55
+ ##
56
+ # New lazy Topic object without making an HTTP request.
57
+ def self.new_lazy name, conn, options = {} #:nodoc:
58
+ options[:autocreate] = true if options[:autocreate].nil?
59
+ new.tap do |t|
60
+ t.gapi = nil
61
+ t.connection = conn
62
+ t.instance_eval do
63
+ @name = conn.topic_path(name, options)
64
+ @autocreate = options[:autocreate]
65
+ end
66
+ end
67
+ end
68
+
69
+ ##
70
+ # The name of the topic in the form of
71
+ # "/projects/project-identifier/topics/topic-name".
72
+ def name
73
+ @gapi ? @gapi["name"] : @name
74
+ end
75
+
76
+ ##
77
+ # Permanently deletes the topic.
78
+ #
79
+ # === Returns
80
+ #
81
+ # +true+ if the topic was deleted.
82
+ #
83
+ # === Example
84
+ #
85
+ # require "gcloud"
86
+ #
87
+ # gcloud = Gcloud.new
88
+ # pubsub = gcloud.pubsub
89
+ #
90
+ # topic = pubsub.topic "my-topic"
91
+ # topic.delete
92
+ #
93
+ def delete
94
+ ensure_connection!
95
+ resp = connection.delete_topic name
96
+ if resp.success?
97
+ true
98
+ else
99
+ fail ApiError.from_response(resp)
100
+ end
101
+ end
102
+
103
+ ##
104
+ # Creates a new Subscription object on the current Topic.
105
+ #
106
+ # === Parameters
107
+ #
108
+ # +subscription_name+::
109
+ # Name of the new subscription. Must start with a letter, and contain
110
+ # only letters ([A-Za-z]), numbers ([0-9], dashes (-), underscores (_),
111
+ # periods (.), tildes (~), plus (+) or percent signs (%). It must be
112
+ # between 3 and 255 characters in length, and it must not start with
113
+ # "goog". (+String+)
114
+ # +options+::
115
+ # An optional Hash for controlling additional behavior. (+Hash+)
116
+ # <code>options[:deadline]</code>::
117
+ # The maximum number of seconds after a subscriber receives a message
118
+ # before the subscriber should acknowledge the message. (+Integer+)
119
+ # <code>options[:endpoint]</code>::
120
+ # A URL locating the endpoint to which messages should be pushed.
121
+ # e.g. "https://example.com/push" (+String+)
122
+ #
123
+ # === Returns
124
+ #
125
+ # Gcloud::Pubsub::Subscription
126
+ #
127
+ # === Examples
128
+ #
129
+ # require "gcloud"
130
+ #
131
+ # gcloud = Gcloud.new
132
+ # pubsub = gcloud.pubsub
133
+ #
134
+ # topic = pubsub.topic "my-topic"
135
+ # sub = topic.subscribe "my-topic-sub"
136
+ # puts sub.name # => "my-topic-sub"
137
+ #
138
+ # The name is optional, and will be generated if not given.
139
+ #
140
+ # require "gcloud"
141
+ #
142
+ # gcloud = Gcloud.new
143
+ # pubsub = gcloud.pubsub
144
+ #
145
+ # topic = pubsub.topic "my-topic"
146
+ # sub = topic.subscribe "my-topic-sub"
147
+ # puts sub.name # => "generated-sub-name"
148
+ #
149
+ # The subscription can be created that waits two minutes for
150
+ # acknowledgement and pushed all messages to an endpoint
151
+ #
152
+ # require "gcloud"
153
+ #
154
+ # gcloud = Gcloud.new
155
+ # pubsub = gcloud.pubsub
156
+ #
157
+ # topic = pubsub.topic "my-topic"
158
+ # sub = topic.subscribe "my-topic-sub",
159
+ # deadline: 120,
160
+ # endpoint: "https://example.com/push"
161
+ #
162
+ def subscribe subscription_name, options = {}
163
+ ensure_connection!
164
+ resp = connection.create_subscription name, subscription_name, options
165
+ if resp.success?
166
+ Subscription.from_gapi resp.data, connection
167
+ else
168
+ fail ApiError.from_response(resp)
169
+ end
170
+ rescue Gcloud::Pubsub::NotFoundError => e
171
+ retry if lazily_create_topic!
172
+ raise e
173
+ end
174
+ alias_method :create_subscription, :subscribe
175
+ alias_method :new_subscription, :subscribe
176
+
177
+ ##
178
+ # Retrieves subscription by name.
179
+ # The difference between this method and Topic#get_subscription is
180
+ # that this method does not make an API call to Pub/Sub to verify the
181
+ # subscription exists.
182
+ #
183
+ # === Parameters
184
+ #
185
+ # +subscription_name+::
186
+ # Name of a subscription. (+String+)
187
+ #
188
+ # === Returns
189
+ #
190
+ # Gcloud::Pubsub::Subscription
191
+ #
192
+ # === Example
193
+ #
194
+ # require "gcloud"
195
+ #
196
+ # gcloud = Gcloud.new
197
+ # pubsub = gcloud.pubsub
198
+ #
199
+ # topic = pubsub.topic "my-topic"
200
+ # subscription = topic.subscription "my-topic-subscription"
201
+ # puts subscription.name
202
+ #
203
+ def subscription subscription_name
204
+ ensure_connection!
205
+
206
+ Subscription.new_lazy subscription_name, connection
207
+ end
208
+
209
+ ##
210
+ # Retrieves a subscription by name.
211
+ # The difference between this method and Topic#subscription is that
212
+ # this method makes an API call to Pub/Sub to verify the subscription
213
+ # exists.
214
+ #
215
+ # === Parameters
216
+ #
217
+ # +subscription_name+::
218
+ # Name of a subscription. (+String+)
219
+ #
220
+ # === Returns
221
+ #
222
+ # Gcloud::Pubsub::Subscription or nil if subscription does not exist
223
+ #
224
+ # === Example
225
+ #
226
+ # require "gcloud"
227
+ #
228
+ # gcloud = Gcloud.new
229
+ # pubsub = gcloud.pubsub
230
+ #
231
+ # topic = pubsub.topic "my-topic"
232
+ # subscription = topic.get_subscription "my-topic-subscription"
233
+ # puts subscription.name
234
+ #
235
+ def get_subscription subscription_name
236
+ ensure_connection!
237
+ resp = connection.get_subscription subscription_name
238
+ if resp.success?
239
+ Subscription.from_gapi resp.data, connection
240
+ else
241
+ nil
242
+ end
243
+ end
244
+ alias_method :find_subscription, :get_subscription
245
+
246
+ ##
247
+ # Retrieves a list of subscription names for the given project.
248
+ #
249
+ # === Parameters
250
+ #
251
+ # +options+::
252
+ # An optional Hash for controlling additional behavior. (+Hash+)
253
+ # <code>options[:token]</code>::
254
+ # The +token+ value returned by the last call to +subscriptions+;
255
+ # indicates that this is a continuation of a call, and that the system
256
+ # should return the next page of data. (+String+)
257
+ # <code>options[:max]</code>::
258
+ # Maximum number of subscriptions to return. (+Integer+)
259
+ #
260
+ # === Returns
261
+ #
262
+ # Array of Subscription objects (Subscription::List)
263
+ #
264
+ # === Examples
265
+ #
266
+ # require "gcloud"
267
+ #
268
+ # gcloud = Gcloud.new
269
+ # pubsub = gcloud.pubsub
270
+ #
271
+ # topic = pubsub.topic "my-topic"
272
+ # subscription = topic.subscriptions
273
+ # subscriptions.each do |subscription|
274
+ # puts subscription.name
275
+ # end
276
+ #
277
+ # If you have a significant number of subscriptions, you may need to
278
+ # paginate through them: (See Subscription::List#token)
279
+ #
280
+ # require "gcloud"
281
+ #
282
+ # gcloud = Gcloud.new
283
+ # pubsub = gcloud.pubsub
284
+ #
285
+ # topic = pubsub.topic "my-topic"
286
+ # all_subs = []
287
+ # tmp_subs = topic.subscriptions
288
+ # while tmp_subs.any? do
289
+ # tmp_subs.each do |subscription|
290
+ # all_subs << subscription
291
+ # end
292
+ # # break loop if no more subscriptions available
293
+ # break if tmp_subs.token.nil?
294
+ # # get the next group of subscriptions
295
+ # tmp_subs = topic.subscriptions token: tmp_subs.token
296
+ # end
297
+ #
298
+ def subscriptions options = {}
299
+ ensure_connection!
300
+ resp = connection.list_topics_subscriptions name, options
301
+ if resp.success?
302
+ Subscription::List.from_resp resp, connection
303
+ else
304
+ fail ApiError.from_response(resp)
305
+ end
306
+ end
307
+ alias_method :find_subscriptions, :subscriptions
308
+ alias_method :list_subscriptions, :subscriptions
309
+
310
+ ##
311
+ # Publishes one or more messages to the topic.
312
+ #
313
+ # === Parameters
314
+ #
315
+ # +data+::
316
+ # The message data. (+String+)
317
+ # +attributes+::
318
+ # Optional attributes for the message. (+Hash+)
319
+ #
320
+ # === Returns
321
+ #
322
+ # Message object when called without a block,
323
+ # Array of Message objects when called with a block
324
+ #
325
+ # === Examples
326
+ #
327
+ # require "gcloud"
328
+ #
329
+ # gcloud = Gcloud.new
330
+ # pubsub = gcloud.pubsub
331
+ #
332
+ # topic = pubsub.topic "my-topic"
333
+ # msg = topic.publish "new-message"
334
+ #
335
+ # Additionally, a message can be published with attributes:
336
+ #
337
+ # require "gcloud"
338
+ #
339
+ # gcloud = Gcloud.new
340
+ # pubsub = gcloud.pubsub
341
+ #
342
+ # topic = pubsub.topic "my-topic"
343
+ # msg = topic.publish "new-message",
344
+ # foo: :bar,
345
+ # this: :that
346
+ #
347
+ # Multiple messages can be published at the same time by passing a block:
348
+ #
349
+ # require "gcloud"
350
+ #
351
+ # gcloud = Gcloud.new
352
+ # pubsub = gcloud.pubsub
353
+ #
354
+ # topic = pubsub.topic "my-topic"
355
+ # msgs = topic.publish do |batch|
356
+ # batch.publish "new-message-1", foo: :bar
357
+ # batch.publish "new-message-2", foo: :baz
358
+ # batch.publish "new-message-3", foo: :bif
359
+ # end
360
+ #
361
+ def publish data = nil, attributes = {}
362
+ ensure_connection!
363
+ batch = Batch.new data, attributes
364
+ yield batch if block_given?
365
+ return nil if batch.messages.count.zero?
366
+ publish_batch_messages batch
367
+ rescue Gcloud::Pubsub::NotFoundError => e
368
+ retry if lazily_create_topic!
369
+ raise e
370
+ end
371
+
372
+ ##
373
+ # Gets the access control policy.
374
+ #
375
+ # === Parameters
376
+ #
377
+ # +options+::
378
+ # An optional Hash for controlling additional behavior. (+Hash+)
379
+ # <code>options[:force]</code>::
380
+ # Force the latest policy to be retrieved from the Pub/Sub service when
381
+ # +true. Otherwise the policy will be memoized to reduce the number of
382
+ # API calls made to the Pub/Sub service. The default is +false+.
383
+ # (+Boolean+)
384
+ #
385
+ # === Returns
386
+ #
387
+ # A hash that conforms to the following structure:
388
+ #
389
+ # {
390
+ # "bindings" => [{
391
+ # "role" => "roles/viewer",
392
+ # "members" => ["serviceAccount:your-service-account"]
393
+ # }],
394
+ # "rules" => []
395
+ # }
396
+ #
397
+ # === Examples
398
+ #
399
+ # By default, the policy values are memoized to reduce the number of API
400
+ # calls to the Pub/Sub service.
401
+ #
402
+ # require "gcloud"
403
+ #
404
+ # gcloud = Gcloud.new
405
+ # pubsub = gcloud.pubsub
406
+ #
407
+ # topic = pubsub.topic "my-topic"
408
+ # puts topic.policy["bindings"]
409
+ # puts topic.policy["rules"]
410
+ #
411
+ # To retrieve the latest policy from the Pub/Sub service, use the +force+
412
+ # flag.
413
+ #
414
+ # require "gcloud"
415
+ #
416
+ # gcloud = Gcloud.new
417
+ # pubsub = gcloud.pubsub
418
+ #
419
+ # topic = pubsub.topic "my-topic"
420
+ # policy = topic.policy force: true
421
+ #
422
+ def policy options = {}
423
+ @policy = nil if options[:force]
424
+ @policy ||= begin
425
+ ensure_connection!
426
+ resp = connection.get_topic_policy name
427
+ policy = resp.data["policy"]
428
+ policy = policy.to_hash if policy.respond_to? :to_hash
429
+ policy
430
+ end
431
+ end
432
+
433
+ ##
434
+ # Sets the access control policy.
435
+ #
436
+ # === Parameters
437
+ #
438
+ # +new_policy+::
439
+ # A hash that conforms to the following structure:
440
+ #
441
+ # {
442
+ # "bindings" => [{
443
+ # "role" => "roles/viewer",
444
+ # "members" => ["serviceAccount:your-service-account"]
445
+ # }],
446
+ # "rules" => []
447
+ # }
448
+ #
449
+ # === Example
450
+ #
451
+ # require "gcloud"
452
+ #
453
+ # gcloud = Gcloud.new
454
+ # pubsub = gcloud.pubsub
455
+ #
456
+ # viewer_policy = {
457
+ # "bindings" => [{
458
+ # "role" => "roles/viewer",
459
+ # "members" => ["serviceAccount:your-service-account"]
460
+ # }]
461
+ # }
462
+ # topic = pubsub.topic "my-topic"
463
+ # topic.policy = viewer_policy
464
+ #
465
+ def policy= new_policy
466
+ ensure_connection!
467
+ resp = connection.set_topic_policy name, new_policy
468
+ if resp.success?
469
+ @policy = resp.data["policy"]
470
+ @policy = @policy.to_hash if @policy.respond_to? :to_hash
471
+ else
472
+ fail ApiError.from_response(resp)
473
+ end
474
+ end
475
+
476
+ ##
477
+ # Determines whether the topic exists in the Pub/Sub service.
478
+ #
479
+ # === Example
480
+ #
481
+ # require "gcloud"
482
+ #
483
+ # gcloud = Gcloud.new
484
+ # pubsub = gcloud.pubsub
485
+ #
486
+ # topic = pubsub.topic "my-topic"
487
+ # topic.exists? #=> true
488
+ #
489
+ def exists?
490
+ # Always true if we have a gapi object
491
+ return true unless @gapi.nil?
492
+ # If we have a value, return it
493
+ return @exists unless @exists.nil?
494
+ ensure_gapi!
495
+ @exists = !@gapi.nil?
496
+ end
497
+
498
+ ##
499
+ # Determines whether the topic object was created with an HTTP call.
500
+ #
501
+ # === Example
502
+ #
503
+ # require "gcloud"
504
+ #
505
+ # gcloud = Gcloud.new
506
+ # pubsub = gcloud.pubsub
507
+ #
508
+ # topic = pubsub.topic "my-topic"
509
+ # topic.lazy? #=> false
510
+ #
511
+ def lazy? #:nodoc:
512
+ @gapi.nil?
513
+ end
514
+
515
+ ##
516
+ # Determines whether the lazy topic object should create a topic on the
517
+ # Pub/Sub service.
518
+ #
519
+ # === Example
520
+ #
521
+ # require "gcloud"
522
+ #
523
+ # gcloud = Gcloud.new
524
+ # pubsub = gcloud.pubsub
525
+ #
526
+ # topic = pubsub.topic "my-topic"
527
+ # topic.autocreate? #=> true
528
+ #
529
+ def autocreate? #:nodoc:
530
+ @autocreate
531
+ end
532
+
533
+ ##
534
+ # New Topic from a Google API Client object.
535
+ def self.from_gapi gapi, conn #:nodoc:
536
+ new.tap do |f|
537
+ f.gapi = gapi
538
+ f.connection = conn
539
+ end
540
+ end
541
+
542
+ protected
543
+
544
+ ##
545
+ # Raise an error unless an active connection is available.
546
+ def ensure_connection!
547
+ fail "Must have active connection" unless connection
548
+ end
549
+
550
+ ##
551
+ # Ensures a Google API object exists.
552
+ def ensure_gapi!
553
+ ensure_connection!
554
+ return @gapi if @gapi
555
+ resp = connection.get_topic @name
556
+ @gapi = resp.data if resp.success?
557
+ end
558
+
559
+ ##
560
+ def lazily_create_topic!
561
+ if lazy? && autocreate?
562
+ resp = connection.create_topic name
563
+ if resp.success?
564
+ @gapi = resp.data
565
+ return true
566
+ end
567
+ end
568
+ nil
569
+ end
570
+
571
+ ##
572
+ # Call the publish API with arrays of data data and attrs.
573
+ def publish_batch_messages batch
574
+ resp = connection.publish name, batch.messages
575
+ if resp.success?
576
+ batch.to_gcloud_messages resp.data["messageIds"]
577
+ else
578
+ fail ApiError.from_response(resp)
579
+ end
580
+ end
581
+
582
+ ##
583
+ # Batch object used to publish multiple messages at once.
584
+ class Batch
585
+ ##
586
+ # The messages to publish
587
+ attr_reader :messages #:nodoc:
588
+
589
+ ##
590
+ # Create a new instance of the object.
591
+ def initialize data = nil, attributes = {} #:nodoc:
592
+ @messages = []
593
+ @mode = :batch
594
+ return if data.nil?
595
+ @mode = :single
596
+ publish data, attributes
597
+ end
598
+
599
+ ##
600
+ # Add multiple messages to the topic.
601
+ # All messages added will be published at once.
602
+ # See Gcloud::Pubsub::Topic#publish
603
+ def publish data, attributes = {}
604
+ @messages << [data, attributes]
605
+ end
606
+
607
+ ##
608
+ # Create Message objects with message ids.
609
+ def to_gcloud_messages message_ids #:nodoc:
610
+ msgs = @messages.zip(Array(message_ids)).map do |arr, id|
611
+ Message.from_gapi "data" => arr[0],
612
+ "attributes" => jsonify_hash(arr[1]),
613
+ "messageId" => id
614
+ end
615
+ # Return just one Message if a single publish,
616
+ # otherwise return the array of Messages.
617
+ if @mode == :single && msgs.count <= 1
618
+ msgs.first
619
+ else
620
+ msgs
621
+ end
622
+ end
623
+
624
+ protected
625
+
626
+ ##
627
+ # Make the hash look like it was returned from the Cloud API.
628
+ def jsonify_hash hash
629
+ if hash.respond_to? :to_h
630
+ hash = hash.to_h
631
+ else
632
+ hash = Hash.try_convert(hash) || {}
633
+ end
634
+ return hash if hash.empty?
635
+ JSON.parse(JSON.dump(hash))
636
+ end
637
+ end
638
+ end
639
+ end
640
+ end