google-cloud-pubsub 2.2.0 → 2.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -26,7 +26,10 @@ module Google
26
26
  ##
27
27
  # @private Represents the Pub/Sub service API, including IAM mixins.
28
28
  class Service
29
- attr_accessor :project, :credentials, :host, :timeout
29
+ attr_accessor :project
30
+ attr_accessor :credentials
31
+ attr_accessor :host
32
+ attr_accessor :timeout
30
33
  ###
31
34
  # The same client_id is used across all streaming pull connections that are created by this client. This is
32
35
  # intentional, as it indicates to the server that any guarantees, such as message ordering, made for a stream
@@ -46,49 +49,56 @@ module Google
46
49
 
47
50
  def subscriber
48
51
  return mocked_subscriber if mocked_subscriber
49
- @subscriber ||= begin
50
- V1::Subscriber::Client.new do |config|
51
- config.credentials = credentials if credentials
52
- config.timeout = timeout if timeout
53
- config.endpoint = host if host
54
- config.lib_name = "gccl"
55
- config.lib_version = Google::Cloud::PubSub::VERSION
56
- config.metadata = { "google-cloud-resource-prefix": "projects/#{@project}" }
57
- end
52
+ @subscriber ||= V1::Subscriber::Client.new do |config|
53
+ config.credentials = credentials if credentials
54
+ config.timeout = timeout if timeout
55
+ config.endpoint = host if host
56
+ config.lib_name = "gccl"
57
+ config.lib_version = Google::Cloud::PubSub::VERSION
58
+ config.metadata = { "google-cloud-resource-prefix": "projects/#{@project}" }
58
59
  end
59
60
  end
60
61
  attr_accessor :mocked_subscriber
61
62
 
62
63
  def publisher
63
64
  return mocked_publisher if mocked_publisher
64
- @publisher ||= begin
65
- V1::Publisher::Client.new do |config|
66
- config.credentials = credentials if credentials
67
- config.timeout = timeout if timeout
68
- config.endpoint = host if host
69
- config.lib_name = "gccl"
70
- config.lib_version = Google::Cloud::PubSub::VERSION
71
- config.metadata = { "google-cloud-resource-prefix": "projects/#{@project}" }
72
- end
65
+ @publisher ||= V1::Publisher::Client.new do |config|
66
+ config.credentials = credentials if credentials
67
+ config.timeout = timeout if timeout
68
+ config.endpoint = host if host
69
+ config.lib_name = "gccl"
70
+ config.lib_version = Google::Cloud::PubSub::VERSION
71
+ config.metadata = { "google-cloud-resource-prefix": "projects/#{@project}" }
73
72
  end
74
73
  end
75
74
  attr_accessor :mocked_publisher
76
75
 
77
76
  def iam
78
77
  return mocked_iam if mocked_iam
79
- @iam ||= begin
80
- V1::IAMPolicy::Client.new do |config|
81
- config.credentials = credentials if credentials
82
- config.timeout = timeout if timeout
83
- config.endpoint = host if host
84
- config.lib_name = "gccl"
85
- config.lib_version = Google::Cloud::PubSub::VERSION
86
- config.metadata = { "google-cloud-resource-prefix" => "projects/#{@project}" }
87
- end
78
+ @iam ||= V1::IAMPolicy::Client.new do |config|
79
+ config.credentials = credentials if credentials
80
+ config.timeout = timeout if timeout
81
+ config.endpoint = host if host
82
+ config.lib_name = "gccl"
83
+ config.lib_version = Google::Cloud::PubSub::VERSION
84
+ config.metadata = { "google-cloud-resource-prefix" => "projects/#{@project}" }
88
85
  end
89
86
  end
90
87
  attr_accessor :mocked_iam
91
88
 
89
+ def schemas
90
+ return mocked_schemas if mocked_schemas
91
+ @schemas ||= V1::SchemaService::Client.new do |config|
92
+ config.credentials = credentials if credentials
93
+ config.timeout = timeout if timeout
94
+ config.endpoint = host if host
95
+ config.lib_name = "gccl"
96
+ config.lib_version = Google::Cloud::PubSub::VERSION
97
+ config.metadata = { "google-cloud-resource-prefix" => "projects/#{@project}" }
98
+ end
99
+ end
100
+ attr_accessor :mocked_schemas
101
+
92
102
  ##
