kitchen-google 3.0.2 → 3.1.1

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7bb68767fd70358c97f209575379ad984b9aab4fd4b3f0377cf1e77b5167796a
4
- data.tar.gz: 83f5317efdf99856679a51a88a95c5d5601dc220989285c54e5771b290b90393
3
+ metadata.gz: 915d1d94065a49854c4c040df37b17d4a1e6853bbffd4872bb6e94d255e070bf
4
+ data.tar.gz: ab9bbf5503260572daf07e281d7087db57110b27ef25fc47f1879cfcc8a1e75d
5
5
  SHA512:
6
- metadata.gz: 59c12a1a8494f2c99df3a5ddb5b68bcb34fde00815e3ed25df084b4e1cb7b96057f030486350870b694ac2cd827fdffc47f94a421c7586118d9338dc7c5870e7
7
- data.tar.gz: 5e1f05a0b84feab76831418aeff5b5900d84a8285fbe3bf0b7b9c046ec958e9e6d46027bb47e493842c3e84f91d2398f094f23058b6f97b82be23a06b86e36bd
6
+ metadata.gz: ddba9a545b204a39596ed608a310e6714976bb1553af5ab18df6fb4bb135f323c18e5cba31097f7ecb603f1366bef4cfbd03c70b761904b26d16183db47d1b39
7
+ data.tar.gz: 66f9605d7a1c529939b3aa1fe0ea375bb1eac6a9689b5ea6cc6427d8f70bf8fdfed63f245f6daab76bca11ef228b52c2e62a989faf1c143ca8511e719f8b1b5b
@@ -21,6 +21,7 @@ require "kitchen"
21
21
  require_relative "gce_version"
22
22
  require_relative "gce/windows_password"
23
23
  require "securerandom" unless defined?(SecureRandom)
24
+ require "time" unless defined?(Time.iso8601)
24
25
  require "timeout" unless defined?(Timeout)
25
26
 
26
27
  module Kitchen
@@ -43,6 +44,12 @@ module Kitchen
43
44
  # @return [Hash] the Test Kitchen state hash for the action in progress
44
45
  attr_accessor :state
45
46
 
47
+ # @!method state=(value)
48
+ # Stores the Test Kitchen state hash for the action in progress.
49
+ #
50
+ # @param value [Hash] the state hash to expose to the helper methods
51
+ # @return [Hash] the assigned state hash
52
+
46
53
  # Maps the short scope aliases accepted by `gcloud` onto the scope
47
54
  # segment of their fully-qualified OAuth 2.0 URL.
48
55
  #
@@ -105,10 +112,18 @@ module Kitchen
105
112
  default_config :metadata, {}
106
113
  default_config :labels, {}
107
114
 
115
+ # Pattern a GCE resource name must match in full. GCE applies the same
116
+ # pattern to instance names and disk names alike.
117
+ #
118
+ # @return [Regexp] the permitted name pattern
119
+ RESOURCE_NAME_REGEX = /(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?)/
120
+
108
121
  # Pattern a GCE disk name must match in full.
109
122
  #
123
+ # @deprecated Use {RESOURCE_NAME_REGEX}, which is the same pattern and
124
+ # says what it actually covers.
110
125
  # @return [Regexp] the permitted disk-name pattern
111
- DISK_NAME_REGEX = /(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?)/
126
+ DISK_NAME_REGEX = RESOURCE_NAME_REGEX
112
127
 
113
128
  # Longest instance name GCE accepts.
114
129
  #
@@ -173,7 +188,8 @@ module Kitchen
173
188
  # before the error is re-raised.
174
189
  #
175
190
  # @param state [Hash] the Test Kitchen state hash, mutated in place with
176
- # `:server_name`, `:hostname` and `:zone`
191
+ # `:server_name`, `:hostname`, `:zone` and `:created_disks`, plus
192
+ # `:password` for Windows guests
177
193
  # @return [void]
