beaker-google 0.3.0 → 0.4.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/.github/workflows/ci.yml +46 -0
- data/.github/workflows/codeql-analysis.yml +70 -0
- data/.github/workflows/release.yml +32 -0
- data/.rubocop.yml +519 -0
- data/.simplecov +1 -1
- data/CHANGELOG.md +18 -0
- data/Gemfile +9 -12
- data/README.md +48 -19
- data/Rakefile +35 -39
- data/beaker-google.gemspec +11 -13
- data/bin/beaker-google +1 -3
- data/lib/beaker/hypervisor/google_compute.rb +134 -115
- data/lib/beaker/hypervisor/google_compute_helper.rb +445 -754
- data/lib/beaker-google/version.rb +1 -1
- metadata +49 -17
- data/.github/workflows/snyk_scan.yaml +0 -23
- data/CODEOWNERS +0 -2
@@ -1,797 +1,488 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'google/apis/compute_v1'
|
4
|
+
require 'google/apis/oslogin_v1'
|
5
|
+
require 'googleauth'
|
2
6
|
require 'json'
|
3
7
|
require 'time'
|
4
8
|
require 'ostruct'
|
5
9
|
|
6
|
-
|
7
|
-
|
8
|
-
class GoogleComputeHelper
|
10
|
+
# TODO: Figure out what to do about the timeout thing
|
11
|
+
# TODO: Implement Google::Apis::RequestOptions on all calls (In lib/google/apis/options.rb)
|
9
12
|
|
10
|
-
|
11
|
-
|
13
|
+
# Beaker helper module for doing API level Google Compute Engine interaction.
|
14
|
+
class Beaker::GoogleComputeHelper
|
15
|
+
class GoogleComputeError < StandardError
|
16
|
+
end
|
12
17
|
|
13
|
-
|
14
|
-
|
15
|
-
AUTH_URL = 'https://www.googleapis.com/auth/compute'
|
16
|
-
API_VERSION = 'v1'
|
17
|
-
BASE_URL = "https://www.googleapis.com/compute/#{API_VERSION}/projects/"
|
18
|
-
CENTOS_PROJECT = 'centos-cloud'
|
19
|
-
DEBIAN_PROJECT = 'debian-cloud'
|
20
|
-
RHEL_PROJECT = 'rhel-cloud'
|
21
|
-
SLES_PROJECT = 'sles-cloud'
|
22
|
-
DEFAULT_ZONE_NAME = 'us-central1-a'
|
23
|
-
DEFAULT_MACHINE_TYPE = 'n1-highmem-2'
|
24
|
-
DEFAULT_DISK_SIZE = 25
|
25
|
-
|
26
|
-
# Create a new instance of the Google Compute Engine helper object
|
27
|
-
#
|
28
|
-
# @param [Hash{Symbol=>String}] options The options hash containing
|
29
|
-
# configuration values
|
30
|
-
#
|
31
|
-
# @option options [String] :gce_project The Google Compute Project name to
|
32
|
-
# connect to
|
33
|
-
#
|
34
|
-
# @option options [String] :gce_keyfile The location of the Google Compute
|
35
|
-
# service account keyfile
|
36
|
-
#
|
37
|
-
# @option options [String] :gce_password The password for the Google Compute
|
38
|
-
# service account key
|
39
|
-
#
|
40
|
-
# @option options [String] :gce_email The email address for the Google
|
41
|
-
# Compute service account
|
42
|
-
#
|
43
|
-
# @option options [String] :gce_machine_type A Google Compute machine type
|
44
|
-
# used to create instances, defaults to n1-highmem-2
|
45
|
-
#
|
46
|
-
# @option options [Integer] :timeout The amount of time to attempt execution
|
47
|
-
# before quiting and exiting with failure
|
48
|
-
def initialize(options)
|
49
|
-
@options = options
|
50
|
-
@logger = options[:logger]
|
51
|
-
try = 1
|
52
|
-
attempts = @options[:timeout].to_i / SLEEPWAIT
|
53
|
-
start = Time.now
|
54
|
-
|
55
|
-
set_client(Beaker::Version::STRING)
|
56
|
-
set_compute_api(API_VERSION, start, attempts)
|
57
|
-
|
58
|
-
@options[:gce_project] = ENV['BEAKER_gce_project'] if ENV['BEAKER_gce_project']
|
59
|
-
@options[:gce_keyfile] = ENV['BEAKER_gce_keyfile'] if ENV['BEAKER_gce_keyfile']
|
60
|
-
|
61
|
-
unless (@options[:gce_keyfile] && File.exist?(@options[:gce_keyfile]))
|
62
|
-
@options[:gce_keyfile] = File.join(ENV['HOME'], '.beaker', 'gce', %(#{@options[:gce_project]}.p12))
|
63
|
-
end
|
64
|
-
|
65
|
-
@options[:gce_password] = ENV['BEAKER_gce_password'] if ENV['BEAKER_gce_password']
|
66
|
-
# This is the GCE default so there's usually not a reason to specify it
|
67
|
-
@options[:gce_password] = 'notasecret' unless @options[:gce_password]
|
68
|
-
|
69
|
-
@options[:gce_email] = ENV['BEAKER_gce_email'] if ENV['BEAKER_gce_email']
|
70
|
-
|
71
|
-
raise 'You must specify a gce_project for Google Compute Engine instances!' unless @options[:gce_project]
|
72
|
-
|
73
|
-
raise "Could not find gce_keyfile for Google Compute Engine at '#{@options[:gce_keyfile]}'!" unless File.exist?(@options[:gce_keyfile])
|
74
|
-
|
75
|
-
raise 'You must specify a gce_email for Google Compute Engine instances!' unless @options[:gce_email]
|
76
|
-
|
77
|
-
authenticate(@options[:gce_keyfile], @options[:gce_password], @options[:gce_email], start, attempts)
|
78
|
-
end
|
18
|
+
SLEEPWAIT = 5
|
79
19
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
BASE_URL + @options[:gce_project] + '/global/zones/' + DEFAULT_ZONE_NAME
|
87
|
-
end
|
20
|
+
AUTH_URL = 'https://www.googleapis.com/auth/compute'
|
21
|
+
API_VERSION = 'v1'
|
22
|
+
BASE_URL = "https://www.googleapis.com/compute/#{API_VERSION}/projects/"
|
23
|
+
DEFAULT_ZONE_NAME = 'us-central1-a'
|
24
|
+
DEFAULT_MACHINE_TYPE = 'e2-standard-4'
|
25
|
+
DEFAULT_NETWORK_NAME = 'default'
|
88
26
|
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
# instances will operate
|
94
|
-
def default_network
|
95
|
-
BASE_URL + @options[:gce_project] + '/global/networks/default'
|
96
|
-
end
|
27
|
+
GCP_AUTH_SCOPE = [
|
28
|
+
Google::Apis::ComputeV1::AUTH_COMPUTE,
|
29
|
+
Google::Apis::OsloginV1::AUTH_CLOUD_PLATFORM_READ_ONLY,
|
30
|
+
].freeze
|
97
31
|
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
#
|
105
|
-
# @raise [Exception] If the provided platform type name is unsupported
|
106
|
-
def get_platform_project(name)
|
107
|
-
if name =~ /debian/
|
108
|
-
return DEBIAN_PROJECT
|
109
|
-
elsif name =~ /centos/
|
110
|
-
return CENTOS_PROJECT
|
111
|
-
elsif name =~ /rhel/
|
112
|
-
return RHEL_PROJECT
|
113
|
-
elsif name =~ /sles/
|
114
|
-
return SLES_PROJECT
|
115
|
-
else
|
116
|
-
raise "Unsupported platform for Google Compute Engine: #{name}"
|
117
|
-
end
|
118
|
-
end
|
32
|
+
##
|
33
|
+
# Create a new instance of the Google Compute Engine helper object
|
34
|
+
#
|
35
|
+
def initialize(options)
|
36
|
+
@options = options
|
37
|
+
@logger = options[:logger]
|
119
38
|
|
120
|
-
|
121
|
-
# Google Compute API
|
122
|
-
#
|
123
|
-
# @param version The version number of Beaker currently running
|
124
|
-
def set_client(version)
|
125
|
-
@client = ::Google::APIClient.new({:application_name => "Beaker", :application_version => version})
|
126
|
-
end
|
39
|
+
set_client(Beaker::Version::STRING)
|
127
40
|
|
128
|
-
#
|
129
|
-
#
|
130
|
-
#
|
131
|
-
#
|
132
|
-
# @param [Integer] start The time when we started code execution, it is
|
133
|
-
# compared to Time.now to determine how many further code execution
|
134
|
-
# attempts remain
|
135
|
-
#
|
136
|
-
# @param [Integer] attempts The total amount of attempts to execute that we
|
137
|
-
# are willing to allow
|
138
|
-
#
|
139
|
-
# @raise [Exception] Raised if we fail to discover the Google Compute API,
|
140
|
-
# either through errors or running out of attempts
|
141
|
-
def set_compute_api version, start, attempts
|
142
|
-
try = (Time.now - start)/SLEEPWAIT
|
143
|
-
while try <= attempts
|
144
|
-
begin
|
145
|
-
@compute = @client.discovered_api('compute', version)
|
146
|
-
@logger.debug("Google Compute API discovered")
|
147
|
-
return
|
148
|
-
rescue => e
|
149
|
-
@logger.debug("Failed to discover Google Compute API")
|
150
|
-
if try >= attempts
|
151
|
-
raise e
|
152
|
-
end
|
153
|
-
end
|
154
|
-
try += 1
|
155
|
-
end
|
156
|
-
end
|
41
|
+
# ::Google::Apis.logger = ::Logger.new(::STDERR)
|
42
|
+
# ::Google::Apis.logger.level = ::Logger::DEBUG
|
43
|
+
# ::Google::Apis.logger.level = ::Logger::WARN
|
157
44
|
|
158
|
-
|
159
|
-
#
|
160
|
-
# @param [String] keyfile The location of the Google Compute Service
|
161
|
-
# Account keyfile to use for authentication
|
162
|
-
#
|
163
|
-
# @param [String] password The password for the provided Google Compute
|
164
|
-
# Service Account key
|
165
|
-
#
|
166
|
-
# @param [String] email The email address of the Google Compute Service
|
167
|
-
# Account we are using to connect
|
168
|
-
#
|
169
|
-
# @param [Integer] start The time when we started code execution, it is
|
170
|
-
# compared to Time.now to determine how many further code execution
|
171
|
-
# attempts remain
|
172
|
-
#
|
173
|
-
# @param [Integer] attempts The total amount of attempts to execute that we
|
174
|
-
# are willing to allow
|
175
|
-
#
|
176
|
-
# @raise [Exception] Raised if we fail to create an authenticated
|
177
|
-
# connection to the Google Compute API, either through errors or running
|
178
|
-
# out of attempts
|
179
|
-
def authenticate(keyfile, password, email, start, attempts)
|
180
|
-
# OAuth authentication, using the service account
|
181
|
-
key = ::Google::APIClient::PKCS12.load_key(keyfile, password)
|
182
|
-
service_account = ::Google::APIClient::JWTAsserter.new(
|
183
|
-
email,
|
184
|
-
AUTH_URL,
|
185
|
-
key)
|
186
|
-
try = (Time.now - start) / SLEEPWAIT
|
187
|
-
while try <= attempts
|
188
|
-
begin
|
189
|
-
@client.authorization = service_account.authorize
|
190
|
-
@logger.debug("Authorized to use Google Compute")
|
191
|
-
return
|
192
|
-
rescue => e
|
193
|
-
@logger.debug("Failed to authorize to use Google Compute")
|
194
|
-
if try >= attempts
|
195
|
-
raise e
|
196
|
-
end
|
197
|
-
end
|
198
|
-
try += 1
|
199
|
-
end
|
200
|
-
end
|
201
|
-
|
202
|
-
# Executes a provided Google Compute request using a previously configured
|
203
|
-
# and authenticated Google Compute client connection
|
204
|
-
#
|
205
|
-
# @param [Hash] req A correctly formatted Google Compute request object
|
206
|
-
#
|
207
|
-
# @param [Integer] start The time when we started code execution, it is
|
208
|
-
# compared to Time.now to determine how many further code execution
|
209
|
-
# attempts remain
|
210
|
-
# @param [Integer] attempts The total amount of attempts to execute that we
|
211
|
-
# are willing to allow
|
212
|
-
#
|
213
|
-
# @raise [Exception] Raised if we fail to execute the request, either
|
214
|
-
# through errors or running out of attempts
|
215
|
-
def execute req, start, attempts
|
216
|
-
last_error = parsed = nil
|
217
|
-
try = (Time.now - start) / SLEEPWAIT
|
218
|
-
while try <= attempts
|
219
|
-
begin
|
220
|
-
result = @client.execute(req)
|
221
|
-
parsed = JSON.parse(result.body)
|
222
|
-
if not result.success?
|
223
|
-
error_code = parsed["error"] ? parsed["error"]["code"] : 0
|
224
|
-
if error_code == 404
|
225
|
-
raise GoogleComputeError, "Resource Not Found: #{result.body}"
|
226
|
-
elsif error_code == 400
|
227
|
-
raise GoogleComputeError, "Bad Request: #{result.body}"
|
228
|
-
else
|
229
|
-
raise GoogleComputeError, "Error attempting Google Compute API execute: #{result.body}"
|
230
|
-
end
|
231
|
-
end
|
232
|
-
return parsed
|
233
|
-
# retry errors
|
234
|
-
rescue Faraday::Error::ConnectionFailed => e
|
235
|
-
@logger.debug "ConnectionFailed attempting Google Compute execute command"
|
236
|
-
try += 1
|
237
|
-
last_error = e
|
238
|
-
end
|
239
|
-
end
|
240
|
-
# we only get down here if we've used up all our tries
|
241
|
-
raise last_error
|
242
|
-
end
|
243
|
-
|
244
|
-
# Determines the latest image available for the provided platform name.
|
245
|
-
#
|
246
|
-
# Only images of the form (platform)-(version)-(version) are currently supported
|
247
|
-
#
|
248
|
-
# @param [String] platform The platform type to search for an instance of.
|
249
|
-
#
|
250
|
-
# @param [Integer] start The time when we started code execution, it is
|
251
|
-
# compared to Time.now to determine how many further code execution
|
252
|
-
# attempts remain
|
253
|
-
#
|
254
|
-
# @param [Integer] attempts The total amount of attempts to execute that we
|
255
|
-
# are willing to allow
|
256
|
-
#
|
257
|
-
# @return [Hash] The image hash of the latest, non-deprecated image for the
|
258
|
-
# provided platform
|
259
|
-
#
|
260
|
-
# @raise [Exception] Raised if we fail to execute the request, either
|
261
|
-
# through errors or running out of attempts
|
262
|
-
def get_latest_image(platform, start, attempts)
|
263
|
-
#break up my platform for information
|
264
|
-
platform_name, platform_version, platform_extra_info = platform.split('-', 3)
|
265
|
-
#find latest image to use
|
266
|
-
result = execute( image_list_req(get_platform_project(platform_name)), start, attempts )
|
267
|
-
images = result["items"]
|
268
|
-
|
269
|
-
#reject images of the wrong version of the given platform
|
270
|
-
images.delete_if { |image| image['name'] !~ /^#{platform_name}-#{platform_version}/}
|
271
|
-
#reject deprecated images
|
272
|
-
images.delete_if { |image| image['deprecated']}
|
273
|
-
#find a match based upon platform type
|
274
|
-
if images.length != 1
|
275
|
-
raise "Unable to find a single matching image for #{platform}, found #{images}"
|
276
|
-
end
|
277
|
-
images[0]
|
278
|
-
end
|
279
|
-
|
280
|
-
# Determines the Google Compute machineType object based upon the selected
|
281
|
-
# gce_machine_type option
|
282
|
-
#
|
283
|
-
# @param [Integer] start The time when we started code execution, it is
|
284
|
-
# compared to Time.now to determine how many further code execution
|
285
|
-
# attempts remain
|
286
|
-
#
|
287
|
-
# @param [Integer] attempts The total amount of attempts to execute that we
|
288
|
-
# are willing to allow
|
289
|
-
#
|
290
|
-
# @return [Hash] The machineType hash
|
291
|
-
#
|
292
|
-
# @raise [Exception] Raised if we fail get the machineType, either through
|
293
|
-
# errors or running out of attempts
|
294
|
-
def get_machineType(start, attempts)
|
295
|
-
execute( machineType_get_req, start, attempts )
|
296
|
-
end
|
45
|
+
@options[:gce_project] = ENV['BEAKER_gce_project'] if ENV['BEAKER_gce_project']
|
297
46
|
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
# attempts remain
|
302
|
-
#
|
303
|
-
# @param [Integer] attempts The total amount of attempts to execute that we
|
304
|
-
# are willing to allow
|
305
|
-
#
|
306
|
-
# @return [Hash] The network hash
|
307
|
-
#
|
308
|
-
# @raise [Exception] Raised if we fail get the network, either through
|
309
|
-
# errors or running out of attempts
|
310
|
-
def get_network(start, attempts)
|
311
|
-
execute( network_get_req, start, attempts)
|
312
|
-
end
|
47
|
+
@options[:gce_zone] = ENV.fetch('BEAKER_gce_zone', DEFAULT_ZONE_NAME)
|
48
|
+
@options[:gce_network] = ENV.fetch('BEAKER_gce_network', DEFAULT_NETWORK_NAME)
|
49
|
+
@options[:gce_subnetwork] = ENV.fetch('BEAKER_gce_subnetwork', nil)
|
313
50
|
|
314
|
-
|
315
|
-
#
|
316
|
-
# @param [Integer] start The time when we started code execution, it is
|
317
|
-
# compared to Time.now to determine how many further code execution
|
318
|
-
# attempts remain
|
319
|
-
#
|
320
|
-
# @param [Integer] attempts The total amount of attempts to execute that we
|
321
|
-
# are willing to allow
|
322
|
-
#
|
323
|
-
# @return [Array[Hash]] The instances array of hashes
|
324
|
-
#
|
325
|
-
# @raise [Exception] Raised if we fail determine the list of existing
|
326
|
-
# instances, either through errors or running out of attempts
|
327
|
-
def list_instances(start, attempts)
|
328
|
-
instances = execute( instance_list_req(), start, attempts )
|
329
|
-
instances["items"]
|
330
|
-
end
|
51
|
+
raise 'You must specify a gce_project for Google Compute Engine instances!' unless @options[:gce_project]
|
331
52
|
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
# compared to Time.now to determine how many further code execution
|
336
|
-
# attempts remain
|
337
|
-
#
|
338
|
-
# @param [Integer] attempts The total amount of attempts to execute that we
|
339
|
-
# are willing to allow
|
340
|
-
#
|
341
|
-
# @return [Array[Hash]] The disks array of hashes
|
342
|
-
#
|
343
|
-
# @raise [Exception] Raised if we fail determine the list of existing
|
344
|
-
# disks, either through errors or running out of attempts
|
345
|
-
def list_disks(start, attempts)
|
346
|
-
disks = execute( disk_list_req(), start, attempts )
|
347
|
-
disks["items"]
|
348
|
-
end
|
53
|
+
authorizer = authenticate
|
54
|
+
@compute = ::Google::Apis::ComputeV1::ComputeService.new
|
55
|
+
@compute.authorization = authorizer
|
349
56
|
|
350
|
-
#
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
# attempts remain
|
355
|
-
#
|
356
|
-
# @param [Integer] attempts The total amount of attempts to execute that we
|
357
|
-
# are willing to allow
|
358
|
-
#
|
359
|
-
# @return [Array[Hash]] The firewalls array of hashes
|
360
|
-
#
|
361
|
-
# @raise [Exception] Raised if we fail determine the list of existing
|
362
|
-
# firewalls, either through errors or running out of attempts
|
363
|
-
def list_firewalls(start, attempts)
|
364
|
-
result = execute( firewall_list_req(), start, attempts )
|
365
|
-
firewalls = result["items"]
|
366
|
-
firewalls.delete_if{|f| f['name'] =~ /default-allow-internal|default-ssh/}
|
367
|
-
firewalls
|
368
|
-
end
|
57
|
+
# Find the appropriate username to log into created instances
|
58
|
+
@cloudoslogin = Google::Apis::OsloginV1::CloudOSLoginService.new
|
59
|
+
@cloudoslogin.authorization = authorizer
|
60
|
+
end
|
369
61
|
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
# compared to Time.now to determine how many further code execution
|
379
|
-
# attempts remain
|
380
|
-
#
|
381
|
-
# @param [Integer] attempts The total amount of attempts to execute that we
|
382
|
-
# are willing to allow
|
383
|
-
#
|
384
|
-
# @raise [Exception] Raised if we fail create the firewall, either through
|
385
|
-
# errors or running out of attempts
|
386
|
-
def create_firewall(name, network, start, attempts)
|
387
|
-
execute( firewall_insert_req( name, network['selfLink'] ), start, attempts )
|
388
|
-
end
|
62
|
+
##
|
63
|
+
# Determines the default Google Compute zone based upon options and
|
64
|
+
# defaults
|
65
|
+
#
|
66
|
+
# @return [String] The name of the zone
|
67
|
+
def default_zone
|
68
|
+
@options[:gce_zone]
|
69
|
+
end
|
389
70
|
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
# @param [Integer] attempts The total amount of attempts to execute that we
|
401
|
-
# are willing to allow
|
402
|
-
#
|
403
|
-
# @raise [Exception] Raised if we fail create the disk, either through
|
404
|
-
# errors or running out of attempts
|
405
|
-
def create_disk(name, img, start, attempts)
|
406
|
-
#create a new boot disk for this instance
|
407
|
-
disk = execute( disk_insert_req( name, img['selfLink'] ), start, attempts )
|
408
|
-
|
409
|
-
status = ''
|
410
|
-
try = (Time.now - start) / SLEEPWAIT
|
411
|
-
while status !~ /READY/ and try <= attempts
|
412
|
-
begin
|
413
|
-
disk = execute( disk_get_req( name ), start, attempts )
|
414
|
-
status = disk['status']
|
415
|
-
rescue GoogleComputeError => e
|
416
|
-
@logger.debug("Waiting for #{name} disk creation")
|
417
|
-
sleep(SLEEPWAIT)
|
418
|
-
end
|
419
|
-
try += 1
|
420
|
-
end
|
421
|
-
if status == ''
|
422
|
-
raise "Unable to create disk #{name}"
|
423
|
-
end
|
424
|
-
disk
|
425
|
-
end
|
71
|
+
##
|
72
|
+
# Get the region name from the provided zone.
|
73
|
+
#
|
74
|
+
# Assume that the region is the name of the zone without
|
75
|
+
# the final - and zone letter
|
76
|
+
#
|
77
|
+
# @return [String] The name of the region
|
78
|
+
def default_region
|
79
|
+
@options[:gce_zone].split('-')[0..1].join('-')
|
80
|
+
end
|
426
81
|
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
# @param [Hash] disk The Google Compute disk to attach to the newly created
|
436
|
-
# instance
|
437
|
-
#
|
438
|
-
# @param [Integer] start The time when we started code execution, it is
|
439
|
-
# compared to Time.now to determine how many further code execution
|
440
|
-
# attempts remain
|
441
|
-
#
|
442
|
-
# @param [Integer] attempts The total amount of attempts to execute that we
|
443
|
-
# are willing to allow
|
444
|
-
#
|
445
|
-
# @raise [Exception] Raised if we fail create the instance, either through
|
446
|
-
# errors or running out of attempts
|
447
|
-
def create_instance(name, img, machineType, disk, start, attempts)
|
448
|
-
#add a new instance of the image
|
449
|
-
instance = execute( instance_insert_req( name, img['selfLink'], machineType['selfLink'], disk['selfLink'] ), start, attempts)
|
450
|
-
status = ''
|
451
|
-
try = (Time.now - start) / SLEEPWAIT
|
452
|
-
while status !~ /RUNNING/ and try <= attempts
|
453
|
-
begin
|
454
|
-
instance = execute( instance_get_req( name ), start, attempts )
|
455
|
-
status = instance['status']
|
456
|
-
rescue GoogleComputeError => e
|
457
|
-
@logger.debug("Waiting for #{name} instance creation")
|
458
|
-
sleep(SLEEPWAIT)
|
459
|
-
end
|
460
|
-
try += 1
|
461
|
-
end
|
462
|
-
if status == ''
|
463
|
-
raise "Unable to create instance #{name}"
|
464
|
-
end
|
465
|
-
instance
|
466
|
-
end
|
82
|
+
##
|
83
|
+
# Determines the default Google Compute network based upon defaults and
|
84
|
+
# options
|
85
|
+
#
|
86
|
+
# @return [String] The short name of the VPC network
|
87
|
+
def default_network
|
88
|
+
@options[:gce_network]
|
89
|
+
end
|
467
90
|
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
#
|
479
|
-
#
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
while status !~ /DONE/ and try <= attempts
|
493
|
-
begin
|
494
|
-
operation = execute( operation_get_req( zone_operation['name'] ), start, attempts )
|
495
|
-
status = operation['status']
|
496
|
-
rescue GoogleComputeError => e
|
497
|
-
@logger.debug("Waiting for tags to be added to #{name}")
|
498
|
-
sleep(SLEEPWAIT)
|
499
|
-
end
|
500
|
-
try += 1
|
501
|
-
end
|
502
|
-
if status == ''
|
503
|
-
raise "Unable to set metaData (#{tags.to_s}) on #{name}"
|
504
|
-
end
|
505
|
-
zone_operation
|
506
|
-
end
|
91
|
+
##
|
92
|
+
# Find the username for ssh to use with this connection
|
93
|
+
#
|
94
|
+
# @return [String] The username for ssh
|
95
|
+
#
|
96
|
+
# @raise [Google::Auth::IDTokens::KeySourceError] if the key source failed to obtain public keys
|
97
|
+
# @raise [Google::Auth::IDTokens::VerificationError] if the token verification failed.
|
98
|
+
# Additional data may be available in the error subclass and message.
|
99
|
+
def ssh_username
|
100
|
+
authorizer = @compute.authorization
|
101
|
+
# This is a bit of a hack based on what I found in a user (default application credentials)
|
102
|
+
# and a service account. There might be a better way of doing this.
|
103
|
+
case authorizer.class.to_s
|
104
|
+
when 'Google::Auth::UserRefreshCredentials'
|
105
|
+
authorizer.refresh!
|
106
|
+
userid = ::Google::Auth::IDTokens.verify_oidc(authorizer.id_token)['email']
|
107
|
+
when 'Google::Auth::ServiceAccountCredentials'
|
108
|
+
userid = authorizer.issuer
|
109
|
+
else
|
110
|
+
raise 'Unknown type of credential'
|
111
|
+
end
|
112
|
+
userid = "users/#{userid}" unless userid.start_with? 'users/'
|
113
|
+
@cloudoslogin.get_user_login_profile(userid).posix_accounts[0].username
|
114
|
+
end
|
507
115
|
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
# Ensure deletion of instance
|
525
|
-
try = (Time.now - start) / SLEEPWAIT
|
526
|
-
while try <= attempts
|
527
|
-
begin
|
528
|
-
result = execute( instance_get_req( name ), start, attempts )
|
529
|
-
@logger.debug("Waiting for #{name} instance deletion")
|
530
|
-
sleep(SLEEPWAIT)
|
531
|
-
rescue GoogleComputeError => e
|
532
|
-
@logger.debug("#{name} instance deleted!")
|
533
|
-
return
|
534
|
-
end
|
535
|
-
try += 1
|
536
|
-
end
|
537
|
-
@logger.debug("#{name} instance was not removed before timeout, may still exist")
|
538
|
-
end
|
116
|
+
##
|
117
|
+
# Infer the network that a given subnetwork is attached to
|
118
|
+
#
|
119
|
+
# @param [String] subnetwork_name The name of the subnetwork
|
120
|
+
#
|
121
|
+
# @return [String] The short name of the network
|
122
|
+
#
|
123
|
+
# @raise [Google::Apis::ServerError] An error occurred on the server and the request can be retried
|
124
|
+
# @raise [Google::Apis::ClientError] The request is invalid and should not be retried without modification
|
125
|
+
# @raise [Google::Apis::AuthorizationError] Authorization is required
|
126
|
+
def default_network_from_subnet(subnetwork_name)
|
127
|
+
subnetwork = @compute.get_subnetwork(@options[:gce_project], default_region, subnetwork_name)
|
128
|
+
m = %r{.*/networks/(?<network_name>.*)\Z}.match subnetwork.network
|
129
|
+
nil if m.nil?
|
130
|
+
m['network_name']
|
131
|
+
end
|
539
132
|
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
|
549
|
-
|
550
|
-
|
551
|
-
|
552
|
-
|
553
|
-
|
554
|
-
|
555
|
-
|
556
|
-
|
557
|
-
|
558
|
-
|
559
|
-
|
560
|
-
|
561
|
-
@logger.debug("Waiting for #{name} disk deletion")
|
562
|
-
sleep(SLEEPWAIT)
|
563
|
-
rescue GoogleComputeError => e
|
564
|
-
@logger.debug("#{name} disk deleted!")
|
565
|
-
return
|
566
|
-
end
|
567
|
-
try += 1
|
568
|
-
end
|
569
|
-
@logger.debug("#{name} disk was not removed before timeout, may still exist")
|
570
|
-
end
|
133
|
+
##
|
134
|
+
# Determine the subnetwork to use for instances
|
135
|
+
#
|
136
|
+
# If the network is the 'default' network, get the 'default' subnetwork for the region.
|
137
|
+
# If no subnet is provided by the user, pick the first one out of the user-provided network
|
138
|
+
#
|
139
|
+
# @return [String] The name of the subnetwork that should be attached to the instances
|
140
|
+
def default_subnetwork
|
141
|
+
network_name = @options[:gce_network]
|
142
|
+
if network_name == 'default'
|
143
|
+
@options[:gce_subnetwork] ||= @compute.get_subnetwork(@options[:gce_project], default_region, 'default').name
|
144
|
+
elsif @options[:gce_subnetwork].nil?
|
145
|
+
# No subnet set, get the first subnet in our current region for the network
|
146
|
+
subnetwork = @compute.get_network(@options[:gce_project], network_name).subnetworks[0]
|
147
|
+
m = %r{.*/subnetworks/(?<subnetwork_name>.*)\Z}.match subnetwork
|
148
|
+
raise "Unable to find a subnetwork in provided network #{network_name}" if m.nil?
|
149
|
+
|
150
|
+
@options[:gce_subnetwork] = m['subnetwork_name']
|
151
|
+
end
|
152
|
+
@options[:gce_subnetwork]
|
153
|
+
end
|
571
154
|
|
572
|
-
|
573
|
-
|
574
|
-
|
575
|
-
|
576
|
-
|
577
|
-
|
578
|
-
|
579
|
-
|
580
|
-
# @param [Integer] attempts The total amount of attempts to execute that we
|
581
|
-
# are willing to allow
|
582
|
-
#
|
583
|
-
# @raise [Exception] Raised if we fail delete the firewall, either through
|
584
|
-
# errors or running out of attempts
|
585
|
-
def delete_firewall(name, start, attempts)
|
586
|
-
result = execute( firewall_delete_req( name ), start, attempts )
|
587
|
-
#ensure deletion of disk
|
588
|
-
try = (Time.now - start) / SLEEPWAIT
|
589
|
-
while try <= attempts
|
590
|
-
begin
|
591
|
-
firewall = execute( firewall_get_req( name ), start, attempts )
|
592
|
-
@logger.debug("Waiting for #{name} firewall deletion")
|
593
|
-
sleep(SLEEPWAIT)
|
594
|
-
rescue GoogleComputeError => e
|
595
|
-
@logger.debug("#{name} firewall deleted!")
|
596
|
-
return
|
597
|
-
end
|
598
|
-
try += 1
|
599
|
-
end
|
600
|
-
@logger.debug("#{name} firewall was not removed before timeout, may still exist")
|
601
|
-
end
|
155
|
+
##
|
156
|
+
# Set the user-agent information for the application.
|
157
|
+
#
|
158
|
+
# @param version The version number of Beaker currently running
|
159
|
+
def set_client(version)
|
160
|
+
::Google::Apis::ClientOptions.default.application_name = 'beaker-google'
|
161
|
+
::Google::Apis::ClientOptions.default.application_version = version
|
162
|
+
end
|
602
163
|
|
603
|
-
|
604
|
-
|
605
|
-
|
606
|
-
|
607
|
-
|
608
|
-
|
609
|
-
|
610
|
-
|
164
|
+
##
|
165
|
+
# Creates an authentication object to use in the various Google APIs
|
166
|
+
#
|
167
|
+
# This method currently supports using application credentials via the
|
168
|
+
# GOOGLE_APPLICATION_CREDENTIALS environment variable, and application default
|
169
|
+
# credentials.
|
170
|
+
#
|
171
|
+
# @return [Google::Auth::UserRefreshCredentials|Google::Auth::ServiceAccountCredentials]
|
172
|
+
# Authorization object to pass to Google APIs
|
173
|
+
def authenticate
|
174
|
+
if ENV['GOOGLE_APPLICATION_CREDENTIALS']
|
175
|
+
::Google::Auth::ServiceAccountCredentials.from_env(scope: GCP_AUTH_SCOPE)
|
176
|
+
else
|
177
|
+
# Fall back to default application auth
|
178
|
+
::Google::Auth.get_application_default(GCP_AUTH_SCOPE)
|
611
179
|
end
|
180
|
+
end
|
612
181
|
|
613
|
-
|
614
|
-
|
615
|
-
|
616
|
-
|
617
|
-
|
618
|
-
|
619
|
-
|
182
|
+
##
|
183
|
+
# Find the correct image object for a given project and name
|
184
|
+
#
|
185
|
+
# @param [String] image_project The project that owns the requested image
|
186
|
+
#
|
187
|
+
# @param [String] name The name of the image in the project. This must
|
188
|
+
# be the exact name of the image
|
189
|
+
#
|
190
|
+
# @return [Google::Apis::ComputeV1::Image]
|
191
|
+
#
|
192
|
+
# @raise [Google::Apis::ServerError] An error occurred on the server and the request can be retried
|
193
|
+
# @raise [Google::Apis::ClientError] The request is invalid and should not be retried without modification
|
194
|
+
# @raise [Google::Apis::AuthorizationError] Authorization is required
|
195
|
+
def get_image(project, name)
|
196
|
+
@compute.get_image(project, name)
|
197
|
+
end
|
620
198
|
|
621
|
-
|
622
|
-
|
623
|
-
|
624
|
-
|
625
|
-
|
626
|
-
|
627
|
-
|
628
|
-
|
629
|
-
|
199
|
+
##
|
200
|
+
# Find the latest non-deprecated image in the given project and family
|
201
|
+
#
|
202
|
+
# @param [String] image_project The project that owns the requested image
|
203
|
+
#
|
204
|
+
# @param [String] family The name of the image family
|
205
|
+
#
|
206
|
+
# @return [Google::Apis::ComputeV1::Image]
|
207
|
+
#
|
208
|
+
# @raise [Google::Apis::ServerError] An error occurred on the server and the request can be retried
|
209
|
+
# @raise [Google::Apis::ClientError] The request is invalid and should not be retried without modification
|
210
|
+
# @raise [Google::Apis::AuthorizationError] Authorization is required
|
211
|
+
def get_latest_image_from_family(image_project, family)
|
212
|
+
@compute.get_image_from_family(image_project, family)
|
213
|
+
end
|
630
214
|
|
631
|
-
|
632
|
-
|
633
|
-
|
634
|
-
|
635
|
-
|
636
|
-
|
637
|
-
|
638
|
-
|
639
|
-
|
215
|
+
##
|
216
|
+
# Determines the Google Compute machineType object based upon the selected
|
217
|
+
# gce_machine_type option
|
218
|
+
#
|
219
|
+
# @param [String] type_name The name of the type to get
|
220
|
+
#
|
221
|
+
# @return [Google::Apis::ComputeV1::MachineType]
|
222
|
+
#
|
223
|
+
# @raise [Google::Apis::ServerError] An error occurred on the server and the request can be retried
|
224
|
+
# @raise [Google::Apis::ClientError] The request is invalid and should not be retried without modification
|
225
|
+
# @raise [Google::Apis::AuthorizationError] Authorization is required
|
226
|
+
def get_machine_type(type_name = DEFAULT_MACHINE_TYPE)
|
227
|
+
@compute.get_machine_type(@options[:gce_project], default_zone, type_name)
|
228
|
+
end
|
640
229
|
|
641
|
-
|
642
|
-
|
643
|
-
|
644
|
-
|
645
|
-
|
646
|
-
|
647
|
-
|
648
|
-
|
649
|
-
|
650
|
-
|
651
|
-
|
652
|
-
:parameters => { 'project' => @options[:gce_project], 'zone' => DEFAULT_ZONE_NAME, 'sourceImage' => source },
|
653
|
-
:body_object => { 'name' => name, 'sizeGb' => DEFAULT_DISK_SIZE } }
|
654
|
-
end
|
230
|
+
##
|
231
|
+
# Determines the Google Compute network object in use for the current connection
|
232
|
+
#
|
233
|
+
# @return [Google::Apis::ComputeV1::Network]
|
234
|
+
#
|
235
|
+
# @raise [Google::Apis::ServerError] An error occurred on the server and the request can be retried
|
236
|
+
# @raise [Google::Apis::ClientError] The request is invalid and should not be retried without modification
|
237
|
+
# @raise [Google::Apis::AuthorizationError] Authorization is required
|
238
|
+
def get_network(network_name = default_network)
|
239
|
+
@compute.get_network(@options[:gce_project], network_name)
|
240
|
+
end
|
655
241
|
|
656
|
-
|
657
|
-
|
658
|
-
|
659
|
-
|
660
|
-
|
661
|
-
|
662
|
-
|
663
|
-
|
664
|
-
|
242
|
+
##
|
243
|
+
# Determines a list of existing Google Compute instances
|
244
|
+
#
|
245
|
+
# @return [Array[Google::Apis::ComputeV1::Instance]]
|
246
|
+
#
|
247
|
+
# @raise [Google::Apis::ServerError] An error occurred on the server and the request can be retried
|
248
|
+
# @raise [Google::Apis::ClientError] The request is invalid and should not be retried without modification
|
249
|
+
# @raise [Google::Apis::AuthorizationError] Authorization is required
|
250
|
+
def list_instances
|
251
|
+
@compute.list_instances(@options[:gce_project], default_zone).items
|
252
|
+
end
|
665
253
|
|
666
|
-
|
667
|
-
|
668
|
-
|
669
|
-
|
670
|
-
|
671
|
-
|
672
|
-
|
673
|
-
|
674
|
-
|
675
|
-
|
676
|
-
|
677
|
-
|
678
|
-
|
679
|
-
|
680
|
-
|
681
|
-
'sourceRanges' => [ "0.0.0.0/0" ] } }
|
682
|
-
end
|
254
|
+
##
|
255
|
+
# Determines a list of existing Google Compute disks
|
256
|
+
#
|
257
|
+
# @param [Integer] start The time when we started code execution, it is
|
258
|
+
# compared to Time.now to determine how many further code execution
|
259
|
+
# attempts remain
|
260
|
+
#
|
261
|
+
# @return [Array[Google::Apis::ComputeV1::Disk]]
|
262
|
+
#
|
263
|
+
# @raise [Google::Apis::ServerError] An error occurred on the server and the request can be retried
|
264
|
+
# @raise [Google::Apis::ClientError] The request is invalid and should not be retried without modification
|
265
|
+
# @raise [Google::Apis::AuthorizationError] Authorization is required
|
266
|
+
def list_disks
|
267
|
+
@compute.list_disks(@options[:gce_project], default_zone).items
|
268
|
+
end
|
683
269
|
|
684
|
-
|
685
|
-
|
686
|
-
|
687
|
-
|
688
|
-
|
689
|
-
|
690
|
-
|
691
|
-
|
692
|
-
|
270
|
+
##
|
271
|
+
# Determines a list of existing Google Compute firewalls
|
272
|
+
#
|
273
|
+
# @return [Array[Google::Apis::ComputeV1::Firewall]]
|
274
|
+
#
|
275
|
+
# @raise [Google::Apis::ServerError] An error occurred on the server and the request can be retried
|
276
|
+
# @raise [Google::Apis::ClientError] The request is invalid and should not be retried without modification
|
277
|
+
# @raise [Google::Apis::AuthorizationError] Authorization is required
|
278
|
+
def list_firewalls
|
279
|
+
@compute.list_firewalls(@options[:gce_project],
|
280
|
+
filter: 'name != default-allow-internal AND name != default-ssh').items
|
281
|
+
end
|
693
282
|
|
694
|
-
|
695
|
-
|
696
|
-
|
697
|
-
|
698
|
-
|
699
|
-
|
700
|
-
|
283
|
+
##
|
284
|
+
# Create a Google Compute firewall
|
285
|
+
#
|
286
|
+
# @param [String] name The name of the firewall to create
|
287
|
+
#
|
288
|
+
# @param [::Google::Apis::ComputeV1::Network] network The Google Compute networkin which to create
|
289
|
+
# the firewall
|
290
|
+
#
|
291
|
+
# @return [Google::Apis::ComputeV1::Operation]
|
292
|
+
#
|
293
|
+
# @raise [Google::Apis::ServerError] An error occurred on the server and the request can be retried
|
294
|
+
# @raise [Google::Apis::ClientError] The request is invalid and should not be retried without modification
|
295
|
+
# @raise [Google::Apis::AuthorizationError] Authorization is required
|
296
|
+
def create_firewall(name, network)
|
297
|
+
firewall_object = ::Google::Apis::ComputeV1::Firewall.new(
|
298
|
+
name: name,
|
299
|
+
allowed: [
|
300
|
+
::Google::Apis::ComputeV1::Firewall::Allowed.new(ip_protocol: 'tcp',
|
301
|
+
ports: ['443', '8140', '61613', '8080', '8081', '22']),
|
302
|
+
],
|
303
|
+
network: network.self_link,
|
304
|
+
# TODO: Is there a better way to do this?
|
305
|
+
sourceRanges: ['0.0.0.0/0'], # Allow from anywhere
|
306
|
+
)
|
307
|
+
operation = @compute.insert_firewall(@options[:gce_project], firewall_object)
|
308
|
+
@compute.wait_global_operation(@options[:gce_project], operation.name)
|
309
|
+
end
|
701
310
|
|
702
|
-
|
703
|
-
|
704
|
-
|
705
|
-
|
706
|
-
|
707
|
-
|
708
|
-
|
709
|
-
|
710
|
-
|
711
|
-
|
311
|
+
##
|
312
|
+
# Add a taget_tag to an existing firewall
|
313
|
+
#
|
314
|
+
# @param [String] the name of the firewall to update
|
315
|
+
#
|
316
|
+
# @ param [String] tag The tag to add to the firewall
|
317
|
+
#
|
318
|
+
# @return [Google::Apis::ComputeV1::Operation]
|
319
|
+
#
|
320
|
+
# @raise [Google::Apis::ServerError] An error occurred on the server and the request can be retried
|
321
|
+
# @raise [Google::Apis::ClientError] The request is invalid and should not be retried without modification
|
322
|
+
# @raise [Google::Apis::AuthorizationError] Authorization is required
|
323
|
+
def add_firewall_tag(name, tag)
|
324
|
+
firewall = @compute.get_firewall(@options[:gce_project], name)
|
325
|
+
firewall.target_tags = [] if firewall.target_tags.nil?
|
326
|
+
firewall.target_tags << tag
|
327
|
+
operation = @compute.patch_firewall(@options[:gce_project], name, firewall)
|
328
|
+
@compute.wait_global_operation(@options[:gce_project], operation.name)
|
329
|
+
end
|
712
330
|
|
713
|
-
|
714
|
-
|
715
|
-
|
716
|
-
|
717
|
-
|
718
|
-
|
719
|
-
|
331
|
+
##
|
332
|
+
# Create a Google Compute disk
|
333
|
+
#
|
334
|
+
# @param [String] name The name of the disk to create
|
335
|
+
#
|
336
|
+
# @param [String] img The existing disk image to clone for this image
|
337
|
+
# or nil to create a blank disk
|
338
|
+
#
|
339
|
+
# @return [Google::Apis::ComputeV1::Operation]
|
340
|
+
#
|
341
|
+
# @raise [Google::Apis::ServerError] An error occurred on the server and the request can be retried
|
342
|
+
# @raise [Google::Apis::ClientError] The request is invalid and should not be retried without modification
|
343
|
+
# @raise [Google::Apis::AuthorizationError] Authorization is required
|
344
|
+
def create_disk(name, size, img = nil)
|
345
|
+
new_disk = ::Google::Apis::ComputeV1::Disk.new(
|
346
|
+
name: name,
|
347
|
+
size_gb: size,
|
348
|
+
source_image: img,
|
349
|
+
)
|
350
|
+
operation = @compute.insert_disk(@options[:gce_project], @options[:gce_zone], new_disk)
|
351
|
+
@compute.wait_zone_operation(@options[:gce_project], @options[:gce_zone], operation.name)
|
352
|
+
end
|
720
353
|
|
721
|
-
|
722
|
-
|
723
|
-
|
724
|
-
|
725
|
-
|
726
|
-
|
727
|
-
|
728
|
-
|
729
|
-
|
730
|
-
|
731
|
-
|
732
|
-
|
733
|
-
|
354
|
+
##
|
355
|
+
# Create a Google Compute instance
|
356
|
+
#
|
357
|
+
# @param [String] name The name of the instance to create
|
358
|
+
#
|
359
|
+
# @param [Google::Apis::ComputeV1::Image] img The Google Compute image to use for instance creation
|
360
|
+
#
|
361
|
+
# @param [Google::Apis::ComputeV1::MachineType] machine_type The Google Compute Machine Type
|
362
|
+
#
|
363
|
+
# @param [Integer] disk_size The size of the boot disk for the new instance. Must be equal to or
|
364
|
+
# greater than the image disk's size
|
365
|
+
#
|
366
|
+
# @return [Google::Apis::ComputeV1::Operation]
|
367
|
+
#
|
368
|
+
# @raise [Google::Apis::ServerError] An error occurred on the server and the request can be retried
|
369
|
+
# @raise [Google::Apis::ClientError] The request is invalid and should not be retried without modification
|
370
|
+
# @raise [Google::Apis::AuthorizationError] Authorization is required
|
371
|
+
def create_instance(name, img, machine_type, disk_size)
|
372
|
+
initialize_params = ::Google::Apis::ComputeV1::AttachedDiskInitializeParams.new(
|
373
|
+
disk_size_gb: disk_size,
|
374
|
+
source_image: img.self_link,
|
375
|
+
)
|
376
|
+
disk_params = ::Google::Apis::ComputeV1::AttachedDisk.new(
|
377
|
+
boot: true,
|
378
|
+
auto_delete: true,
|
379
|
+
initialize_params: initialize_params,
|
380
|
+
)
|
381
|
+
# attached_network = ::Google::Apis::ComputeV1::networkInterfaces.new()
|
382
|
+
tags = ::Google::Apis::ComputeV1::Tags.new(
|
383
|
+
items: [name],
|
384
|
+
)
|
385
|
+
network_interface = ::Google::Apis::ComputeV1::NetworkInterface.new(
|
386
|
+
network: get_network(default_network).self_link,
|
387
|
+
subnetwork: @compute.get_subnetwork(@options[:gce_project], default_region, default_subnetwork).self_link,
|
388
|
+
# Create an AccessConfig to add a NAT IP to the host.
|
389
|
+
# TODO: Make this configurable
|
390
|
+
access_configs: [
|
391
|
+
::Google::Apis::ComputeV1::AccessConfig.new(
|
392
|
+
network_tier: 'STANDARD',
|
393
|
+
),
|
394
|
+
],
|
395
|
+
)
|
396
|
+
new_instance = ::Google::Apis::ComputeV1::Instance.new(
|
397
|
+
machine_type: machine_type.self_link,
|
398
|
+
name: name,
|
399
|
+
disks: [disk_params],
|
400
|
+
network_interfaces: [network_interface],
|
401
|
+
tags: tags,
|
402
|
+
)
|
403
|
+
operation = @compute.insert_instance(@options[:gce_project], @options[:gce_zone], new_instance)
|
404
|
+
@compute.wait_zone_operation(@options[:gce_project], @options[:gce_zone], operation.name)
|
405
|
+
end
|
734
406
|
|
735
|
-
|
736
|
-
|
737
|
-
|
738
|
-
|
739
|
-
|
740
|
-
|
741
|
-
|
407
|
+
##
|
408
|
+
# Get the named instace from Google Compute Image
|
409
|
+
#
|
410
|
+
# @param [String] name The name of the instance
|
411
|
+
#
|
412
|
+
# @return [Google::Apis::ComputeV1::Instance]
|
413
|
+
#
|
414
|
+
# @raise [Google::Apis::ServerError] An error occurred on the server and the request can be retried
|
415
|
+
# @raise [Google::Apis::ClientError] The request is invalid and should not be retried without modification
|
416
|
+
# @raise [Google::Apis::AuthorizationError] Authorization is required
|
417
|
+
def get_instance(name)
|
418
|
+
@compute.get_instance(@options[:gce_project], @options[:gce_zone], name)
|
419
|
+
end
|
742
420
|
|
743
|
-
|
744
|
-
|
745
|
-
|
746
|
-
|
747
|
-
|
748
|
-
|
749
|
-
|
750
|
-
|
751
|
-
|
421
|
+
##
|
422
|
+
# Set key/value metadata pairs to a Google Compute instance
|
423
|
+
#
|
424
|
+
# This function replaces any existing items in the metadata hash!
|
425
|
+
#
|
426
|
+
# @param [String] name The name of the instance to set metadata
|
427
|
+
#
|
428
|
+
# @param [String] data An array of hashes to set ass metadata. Each array
|
429
|
+
# item should have a 'key' and 'value' key.
|
430
|
+
#
|
431
|
+
# @return [Google::Apis::ComputeV1::Operation]
|
432
|
+
#
|
433
|
+
# @raise [Google::Apis::ServerError] An error occurred on the server and the request can be retried
|
434
|
+
# @raise [Google::Apis::ClientError] The request is invalid and should not be retried without modification
|
435
|
+
# @raise [Google::Apis::AuthorizationError] Authorization is required
|
436
|
+
def set_metadata_on_instance(name, data)
|
437
|
+
instance = @compute.get_instance(@options[:gce_project], @options[:gce_zone], name)
|
438
|
+
mdata = instance.metadata.dup
|
439
|
+
mdata.items = data
|
440
|
+
operation = @compute.set_instance_metadata(@options[:gce_project], @options[:gce_zone], name, mdata)
|
441
|
+
@compute.wait_zone_operation(@options[:gce_project], @options[:gce_zone], operation.name)
|
442
|
+
end
|
752
443
|
|
753
|
-
|
754
|
-
|
755
|
-
|
756
|
-
|
757
|
-
|
758
|
-
|
759
|
-
|
760
|
-
|
761
|
-
|
444
|
+
##
|
445
|
+
# Delete a Google Compute instance
|
446
|
+
#
|
447
|
+
# @param [String] name The name of the instance to delete
|
448
|
+
#
|
449
|
+
# @return [Google::Apis::ComputeV1::Operation]
|
450
|
+
#
|
451
|
+
# @raise [Google::Apis::ServerError] An error occurred on the server and the request can be retried
|
452
|
+
# @raise [Google::Apis::ClientError] The request is invalid and should not be retried without modification
|
453
|
+
# @raise [Google::Apis::AuthorizationError] Authorization is required
|
454
|
+
def delete_instance(name)
|
455
|
+
operation = @compute.delete_instance(@options[:gce_project], default_zone, name)
|
456
|
+
@compute.wait_zone_operation(@options[:gce_project], @options[:gce_zone], operation.name)
|
457
|
+
end
|
762
458
|
|
763
|
-
|
764
|
-
|
765
|
-
|
766
|
-
|
767
|
-
|
768
|
-
|
769
|
-
|
770
|
-
|
771
|
-
|
772
|
-
|
773
|
-
|
774
|
-
|
775
|
-
|
776
|
-
|
777
|
-
{ :api_method => @compute.instances.insert,
|
778
|
-
:parameters => { 'project' => @options[:gce_project], 'zone' => DEFAULT_ZONE_NAME },
|
779
|
-
:body_object => { 'name' => name,
|
780
|
-
'image' => image,
|
781
|
-
'zone' => default_zone,
|
782
|
-
'machineType' => machineType,
|
783
|
-
'disks' => [ { 'source' => disk,
|
784
|
-
'type' => 'PERSISTENT', 'boot' => 'true'} ],
|
785
|
-
'networkInterfaces' => [ { 'accessConfigs' => [{ 'type' => 'ONE_TO_ONE_NAT', 'name' => 'External NAT' }],
|
786
|
-
'network' => default_network } ] } }
|
787
|
-
end
|
459
|
+
##
|
460
|
+
# Delete a Google Compute disk
|
461
|
+
#
|
462
|
+
# @param [String] name The name of the disk to delete
|
463
|
+
#
|
464
|
+
# @return [Google::Apis::ComputeV1::Operation]
|
465
|
+
#
|
466
|
+
# @raise [Google::Apis::ServerError] An error occurred on the server and the request can be retried
|
467
|
+
# @raise [Google::Apis::ClientError] The request is invalid and should not be retried without modification
|
468
|
+
# @raise [Google::Apis::AuthorizationError] Authorization is required
|
469
|
+
def delete_disk(name)
|
470
|
+
operation = @compute.delete_disk(@options[:gce_project], default_zone, name)
|
471
|
+
@compute.wait_zone_operation(@options[:gce_project], @options[:gce_zone], operation.name)
|
472
|
+
end
|
788
473
|
|
789
|
-
|
790
|
-
|
791
|
-
|
792
|
-
|
793
|
-
|
794
|
-
|
795
|
-
|
474
|
+
##
|
475
|
+
# Delete a Google Compute firewall
|
476
|
+
#
|
477
|
+
# @param [String] name The name of the firewall to delete
|
478
|
+
#
|
479
|
+
# @return [Google::Apis::ComputeV1::Operation]
|
480
|
+
#
|
481
|
+
# @raise [Google::Apis::ServerError] An error occurred on the server and the request can be retried
|
482
|
+
# @raise [Google::Apis::ClientError] The request is invalid and should not be retried without modification
|
483
|
+
# @raise [Google::Apis::AuthorizationError] Authorization is required
|
484
|
+
def delete_firewall(name)
|
485
|
+
operation = @compute.delete_firewall(@options[:gce_project], name)
|
486
|
+
@compute.wait_global_operation(@options[:gce_project], operation.name)
|
796
487
|
end
|
797
488
|
end
|