gcloud 0.1.1 → 0.2.0

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