93
103
  # Gets the configuration of a topic.
94
104
  # Since the topic only has the name attribute,
@@ -111,18 +121,35 @@ module Google
111
121
 
112
122
  ##
113
123
  # Creates the given topic with the given name.
114
- def create_topic topic_name, labels: nil, kms_key_name: nil, persistence_regions: nil, options: {}
124
+ def create_topic topic_name,
125
+ labels: nil,
126
+ kms_key_name: nil,
127
+ persistence_regions: nil,
128
+ schema_name: nil,
129
+ message_encoding: nil,
130
+ options: {}
115
131
  if persistence_regions
116
- message_storage_policy = {
132
+ message_storage_policy = Google::Cloud::PubSub::V1::MessageStoragePolicy.new(
117
133
  allowed_persistence_regions: Array(persistence_regions)
118
- }
134
+ )
135
+ end
136
+
137
+ if schema_name || message_encoding
138
+ unless schema_name && message_encoding
139
+ raise ArgumentError, "Schema settings must include both schema_name and message_encoding."
140
+ end
141
+ schema_settings = Google::Cloud::PubSub::V1::SchemaSettings.new(
142
+ schema: schema_path(schema_name),
143
+ encoding: message_encoding.to_s.upcase
144
+ )
119
145
  end
120
146
 
121
147
  publisher.create_topic \
122
148
  name: topic_path(topic_name, options),
123
149
  labels: labels,
124
150
  kms_key_name: kms_key_name,
125
- message_storage_policy: message_storage_policy
151
+ message_storage_policy: message_storage_policy,
152
+ schema_settings: schema_settings
126
153
  end
127
154
 
128
155
  def update_topic topic_obj, *fields
@@ -294,6 +321,89 @@ module Google
294
321
  end
295
322
  end
296
323
 
324
+ ##
325
+ # Lists schemas in the current (or given) project.
326
+ # @param view [String, Symbol, nil] Possible values:
327
+ # * `BASIC` - Include the name and type of the schema, but not the definition.
328
+ # * `FULL` - Include all Schema object fields.
329
+ #
330
+ def list_schemas view, options = {}
331
+ schema_view = Google::Cloud::PubSub::V1::SchemaView.const_get view.to_s.upcase
332
+ paged_enum = schemas.list_schemas parent: project_path(options),
333
+ view: schema_view,
334
+ page_size: options[:max],
335
+ page_token: options[:token]
336
+
337
+ paged_enum.response
338
+ end
339
+
340
+ ##
341
+ # Creates a schema in the current (or given) project.
342
+ def create_schema schema_id, type, definition, options = {}
343
+ schema = Google::Cloud::PubSub::V1::Schema.new(
344
+ type: type,
345
+ definition: definition
346
+ )
347
+ schemas.create_schema parent: project_path(options),
348
+ schema: schema,
349
+ schema_id: schema_id
350
+ end
351
+
352
+ ##
353
+ # Gets the details of a schema.
354
+ # @param view [String, Symbol, nil] The set of fields to return in the response. Possible values:
355
+ # * `BASIC` - Include the name and type of the schema, but not the definition.
356
+ # * `FULL` - Include all Schema object fields.
357
+ #
358
+ def get_schema schema_name, view, options = {}
359
+ schema_view = Google::Cloud::PubSub::V1::SchemaView.const_get view.to_s.upcase
360
+ schemas.get_schema name: schema_path(schema_name, options),
361
+ view: schema_view
362
+ end
363
+
364
+ ##
365
+ # Delete a schema.
366
+ def delete_schema schema_name
367
+ schemas.delete_schema name: schema_path(schema_name)
368
+ end
369
+
370
+ ##
371
+ # Validate the definition string intended for a schema.
372
+ def validate_schema type, definition, options = {}
373
+ schema = Google::Cloud::PubSub::V1::Schema.new(
374
+ type: type,
375
+ definition: definition
376
+ )
377
+ schemas.validate_schema parent: project_path(options),
378
+ schema: schema
379
+ end
380
+
381
+ ##
382
+ # Validates a message against a schema.
383
+ #
384
+ # @param message_data [String] Message to validate against the provided `schema_spec`.
385
+ # @param message_encoding [Google::Cloud::PubSub::V1::Encoding] The encoding expected for messages.
386
+ # @param schema_name [String] Name of the schema against which to validate.
387
+ # @param project [String] Name of the project if not the default project.
388
+ # @param type [String] Ad-hoc schema type against which to validate.
389
+ # @param definition [String] Ad-hoc schema definition against which to validate.
390
+ #
391
+ def validate_message message_data, message_encoding, schema_name: nil, project: nil, type: nil, definition: nil
392
+ if type && definition
393
+ schema = Google::Cloud::PubSub::V1::Schema.new(
394
+ type: type,
395
+ definition: definition
396
+ )
397
+ end
398
+ schemas.validate_message parent: project_path(project: project),
399
+ name: schema_path(schema_name),
400
+ schema: schema,
401
+ message: message_data,
402
+ encoding: message_encoding
403
+ end
404
+
405
+ # Helper methods
406
+
297
407
  def get_topic_policy topic_name, options = {}
