google-cloud-pubsub 0.20.0

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