178
194
  # @raise [StandardError] if instance creation fails for any reason
179
195
  def create(state)
@@ -182,20 +198,28 @@ module Kitchen
182
198
 
183
199
  validate!
184
200
 
201
+ # The disk configuration has to be settled first: the instance name is
202
+ # budgeted against the longest disk name derived from it.
203
+ create_disks_config
204
+
185
205
  server_name = generate_server_name
186
206
 
187
- create_disks_config
207
+ # Pin the zone before anything billable exists. In region mode it is
208
+ # picked at random, so cleanup would otherwise have no way to know
209
+ # where to look for the disks created below.
210
+ state[:zone] = zone
211
+
212
+ # Record the name before the request goes out, not after it returns.
213
+ # GCE starts billing as soon as the insert is accepted, and it may
214
+ # accept one whose response never reaches us -- an interrupted or
215
+ # timed-out request can leave an instance running that the state file
216
+ # knows nothing about, holding disks that cleanup then cannot delete.
217
+ # `destroy` copes with a name that never became an instance.
218
+ state[:server_name] = server_name
188
219
 
189
220
  info("Creating GCE instance <#{server_name}> in project #{project}, zone #{zone}...")
190
221
  operation = connection.insert_instance(project, zone, create_instance_object(server_name))
191
222
 
192
- # GCE starts billing for the instance as soon as the insert is
193
- # accepted, so record it before waiting on the operation. Anything that
194
- # goes wrong from here on can then be torn down by the rescue below,
195
- # and by `kitchen destroy` if the process does not survive to run it.
196
- state[:server_name] = server_name
197
- state[:zone] = zone
198
-
199
223
  wait_for_operation(operation)
200
224
 
201
225
  state[:hostname] = ip_address_for(server_instance(server_name))
@@ -220,33 +244,101 @@ module Kitchen
220
244
  raise
221
245
  end
222
246
 
223
- # Destroys the GCE instance recorded in the state file.
247
+ # Destroys the GCE instance recorded in the state file, together with any
248
+ # standalone disks an earlier create left behind.
249
+ #
250
+ # An instance that no longer exists in GCE is treated as already
251
+ # destroyed, but the state file is still cleared: a create that fails
252
+ # after `insert_instance` records a server that may never have come into
253
+ # being, and leaving the name behind would make {#create}'s idempotency
254
+ # guard skip every subsequent retry.
224
255
  #
225
- # Does nothing when the state file records no server, or when the
226
- # instance no longer exists in GCE.
256
+ # Disks are deleted after the instance, which holds them until it is
257
+ # gone. A create interrupted before it reached `insert_instance` records
258
+ # no server at all, so they are cleaned up whether or not there is one.
227
259
  #
228
260
  # @param state [Hash] the Test Kitchen state hash, mutated in place to
229
- # remove `:server_name`, `:hostname` and `:zone`
261
+ # remove `:server_name`, `:hostname`, `:zone` and `:created_disks`
230
262
  # @return [void]
231
263
  def destroy(state)
232
264
  @state = state
233
265
  server_name = state[:server_name]
234
- return if server_name.nil?
235
266
 
236
- unless server_exist?(server_name)
237
- info("GCE instance <#{server_name}> does not exist - assuming it has been already destroyed.")
238
- return
267
+ unless server_name.nil?
268
+ if server_exist?(server_name)
269
+ info("Destroying GCE instance <#{server_name}>...")
270
+ wait_for_operation(connection.delete_instance(project, zone, server_name))
271
+ info("GCE instance <#{server_name}> destroyed.")
272
+ else
273
+ info("GCE instance <#{server_name}> does not exist - assuming it has been already destroyed.")
274
+ end
239
275
  end
240
276
 
241
- info("Destroying GCE instance <#{server_name}>...")
242
- wait_for_operation(connection.delete_instance(project, zone, server_name))
243
- info("GCE instance <#{server_name}> destroyed.")
277
+ delete_created_disks
244
278
 