298
408
  iam.get_iam_policy resource: topic_path(topic_name, options)
299
409
  end
@@ -338,6 +448,11 @@ module Google
338
448
  "#{project_path options}/snapshots/#{snapshot_name}"
339
449
  end
340
450
 
451
+ def schema_path schema_name, options = {}
452
+ return schema_name if schema_name.nil? || schema_name.to_s.include?("/")
453
+ "#{project_path options}/schemas/#{schema_name}"
454
+ end
455
+
341
456
  def inspect
342
457
  "#<#{self.class.name} (#{@project})>"
343
458
  end
@@ -15,6 +15,7 @@
15
15
 
16
16
  require "google/cloud/errors"
17
17
  require "google/cloud/pubsub/snapshot/list"
18
+ require "google/cloud/pubsub/v1"
18
19
 
19
20
  module Google
20
21
  module Cloud
@@ -58,8 +59,10 @@ module Google
58
59
  end
59
60
 
60
61
  ##
61
- # The name of the snapshot. Format is
62
- # `projects/{project}/snapshots/{snap}`.
62
+ # The name of the snapshot.
63
+ #
64
+ # @return [String] A fully-qualified snapshot name in the form
65
+ # `projects/{project_id}/snapshots/{snapshot_id}`.
63
66
  def name
64
67
  @grpc.name
65
68
  end
@@ -125,12 +125,12 @@ module Google
125
125
  # puts snapshot.name
126
126
  # end
127
127
  #
128
- def all request_limit: nil
128
+ def all request_limit: nil, &block
129
129
  request_limit = request_limit.to_i if request_limit
130
130
  return enum_for :all, request_limit: request_limit unless block_given?
131
131
  results = self
132
132
  loop do
133
- results.each { |r| yield r }
133
+ results.each(&block)
134
134
  if request_limit
135
135
  request_limit -= 1
136
136
  break if request_limit.negative?
@@ -64,8 +64,13 @@ module Google
64
64
  class Subscriber
65
65
  include MonitorMixin
66
66
 
67
- attr_reader :subscription_name, :callback, :deadline, :streams, :message_ordering, :callback_threads,
68
- :push_threads
67
+ attr_reader :subscription_name
68
+ attr_reader :callback
69
+ attr_reader :deadline
70
+ attr_reader :streams
71
+ attr_reader :message_ordering
72
+ attr_reader :callback_threads
73
+ attr_reader :push_threads
69
74
 
70
75
  ##
71
76
  # @private Implementation attributes.
@@ -306,6 +311,17 @@ module Google
306
311
  # @deprecated Use {#max_outstanding_bytes}.
307
312
  alias inventory_bytesize max_outstanding_bytes
308
313
 
314
+ ##
315
+ # Whether to enforce flow control at the client side only or to enforce it at both the client and
316
+ # the server. For more details about flow control see https://cloud.google.com/pubsub/docs/pull#config.
317
+ #
318
+ # @return [Boolean] `true` when only client side flow control is enforced, `false` when both client and
319
+ # server side flow control are enforced.
320
+ #
321
+ def use_legacy_flow_control?
322
+ @inventory[:use_legacy_flow_control]
323
+ end
324
+
309
325
  ##
310
326
  # The number of seconds that received messages can be held awaiting processing. Default is 3,600 (1 hour).
311
327
  #
@@ -334,7 +350,8 @@ module Google
334
350
  limit: @inventory[:max_outstanding_messages].fdiv(@streams).ceil,
335
351
  bytesize: @inventory[:max_outstanding_bytes].fdiv(@streams).ceil,
