google-cloud-pubsub 0.20.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,570 @@
1
+ # Copyright 2015 Google Inc. All rights reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+
16
+ require "google/cloud/errors"
17
+ require "google/cloud/pubsub/subscription/list"
18
+ require "google/cloud/pubsub/received_message"
19
+
20
+ module Google
21
+ module Cloud
22
+ module Pubsub
23
+ ##
24
+ # # Subscription
25
+ #
26
+ # A named resource representing the stream of messages from a single,
27
+ # specific {Topic}, to be delivered to the subscribing application.
28
+ #
29
+ # @example
30
+ # require "google/cloud"
31
+ #
32
+ # gcloud = Google::Cloud.new
33
+ # pubsub = gcloud.pubsub
34
+ #
35
+ # sub = pubsub.subscription "my-topic-sub"
36
+ # msgs = sub.pull
37
+ # msgs.each { |msg| msg.acknowledge! }
38
+ #
39
+ class Subscription
40
+ ##
41
+ # @private The gRPC Service object.
42
+ attr_accessor :service
43
+
44
+ ##
45
+ # @private The gRPC Google::Pubsub::V1::Subscription object.
46
+ attr_accessor :grpc
47
+
48
+ ##
49
+ # @private Create an empty {Subscription} object.
50
+ def initialize
51
+ @service = nil
52
+ @grpc = Google::Pubsub::V1::Subscription.new
53
+ @name = nil
54
+ @exists = nil
55
+ end
56
+
57
+ ##
58
+ # @private New lazy {Topic} object without making an HTTP request.
59
+ def self.new_lazy name, service, options = {}
60
+ new.tap do |s|
61
+ s.grpc = nil
62
+ s.service = service
63
+ s.instance_variable_set "@name",
64
+ service.subscription_path(name, options)
65
+ end
66
+ end
67
+
68
+ ##
69
+ # The name of the subscription.
70
+ def name
71
+ @grpc ? @grpc.name : @name
72
+ end
73
+
74
+ ##
75
+ # The {Topic} from which this subscription receives messages.
76
+ #
77
+ # @return [Topic]
78
+ #
79
+ # @example
80
+ # require "google/cloud"
81
+ #
82
+ # gcloud = Google::Cloud.new
83
+ # pubsub = gcloud.pubsub
84
+ #
85
+ # sub = pubsub.subscription "my-topic-sub"
86
+ # sub.topic.name #=> "projects/my-project/topics/my-topic"
87
+ #
88
+ def topic
89
+ ensure_grpc!
90
+ Topic.new_lazy @grpc.topic, service
91
+ end
92
+
93
+ ##
94
+ # This value is the maximum number of seconds after a subscriber
95
+ # receives a message before the subscriber should acknowledge the
96
+ # message.
97
+ def deadline
98
+ ensure_grpc!
99
+ @grpc.ack_deadline_seconds
100
+ end
101
+
102
+ ##
103
+ # Returns the URL locating the endpoint to which messages should be
104
+ # pushed.
105
+ def endpoint
106
+ ensure_grpc!
107
+ @grpc.push_config.push_endpoint if @grpc.push_config
108
+ end
109
+
110
+ ##
111
+ # Sets the URL locating the endpoint to which messages should be pushed.
112
+ def endpoint= new_endpoint
113
+ ensure_service!
114
+ service.modify_push_config name, new_endpoint, {}
115
+ @grpc.push_config = Google::Pubsub::V1::PushConfig.new(
116
+ push_endpoint: new_endpoint,
117
+ attributes: {}
118
+ ) if @grpc
119
+ end
120
+
121
+ ##
122
+ # Determines whether the subscription exists in the Pub/Sub service.
123
+ #
124
+ # @example
125
+ # require "google/cloud"
126
+ #
127
+ # gcloud = Google::Cloud.new
128
+ # pubsub = gcloud.pubsub
129
+ #
130
+ # sub = pubsub.subscription "my-topic-sub"
131
+ # sub.exists? #=> true
132
+ #
133
+ def exists?
134
+ # Always true if we have a grpc object
135
+ return true unless @grpc.nil?
136
+ # If we have a value, return it
137
+ return @exists unless @exists.nil?
138
+ ensure_grpc!
139
+ @exists = !@grpc.nil?
140
+ rescue Google::Cloud::NotFoundError
141
+ @exists = false
142
+ end
143
+
144
+ ##
145
+ # @private
146
+ # Determines whether the subscription object was created with an
147
+ # HTTP call.
148
+ #
149
+ # @example
150
+ # require "google/cloud"
151
+ #
152
+ # gcloud = Google::Cloud.new
153
+ # pubsub = gcloud.pubsub
154
+ #
155
+ # sub = pubsub.get_subscription "my-topic-sub"
156
+ # sub.lazy? #=> false
157
+ #
158
+ def lazy?
159
+ @grpc.nil?
160
+ end
161
+
162
+ ##
163
+ # Deletes an existing subscription.
164
+ # All pending messages in the subscription are immediately dropped.
165
+ #
166
+ # @return [Boolean] Returns `true` if the subscription was deleted.
167
+ #
168
+ # @example
169
+ # require "google/cloud"
170
+ #
171
+ # gcloud = Google::Cloud.new
172
+ # pubsub = gcloud.pubsub
173
+ #
174
+ # sub = pubsub.subscription "my-topic-sub"
175
+ # sub.delete
176
+ #
177
+ def delete
178
+ ensure_service!
179
+ service.delete_subscription name
180
+ true
181
+ end
182
+
183
+ ##
184
+ # Pulls messages from the server. Returns an empty list if there are no
185
+ # messages available in the backlog. Raises an ApiError with status
186
+ # `UNAVAILABLE` if there are too many concurrent pull requests pending
187
+ # for the given subscription.
188
+ #
189
+ # @param [Boolean] immediate When `true` the system will respond
190
+ # immediately even if it is not able to return messages. When `false`
191
+ # the system is allowed to wait until it can return least one message.
192
+ # No messages are returned when a request times out. The default value
193
+ # is `true`.
194
+ # @param [Integer] max The maximum number of messages to return for this
195
+ # request. The Pub/Sub system may return fewer than the number
196
+ # specified. The default value is `100`, the maximum value is `1000`.
197
+ # @param [Boolean] autoack Automatically acknowledge the message as it
198
+ # is pulled. The default value is `false`.
199
+ #
200
+ # @return [Array<Google::Cloud::Pubsub::ReceivedMessage>]
201
+ #
202
+ # @example
203
+ # require "google/cloud"
204
+ #
205
+ # gcloud = Google::Cloud.new
206
+ # pubsub = gcloud.pubsub
207
+ #
208
+ # sub = pubsub.subscription "my-topic-sub"
209
+ # sub.pull.each { |msg| msg.acknowledge! }
210
+ #
211
+ # @example A maximum number of messages returned can also be specified:
212
+ # require "google/cloud"
213
+ #
214
+ # gcloud = Google::Cloud.new
215
+ # pubsub = gcloud.pubsub
216
+ #
217
+ # sub = pubsub.subscription "my-topic-sub", max: 10
218
+ # sub.pull.each { |msg| msg.acknowledge! }
219
+ #
220
+ # @example The call can block until messages are available:
221
+ # require "google/cloud"
222
+ #
223
+ # gcloud = Google::Cloud.new
224
+ # pubsub = gcloud.pubsub
225
+ #
226
+ # sub = pubsub.subscription "my-topic-sub"
227
+ # msgs = sub.pull immediate: false
228
+ # msgs.each { |msg| msg.acknowledge! }
229
+ #
230
+ def pull immediate: true, max: 100, autoack: false
231
+ ensure_service!
232
+ options = { immediate: immediate, max: max }
233
+ list_grpc = service.pull name, options
234
+ messages = Array(list_grpc.received_messages).map do |msg_grpc|
235
+ ReceivedMessage.from_grpc msg_grpc, self
236
+ end
237
+ acknowledge messages if autoack
238
+ messages
239
+ rescue Google::Cloud::DeadlineExceededError
240
+ []
241
+ end
242
+
243
+ ##
244
+ # Pulls from the server while waiting for messages to become available.
245
+ # This is the same as:
246
+ #
247
+ # subscription.pull immediate: false
248
+ #
249
+ # @param [Integer] max The maximum number of messages to return for this
250
+ # request. The Pub/Sub system may return fewer than the number
251
+ # specified. The default value is `100`, the maximum value is `1000`.
252
+ # @param [Boolean] autoack Automatically acknowledge the message as it
253
+ # is pulled. The default value is `false`.
254
+ #
255
+ # @return [Array<Google::Cloud::Pubsub::ReceivedMessage>]
256
+ #
257
+ # @example
258
+ # require "google/cloud"
259
+ #
260
+ # gcloud = Google::Cloud.new
261
+ # pubsub = gcloud.pubsub
262
+ #
263
+ # sub = pubsub.subscription "my-topic-sub"
264
+ # msgs = sub.wait_for_messages
265
+ # msgs.each { |msg| msg.acknowledge! }
266
+ #
267
+ def wait_for_messages max: 100, autoack: false
268
+ pull immediate: false, max: max, autoack: autoack
269
+ end
270
+
271
+ ##
272
+ # Poll the backend for new messages. This runs a loop to ping the API,
273
+ # blocking indefinitely, yielding retrieved messages as they are
274
+ # received.
275
+ #
276
+ # @param [Integer] max The maximum number of messages to return for this
277
+ # request. The Pub/Sub system may return fewer than the number
278
+ # specified. The default value is `100`, the maximum value is `1000`.
279
+ # @param [Boolean] autoack Automatically acknowledge the message as it
280
+ # is pulled. The default value is `false`.
281
+ # @param [Number] delay The number of seconds to pause between requests
282
+ # when the Google Cloud service has no messages to return. The default
283
+ # value is `1`.
284
+ # @yield [msg] a block for processing new messages
285
+ # @yieldparam [ReceivedMessage] msg the newly received message
286
+ #
287
+ # @example
288
+ # require "google/cloud"
289
+ #
290
+ # gcloud = Google::Cloud.new
291
+ # pubsub = gcloud.pubsub
292
+ #
293
+ # sub = pubsub.subscription "my-topic-sub"
294
+ # sub.listen do |msg|
295
+ # # process msg
296
+ # end
297
+ #
298
+ # @example Limit number of messages pulled per API request with `max`:
299
+ # require "google/cloud"
300
+ #
301
+ # gcloud = Google::Cloud.new
302
+ # pubsub = gcloud.pubsub
303
+ #
304
+ # sub = pubsub.subscription "my-topic-sub"
305
+ # sub.listen max: 20 do |msg|
306
+ # # process msg
307
+ # end
308
+ #
309
+ # @example Automatically acknowledge messages with `autoack`:
310
+ # require "google/cloud"
311
+ #
312
+ # gcloud = Google::Cloud.new
313
+ # pubsub = gcloud.pubsub
314
+ #
315
+ # sub = pubsub.subscription "my-topic-sub"
316
+ # sub.listen autoack: true do |msg|
317
+ # # process msg
318
+ # end
319
+ #
320
+ def listen max: 100, autoack: false, delay: 1
321
+ loop do
322
+ msgs = wait_for_messages max: max, autoack: autoack
323
+ if msgs.any?
324
+ msgs.each { |msg| yield msg }
325
+ else
326
+ sleep delay
327
+ end
328
+ end
329
+ end
330
+
331
+ ##
332
+ # Acknowledges receipt of a message. After an ack,
333
+ # the Pub/Sub system can remove the message from the subscription.
334
+ # Acknowledging a message whose ack deadline has expired may succeed,
335
+ # although the message may have been sent again.
336
+ # Acknowledging a message more than once will not result in an error.
337
+ # This is only used for messages received via pull.
338
+ #
339
+ # @param [ReceivedMessage, String] messages One or more
340
+ # {ReceivedMessage} objects or ack_id values.
341
+ #
342
+ # @example
343
+ # require "google/cloud"
344
+ #
345
+ # gcloud = Google::Cloud.new
346
+ # pubsub = gcloud.pubsub
347
+ #
348
+ # sub = pubsub.subscription "my-topic-sub"
349
+ # messages = sub.pull
350
+ # sub.acknowledge messages
351
+ #
352
+ def acknowledge *messages
353
+ ack_ids = coerce_ack_ids messages
354
+ return true if ack_ids.empty?
355
+ ensure_service!
356
+ service.acknowledge name, *ack_ids
357
+ true
358
+ end
359
+ alias_method :ack, :acknowledge
360
+
361
+ ##
362
+ # Modifies the acknowledge deadline for messages.
363
+ #
364
+ # This indicates that more time is needed to process the messages, or to
365
+ # make the messages available for redelivery if the processing was
366
+ # interrupted.
367
+ #
368
+ # @param [Integer] new_deadline The new ack deadline in seconds from the
369
+ # time this request is sent to the Pub/Sub system. Must be >= 0. For
370
+ # example, if the value is `10`, the new ack deadline will expire 10
371
+ # seconds after the call is made. Specifying `0` may immediately make
372
+ # the message available for another pull request.
373
+ # @param [ReceivedMessage, String] messages One or more
374
+ # {ReceivedMessage} objects or ack_id values.
375
+ #
376
+ # @example
377
+ # require "google/cloud"
378
+ #
379
+ # gcloud = Google::Cloud.new
380
+ # pubsub = gcloud.pubsub
381
+ #
382
+ # sub = pubsub.subscription "my-topic-sub"
383
+ # messages = sub.pull
384
+ # sub.delay 120, messages
385
+ #
386
+ def delay new_deadline, *messages
387
+ ack_ids = coerce_ack_ids messages
388
+ ensure_service!
389
+ service.modify_ack_deadline name, ack_ids, new_deadline
390
+ true
391
+ end
392
+
393
+ ##
394
+ # Gets the [Cloud IAM](https://cloud.google.com/iam/) access control
395
+ # policy for this subscription.
396
+ #
397
+ # @see https://cloud.google.com/pubsub/reference/rpc/google.iam.v1#iampolicy
398
+ # google.iam.v1.IAMPolicy
399
+ #
400
+ # @param [Boolean] force Force the latest policy to be retrieved from
401
+ # the Pub/Sub service when `true`. Otherwise the policy will be
402
+ # memoized to reduce the number of API calls made to the Pub/Sub
403
+ # service. The default is `false`.
404
+ #
405
+ # @yield [policy] A block for updating the policy. The latest policy
406
+ # will be read from the Pub/Sub service and passed to the block. After
407
+ # the block completes, the modified policy will be written to the
408
+ # service.
409
+ # @yieldparam [Policy] policy the current Cloud IAM Policy for this
410
+ # subscription
411
+ #
412
+ # @return [Policy] the current Cloud IAM Policy for this subscription
413
+ #
414
+ # @example Policy values are memoized to reduce the number of API calls:
415
+ # require "google/cloud"
416
+ #
417
+ # gcloud = Google::Cloud.new
418
+ # pubsub = gcloud.pubsub
419
+ # sub = pubsub.subscription "my-subscription"
420
+ #
421
+ # policy = sub.policy # API call
422
+ # policy_2 = sub.policy # No API call
423
+ #
424
+ # @example Use `force` to retrieve the latest policy from the service:
425
+ # require "google/cloud"
426
+ #
427
+ # gcloud = Google::Cloud.new
428
+ # pubsub = gcloud.pubsub
429
+ # sub = pubsub.subscription "my-subscription"
430
+ #
431
+ # policy = sub.policy force: true # API call
432
+ # policy_2 = sub.policy force: true # API call
433
+ #
434
+ # @example Update the policy by passing a block:
435
+ # require "google/cloud"
436
+ #
437
+ # gcloud = Google::Cloud.new
438
+ # pubsub = gcloud.pubsub
439
+ # sub = pubsub.subscription "my-subscription"
440
+ #
441
+ # policy = sub.policy do |p|
442
+ # p.add "roles/owner", "user:owner@example.com"
443
+ # end # 2 API calls
444
+ #
445
+ def policy force: nil
446
+ @policy = nil if force || block_given?
447
+ @policy ||= begin
448
+ ensure_service!
449
+ grpc = service.get_subscription_policy name
450
+ Policy.from_grpc grpc
451
+ end
452
+ return @policy unless block_given?
453
+ p = @policy.deep_dup
454
+ yield p
455
+ self.policy = p
456
+ end
457
+
458
+ ##
459
+ # Updates the [Cloud IAM](https://cloud.google.com/iam/) access control
460
+ # policy for this subscription. The policy should be read from
461
+ # {#policy}. See {Google::Cloud::Pubsub::Policy} for an explanation of
462
+ # the policy `etag` property and how to modify policies.
463
+ #
464
+ # You can also update the policy by passing a block to {#policy}, which
465
+ # will call this method internally after the block completes.
466
+ #
467
+ # @see https://cloud.google.com/pubsub/reference/rpc/google.iam.v1#iampolicy
468
+ # google.iam.v1.IAMPolicy
469
+ #
470
+ # @param [Policy] new_policy a new or modified Cloud IAM Policy for this
471
+ # subscription
472
+ #
473
+ # @example
474
+ # require "google/cloud"
475
+ #
476
+ # gcloud = Google::Cloud.new
477
+ # pubsub = gcloud.pubsub
478
+ # sub = pubsub.subscription "my-subscription"
479
+ #
480
+ # policy = sub.policy # API call
481
+ #
482
+ # policy.add "roles/owner", "user:owner@example.com"
483
+ #
484
+ # sub.policy = policy # API call
485
+ #
486
+ def policy= new_policy
487
+ ensure_service!
488
+ grpc = service.set_subscription_policy name, new_policy.to_grpc
489
+ @policy = Policy.from_grpc grpc
490
+ end
491
+
492
+ ##
493
+ # Tests the specified permissions against the [Cloud
494
+ # IAM](https://cloud.google.com/iam/) access control policy.
495
+ #
496
+ # @see https://cloud.google.com/iam/docs/managing-policies Managing
497
+ # Policies
498
+ #
499
+ # @param [String, Array<String>] permissions The set of permissions to
500
+ # check access for. Permissions with wildcards (such as `*` or
501
+ # `storage.*`) are not allowed.
502
+ #
503
+ # The permissions that can be checked on a subscription are:
504
+ #
505
+ # * pubsub.subscriptions.consume
506
+ # * pubsub.subscriptions.get
507
+ # * pubsub.subscriptions.delete
508
+ # * pubsub.subscriptions.update
509
+ # * pubsub.subscriptions.getIamPolicy
510
+ # * pubsub.subscriptions.setIamPolicy
511
+ #
512
+ # @return [Array<String>] The permissions that have access.
513
+ #
514
+ # @example
515
+ # require "google/cloud"
516
+ #
517
+ # gcloud = Google::Cloud.new
518
+ # pubsub = gcloud.pubsub
519
+ # sub = pubsub.subscription "my-subscription"
520
+ # perms = sub.test_permissions "pubsub.subscriptions.get",
521
+ # "pubsub.subscriptions.consume"
522
+ # perms.include? "pubsub.subscriptions.get" #=> true
523
+ # perms.include? "pubsub.subscriptions.consume" #=> false
524
+ #
525
+ def test_permissions *permissions
526
+ permissions = Array(permissions).flatten
527
+ ensure_service!
528
+ grpc = service.test_subscription_permissions name, permissions
529
+ grpc.permissions
530
+ end
531
+
532
+ ##
533
+ # @private New Subscription from a Google::Pubsub::V1::Subscription
534
+ # object.
535
+ def self.from_grpc grpc, service
536
+ new.tap do |f|
537
+ f.grpc = grpc
538
+ f.service = service
539
+ end
540
+ end
541
+
542
+ protected
543
+
544
+ ##
545
+ # @private Raise an error unless an active connection to the service is
546
+ # available.
547
+ def ensure_service!
548
+ fail "Must have active connection to service" unless service
549
+ end
550
+
551
+ ##
552
+ # Ensures a Google::Pubsub::V1::Subscription object exists.
553
+ def ensure_grpc!
554
+ ensure_service!
555
+ return @grpc if @grpc
556
+ @grpc = service.get_subscription @name
557
+ end
558
+
559
+ ##
560
+ # Makes sure the values are the `ack_id`. If given several
561
+ # {ReceivedMessage} objects extract the `ack_id` values.
562
+ def coerce_ack_ids messages
563
+ Array(messages).flatten.map do |msg|
564
+ msg.respond_to?(:ack_id) ? msg.ack_id : msg.to_s
565
+ end
566
+ end
567
+ end
568
+ end
569
+ end
570
+ end