google-cloud-pubsub 2.2.0 → 2.5.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.
@@ -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