245
279
  state.delete(:server_name)
246
280
  state.delete(:hostname)
247
281
  state.delete(:zone)
248
282
  end
249
283
 
284
+ # Reports whether the instance recorded in the state file is actually
285
+ # running, by asking GCE rather than trusting the state file.
286
+ #
287
+ # Test Kitchen 4 calls this for `kitchen list --live` and the `kitchen
288
+ # status` alias. Earlier versions never call it, so implementing it is
289
+ # safe across the whole supported range.
290
+ #
291
+ # The state file records only what the last action did, which goes stale
292
+ # in both directions: a preemptible instance GCE has reclaimed still
293
+ # reads as "Created", and an instance deleted outside Test Kitchen leaves
294
+ # a server name behind that no longer resolves.
295
+ #
296
+ # @param state [Hash] the Test Kitchen state hash
297
+ # @return [Hash] the status, in the shape `Kitchen::Instance` expects
298
+ def status(state)
299
+ @state = state
300
+ server_name = state[:server_name]
301
+
302
+ return status_report(live: false, state_name: "not created") if server_name.nil?
303
+
304
+ gce_status = server_instance(server_name).status.to_s.downcase
305
+
306
+ status_report(
307
+ live: gce_status == "running",
308
+ state_name: gce_status,
309
+ resource_id: server_name
310
+ )
311
+ rescue Google::Apis::ClientError => e
312
+ debug("API error: #{e.message}")
313
+
314
+ status_report(
315
+ live: false,
316
+ state_name: "not found",
317
+ resource_id: server_name,
318
+ message: "Instance #{server_name} is recorded in the state file but no longer exists " \
319
+ "in project #{project}, zone #{zone}."
320
+ )
321
+ end
322
+
323
+ # Builds a status hash in the shape `Kitchen::Instance` expects.
324
+ #
325
+ # @param live [Boolean] whether the instance is up and usable
326
+ # @param state_name [String] the state to display
327
+ # @param resource_id [String, nil] the GCE instance name
328
+ # @param message [String, nil] anything the user should know
329
+ # @return [Hash] the status
330
+ # @api private
331
+ def status_report(live:, state_name:, resource_id: nil, message: nil)
332
+ {
333
+ live: live,
334
+ state: state_name,
335
+ source: "driver",
336
+ resource_id: resource_id,
337
+ message: message,
338
+ checked_at: Time.now.utc.iso8601,
339
+ }.compact
340
+ end
341
+
250
342
  # Whether the deprecated single-boot-disk options are configured.
251
343
  #
252
344
  # @return [Boolean] true if any of `autodelete_disk`, `disk_size` or
@@ -317,8 +409,9 @@ module Kitchen
317
409
  #
318
410
  # @param disks [Hash] the raw `disks` configuration, keyed by disk name
319
411
  # @return [Hash{Symbol => Hash}] the normalised disk configuration
320
- # @raise [RuntimeError] if a disk name or type is invalid, or more than
321
- # one boot disk is specified
412
+ # @raise [RuntimeError] if a disk name or type is invalid, if more than
413
+ # one boot disk is specified, or if {#assign_boot_disk} finds no disks
414
+ # at all and none eligible to boot
322
415
  # @api private
323
416
  def normalize_disks(disks)
324
417
  normalized = disks.each_with_object({}) do |(disk_name, disk_config), memo|
@@ -333,12 +426,15 @@ module Kitchen
333
426
  # Applies the disk defaults to one disk entry and validates the result.
334
427
  #
335
428
  # @param disk_name [String, Symbol] the disk's name, used in error messages
336
- # @param disk_config [Hash] the user-supplied configuration for this disk
429
+ # @param disk_config [Hash, nil] the user-supplied configuration for this
430
+ # disk. A bare `disk-name:` in `kitchen.yml` parses to nil rather than
431
+ # to an empty hash, and means the same thing: take the defaults.
337
432
  # @return [Hash] the disk configuration with defaults applied