336
352
  extension: @inventory[:max_total_lease_duration],
337
- max_duration_per_lease_extension: @inventory[:max_duration_per_lease_extension]
353
+ max_duration_per_lease_extension: @inventory[:max_duration_per_lease_extension],
354
+ use_legacy_flow_control: @inventory[:use_legacy_flow_control]
338
355
  }
339
356
  end
340
357
 
@@ -377,6 +394,7 @@ module Google
377
394
  @inventory[:max_outstanding_bytes] = Integer(@inventory[:max_outstanding_bytes] || 100_000_000)
378
395
  @inventory[:max_total_lease_duration] = Integer(@inventory[:max_total_lease_duration] || 3600)
379
396
  @inventory[:max_duration_per_lease_extension] = Integer(@inventory[:max_duration_per_lease_extension] || 0)
397
+ @inventory[:use_legacy_flow_control] = @inventory[:use_legacy_flow_control] || false
380
398
  end
381
399
 
382
400
  def default_error_callbacks
@@ -30,15 +30,22 @@ module Google
30
30
 
31
31
  include MonitorMixin
32
32
 
33
- attr_reader :stream, :limit, :bytesize, :extension, :max_duration_per_lease_extension
34
-
35
- def initialize stream, limit:, bytesize:, extension:, max_duration_per_lease_extension:
33
+ attr_reader :stream
34
+ attr_reader :limit
35
+ attr_reader :bytesize
36
+ attr_reader :extension
37
+ attr_reader :max_duration_per_lease_extension
38
+ attr_reader :use_legacy_flow_control
39
+
40
+ def initialize stream, limit:, bytesize:, extension:, max_duration_per_lease_extension:,
41
+ use_legacy_flow_control:
36
42
  super()
37
43
  @stream = stream
38
44
  @limit = limit
39
45
  @bytesize = bytesize
40
46
  @extension = extension
41
47
  @max_duration_per_lease_extension = max_duration_per_lease_extension
48
+ @use_legacy_flow_control = use_legacy_flow_control
42
49
  @inventory = {}
43
50
  @wait_cond = new_cond
44
51
  end
@@ -58,7 +58,7 @@ module Google
58
58
  @paused = nil
59
59
  @pause_cond = new_cond
60
60
 
61
- @inventory = Inventory.new self, @subscriber.stream_inventory
61
+ @inventory = Inventory.new self, **@subscriber.stream_inventory
62
62
 
63
63
  @sequencer = Sequencer.new(&method(:perform_callback_async)) if subscriber.message_ordering
64
64
 
@@ -363,8 +363,8 @@ module Google
363
363
  req.modify_deadline_ack_ids += @inventory.ack_ids
364
364
  req.modify_deadline_seconds += @inventory.ack_ids.map { @subscriber.deadline }
365
365
  req.client_id = @subscriber.service.client_id
366
- req.max_outstanding_messages = @inventory.limit
367
- req.max_outstanding_bytes = @inventory.bytesize
366
+ req.max_outstanding_messages = @inventory.use_legacy_flow_control ? 0 : @inventory.limit
367
+ req.max_outstanding_bytes = @inventory.use_legacy_flow_control ? 0 : @inventory.bytesize
368
368
  end
369
369
  end
370
370
 
@@ -25,7 +25,8 @@ module Google
25
25
  class TimedUnaryBuffer
26
26
  include MonitorMixin
27
27
 
28
- attr_reader :max_bytes, :interval
28
+ attr_reader :max_bytes
29
+ attr_reader :interval
29
30
 
30
31
  def initialize subscriber, max_bytes: 500_000, interval: 1.0
31
32
  super() # to init MonitorMixin
@@ -138,7 +139,7 @@ module Google
138
139
  end
139
140
 
140
141
  groups = prev_reg.each_pair.group_by { |_ack_id, delay| delay }
141
- req_hash = Hash[groups.map { |k, v| [k, v.map(&:first)] }]
142
+ req_hash = groups.transform_values { |v| v.map(&:first) }
142
143
 
143
144
  requests = { acknowledge: [] }
144
145
  ack_ids = Array(req_hash.delete(:ack)) # ack has no deadline set
@@ -215,11 +216,9 @@ module Google
215
216
 
216
217
  def add_future pool
217
218
  Concurrent::Promises.future_on pool do
