kitchen-sfmc-google 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +5 -0
- data/.rubocop.yml +1 -0
- data/.rubocop_todo.yml +11 -0
- data/.travis.yml +9 -0
- data/CHANGELOG.md +97 -0
- data/Gemfile +6 -0
- data/LICENSE +15 -0
- data/README.md +345 -0
- data/Rakefile +14 -0
- data/kitchen-sfmc-google.gemspec +28 -0
- data/lib/kitchen/driver/sfmc_google.rb +543 -0
- data/lib/kitchen/driver/sfmc_google_version.rb +23 -0
- data/spec/kitchen/driver/sfmc_google_spec.rb +1061 -0
- data/spec/spec_helper.rb +21 -0
- metadata +172 -0
data/Rakefile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
require "bundler/gem_tasks"
|
4
|
+
require "rspec/core/rake_task"
|
5
|
+
|
6
|
+
RSpec::Core::RakeTask.new(:spec)
|
7
|
+
|
8
|
+
require "chefstyle"
|
9
|
+
require "rubocop/rake_task"
|
10
|
+
RuboCop::RakeTask.new(:style) do |task|
|
11
|
+
task.options << "--display-cop-names"
|
12
|
+
end
|
13
|
+
|
14
|
+
task default: [:spec, :style]
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "kitchen/driver/sfmc_google_version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "kitchen-sfmc-google"
|
7
|
+
s.version = Kitchen::Driver::SFMC_GOOGLE_VERSION
|
8
|
+
s.date = "2016-03-10"
|
9
|
+
s.summary = "Kitchen::Driver::Gce"
|
10
|
+
s.description = "A Test-Kitchen driver for Google Compute Engine"
|
11
|
+
s.authors = ["Andrew Leonard", "Chef Partner Engineering"]
|
12
|
+
s.email = ["andy@hurricane-ridge.com", "partnereng@chef.io"]
|
13
|
+
s.files = `git ls-files`.split($/)
|
14
|
+
s.homepage = "https://github.com/test-kitchen/kitchen-google"
|
15
|
+
s.license = "Apache 2.0"
|
16
|
+
|
17
|
+
s.add_dependency "gcewinpass", "~> 1.0"
|
18
|
+
s.add_dependency "google-api-client", "~> 0.9.0"
|
19
|
+
s.add_dependency "test-kitchen"
|
20
|
+
|
21
|
+
s.add_development_dependency "bundler"
|
22
|
+
s.add_development_dependency "pry"
|
23
|
+
s.add_development_dependency "rake", "~> 10.5"
|
24
|
+
s.add_development_dependency "rspec"
|
25
|
+
s.add_development_dependency "rubocop"
|
26
|
+
|
27
|
+
s.required_ruby_version = ">= 2.0"
|
28
|
+
end
|
@@ -0,0 +1,543 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
#
|
3
|
+
# Author:: Andrew Leonard (<andy@hurricane-ridge.com>)
|
4
|
+
# Author:: Chef Partner Engineering (<partnereng@chef.io>)
|
5
|
+
#
|
6
|
+
# Copyright (C) 2013-2016, Andrew Leonard and Chef Software, Inc.
|
7
|
+
#
|
8
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
9
|
+
# you may not use this file except in compliance with the License.
|
10
|
+
# You may obtain a copy of the License at
|
11
|
+
#
|
12
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
13
|
+
#
|
14
|
+
# Unless required by applicable law or agreed to in writing, software
|
15
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
16
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
17
|
+
# See the License for the specific language governing permissions and
|
18
|
+
# limitations under the License.
|
19
|
+
|
20
|
+
require "gcewinpass"
|
21
|
+
require "google/apis/compute_v1"
|
22
|
+
require "kitchen"
|
23
|
+
require "kitchen/driver/sfmc_google_version"
|
24
|
+
require "securerandom"
|
25
|
+
|
26
|
+
module Kitchen
|
27
|
+
module Driver
|
28
|
+
# Google Compute Engine driver for Test Kitchen
|
29
|
+
#
|
30
|
+
# @author Andrew Leonard <andy@hurricane-ridge.com>
|
31
|
+
class SfmcGoogle < 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
|
+
|
61
|
+
default_config :region, nil
|
62
|
+
default_config :zone, nil
|
63
|
+
|
64
|
+
default_config :autodelete_disk, true
|
65
|
+
default_config :disk_size, 10
|
66
|
+
default_config :disk_type, "pd-standard"
|
67
|
+
default_config :machine_type, "n1-standard-1"
|
68
|
+
default_config :network, "default"
|
69
|
+
default_config :subnet, nil
|
70
|
+
default_config :inst_name, nil
|
71
|
+
default_config :service_account_name, "default"
|
72
|
+
default_config :service_account_scopes, []
|
73
|
+
default_config :tags, []
|
74
|
+
default_config :preemptible, false
|
75
|
+
default_config :auto_restart, false
|
76
|
+
default_config :auto_migrate, false
|
77
|
+
default_config :image_family, nil
|
78
|
+
default_config :image_name, nil
|
79
|
+
default_config :image_project, nil
|
80
|
+
default_config :email, nil
|
81
|
+
default_config :use_private_ip, false
|
82
|
+
default_config :wait_time, 600
|
83
|
+
default_config :refresh_rate, 2
|
84
|
+
|
85
|
+
def name
|
86
|
+
"Google Compute (GCE)"
|
87
|
+
end
|
88
|
+
|
89
|
+
def create(state)
|
90
|
+
@state = state
|
91
|
+
return if state[:server_name]
|
92
|
+
|
93
|
+
validate!
|
94
|
+
|
95
|
+
server_name = generate_server_name
|
96
|
+
|
97
|
+
info("Creating GCE instance <#{server_name}> in project #{project}, zone #{zone}...")
|
98
|
+
operation = connection.insert_instance(project, zone, create_instance_object(server_name))
|
99
|
+
|
100
|
+
info("Zone operation #{operation.name} created. Waiting for it to complete...")
|
101
|
+
wait_for_operation(operation)
|
102
|
+
|
103
|
+
server = server_instance(server_name)
|
104
|
+
state[:server_name] = server_name
|
105
|
+
state[:hostname] = ip_address_for(server)
|
106
|
+
state[:zone] = zone
|
107
|
+
|
108
|
+
info("Server <#{server_name}> created.")
|
109
|
+
|
110
|
+
update_windows_password(server_name)
|
111
|
+
|
112
|
+
info("Waiting for server <#{server_name}> to be ready...")
|
113
|
+
wait_for_server
|
114
|
+
|
115
|
+
info("GCE instance <#{server_name}> created and ready.")
|
116
|
+
rescue => e
|
117
|
+
error("Error encountered during server creation: #{e.class}: #{e.message}")
|
118
|
+
destroy(state)
|
119
|
+
raise
|
120
|
+
end
|
121
|
+
|
122
|
+
def destroy(state)
|
123
|
+
@state = state
|
124
|
+
server_name = state[:server_name]
|
125
|
+
return if server_name.nil?
|
126
|
+
|
127
|
+
unless server_exist?(server_name)
|
128
|
+
info("GCE instance <#{server_name}> does not exist - assuming it has been already destroyed.")
|
129
|
+
return
|
130
|
+
end
|
131
|
+
|
132
|
+
info("Destroying GCE instance <#{server_name}>...")
|
133
|
+
wait_for_operation(connection.delete_instance(project, zone, server_name))
|
134
|
+
info("GCE instance <#{server_name}> destroyed.")
|
135
|
+
|
136
|
+
state.delete(:server_name)
|
137
|
+
state.delete(:hostname)
|
138
|
+
state.delete(:zone)
|
139
|
+
end
|
140
|
+
|
141
|
+
def validate!
|
142
|
+
raise "Project #{config[:project]} is not a valid project" unless valid_project?
|
143
|
+
raise "Either zone or region must be specified" unless config[:zone] || config[:region]
|
144
|
+
raise "'any' is no longer a valid region" if config[:region] == "any"
|
145
|
+
raise "Zone #{config[:zone]} is not a valid zone" if config[:zone] && !valid_zone?
|
146
|
+
raise "Region #{config[:region]} is not a valid region" if config[:region] && !valid_region?
|
147
|
+
raise "Machine type #{config[:machine_type]} is not valid" unless valid_machine_type?
|
148
|
+
raise "Disk type #{config[:disk_type]} is not valid" unless valid_disk_type?
|
149
|
+
raise "Either image family or name must be specified" unless config[:image_family] || config[:image_name]
|
150
|
+
raise "Disk image #{config[:image_name]} is not valid - check your image name and image project" if boot_disk_source_image.nil?
|
151
|
+
raise "Network #{config[:network]} is not valid" unless valid_network?
|
152
|
+
raise "Subnet #{config[:subnet]} is not valid" if config[:subnet] && !valid_subnet?
|
153
|
+
raise "Email address of GCE user is not set" if winrm_transport? && config[:email].nil?
|
154
|
+
|
155
|
+
warn("Both zone and region specified - region will be ignored.") if config[:zone] && config[:region]
|
156
|
+
warn("Both image family and name specified - image family will be ignored") if config[:image_family] && config[:image_name]
|
157
|
+
warn("Image project not specified - searching current project only") unless config[:image_project]
|
158
|
+
warn("Auto-migrate disabled for preemptible instance") if preemptible? && config[:auto_migrate]
|
159
|
+
warn("Auto-restart disabled for preemptible instance") if preemptible? && config[:auto_restart]
|
160
|
+
end
|
161
|
+
|
162
|
+
def connection
|
163
|
+
return @connection unless @connection.nil?
|
164
|
+
|
165
|
+
@connection = Google::Apis::ComputeV1::ComputeService.new
|
166
|
+
@connection.authorization = authorization
|
167
|
+
@connection.client_options = Google::Apis::ClientOptions.new.tap do |opts|
|
168
|
+
opts.application_name = "kitchen-google"
|
169
|
+
opts.application_version = Kitchen::Driver::GCE_VERSION
|
170
|
+
end
|
171
|
+
|
172
|
+
@connection
|
173
|
+
end
|
174
|
+
|
175
|
+
def authorization
|
176
|
+
@authorization ||= Google::Auth.get_application_default(
|
177
|
+
[
|
178
|
+
"https://www.googleapis.com/auth/cloud-platform",
|
179
|
+
"https://www.googleapis.com/auth/compute",
|
180
|
+
]
|
181
|
+
)
|
182
|
+
end
|
183
|
+
|
184
|
+
def winrm_transport?
|
185
|
+
instance.transport.name.casecmp("winrm") == 0
|
186
|
+
end
|
187
|
+
|
188
|
+
def update_windows_password(server_name)
|
189
|
+
return unless winrm_transport?
|
190
|
+
|
191
|
+
username = instance.transport[:username]
|
192
|
+
|
193
|
+
info("Resetting the Windows password for user #{username} on #{server_name}...")
|
194
|
+
|
195
|
+
state[:password] = GoogleComputeWindowsPassword.new(
|
196
|
+
project: project,
|
197
|
+
zone: zone,
|
198
|
+
instance_name: server_name,
|
199
|
+
email: config[:email],
|
200
|
+
username: username
|
201
|
+
).new_password
|
202
|
+
|
203
|
+
info("Password reset complete on #{server_name} complete.")
|
204
|
+
end
|
205
|
+
|
206
|
+
def check_api_call(&block)
|
207
|
+
yield
|
208
|
+
rescue Google::Apis::ClientError => e
|
209
|
+
debug("API error: #{e.message}")
|
210
|
+
false
|
211
|
+
else
|
212
|
+
true
|
213
|
+
end
|
214
|
+
|
215
|
+
def valid_project?
|
216
|
+
check_api_call { connection.get_project(project) }
|
217
|
+
end
|
218
|
+
|
219
|
+
def valid_machine_type?
|
220
|
+
return false if config[:machine_type].nil?
|
221
|
+
check_api_call { connection.get_machine_type(project, zone, config[:machine_type]) }
|
222
|
+
end
|
223
|
+
|
224
|
+
def valid_network?
|
225
|
+
return false if config[:network].nil?
|
226
|
+
check_api_call { connection.get_network(project, config[:network]) }
|
227
|
+
end
|
228
|
+
|
229
|
+
def valid_subnet?
|
230
|
+
return false if config[:subnet].nil?
|
231
|
+
check_api_call { connection.get_subnetwork(project, region, config[:subnet]) }
|
232
|
+
end
|
233
|
+
|
234
|
+
def valid_zone?
|
235
|
+
return false if config[:zone].nil?
|
236
|
+
check_api_call { connection.get_zone(project, config[:zone]) }
|
237
|
+
end
|
238
|
+
|
239
|
+
def valid_region?
|
240
|
+
return false if config[:region].nil?
|
241
|
+
check_api_call { connection.get_region(project, config[:region]) }
|
242
|
+
end
|
243
|
+
|
244
|
+
def valid_disk_type?
|
245
|
+
return false if config[:disk_type].nil?
|
246
|
+
check_api_call { connection.get_disk_type(project, zone, config[:disk_type]) }
|
247
|
+
end
|
248
|
+
|
249
|
+
def image_exist?
|
250
|
+
check_api_call { connection.get_image(image_project, image_name) }
|
251
|
+
end
|
252
|
+
|
253
|
+
def server_exist?(server_name)
|
254
|
+
check_api_call { server_instance(server_name) }
|
255
|
+
end
|
256
|
+
|
257
|
+
def project
|
258
|
+
config[:project]
|
259
|
+
end
|
260
|
+
|
261
|
+
def image_name
|
262
|
+
@image_name ||= config[:image_name] || image_name_for_family(config[:image_family])
|
263
|
+
end
|
264
|
+
|
265
|
+
def image_project
|
266
|
+
config[:image_project].nil? ? project : config[:image_project]
|
267
|
+
end
|
268
|
+
|
269
|
+
def region
|
270
|
+
config[:region].nil? ? region_for_zone : config[:region]
|
271
|
+
end
|
272
|
+
|
273
|
+
def region_for_zone
|
274
|
+
@region_for_zone ||= connection.get_zone(project, zone).region.split("/").last
|
275
|
+
end
|
276
|
+
|
277
|
+
def zone
|
278
|
+
@zone ||= state[:zone] || config[:zone] || find_zone
|
279
|
+
end
|
280
|
+
|
281
|
+
def find_zone
|
282
|
+
zone = zones_in_region.sample
|
283
|
+
raise "Unable to find a suitable zone in #{region}" if zone.nil?
|
284
|
+
|
285
|
+
zone.name
|
286
|
+
end
|
287
|
+
|
288
|
+
def zones_in_region
|
289
|
+
connection.list_zones(project).items.select do |zone|
|
290
|
+
zone.status == "UP" &&
|
291
|
+
zone.region.split("/").last == region
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
def server_instance(server_name)
|
296
|
+
connection.get_instance(project, zone, server_name)
|
297
|
+
end
|
298
|
+
|
299
|
+
def ip_address_for(server)
|
300
|
+
config[:use_private_ip] ? private_ip_for(server) : public_ip_for(server)
|
301
|
+
end
|
302
|
+
|
303
|
+
def private_ip_for(server)
|
304
|
+
server.network_interfaces.first.network_ip
|
305
|
+
rescue NoMethodError
|
306
|
+
raise "Unable to determine private IP for instance"
|
307
|
+
end
|
308
|
+
|
309
|
+
def public_ip_for(server)
|
310
|
+
server.network_interfaces.first.access_configs.first.nat_ip
|
311
|
+
rescue NoMethodError
|
312
|
+
raise "Unable to determine public IP for instance"
|
313
|
+
end
|
314
|
+
|
315
|
+
def create_instance_object(server_name)
|
316
|
+
inst_obj = Google::Apis::ComputeV1::Instance.new
|
317
|
+
inst_obj.name = server_name
|
318
|
+
inst_obj.disks = [boot_disk(server_name)]
|
319
|
+
inst_obj.machine_type = machine_type_url
|
320
|
+
inst_obj.metadata = instance_metadata
|
321
|
+
inst_obj.network_interfaces = instance_network_interfaces
|
322
|
+
inst_obj.scheduling = instance_scheduling
|
323
|
+
inst_obj.service_accounts = instance_service_accounts unless instance_service_accounts.nil?
|
324
|
+
inst_obj.tags = instance_tags
|
325
|
+
|
326
|
+
inst_obj
|
327
|
+
end
|
328
|
+
|
329
|
+
def generate_server_name
|
330
|
+
name = "tk-#{instance.name.downcase}-#{SecureRandom.hex(3)}"
|
331
|
+
|
332
|
+
if name.length > 63
|
333
|
+
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.")
|
334
|
+
name = "tk-#{SecureRandom.uuid}"
|
335
|
+
end
|
336
|
+
|
337
|
+
name.gsub(/([^-a-z0-9])/, "-")
|
338
|
+
end
|
339
|
+
|
340
|
+
def boot_disk(server_name)
|
341
|
+
disk = Google::Apis::ComputeV1::AttachedDisk.new
|
342
|
+
params = Google::Apis::ComputeV1::AttachedDiskInitializeParams.new
|
343
|
+
|
344
|
+
disk.boot = true
|
345
|
+
disk.auto_delete = config[:autodelete_disk]
|
346
|
+
params.disk_name = server_name
|
347
|
+
params.disk_size_gb = config[:disk_size]
|
348
|
+
params.disk_type = disk_type_url_for(config[:disk_type])
|
349
|
+
params.source_image = boot_disk_source_image
|
350
|
+
|
351
|
+
disk.initialize_params = params
|
352
|
+
disk
|
353
|
+
end
|
354
|
+
|
355
|
+
def disk_type_url_for(type)
|
356
|
+
"zones/#{zone}/diskTypes/#{type}"
|
357
|
+
end
|
358
|
+
|
359
|
+
def boot_disk_source_image
|
360
|
+
@boot_disk_source ||= image_url
|
361
|
+
end
|
362
|
+
|
363
|
+
def image_url
|
364
|
+
return "projects/#{image_project}/global/images/#{image_name}" if image_exist?
|
365
|
+
end
|
366
|
+
|
367
|
+
def image_name_for_family(image_family)
|
368
|
+
image = connection.get_image_from_family(image_project, image_family)
|
369
|
+
image.name
|
370
|
+
end
|
371
|
+
|
372
|
+
def machine_type_url
|
373
|
+
"zones/#{zone}/machineTypes/#{config[:machine_type]}"
|
374
|
+
end
|
375
|
+
|
376
|
+
def instance_metadata
|
377
|
+
metadata = {
|
378
|
+
"created-by" => "test-kitchen",
|
379
|
+
"test-kitchen-instance" => instance.name,
|
380
|
+
"test-kitchen-user" => env_user,
|
381
|
+
}
|
382
|
+
|
383
|
+
Google::Apis::ComputeV1::Metadata.new.tap do |metadata_obj|
|
384
|
+
metadata_obj.items = metadata.each_with_object([]) do |(k, v), memo|
|
385
|
+
memo << Google::Apis::ComputeV1::Metadata::Item.new.tap do |item|
|
386
|
+
item.key = k
|
387
|
+
item.value = v
|
388
|
+
end
|
389
|
+
end
|
390
|
+
end
|
391
|
+
end
|
392
|
+
|
393
|
+
def env_user
|
394
|
+
ENV["USER"] || "unknown"
|
395
|
+
end
|
396
|
+
|
397
|
+
def instance_network_interfaces
|
398
|
+
interface = Google::Apis::ComputeV1::NetworkInterface.new
|
399
|
+
interface.network = network_url
|
400
|
+
interface.subnetwork = subnet_url if subnet_url
|
401
|
+
interface.access_configs = interface_access_configs
|
402
|
+
|
403
|
+
Array(interface)
|
404
|
+
end
|
405
|
+
|
406
|
+
def network_url
|
407
|
+
"projects/#{project}/global/networks/#{config[:network]}"
|
408
|
+
end
|
409
|
+
|
410
|
+
def subnet_url
|
411
|
+
return unless config[:subnet]
|
412
|
+
|
413
|
+
"projects/#{project}/regions/#{region}/subnetworks/#{config[:subnet]}"
|
414
|
+
end
|
415
|
+
|
416
|
+
def interface_access_configs
|
417
|
+
return [] if config[:use_private_ip]
|
418
|
+
|
419
|
+
access_config = Google::Apis::ComputeV1::AccessConfig.new
|
420
|
+
access_config.name = "External NAT"
|
421
|
+
access_config.type = "ONE_TO_ONE_NAT"
|
422
|
+
|
423
|
+
Array(access_config)
|
424
|
+
end
|
425
|
+
|
426
|
+
def instance_scheduling
|
427
|
+
Google::Apis::ComputeV1::Scheduling.new.tap do |scheduling|
|
428
|
+
scheduling.automatic_restart = auto_restart?.to_s
|
429
|
+
scheduling.preemptible = preemptible?.to_s
|
430
|
+
scheduling.on_host_maintenance = migrate_setting
|
431
|
+
end
|
432
|
+
end
|
433
|
+
|
434
|
+
def preemptible?
|
435
|
+
config[:preemptible]
|
436
|
+
end
|
437
|
+
|
438
|
+
def auto_migrate?
|
439
|
+
preemptible? ? false : config[:auto_migrate]
|
440
|
+
end
|
441
|
+
|
442
|
+
def auto_restart?
|
443
|
+
preemptible? ? false : config[:auto_restart]
|
444
|
+
end
|
445
|
+
|
446
|
+
def migrate_setting
|
447
|
+
auto_migrate? ? "MIGRATE" : "TERMINATE"
|
448
|
+
end
|
449
|
+
|
450
|
+
def instance_service_accounts
|
451
|
+
return if config[:service_account_scopes].nil? || config[:service_account_scopes].empty?
|
452
|
+
|
453
|
+
service_account = Google::Apis::ComputeV1::ServiceAccount.new
|
454
|
+
service_account.email = config[:service_account_name]
|
455
|
+
service_account.scopes = config[:service_account_scopes].map { |scope| service_account_scope_url(scope) }
|
456
|
+
|
457
|
+
Array(service_account)
|
458
|
+
end
|
459
|
+
|
460
|
+
def service_account_scope_url(scope)
|
461
|
+
return scope if scope.start_with?("https://www.googleapis.com/auth/")
|
462
|
+
"https://www.googleapis.com/auth/#{translate_scope_alias(scope)}"
|
463
|
+
end
|
464
|
+
|
465
|
+
def translate_scope_alias(scope_alias)
|
466
|
+
SCOPE_ALIAS_MAP.fetch(scope_alias, scope_alias)
|
467
|
+
end
|
468
|
+
|
469
|
+
def instance_tags
|
470
|
+
Google::Apis::ComputeV1::Tags.new.tap { |tag_obj| tag_obj.items = config[:tags] }
|
471
|
+
end
|
472
|
+
|
473
|
+
def wait_time
|
474
|
+
config[:wait_time]
|
475
|
+
end
|
476
|
+
|
477
|
+
def refresh_rate
|
478
|
+
config[:refresh_rate]
|
479
|
+
end
|
480
|
+
|
481
|
+
def wait_for_status(requested_status, &block)
|
482
|
+
last_status = ""
|
483
|
+
|
484
|
+
begin
|
485
|
+
Timeout.timeout(wait_time) do
|
486
|
+
loop do
|
487
|
+
item = yield
|
488
|
+
current_status = item.status
|
489
|
+
|
490
|
+
unless last_status == current_status
|
491
|
+
last_status = current_status
|
492
|
+
info("Current status: #{current_status}")
|
493
|
+
end
|
494
|
+
|
495
|
+
break if current_status == requested_status
|
496
|
+
|
497
|
+
sleep refresh_rate
|
498
|
+
end
|
499
|
+
end
|
500
|
+
rescue Timeout::Error
|
501
|
+
error("Request did not complete in #{wait_time} seconds. Check the Google Cloud Console for more info.")
|
502
|
+
raise
|
503
|
+
end
|
504
|
+
end
|
505
|
+
|
506
|
+
def wait_for_operation(operation)
|
507
|
+
operation_name = operation.name
|
508
|
+
|
509
|
+
wait_for_status("DONE") { zone_operation(operation_name) }
|
510
|
+
|
511
|
+
errors = operation_errors(operation_name)
|
512
|
+
return if errors.empty?
|
513
|
+
|
514
|
+
errors.each do |error|
|
515
|
+
error("#{error.code}: #{error.message}")
|
516
|
+
end
|
517
|
+
|
518
|
+
raise "Operation #{operation_name} failed."
|
519
|
+
end
|
520
|
+
|
521
|
+
def wait_for_server
|
522
|
+
begin
|
523
|
+
instance.transport.connection(state).wait_until_ready
|
524
|
+
rescue
|
525
|
+
error("Server not reachable. Destroying server...")
|
526
|
+
destroy(state)
|
527
|
+
raise
|
528
|
+
end
|
529
|
+
end
|
530
|
+
|
531
|
+
def zone_operation(operation_name)
|
532
|
+
connection.get_zone_operation(project, zone, operation_name)
|
533
|
+
end
|
534
|
+
|
535
|
+
def operation_errors(operation_name)
|
536
|
+
operation = zone_operation(operation_name)
|
537
|
+
return [] if operation.error.nil?
|
538
|
+
|
539
|
+
operation.error.errors
|
540
|
+
end
|
541
|
+
end
|
542
|
+
end
|
543
|
+
end
|