338
433
  # @raise [RuntimeError] if the disk type is invalid, a local SSD is
339
434
  # marked bootable, or a size is given for a local SSD
340
435
  # @api private
341
436
  def normalize_disk(disk_name, disk_config)
437
+ disk_config ||= {}
342
438
  normalized = DISK_DEFAULT_CONFIG.merge(disk_config)
343
439
 
344
440
  unless valid_disk_type?(normalized[:disk_type])
@@ -405,7 +501,8 @@ module Kitchen
405
501
  #
406
502
  # @return [void]
407
503
  # @raise [RuntimeError] if any configured project, zone, region, machine
408
- # type, network, subnet, image or disk setting is invalid
504
+ # type, network, subnet, image or disk setting is invalid, or if the
505
+ # WinRM transport is in use without `email` set
409
506
  def validate!
410
507
  raise "Project #{config[:project]} is not a valid project" unless valid_project?
411
508
  raise "Either zone or region must be specified" unless config[:zone] || config[:region]
@@ -449,7 +546,8 @@ module Kitchen
449
546
 
450
547
  # Application default credentials scoped for Compute Engine.
451
548
  #
452
- # @return [Google::Auth::Credentials] the resolved credentials
549
+ # @return [Signet::OAuth2::Client] the resolved application default
550
+ # credentials
453
551
  def authorization