218
- begin
219
- yield
220
- rescue StandardError => e
221
- error! e
222
- end
219
+ yield
220
+ rescue StandardError => e
221
+ error! e
223
222
  end
224
223
  end
225
224
  end
@@ -77,7 +77,9 @@ module Google
77
77
  ##
78
78
  # The name of the subscription.
79
79
  #
80
- # @return [String]
80
+ # @return [String] A fully-qualified subscription name in the form
81
+ # `projects/{project_id}/subscriptions/{subscription_id}`.
82
+ #
81
83
  def name
82
84
  return @resource_name if reference?
83
85
  @grpc.name
@@ -330,7 +332,7 @@ module Google
330
332
  # If {#expires_in=} is not set, a *default* value of of 31 days will be
331
333
  # used. The minimum allowed value is 1 day.
332
334
  #
333
- # Makes an API call to retrieve the value when called on a
335
+ # Makes an API call to retrieve the expires_in value when called on a
334
336
  # reference object. See {#reference?}.
335
337
  #
336
338
  # @return [Numeric, nil] The expiration duration, or `nil` if unset.
@@ -365,6 +367,9 @@ module Google
365
367
  # `attributes` field matches the filter are delivered on this subscription. If empty, then no messages are
366
368
  # filtered out.
367
369
  #
370
+ # Makes an API call to retrieve the filter value when called on a reference
371
+ # object. See {#reference?}.
372
+ #
368
373
  # @return [String] The frozen filter string.
369
374
  #
370
375
  def filter
@@ -409,6 +414,9 @@ module Google
409
414
  # The operation will fail if the topic does not exist. Users should ensure that there is a subscription attached
410
415
  # to this topic since messages published to a topic with no subscriptions are lost.
411
416
  #
417
+ # Makes an API call to retrieve the dead_letter_policy value when called on a
418
+ # reference object. See {#reference?}.
419
+ #
412
420
  # See also {#dead_letter_topic}, {#dead_letter_max_delivery_attempts=}, {#dead_letter_max_delivery_attempts}
413
421
  # and {#remove_dead_letter_policy}.
414
422
  #
@@ -448,7 +456,7 @@ module Google
448
456
  # See also {#dead_letter_max_delivery_attempts=}, {#dead_letter_topic=}, {#dead_letter_topic}
449
457
  # and {#remove_dead_letter_policy}.
450
458
  #
451
- # Makes an API call to retrieve the value when called on a reference object. See {#reference?}.
459
+ # Makes an API call to retrieve the dead_letter_policy when called on a reference object. See {#reference?}.
452
460
  #
453
461
  # @return [Integer, nil] A value between `5` and `100`, or `nil` if no dead letter policy is configured.
454
462
  #
@@ -477,6 +485,8 @@ module Google
477
485
  #
478
486
  # This field will be honored on a best effort basis. If this parameter is 0, a default value of 5 is used.
479
487
  #
488
+ # Makes an API call to retrieve the dead_letter_policy when called on a reference object. See {#reference?}.
489
+ #
480
490
  # The dead letter topic must be set first. See {#dead_letter_topic=}, {#dead_letter_topic} and
481
491
  # {#remove_dead_letter_policy}.
482
492
  #
@@ -512,6 +522,8 @@ module Google
512
522
  # Removes an existing dead letter policy. A dead letter policy specifies the conditions for dead lettering
513
523
  # messages in the subscription. If a dead letter policy is not set, dead lettering is disabled.
514
524
  #
525
+ # Makes an API call to retrieve the dead_letter_policy when called on a reference object. See {#reference?}.
526
+ #
515
527
  # See {#dead_letter_topic}, {#dead_letter_topic=}, {#dead_letter_max_delivery_attempts} and
516
528
  # {#dead_letter_max_delivery_attempts=}.
517
529
  #
@@ -547,8 +559,7 @@ module Google
547
559
  # for healthy subscribers. Retry Policy will be triggered on NACKs or acknowledgement deadline exceeded events
548
560
  # for a given message.
549
561
  #
550
- # **EXPERIMENTAL:** This API might be changed in backward-incompatible ways and is not recommended for
551
- # production use. It is not subject to any SLA or deprecation policy.
562
+ # Makes an API call to retrieve the retry_policy when called on a reference object. See {#reference?}.
552
563
  #
553
564
  # @return [RetryPolicy, nil] The retry policy for the subscription, or `nil`.
