kitchen-google 0.3.0 → 1.0.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.
- checksums.yaml +4 -4
- data/.travis.yml +1 -1
- data/CHANGELOG.md +14 -0
- data/Gemfile +3 -1
- data/README.md +215 -88
- data/Rakefile +9 -3
- data/kitchen-google.gemspec +22 -20
- data/lib/kitchen/driver/gce.rb +457 -97
- data/lib/kitchen/driver/gce_version.rb +23 -0
- data/spec/kitchen/driver/gce_spec.rb +839 -276
- data/spec/spec_helper.rb +2 -2
- metadata +36 -46
data/lib/kitchen/driver/gce.rb
CHANGED
@@ -1,8 +1,9 @@
|
|
1
1
|
# -*- coding: utf-8 -*-
|
2
2
|
#
|
3
3
|
# Author:: Andrew Leonard (<andy@hurricane-ridge.com>)
|
4
|
+
# Author:: Chef Partner Engineering (<partnereng@chef.io>)
|
4
5
|
#
|
5
|
-
# Copyright (C) 2013-
|
6
|
+
# Copyright (C) 2013-2016, Andrew Leonard and Chef Software, Inc.
|
6
7
|
#
|
7
8
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
8
9
|
# you may not use this file except in compliance with the License.
|
@@ -16,154 +17,513 @@
|
|
16
17
|
# See the License for the specific language governing permissions and
|
17
18
|
# limitations under the License.
|
18
19
|
|
19
|
-
require
|
20
|
-
require
|
21
|
-
|
22
|
-
require
|
20
|
+
require "gcewinpass"
|
21
|
+
require "google/apis/compute_v1"
|
22
|
+
require "kitchen"
|
23
|
+
require "kitchen/driver/gce_version"
|
24
|
+
require "securerandom"
|
23
25
|
|
24
26
|
module Kitchen
|
25
27
|
module Driver
|
26
28
|
# Google Compute Engine driver for Test Kitchen
|
27
29
|
#
|
28
30
|
# @author Andrew Leonard <andy@hurricane-ridge.com>
|
29
|
-
class Gce < Kitchen::Driver::
|
30
|
-
|
31
|
+
class Gce < Kitchen::Driver::Base
|
32
|
+
attr_accessor :state
|
33
|
+
|
34
|
+
SCOPE_ALIAS_MAP = {
|
35
|
+
"bigquery" => "bigquery",
|
36
|
+
"cloud-platform" => "cloud-platform",
|
37
|
+
"compute-ro" => "compute.readonly",
|
38
|
+
"compute-rw" => "compute",
|
39
|
+
"datastore" => "datastore",
|
40
|
+
"logging-write" => "logging.write",
|
41
|
+
"monitoring" => "monitoring",
|
42
|
+
"monitoring-write" => "monitoring.write",
|
43
|
+
"service-control" => "servicecontrol",
|
44
|
+
"service-management" => "service.management",
|
45
|
+
"sql" => "sqlservice",
|
46
|
+
"sql-admin" => "sqlservice.admin",
|
47
|
+
"storage-full" => "devstorage.full_control",
|
48
|
+
"storage-ro" => "devstorage.read_only",
|
49
|
+
"storage-rw" => "devstorage.read_write",
|
50
|
+
"taskqueue" => "taskqueue",
|
51
|
+
"useraccounts-ro" => "cloud.useraccounts.readonly",
|
52
|
+
"useraccounts-rw" => "cloud.useraccounts",
|
53
|
+
"userinfo-email" => "userinfo.email",
|
54
|
+
}
|
55
|
+
|
56
|
+
kitchen_driver_api_version 2
|
57
|
+
plugin_version Kitchen::Driver::GCE_VERSION
|
58
|
+
|
59
|
+
required_config :project
|
60
|
+
required_config :image_name
|
61
|
+
|
62
|
+
default_config :region, nil
|
63
|
+
default_config :zone, nil
|
64
|
+
|
31
65
|
default_config :autodelete_disk, true
|
32
66
|
default_config :disk_size, 10
|
33
|
-
default_config :
|
34
|
-
default_config :
|
67
|
+
default_config :disk_type, "pd-standard"
|
68
|
+
default_config :machine_type, "n1-standard-1"
|
69
|
+
default_config :network, "default"
|
35
70
|
default_config :inst_name, nil
|
36
|
-
default_config :
|
71
|
+
default_config :service_account_name, "default"
|
72
|
+
default_config :service_account_scopes, []
|
37
73
|
default_config :tags, []
|
38
|
-
default_config :username, ENV['USER']
|
39
|
-
default_config :zone_name, nil
|
40
|
-
default_config :google_key_location, nil
|
41
|
-
default_config :google_json_key_location, nil
|
42
74
|
default_config :preemptible, false
|
43
75
|
default_config :auto_restart, false
|
76
|
+
default_config :auto_migrate, false
|
77
|
+
default_config :image_project, nil
|
78
|
+
default_config :email, nil
|
79
|
+
default_config :use_private_ip, false
|
80
|
+
default_config :wait_time, 600
|
81
|
+
default_config :refresh_rate, 2
|
44
82
|
|
45
|
-
|
46
|
-
|
47
|
-
|
83
|
+
def name
|
84
|
+
"Google Compute (GCE)"
|
85
|
+
end
|
48
86
|
|
49
87
|
def create(state)
|
50
|
-
|
88
|
+
@state = state
|
89
|
+
return if state[:server_name]
|
51
90
|
|
52
|
-
|
53
|
-
state[:server_id] = instance.identity
|
91
|
+
validate!
|
54
92
|
|
55
|
-
|
93
|
+
server_name = generate_server_name
|
56
94
|
|
57
|
-
|
95
|
+
info("Creating GCE instance <#{server_name}> in project #{project}, zone #{zone}...")
|
96
|
+
operation = connection.insert_instance(project, zone, create_instance_object(server_name))
|
58
97
|
|
59
|
-
|
60
|
-
|
98
|
+
info("Zone operation #{operation.name} created. Waiting for it to complete...")
|
99
|
+
wait_for_operation(operation)
|
100
|
+
|
101
|
+
server = server_instance(server_name)
|
102
|
+
state[:server_name] = server_name
|
103
|
+
state[:hostname] = ip_address_for(server)
|
104
|
+
state[:zone] = zone
|
105
|
+
|
106
|
+
info("Server <#{server_name}> created.")
|
107
|
+
|
108
|
+
update_windows_password(server_name)
|
109
|
+
|
110
|
+
info("Waiting for server <#{server_name}> to be ready...")
|
111
|
+
wait_for_server
|
112
|
+
|
113
|
+
info("GCE instance <#{server_name}> created and ready.")
|
114
|
+
rescue => e
|
115
|
+
error("Error encountered during server creation: #{e.class}: #{e.message}")
|
116
|
+
destroy(state)
|
117
|
+
raise
|
61
118
|
end
|
62
119
|
|
63
120
|
def destroy(state)
|
64
|
-
|
121
|
+
@state = state
|
122
|
+
server_name = state[:server_name]
|
123
|
+
return if server_name.nil?
|
124
|
+
|
125
|
+
unless server_exist?(server_name)
|
126
|
+
info("GCE instance <#{server_name}> does not exist - assuming it has been already destroyed.")
|
127
|
+
return
|
128
|
+
end
|
129
|
+
|
130
|
+
info("Destroying GCE instance <#{server_name}>...")
|
131
|
+
wait_for_operation(connection.delete_instance(project, zone, server_name))
|
132
|
+
info("GCE instance <#{server_name}> destroyed.")
|
65
133
|
|
66
|
-
|
67
|
-
instance.destroy unless instance.nil?
|
68
|
-
info("GCE instance <#{state[:server_id]}> destroyed.")
|
69
|
-
state.delete(:server_id)
|
134
|
+
state.delete(:server_name)
|
70
135
|
state.delete(:hostname)
|
136
|
+
state.delete(:zone)
|
71
137
|
end
|
72
138
|
|
73
|
-
|
139
|
+
def validate!
|
140
|
+
raise "Project #{config[:project]} is not a valid project" unless valid_project?
|
141
|
+
raise "Either zone or region must be specified" unless config[:zone] || config[:region]
|
142
|
+
raise "'any' is no longer a valid region" if config[:region] == "any"
|
143
|
+
raise "Zone #{config[:zone]} is not a valid zone" if config[:zone] && !valid_zone?
|
144
|
+
raise "Region #{config[:region]} is not a valid region" if config[:region] && !valid_region?
|
145
|
+
raise "Machine type #{config[:machine_type]} is not valid" unless valid_machine_type?
|
146
|
+
raise "Disk type #{config[:disk_type]} is not valid" unless valid_disk_type?
|
147
|
+
raise "Network #{config[:network]} is not valid" unless valid_network?
|
148
|
+
raise "Email address of GCE user is not set" if winrm_transport? && config[:email].nil?
|
149
|
+
|
150
|
+
warn("Both zone and region specified - region will be ignored.") if config[:zone] && config[:region]
|
151
|
+
end
|
74
152
|
|
75
153
|
def connection
|
76
|
-
|
77
|
-
provider: 'google',
|
78
|
-
google_client_email: config[:google_client_email],
|
79
|
-
google_project: config[:google_project]
|
80
|
-
}
|
154
|
+
return @connection unless @connection.nil?
|
81
155
|
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
156
|
+
@connection = Google::Apis::ComputeV1::ComputeService.new
|
157
|
+
@connection.authorization = authorization
|
158
|
+
@connection.client_options = Google::Apis::ClientOptions.new.tap do |opts|
|
159
|
+
opts.application_name = "kitchen-google"
|
160
|
+
opts.application_version = Kitchen::Driver::GCE_VERSION
|
87
161
|
end
|
88
162
|
|
89
|
-
|
163
|
+
@connection
|
90
164
|
end
|
91
165
|
|
92
|
-
def
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
166
|
+
def authorization
|
167
|
+
@authorization ||= Google::Auth.get_application_default(
|
168
|
+
[
|
169
|
+
"https://www.googleapis.com/auth/cloud-platform",
|
170
|
+
"https://www.googleapis.com/auth/compute",
|
171
|
+
]
|
98
172
|
)
|
173
|
+
end
|
174
|
+
|
175
|
+
def winrm_transport?
|
176
|
+
instance.transport.name.downcase == "winrm"
|
177
|
+
end
|
178
|
+
|
179
|
+
def update_windows_password(server_name)
|
180
|
+
return unless winrm_transport?
|
181
|
+
|
182
|
+
username = instance.transport[:username]
|
183
|
+
|
184
|
+
info("Resetting the Windows password for user #{username} on #{server_name}...")
|
185
|
+
|
186
|
+
state[:password] = GoogleComputeWindowsPassword.new(
|
187
|
+
project: project,
|
188
|
+
zone: zone,
|
189
|
+
instance_name: server_name,
|
190
|
+
email: config[:email],
|
191
|
+
username: username
|
192
|
+
).new_password
|
193
|
+
|
194
|
+
info("Password reset complete on #{server_name} complete.")
|
195
|
+
end
|
196
|
+
|
197
|
+
def check_api_call(&block)
|
198
|
+
block.call
|
199
|
+
rescue Google::Apis::ClientError => e
|
200
|
+
debug("API error: #{e.message}")
|
201
|
+
false
|
202
|
+
else
|
203
|
+
true
|
204
|
+
end
|
205
|
+
|
206
|
+
def valid_project?
|
207
|
+
check_api_call { connection.get_project(project) }
|
208
|
+
end
|
209
|
+
|
210
|
+
def valid_machine_type?
|
211
|
+
return false if config[:machine_type].nil?
|
212
|
+
check_api_call { connection.get_machine_type(project, zone, config[:machine_type]) }
|
213
|
+
end
|
214
|
+
|
215
|
+
def valid_network?
|
216
|
+
return false if config[:network].nil?
|
217
|
+
check_api_call { connection.get_network(project, config[:network]) }
|
218
|
+
end
|
219
|
+
|
220
|
+
def valid_zone?
|
221
|
+
return false if config[:zone].nil?
|
222
|
+
check_api_call { connection.get_zone(project, config[:zone]) }
|
223
|
+
end
|
224
|
+
|
225
|
+
def valid_region?
|
226
|
+
return false if config[:region].nil?
|
227
|
+
check_api_call { connection.get_region(project, config[:region]) }
|
228
|
+
end
|
229
|
+
|
230
|
+
def valid_disk_type?
|
231
|
+
return false if config[:disk_type].nil?
|
232
|
+
check_api_call { connection.get_disk_type(project, zone, config[:disk_type]) }
|
233
|
+
end
|
234
|
+
|
235
|
+
def image_exist?(image_project, image_name)
|
236
|
+
check_api_call { connection.get_image(image_project, image_name) }
|
237
|
+
end
|
238
|
+
|
239
|
+
def server_exist?(server_name)
|
240
|
+
check_api_call { server_instance(server_name) }
|
241
|
+
end
|
242
|
+
|
243
|
+
def project
|
244
|
+
config[:project]
|
245
|
+
end
|
246
|
+
|
247
|
+
def region
|
248
|
+
config[:region]
|
249
|
+
end
|
250
|
+
|
251
|
+
def zone
|
252
|
+
@zone ||= state[:zone] || config[:zone] || find_zone
|
253
|
+
end
|
254
|
+
|
255
|
+
def find_zone
|
256
|
+
zone = zones_in_region.sample
|
257
|
+
raise "Unable to find a suitable zone in #{region}" if zone.nil?
|
258
|
+
|
259
|
+
zone.name
|
260
|
+
end
|
261
|
+
|
262
|
+
def zones_in_region
|
263
|
+
connection.list_zones(project).items.select do |zone|
|
264
|
+
zone.status == "UP" &&
|
265
|
+
zone.region.split("/").last == region
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
def server_instance(server_name)
|
270
|
+
connection.get_instance(project, zone, server_name)
|
271
|
+
end
|
272
|
+
|
273
|
+
def ip_address_for(server)
|
274
|
+
config[:use_private_ip] ? private_ip_for(server) : public_ip_for(server)
|
275
|
+
end
|
276
|
+
|
277
|
+
def private_ip_for(server)
|
278
|
+
server.network_interfaces.first.network_ip
|
279
|
+
rescue NoMethodError
|
280
|
+
raise "Unable to determine private IP for instance"
|
281
|
+
end
|
282
|
+
|
283
|
+
def public_ip_for(server)
|
284
|
+
server.network_interfaces.first.access_configs.first.nat_ip
|
285
|
+
rescue NoMethodError
|
286
|
+
raise "Unable to determine public IP for instance"
|
287
|
+
end
|
99
288
|
|
100
|
-
|
289
|
+
def create_instance_object(server_name)
|
290
|
+
inst_obj = Google::Apis::ComputeV1::Instance.new
|
291
|
+
inst_obj.name = server_name
|
292
|
+
inst_obj.disks = [boot_disk(server_name)]
|
293
|
+
inst_obj.machine_type = machine_type_url
|
294
|
+
inst_obj.metadata = instance_metadata
|
295
|
+
inst_obj.network_interfaces = instance_network_interfaces
|
296
|
+
inst_obj.scheduling = instance_scheduling
|
297
|
+
inst_obj.service_accounts = instance_service_accounts unless instance_service_accounts.nil?
|
298
|
+
inst_obj.tags = instance_tags
|
299
|
+
|
300
|
+
inst_obj
|
301
|
+
end
|
302
|
+
|
303
|
+
def generate_server_name
|
304
|
+
name = "tk-#{instance.name.downcase}-#{SecureRandom.hex(3)}"
|
305
|
+
|
306
|
+
if name.length > 63
|
307
|
+
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.")
|
308
|
+
name = "tk-#{SecureRandom.uuid}"
|
309
|
+
end
|
310
|
+
|
311
|
+
name.gsub(/([^-a-z0-9])/, "-")
|
312
|
+
end
|
313
|
+
|
314
|
+
def boot_disk(server_name)
|
315
|
+
disk = Google::Apis::ComputeV1::AttachedDisk.new
|
316
|
+
params = Google::Apis::ComputeV1::AttachedDiskInitializeParams.new
|
317
|
+
|
318
|
+
disk.boot = true
|
319
|
+
disk.auto_delete = config[:autodelete_disk]
|
320
|
+
params.disk_name = server_name
|
321
|
+
params.disk_size_gb = config[:disk_size]
|
322
|
+
params.disk_type = disk_type_url_for(config[:disk_type])
|
323
|
+
params.source_image = disk_image_url
|
324
|
+
|
325
|
+
disk.initialize_params = params
|
101
326
|
disk
|
102
327
|
end
|
103
328
|
|
104
|
-
def
|
105
|
-
|
329
|
+
def disk_type_url_for(type)
|
330
|
+
"zones/#{zone}/diskTypes/#{type}"
|
331
|
+
end
|
332
|
+
|
333
|
+
def disk_image_url
|
334
|
+
# if the user provided an image_project, assume they want it, no questions asked
|
335
|
+
return image_url_for(config[:image_project], config[:image_name]) unless config[:image_project].nil?
|
336
|
+
|
337
|
+
# no image project has been provided. We'll first check the user's project for the image.
|
338
|
+
url = image_url_for(project, config[:image_name])
|
339
|
+
return url unless url.nil?
|
106
340
|
|
107
|
-
|
108
|
-
config[:
|
341
|
+
# Image not found in user's project. Is there a public project this image might exist in?
|
342
|
+
public_project = public_project_for_image(config[:image_name])
|
343
|
+
if public_project
|
344
|
+
return image_url_for(public_project, config[:image_name])
|
345
|
+
end
|
109
346
|
|
110
|
-
|
111
|
-
|
347
|
+
# No image in user's project or public project, so it doesn't exist.
|
348
|
+
nil
|
112
349
|
end
|
113
350
|
|
114
|
-
def
|
115
|
-
|
116
|
-
name: config[:inst_name],
|
117
|
-
disks: [disk.get_as_boot_disk(true, config[:autodelete_disk])],
|
118
|
-
machine_type: config[:machine_type],
|
119
|
-
network: config[:network],
|
120
|
-
service_accounts: config[:service_accounts],
|
121
|
-
tags: config[:tags],
|
122
|
-
zone_name: config[:zone_name],
|
123
|
-
public_key_path: config[:public_key_path],
|
124
|
-
username: config[:username],
|
125
|
-
preemptible: config[:preemptible],
|
126
|
-
on_host_maintenance: config[:preemptible] ? 'TERMINATE': 'MIGRATE',
|
127
|
-
auto_restart: config[:auto_restart]
|
128
|
-
)
|
351
|
+
def image_url_for(image_project, image_name)
|
352
|
+
return "projects/#{image_project}/global/images/#{image_name}" if image_exist?(image_project, image_name)
|
129
353
|
end
|
130
354
|
|
131
|
-
def
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
355
|
+
def public_project_for_image(image)
|
356
|
+
case image
|
357
|
+
when /centos/
|
358
|
+
"centos-cloud"
|
359
|
+
when /container-vm/
|
360
|
+
"google-containers"
|
361
|
+
when /coreos/
|
362
|
+
"coreos-cloud"
|
363
|
+
when /debian/
|
364
|
+
"debian-cloud"
|
365
|
+
when /opensuse-cloud/
|
366
|
+
"opensuse-cloud"
|
367
|
+
when /rhel/
|
368
|
+
"rhel-cloud"
|
369
|
+
when /sles/
|
370
|
+
"suse-cloud"
|
371
|
+
when /ubuntu/
|
372
|
+
"ubuntu-os-cloud"
|
373
|
+
when /windows/
|
374
|
+
"windows-cloud"
|
140
375
|
end
|
141
|
-
gen_name
|
142
376
|
end
|
143
377
|
|
144
|
-
def
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
378
|
+
def machine_type_url
|
379
|
+
"zones/#{zone}/machineTypes/#{config[:machine_type]}"
|
380
|
+
end
|
381
|
+
|
382
|
+
def instance_metadata
|
383
|
+
metadata = {
|
384
|
+
"created-by" => "test-kitchen",
|
385
|
+
"test-kitchen-instance" => instance.name,
|
386
|
+
"test-kitchen-user" => env_user,
|
387
|
+
}
|
388
|
+
|
389
|
+
Google::Apis::ComputeV1::Metadata.new.tap do |metadata_obj|
|
390
|
+
metadata_obj.items = metadata.each_with_object([]) do |(k, v), memo|
|
391
|
+
memo << Google::Apis::ComputeV1::Metadata::Item.new.tap do |item|
|
392
|
+
item.key = k
|
393
|
+
item.value = v
|
394
|
+
end
|
395
|
+
end
|
149
396
|
end
|
150
|
-
|
151
|
-
|
397
|
+
end
|
398
|
+
|
399
|
+
def env_user
|
400
|
+
ENV["USER"] || "unknown"
|
401
|
+
end
|
402
|
+
|
403
|
+
def instance_network_interfaces
|
404
|
+
interface = Google::Apis::ComputeV1::NetworkInterface.new
|
405
|
+
interface.network = network_url
|
406
|
+
interface.access_configs = interface_access_configs
|
407
|
+
|
408
|
+
Array(interface)
|
409
|
+
end
|
410
|
+
|
411
|
+
def network_url
|
412
|
+
"projects/#{project}/global/networks/#{config[:network]}"
|
413
|
+
end
|
414
|
+
|
415
|
+
def interface_access_configs
|
416
|
+
return [] if config[:use_private_ip]
|
417
|
+
|
418
|
+
access_config = Google::Apis::ComputeV1::AccessConfig.new
|
419
|
+
access_config.name = "External NAT"
|
420
|
+
access_config.type = "ONE_TO_ONE_NAT"
|
421
|
+
|
422
|
+
Array(access_config)
|
423
|
+
end
|
424
|
+
|
425
|
+
def instance_scheduling
|
426
|
+
Google::Apis::ComputeV1::Scheduling.new.tap do |scheduling|
|
427
|
+
scheduling.automatic_restart = config[:auto_restart].to_s
|
428
|
+
scheduling.preemptible = config[:preemptible].to_s
|
429
|
+
scheduling.on_host_maintenance = migrate_setting
|
152
430
|
end
|
153
|
-
fail 'No up zones in region' unless zones.length >= 1
|
154
|
-
zones.sample.name
|
155
431
|
end
|
156
432
|
|
157
|
-
def
|
158
|
-
|
159
|
-
|
160
|
-
|
433
|
+
def migrate_setting
|
434
|
+
config[:auto_migrate] ? "MIGRATE" : "TERMINATE"
|
435
|
+
end
|
436
|
+
|
437
|
+
def instance_service_accounts
|
438
|
+
return if config[:service_account_scopes].nil? || config[:service_account_scopes].empty?
|
439
|
+
|
440
|
+
service_account = Google::Apis::ComputeV1::ServiceAccount.new
|
441
|
+
service_account.email = config[:service_account_name]
|
442
|
+
service_account.scopes = config[:service_account_scopes].map { |scope| service_account_scope_url(scope) }
|
443
|
+
|
444
|
+
Array(service_account)
|
445
|
+
end
|
446
|
+
|
447
|
+
def service_account_scope_url(scope)
|
448
|
+
return scope if scope.start_with?("https://www.googleapis.com/auth/")
|
449
|
+
"https://www.googleapis.com/auth/#{translate_scope_alias(scope)}"
|
450
|
+
end
|
451
|
+
|
452
|
+
def translate_scope_alias(scope_alias)
|
453
|
+
SCOPE_ALIAS_MAP.fetch(scope_alias, scope_alias)
|
454
|
+
end
|
455
|
+
|
456
|
+
def instance_tags
|
457
|
+
Google::Apis::ComputeV1::Tags.new.tap { |tag_obj| tag_obj.items = config[:tags] }
|
458
|
+
end
|
459
|
+
|
460
|
+
def wait_time
|
461
|
+
config[:wait_time]
|
462
|
+
end
|
463
|
+
|
464
|
+
def refresh_rate
|
465
|
+
config[:refresh_rate]
|
466
|
+
end
|
467
|
+
|
468
|
+
def wait_for_status(requested_status, &block)
|
469
|
+
last_status = ""
|
470
|
+
|
471
|
+
begin
|
472
|
+
Timeout.timeout(wait_time) do
|
473
|
+
loop do
|
474
|
+
item = block.call
|
475
|
+
current_status = item.status
|
476
|
+
|
477
|
+
unless last_status == current_status
|
478
|
+
last_status = current_status
|
479
|
+
info("Current status: #{current_status}")
|
480
|
+
end
|
481
|
+
|
482
|
+
break if current_status == requested_status
|
483
|
+
|
484
|
+
sleep refresh_rate
|
485
|
+
end
|
486
|
+
end
|
487
|
+
rescue Timeout::Error
|
488
|
+
error("Request did not complete in #{wait_time} seconds. Check the Google Cloud Console for more info.")
|
489
|
+
raise
|
490
|
+
end
|
491
|
+
end
|
492
|
+
|
493
|
+
def wait_for_operation(operation)
|
494
|
+
operation_name = operation.name
|
495
|
+
|
496
|
+
wait_for_status("DONE") { zone_operation(operation_name) }
|
497
|
+
|
498
|
+
errors = operation_errors(operation_name)
|
499
|
+
return if errors.empty?
|
500
|
+
|
501
|
+
errors.each do |error|
|
502
|
+
error("#{error.code}: #{error.message}")
|
503
|
+
end
|
504
|
+
|
505
|
+
raise "Operation #{operation_name} failed."
|
506
|
+
end
|
507
|
+
|
508
|
+
def wait_for_server
|
509
|
+
begin
|
510
|
+
instance.transport.connection(state).wait_until_ready
|
511
|
+
rescue
|
512
|
+
error("Server not reachable. Destroying server...")
|
513
|
+
destroy(state)
|
514
|
+
raise
|
161
515
|
end
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
516
|
+
end
|
517
|
+
|
518
|
+
def zone_operation(operation_name)
|
519
|
+
connection.get_zone_operation(project, zone, operation_name)
|
520
|
+
end
|
521
|
+
|
522
|
+
def operation_errors(operation_name)
|
523
|
+
operation = zone_operation(operation_name)
|
524
|
+
return [] if operation.error.nil?
|
525
|
+
|
526
|
+
operation.error.errors
|
167
527
|
end
|
168
528
|
end
|
169
529
|
end
|