454
552
  @authorization ||= Google::Auth.get_application_default(
455
553
  [
@@ -502,6 +600,7 @@ module Kitchen
502
600
  # Runs an API call and reports whether it succeeded, swallowing client
503
601
  # errors so callers can use it as a validity predicate.
504
602
  #
603
+ # @param block [Proc] the API call to attempt, called with no arguments
505
604
  # @yield the API call to attempt
506
605
  # @return [Boolean] true if the call succeeded, false on a client error
507
606
  def check_api_call(&block)
@@ -750,16 +849,97 @@ module Kitchen
750
849
  # Builds a unique, GCE-legal instance name, falling back to a UUID when
751
850
  # the Test Kitchen instance name would make it too long.
752
851
  #
852
+ # Uppercase is folded rather than discarded. GCE disallows it, but it
853
+ # still carries meaning, and replacing it with hyphens turned an
854
+ # `inst_name` of `MyTestVM` into `-y-est--` -- a name the user could not
855
+ # recognise as anything they had typed.
856
+ #
753
857
  # @return [String] the instance name
858
+ # @raise [RuntimeError] if no legal name can be salvaged
754
859
  def generate_server_name
755
- name = config[:inst_name] || "tk-#{instance.name.downcase}-#{SecureRandom.hex(3)}"
860
+ name = (config[:inst_name] || "tk-#{instance.name}-#{SecureRandom.hex(3)}").downcase
756
861
 
757
- if name.length > MAX_INSTANCE_NAME_LENGTH
862
+ if name.length > max_server_name_length
758
863
  warn("The TK instance name (#{instance.name}) has been removed from the GCE instance name due to size limitations. Consider setting shorter platform or suite names.")
759
- name = "tk-#{SecureRandom.uuid}"
864
+ name = fallback_server_name
760
865
  end
761
866
 
762
- name.gsub(/([^-a-z0-9])/, "-")
867
+ validated_server_name(name.gsub(/([^-a-z0-9])/, "-"))
868
+ end
869
+
870
+ # Checks a generated instance name against the pattern GCE enforces.
871
+ #
872
+ # Substitution cannot fix every name -- a leading underscore becomes a
873
+ # leading hyphen, which GCE still rejects -- so say so here rather than
874
+ # letting the API refuse a name the user never wrote.
875
+ #
876
+ # @param name [String] the generated name
877
+ # @return [String] the same name, once it is known to be legal
878
+ # @raise [RuntimeError] if the name does not match {RESOURCE_NAME_REGEX}
879
+ # @api private
880
+ def validated_server_name(name)
881
+ return name if name.match?(/\A#{RESOURCE_NAME_REGEX}\z/)
882
+
883
+ raise "Instance name #{name} is not valid. GCE names must match " \
884
+ "#{RESOURCE_NAME_REGEX.source} - they start with a lowercase letter, and end " \
885
+ "with a lowercase letter or a digit."
886
+ end
887
+
888
+ # Longest instance name that still leaves room for every disk named
889
+ # after it.
890
+ #
891
+ # GCE allows {MAX_INSTANCE_NAME_LENGTH} characters for an instance name
892
+ # and the same for a disk name, and {#create_disks} names each disk
893
+ # `"<instance>-<disk>"`. Spending the whole budget on the instance is
894
+ # what let a perfectly legal instance name produce an illegal disk name.
895
+ #
896
+ # @return [Integer] the maximum length of a generated instance name
897
+ # @raise [RuntimeError] if a configured disk name is long enough that
898
+ # fewer than {#fallback_server_name_length} characters remain
899
+ # @api private
900
+ def max_server_name_length
901
+ limit = MAX_INSTANCE_NAME_LENGTH - longest_disk_name_suffix
902
+
903
+ if limit < fallback_server_name_length
904
+ raise "Disk name #{longest_disk_name} is too long. GCE allows " \
905
+ "#{MAX_INSTANCE_NAME_LENGTH} characters for the disk name " \
906
+ "<instance name>-#{longest_disk_name}, which leaves no room for the instance name."
907
+ end
908
+
909
+ limit
910
+ end
911
+
912
+ # The longest disk name configured, which bounds the instance name.
913
+ #
914
+ # @return [String, nil] the longest disk name
915
+ # @api private
916
+ def longest_disk_name
917
+ Array(config[:disks]&.keys).map(&:to_s).max_by(&:length)
918
+ end
919
+
920
+ # Characters the longest disk name costs an instance name, including the
921
+ # hyphen joining the two.
922
+ #
923
+ # @return [Integer] the reserved length
924
+ # @api private
925
+ def longest_disk_name_suffix
926
+ longest_disk_name.nil? ? 0 : longest_disk_name.length + 1
927
+ end
928
+
929
+ # Name used when the Test Kitchen instance name will not fit.
930
+ #
931
+ # @return [String] a UUID-based instance name
932
+ # @api private
933
+ def fallback_server_name
934
+ "tk-#{SecureRandom.uuid}"
935
+ end
936
+
937
+ # Length of a {#fallback_server_name}, which is fixed.
938
+ #
939
+ # @return [Integer] the fallback name's length
940
+ # @api private
941
+ def fallback_server_name_length
942
+ @fallback_server_name_length ||= fallback_server_name.length
763
943
  end
764
944
 
765
945
  # Builds every disk for the instance, creating standalone persistent
@@ -809,10 +989,11 @@ module Kitchen
809
989
  params.source_image = boot_disk_source_image
810
990
  params.disk_name = unique_disk_name
811
991
  else
812
- params.disk_size_gb = disk_size_for_image(disk_config[:disk_size], disk_config[:custom_image])
813
- info("Creating a #{params.disk_size_gb} GB extra disk named #{unique_disk_name} from image #{disk_config[:custom_image]}...")
814
- params.source_image = image_url(disk_config[:custom_image])
992
+ custom_image = disk_config[:custom_image]
993
+ params.source_image = custom_image_url(custom_image)
994
+ params.disk_size_gb = disk_size_for_image(disk_config[:disk_size], custom_image)
815
995
  params.disk_name = unique_disk_name
996
+ info("Creating a #{params.disk_size_gb} GB extra disk named #{unique_disk_name} from image #{custom_image}...")
816
997
  end
817
998
  disk.initialize_params = params
818
999
  disk
@@ -886,21 +1067,28 @@ module Kitchen
886
1067
  attached_disk
887
1068
  end
888
1069
 
889
- # Names of the standalone disks this driver created during the current
890
- # action, tracked so they can be cleaned up if creation fails.
1070
+ # Names of the standalone disks this driver has created, held in the Test
1071
+ # Kitchen state file rather than in memory.
1072
+ #
1073
+ # These disks bill from the moment GCE creates them, which is before the
1074
+ # instance exists, and the driver's own cleanup cannot be relied on to
1075
+ # remove them: `Interrupt` is not a `StandardError`, so pressing Ctrl-C
1076
+ # during a create skips the rescue entirely. Test Kitchen writes the
1077
+ # state file whatever happens, so recording the names there is what makes
1078
+ # a later `kitchen destroy` able to find them.
891
1079
  #
892
1080
  # @return [Array<String>] the created disk names
893
1081
  def created_disk_names
894
- @created_disk_names ||= []
1082
+ state[:created_disks] ||= []
895
1083
  end
896
1084
 
897
- # Deletes every standalone disk created during a failed create, so a
898
- # partial run does not leave billable disks behind.
1085
+ # Deletes every standalone disk this driver created, so a partial run
1086
+ # does not leave billable disks behind.
899
1087
  #
900
1088
  # @return [void]
901
1089
  def delete_created_disks
902
1090
  created_disk_names.each { |disk_name| delete_disk(disk_name) }
903
- created_disk_names.clear
1091
+ state.delete(:created_disks)
904
1092
  end
905
1093
 
906
1094
  # Deletes a standalone persistent disk, tolerating one that is already
@@ -952,13 +1140,44 @@ module Kitchen
952
1140
  "projects/#{image_project}/global/images/#{image}" if image_exist?(image)
953
1141
  end
954
1142
 
1143
+ # URL of a non-boot disk's `custom_image`, which must exist.
1144
+ #
1145
+ # GCE reads a missing `sourceImage` as a request for a blank disk, so an
1146
+ # image the driver cannot resolve would otherwise produce an empty disk
1147
+ # and a successful run -- a test that silently verifies nothing. The boot
1148
+ # disk has been guarded against exactly this in {#validate!} for years.
1149
+ #
1150
+ # The message names the project searched because that is where the
1151
+ # surprise usually is: `custom_image` is resolved in `image_project`, so
1152
+ # booting from a public image such as ubuntu-os-cloud sends the lookup
1153
+ # there rather than to the user's own project.
1154
+ #
1155
+ # @param image [String] the custom image name
1156
+ # @return [String] the image URL
1157
+ # @raise [RuntimeError] if the image does not exist in the image project
1158
+ # @api private
1159
+ def custom_image_url(image)
1160
+ image_url(image) ||
1161
+ raise("Disk image #{image} was not found in project #{image_project} - check the " \
1162
+ "custom_image name, and note that custom images are looked up in image_project.")
1163
+ end
1164
+
955
1165
  # Resolves the current image name for an image family.
956
1166
  #
1167
+ # A family the API cannot see is a configuration mistake like any other,
1168
+ # so report it the way {#validate!} reports an unresolvable `image_name`
1169
+ # rather than letting the Google client's own error escape.
1170
+ #
957
1171
  # @param image_family [String] the image family
958
1172
  # @return [String] the image name
1173
+ # @raise [RuntimeError] if the family does not exist in the image project
959
1174
  def image_name_for_family(image_family)
960
1175
  image = connection.get_image_from_family(image_project, image_family)
961
1176
  image.name
1177
+ rescue Google::Apis::ClientError => e
1178
+ debug("API error: #{e.message}")
1179
+ raise "Image family #{image_family} is not valid - it was not found in project " \
1180
+ "#{image_project}. Check the family name and image_project."
962
1181
  end
963
1182
 
964
1183
  # Partial URL identifying the machine type in the target zone.
@@ -1000,10 +1219,11 @@ module Kitchen
1000
1219
  guest_accelerator_configs
1001
1220
  end
1002
1221
 
1003
- # The instance metadata, merging the driver's own keys over any the user
1004
- # configured and adding a WinRM bootstrap script for Windows guests.
1222
+ # The instance metadata: the driver's own keys, overlaid with any the
1223
+ # user configured, plus a WinRM bootstrap script for Windows guests.
1005
1224
  #
1006
- # @return [Hash{String => String}] the metadata
1225
+ # @return [Hash{String => Object}] the metadata, string-keyed; user
1226
+ # values keep whatever type `kitchen.yml` gave them
1007
1227
  def metadata
1008
1228
  default_metadata = {
1009
1229
  "created-by" => "test-kitchen",
@@ -1018,7 +1238,28 @@ module Kitchen
1018
1238
  end
1019
1239
  end
1020
1240
 
1021
- config[:metadata].merge(default_metadata)
1241
+ default_metadata.merge(user_metadata(default_metadata.keys))
1242
+ end
1243
+
1244
+ # The user's own metadata, keyed the way the GCE API expects.
1245
+ #
1246
+ # Test Kitchen symbolises every key it loads from `kitchen.yml`, so a
1247
+ # user writing `created-by:` reaches the driver as `:"created-by"` and
1248
+ # never as the string the driver sets itself. Merging the two hashes
1249
+ # untouched therefore sends both spellings of the same key, and GCE
1250
+ # rejects the instance outright with "Metadata has duplicate keys".
1251
+ #
1252
+ # @param driver_keys [Array<String>] keys the driver sets itself, warned
1253
+ # about when the user replaces one
1254
+ # @return [Hash{String => Object}] the user's metadata, string-keyed
1255
+ # @api private
1256
+ def user_metadata(driver_keys)
1257
+ metadata = config[:metadata].transform_keys(&:to_s)
1258
+
1259
+ overridden = driver_keys & metadata.keys
1260
+ warn("Overriding metadata set by the driver: #{overridden.join(", ")}") unless overridden.empty?
1261
+
1262
+ metadata
1022
1263
  end
1023
1264
 
1024
1265
  # The metadata in the form the GCE API expects.
@@ -1204,6 +1445,7 @@ module Kitchen
1204
1445
  # logging each status change.
1205
1446
  #
1206
1447
  # @param requested_status [String] the status to wait for
1448
+ # @param block [Proc] called once per poll to re-fetch the resource
1207
1449
  # @yieldreturn [#status] the resource to poll
1208
1450
  # @return [void]
1209
1451
  # @raise [Timeout::Error] if the status is not reached within {#wait_time}
@@ -28,6 +28,6 @@ module Kitchen
28
28
  # release. Keep it on one line.
29
29
  #
30
30
  # @return [String] the gem version
31
- GCE_VERSION = "3.0.2"
31
+ GCE_VERSION = "3.1.1"
32
32
  end
33
33
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kitchen-google
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.2
4
+ version: 3.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Test Kitchen Team
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-08-23 00:00:00.000000000 Z
11
+ date: 2026-10-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: google-apis-compute_v1
@@ -100,6 +100,20 @@ dependencies:
100
100
  - - ">="
101
101
  - !ruby/object:Gem::Version
102
102
  version: '0.1'
103
+ - !ruby/object:Gem::Dependency
104
+ name: time
105
+ requirement: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - ">="
108
+ - !ruby/object:Gem::Version
109
+ version: '0.1'
110
+ type: :runtime
111
+ prerelease: false
112
+ version_requirements: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ version: '0.1'
103
117
  - !ruby/object:Gem::Dependency
104
118
  name: timeout
105
119
  requirement: !ruby/object:Gem::Requirement