554
565
  #
@@ -576,9 +587,6 @@ module Google
576
587
  # for healthy subscribers. Retry Policy will be triggered on NACKs or acknowledgement deadline exceeded events
577
588
  # for a given message.
578
589
  #
579
- # **EXPERIMENTAL:** This API might be changed in backward-incompatible ways and is not recommended for
580
- # production use. It is not subject to any SLA or deprecation policy.
581
- #
582
590
  # @param [RetryPolicy, nil] new_retry_policy A new retry policy for the subscription, or `nil`.
583
591
  #
584
592
  # @example
@@ -594,10 +602,11 @@ module Google
594
602
  # sub.retry_policy.maximum_backoff #=> 300
595
603
  #
596
604
  def retry_policy= new_retry_policy
597
- ensure_grpc!
605
+ ensure_service!
598
606
  new_retry_policy = new_retry_policy.to_grpc if new_retry_policy
599
607
  update_grpc = Google::Cloud::PubSub::V1::Subscription.new name: name, retry_policy: new_retry_policy
600
608
  @grpc = service.update_subscription update_grpc, :retry_policy
609
+ @resource_name = nil
601
610
  end
602
611
 
603
612
  ##
@@ -629,7 +638,7 @@ module Google
629
638
  #
630
639
  # See {Topic#subscribe} and {#detach}.
631
640
  #
632
- # Makes an API call to retrieve the value when called on a
641
+ # Makes an API call to retrieve the detached value when called on a
633
642
  # reference object. See {#reference?}.
634
643
  #
635
644
  # @return [Boolean]
@@ -870,6 +879,9 @@ module Google
870
879
  # Default is 1,000. (Note: replaces `:limit`, which is deprecated.)
871
880
  # * `:max_outstanding_bytes` [Integer] The total byte size of received messages to be collected by
872
881
  # subscriber. Default is 100,000,000 (100MB). (Note: replaces `:bytesize`, which is deprecated.)
882
+ # * `:use_legacy_flow_control` [Boolean] Disables enforcing flow control settings at the Cloud PubSub
883
+ # server and the less accurate method of only enforcing flow control at the client side is used instead.
884
+ # Default is false.
873
885
  # * `:max_total_lease_duration` [Integer] The number of seconds that received messages can be held awaiting
874
886
  # processing. Default is 3,600 (1 hour). (Note: replaces `:extension`, which is deprecated.)
875
887
  # * `:max_duration_per_lease_extension` [Integer] The maximum amount of time in seconds for a single lease
@@ -1054,14 +1066,19 @@ module Google
1054
1066
  # * Any messages published to the subscription's topic following the
1055
1067
  # successful completion of the `create_snapshot` operation.
1056
1068
  #
1057
- # @param [String, nil] snapshot_name Name of the new snapshot. If the
1058
- # name is not provided, the server will assign a random name
1059
- # for this snapshot on the same project as the subscription. The
1060
- # format is `projects/{project}/snapshots/{snap}`. The name must start
1061
- # with a letter, and contain only letters ([A-Za-z]), numbers
1062
- # ([0-9], dashes (-), underscores (_), periods (.), tildes (~), plus
1063
- # (+) or percent signs (%). It must be between 3 and 255 characters in
1064
- # length, and it must not start with "goog". Optional.
1069
+ # @param [String, nil] snapshot_name Name of the new snapshot. Optional.
1070
+ # If the name is not provided, the server will assign a random name
1071
+ # for this snapshot on the same project as the subscription.
1072
+ # The value can be a simple snapshot ID (relative name), in which
1073
+ # case the current project ID will be supplied, or a fully-qualified
1074
+ # snapshot name in the form
1075
+ # `projects/{project_id}/snapshots/{snapshot_id}`.
1076
+ #
1077
+ # The snapshot ID (relative name) must start with a letter, and
1078
+ # contain only letters (`[A-Za-z]`), numbers (`[0-9]`), dashes (`-`),
1079
+ # underscores (`_`), periods (`.`), tildes (`~`), plus (`+`) or percent
1080
+ # signs (`%`). It must be between 3 and 255 characters in length, and
1081
+ # it must not start with `goog`.
1065
1082
  # @param [Hash] labels A hash of user-provided labels associated with
1066
1083
  # the snapshot. You can use these to organize and group your
1067
1084
  # snapshots. Label keys and values can be no longer than 63