kitchen-google-as 1.2.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 +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-google-as.gemspec +28 -0
- data/lib/kitchen/driver/gce_as.rb +543 -0
- data/lib/kitchen/driver/gce_as_version.rb +23 -0
- data/spec/kitchen/driver/gce_as_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/gce_as_version"
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |s|
|
|
6
|
+
s.name = "kitchen-google-as"
|
|
7
|
+
s.version = Kitchen::Driver::GCE_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/gce_as_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 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
|
+
|
|
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
|