beaker-google 0.2.1 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/dependabot.yml +8 -0
- 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 +50 -15
data/Gemfile
CHANGED
@@ -1,27 +1,24 @@
|
|
1
|
-
source ENV
|
1
|
+
source ENV.fetch('GEM_SOURCE', 'https://rubygems.org')
|
2
2
|
|
3
3
|
gemspec
|
4
4
|
|
5
|
-
|
6
|
-
|
7
5
|
def location_for(place, fake_version = nil)
|
8
6
|
if place =~ /^git:([^#]*)#(.*)/
|
9
|
-
[fake_version, { :
|
10
|
-
elsif place =~
|
11
|
-
['>= 0', { :
|
7
|
+
[fake_version, { git: Regexp.last_match(1), branch: Regexp.last_match(2), require: false }].compact
|
8
|
+
elsif place =~ %r{^file://(.*)}
|
9
|
+
['>= 0', { path: File.expand_path(Regexp.last_match(1)), require: false }]
|
12
10
|
else
|
13
|
-
[place, { :
|
11
|
+
[place, { require: false }]
|
14
12
|
end
|
15
13
|
end
|
16
14
|
|
17
|
-
|
18
15
|
# We don't put beaker in as a test dependency because we
|
19
16
|
# don't want to create a transitive dependency
|
20
17
|
group :acceptance_testing do
|
21
|
-
gem
|
18
|
+
gem 'beaker', *location_for(ENV.fetch('BEAKER_VERSION', '~> 4.0'))
|
22
19
|
end
|
23
20
|
|
24
21
|
|
25
|
-
if File.exists? "#{__FILE__}.local"
|
26
|
-
|
27
|
-
end
|
22
|
+
# if File.exists? "#{__FILE__}.local"
|
23
|
+
# eval(File.read("#{__FILE__}.local"), binding)
|
24
|
+
# end
|
data/README.md
CHANGED
@@ -1,40 +1,69 @@
|
|
1
1
|
# beaker-google
|
2
2
|
|
3
|
+
[![License](https://img.shields.io/github/license/voxpupuli/beaker-google.svg)](https://github.com/voxpupuli/beaker-google/blob/master/LICENSE)
|
4
|
+
[![Test](https://github.com/voxpupuli/beaker-google/actions/workflows/ci.yml/badge.svg)](https://github.com/voxpupuli/beaker-google/actions/workflows/ci.yml)
|
5
|
+
[![codecov](https://codecov.io/gh/voxpupuli/beaker-google/branch/main/graph/badge.svg)](https://codecov.io/gh/voxpupuli/beaker-google)
|
6
|
+
[![Release](https://github.com/voxpupuli/beaker-google/actions/workflows/release.yml/badge.svg)](https://github.com/voxpupuli/beaker-google/actions/workflows/release.yml)
|
7
|
+
[![RubyGem Version](https://img.shields.io/gem/v/beaker-google.svg)](https://rubygems.org/gems/beaker-google)
|
8
|
+
[![RubyGem Downloads](https://img.shields.io/gem/dt/beaker-google.svg)](https://rubygems.org/gems/beaker-google)
|
9
|
+
[![Donated by Puppet Inc](https://img.shields.io/badge/donated%20by-Puppet%20Inc-fb7047.svg)](#transfer-notice)
|
10
|
+
|
3
11
|
Beaker library to use the Google hypervisor
|
4
12
|
|
5
13
|
# How to use this wizardry
|
6
14
|
|
7
|
-
This is a gem that allows you to use hosts with [
|
8
|
-
|
9
|
-
See the [documentation](docs/manual.md) for the full manual.
|
15
|
+
This is a gem that allows you to use hosts with [Google Compute](https://cloud.google.com/compute) hypervisor with [Beaker](https://github.com/voxpupuli/beaker).
|
10
16
|
|
11
17
|
Beaker will automatically load the appropriate hypervisors for any given hosts file, so as long as your project dependencies are satisfied there's nothing else to do. No need to `require` this library in your tests.
|
12
18
|
|
13
|
-
## With Beaker 3.x
|
14
|
-
This gem is already included as [beaker dependency](https://github.com/puppetlabs/beaker/blob/master/beaker.gemspec)
|
15
|
-
for you, so you don't need to do anything special to use this gem's
|
16
|
-
functionality with Beaker.
|
17
|
-
|
18
|
-
This library is included as a dependency of Beaker 3.x versions, so there's nothing to do.
|
19
|
-
|
20
19
|
## With Beaker 4.x
|
21
20
|
|
22
21
|
As of Beaker 4.0, all hypervisor and DSL extension libraries have been removed and are no longer dependencies. In order to use a specific hypervisor or DSL extension library in your project, you will need to include them alongside Beaker in your Gemfile or project.gemspec. E.g.
|
23
22
|
|
24
|
-
|
23
|
+
```ruby
|
25
24
|
# Gemfile
|
26
25
|
gem 'beaker', '~>4.0'
|
27
|
-
gem 'beaker-
|
26
|
+
gem 'beaker-google'
|
28
27
|
# project.gemspec
|
29
28
|
s.add_runtime_dependency 'beaker', '~>4.0'
|
30
|
-
s.add_runtime_dependency 'beaker-
|
31
|
-
|
29
|
+
s.add_runtime_dependency 'beaker-google'
|
30
|
+
```
|
31
|
+
|
32
|
+
## Authentication
|
33
|
+
|
34
|
+
You must be authenticated to Google Compute Engine to be able to use `beaker-google`. Authentication is attempted in two different ways, and the first that succeeds is used.
|
35
|
+
|
36
|
+
- Using the environment variable [`GOOGLE_APPLICATION_CREDENTIALS`](https://cloud.google.com/docs/authentication/production#passing_variable), which points to a file containing the credentials for a GCP service account, created by `gcloud iam service-accounts keys create` (or equivalent).
|
37
|
+
- Using [Application Default Credentials](https://cloud.google.com/docs/authentication/production).
|
38
|
+
|
39
|
+
## Configuration
|
40
|
+
|
41
|
+
The behavior of this library can be configured using either the beaker host configuration file, or environment variables.
|
42
|
+
|
43
|
+
| configuration option | required | default | description |
|
44
|
+
| -------------------- | ---------------- | -------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
45
|
+
| gce_project | true | | The ID of the Google GCP project to host resources. |
|
46
|
+
| gce_zone | true | | The zone to place compute instances in. The region is calculated from the zone name. |
|
47
|
+
| gce_network | false | Default | The name of the network to attach to instances. If the project uses the default network, this and `gce_subnetwork` can be left empty. |
|
48
|
+
| gce_subnetwork | false | Default | THe name of the subnetwork to attach to the instances network interface. If the Default network is not used, this must be supplied. |
|
49
|
+
| gce_ssh_private_key | false | $HOME/.ssh/google_compute_engine | The file path of the private key to use to connect to instances. If using the key created by the gcloud tool, this can be left blank. |
|
50
|
+
| gce_ssh_public_key | false | <gce_ssh_private_key>.pub | The file path of the public key to upload to the instance. If left blank, attempt to use the file at `gce_ssh_private_key` with a `.pub` extension. |
|
51
|
+
| gce_machine_type | false | e2-standard-4 | The machine type to use for the instance. If the `BEAKER_gce_machine_type` environment variable is set, it will be used for all hosts. |
|
52
|
+
| volume_size | false | Source Image disk's size | The size of the boot disk for the image. If unset, the disk will be the same size as the image's boot disk. Provided size must be equal to or larger than the image's disk size. |
|
53
|
+
| image | true or `family` | | The image to use for creating this instance. It can be either in the form `{project}/{image}` to use an image in a different project, or `{image}`, which will look for the image in `gce_project`. |
|
54
|
+
| family | true or `image` | | The image family to use for creating this instance. It can be either in the form `{project}/{family}` to use an image from a family in a different project, or `{family}`, which will look for the image family in `gce_project`. The latest non-deprecated image in the family will be used. |
|
55
|
+
|
56
|
+
All the variables in the list can be set in the Beaker host configuration file, or the ones starting with `gce_` can be overridden by environment variables in the form `BEAKER_gce_...`. i.e. To override the `gce_machine_type` setting in the environment, set `BEAKER_gce_machine_type`.
|
57
|
+
|
58
|
+
# Cleanup
|
59
|
+
|
60
|
+
In cases where the beaker process is killed before finishing, it may leave resources in GCP. These resources will need to be manually deleted.
|
32
61
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
62
|
+
| Resource Type | Name Pattern | Count |
|
63
|
+
| ------------- | ------------------- | ------------------------------------------- |
|
64
|
+
| Firewall | `beaker-<number>-*` | 1 |
|
65
|
+
| Instance | `beaker-*` | One or more depending on test configuration |
|
37
66
|
|
38
67
|
# Contributing
|
39
68
|
|
40
|
-
Please refer to
|
69
|
+
Please refer to voxpupuli/beaker's [contributing](https://github.com/voxpupuli/beaker/blob/master/CONTRIBUTING.md) guide.
|
data/Rakefile
CHANGED
@@ -5,65 +5,61 @@ require 'rspec/core/rake_task'
|
|
5
5
|
# Documentation Tasks
|
6
6
|
#
|
7
7
|
###########################################################
|
8
|
-
DOCS_DAEMON =
|
8
|
+
DOCS_DAEMON = 'yard server --reload --daemon --server thin'
|
9
9
|
FOREGROUND_SERVER = 'bundle exec yard server --reload --verbose --server thin lib/beaker'
|
10
10
|
|
11
|
-
def running?(
|
11
|
+
def running?(cmdline)
|
12
12
|
ps = `ps -ef`
|
13
|
-
found = ps.lines.grep(
|
14
|
-
if found.length > 1
|
15
|
-
raise StandardError, "Found multiple YARD Servers. Don't know what to do."
|
16
|
-
end
|
13
|
+
found = ps.lines.grep(/#{Regexp.quote(cmdline)}/)
|
14
|
+
raise StandardError, "Found multiple YARD Servers. Don't know what to do." if found.length > 1
|
17
15
|
|
18
16
|
yes = found.empty? ? false : true
|
19
|
-
|
17
|
+
[yes, found.first]
|
20
18
|
end
|
21
19
|
|
22
|
-
def pid_from(
|
20
|
+
def pid_from(output)
|
23
21
|
output.squeeze(' ').strip.split(' ')[1]
|
24
22
|
end
|
25
23
|
|
26
24
|
desc 'Start the documentation server in the foreground'
|
27
|
-
task :
|
25
|
+
task docs: 'docs:clear' do
|
28
26
|
original_dir = Dir.pwd
|
29
|
-
Dir.chdir(
|
27
|
+
Dir.chdir(__dir__)
|
30
28
|
sh FOREGROUND_SERVER
|
31
|
-
Dir.chdir(
|
29
|
+
Dir.chdir(original_dir)
|
32
30
|
end
|
33
31
|
|
34
32
|
namespace :docs do
|
35
|
-
|
36
33
|
desc 'Clear the generated documentation cache'
|
37
34
|
task :clear do
|
38
35
|
original_dir = Dir.pwd
|
39
|
-
Dir.chdir(
|
36
|
+
Dir.chdir(__dir__)
|
40
37
|
sh 'rm -rf docs'
|
41
|
-
Dir.chdir(
|
38
|
+
Dir.chdir(original_dir)
|
42
39
|
end
|
43
40
|
|
44
41
|
desc 'Generate static documentation'
|
45
|
-
task :
|
42
|
+
task gen: 'docs:clear' do
|
46
43
|
original_dir = Dir.pwd
|
47
|
-
Dir.chdir(
|
44
|
+
Dir.chdir(__dir__)
|
48
45
|
output = `bundle exec yard doc`
|
49
46
|
puts output
|
50
|
-
if output =~ /\[warn\]|\[error\]/
|
51
|
-
|
52
|
-
|
53
|
-
Dir.chdir( original_dir )
|
47
|
+
raise 'Errors/Warnings during yard documentation generation' if output =~ /\[warn\]|\[error\]/
|
48
|
+
|
49
|
+
Dir.chdir(original_dir)
|
54
50
|
end
|
55
51
|
|
56
52
|
desc 'Run the documentation server in the background, alias `bg`'
|
57
|
-
task :
|
58
|
-
yes, output = running?(
|
53
|
+
task background: 'docs:clear' do
|
54
|
+
yes, output = running?(DOCS_DAEMON)
|
59
55
|
if yes
|
60
|
-
puts
|
61
|
-
puts "Found one running with pid #{pid_from(
|
56
|
+
puts 'Not starting a new YARD Server...'
|
57
|
+
puts "Found one running with pid #{pid_from(output)}."
|
62
58
|
else
|
63
59
|
original_dir = Dir.pwd
|
64
|
-
Dir.chdir(
|
60
|
+
Dir.chdir(__dir__)
|
65
61
|
sh "bundle exec #{DOCS_DAEMON}"
|
66
|
-
Dir.chdir(
|
62
|
+
Dir.chdir(original_dir)
|
67
63
|
end
|
68
64
|
end
|
69
65
|
|
@@ -71,37 +67,37 @@ namespace :docs do
|
|
71
67
|
|
72
68
|
desc 'Check the status of the documentation server'
|
73
69
|
task :status do
|
74
|
-
yes, output = running?(
|
70
|
+
yes, output = running?(DOCS_DAEMON)
|
75
71
|
if yes
|
76
|
-
pid = pid_from(
|
72
|
+
pid = pid_from(output)
|
77
73
|
puts "Found a YARD Server running with pid #{pid}"
|
78
74
|
else
|
79
|
-
puts
|
75
|
+
puts 'Could not find a running YARD Server.'
|
80
76
|
end
|
81
77
|
end
|
82
78
|
|
83
|
-
desc
|
79
|
+
desc 'Stop a running YARD Server'
|
84
80
|
task :stop do
|
85
|
-
yes, output = running?(
|
81
|
+
yes, output = running?(DOCS_DAEMON)
|
86
82
|
if yes
|
87
|
-
pid = pid_from(
|
83
|
+
pid = pid_from(output)
|
88
84
|
puts "Found a YARD Server running with pid #{pid}"
|
89
85
|
`kill #{pid}`
|
90
|
-
puts
|
91
|
-
yes, output = running?(
|
86
|
+
puts 'Stopping...'
|
87
|
+
yes, output = running?(DOCS_DAEMON)
|
92
88
|
if yes
|
93
89
|
`kill -9 #{pid}`
|
94
|
-
yes, output = running?(
|
90
|
+
yes, output = running?(DOCS_DAEMON)
|
95
91
|
if yes
|
96
|
-
puts
|
92
|
+
puts 'Could not Stop Server!'
|
97
93
|
else
|
98
|
-
puts
|
94
|
+
puts 'Server stopped.'
|
99
95
|
end
|
100
96
|
else
|
101
|
-
puts
|
97
|
+
puts 'Server stopped.'
|
102
98
|
end
|
103
99
|
else
|
104
|
-
puts
|
100
|
+
puts 'Could not find a running YARD Server'
|
105
101
|
end
|
106
102
|
end
|
107
103
|
end
|
data/beaker-google.gemspec
CHANGED
@@ -5,11 +5,11 @@ require 'beaker-google/version'
|
|
5
5
|
Gem::Specification.new do |s|
|
6
6
|
s.name = "beaker-google"
|
7
7
|
s.version = BeakerGoogle::VERSION
|
8
|
-
s.authors = ["
|
9
|
-
s.email = ["
|
10
|
-
s.homepage = "https://github.com/
|
8
|
+
s.authors = ["Puppet", "Voxpupuli"]
|
9
|
+
s.email = ["voxpupuli@groups.io"]
|
10
|
+
s.homepage = "https://github.com/voxpupuli/beaker-google"
|
11
11
|
s.summary = %q{Beaker DSL Extension Helpers!}
|
12
|
-
s.description = %q{
|
12
|
+
s.description = %q{Google Compute Engine support for the Beaker acceptance testing tool.}
|
13
13
|
s.license = 'Apache2'
|
14
14
|
|
15
15
|
s.files = `git ls-files`.split("\n")
|
@@ -17,16 +17,13 @@ Gem::Specification.new do |s|
|
|
17
17
|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
18
18
|
s.require_paths = ["lib"]
|
19
19
|
|
20
|
+
s.required_ruby_version = Gem::Requirement.new('>= 2.4')
|
21
|
+
|
20
22
|
# Testing dependencies
|
21
23
|
s.add_development_dependency 'rspec', '~> 3.0'
|
22
24
|
s.add_development_dependency 'rspec-its'
|
23
|
-
|
24
|
-
|
25
|
-
s.add_development_dependency 'fakefs', '~> 0.6', '< 0.14'
|
26
|
-
else
|
27
|
-
s.add_development_dependency 'fakefs', '~> 0.6'
|
28
|
-
end
|
29
|
-
s.add_development_dependency 'rake', '~> 10.1'
|
25
|
+
s.add_development_dependency 'fakefs', '~> 1.8'
|
26
|
+
s.add_development_dependency 'rake', '~> 13.0'
|
30
27
|
s.add_development_dependency 'simplecov'
|
31
28
|
s.add_development_dependency 'pry', '~> 0.10'
|
32
29
|
|
@@ -36,7 +33,8 @@ Gem::Specification.new do |s|
|
|
36
33
|
|
37
34
|
# Run time dependencies
|
38
35
|
s.add_runtime_dependency 'stringify-hash', '~> 0.0.0'
|
39
|
-
s.add_runtime_dependency 'google-api-client', '~> 0.8'
|
40
36
|
|
37
|
+
s.add_runtime_dependency 'google-apis-compute_v1', '~> 0.1'
|
38
|
+
s.add_runtime_dependency 'google-apis-oslogin_v1', '~> 0.1'
|
39
|
+
s.add_runtime_dependency 'googleauth', '~> 1.2'
|
41
40
|
end
|
42
|
-
|
data/bin/beaker-google
CHANGED
@@ -1,37 +1,52 @@
|
|
1
1
|
require 'time'
|
2
2
|
|
3
3
|
module Beaker
|
4
|
-
|
5
4
|
# Beaker support for the Google Compute Engine.
|
6
5
|
class GoogleCompute < Beaker::Hypervisor
|
7
|
-
|
8
6
|
SLEEPWAIT = 5
|
9
7
|
|
10
8
|
# Hours before an instance is considered a zombie
|
11
9
|
ZOMBIE = 3
|
12
10
|
|
13
11
|
# Do some reasonable sleuthing on the SSH public key for GCE
|
14
|
-
def find_google_ssh_public_key
|
15
|
-
keyfile = ENV.fetch('BEAKER_gce_ssh_public_key', File.join(ENV['HOME'], '.ssh', 'google_compute_engine.pub'))
|
16
12
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
13
|
+
##
|
14
|
+
# Try to find the private ssh key file
|
15
|
+
#
|
16
|
+
# @return [String] The file path for the private key file
|
17
|
+
#
|
18
|
+
# @raise [Error] if the private key can not be found
|
19
|
+
def find_google_ssh_private_key
|
20
|
+
private_keyfile = ENV.fetch('BEAKER_gce_ssh_public_key',
|
21
|
+
File.join(ENV.fetch('HOME', nil), '.ssh', 'google_compute_engine'))
|
22
|
+
private_keyfile = @options[:gce_ssh_private_key] if @options[:gce_ssh_private_key] && !File.exist?(private_keyfile)
|
23
|
+
raise("Could not find GCE Private SSH key at '#{keyfile}'") unless File.exist?(private_keyfile)
|
24
|
+
@options[:gce_ssh_private_key] = private_keyfile
|
25
|
+
private_keyfile
|
26
|
+
end
|
22
27
|
|
23
|
-
|
28
|
+
##
|
29
|
+
# Try to find the public key file based on the location of the private key or provided data
|
30
|
+
#
|
31
|
+
# @return [String] The file path for the public key file
|
32
|
+
#
|
33
|
+
# @raise [Error] if the public key can not be found
|
34
|
+
def find_google_ssh_public_key
|
35
|
+
private_keyfile = find_google_ssh_private_key
|
36
|
+
public_keyfile = private_keyfile << '.pub'
|
37
|
+
public_keyfile = @options[:gce_ssh_public_key] if @options[:gce_ssh_public_key] && !File.exist?(public_keyfile)
|
38
|
+
raise("Could not find GCE Public SSH key at '#{keyfile}'") unless File.exist?(public_keyfile)
|
39
|
+
@options[:gce_ssh_public_key] = public_keyfile
|
40
|
+
public_keyfile
|
24
41
|
end
|
25
42
|
|
26
|
-
#
|
27
|
-
# :
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
{:key => :jenkins_build_url, :value => @options[:jenkins_build_url]},
|
32
|
-
{:key => :sshKeys, :value => "google_compute:#{File.read(find_google_ssh_public_key).strip}" }
|
33
|
-
].delete_if { |member| member[:value].nil? or member[:value].empty?}
|
43
|
+
# IP is the only way we can be sure to connect
|
44
|
+
# TODO: This isn't being called
|
45
|
+
# rubocop:disable Lint/UnusedMethodArgument
|
46
|
+
def connection_preference(host)
|
47
|
+
[:ip]
|
34
48
|
end
|
49
|
+
# rubocop:enable Lint/UnusedMethodArgument
|
35
50
|
|
36
51
|
# Create a new instance of the Google Compute Engine hypervisor object
|
37
52
|
#
|
@@ -60,6 +75,7 @@ module Beaker
|
|
60
75
|
def initialize(google_hosts, options)
|
61
76
|
require 'beaker/hypervisor/google_compute_helper'
|
62
77
|
|
78
|
+
super
|
63
79
|
@options = options
|
64
80
|
@logger = options[:logger]
|
65
81
|
@hosts = google_hosts
|
@@ -70,67 +86,127 @@ module Beaker
|
|
70
86
|
# Create and configure virtual machines in the Google Compute Engine,
|
71
87
|
# including their associated disks and firewall rules
|
72
88
|
def provision
|
73
|
-
attempts = @options[:timeout].to_i / SLEEPWAIT
|
74
89
|
start = Time.now
|
75
|
-
|
76
90
|
test_group_identifier = "beaker-#{start.to_i}-"
|
77
91
|
|
78
|
-
# get machineType resource, used by all instances
|
79
|
-
machineType = @gce_helper.get_machineType(start, attempts)
|
80
|
-
|
81
92
|
# set firewall to open pe ports
|
82
|
-
network = @gce_helper.get_network
|
93
|
+
network = @gce_helper.get_network
|
94
|
+
|
83
95
|
@firewall = test_group_identifier + generate_host_name
|
84
|
-
@gce_helper.create_firewall(@firewall, network, start, attempts)
|
85
96
|
|
86
|
-
@
|
97
|
+
@gce_helper.create_firewall(@firewall, network)
|
87
98
|
|
99
|
+
@logger.debug("Created Google Compute firewall #{@firewall}")
|
88
100
|
|
89
101
|
@hosts.each do |host|
|
102
|
+
|
103
|
+
machine_type_name = ENV.fetch('BEAKER_gce_machine_type', host['gce_machine_type'])
|
104
|
+
raise "Must provide a machine type name in 'gce_machine_type'." if machine_type_name.nil?
|
105
|
+
# Get the GCE machine type object for this host
|
106
|
+
machine_type = @gce_helper.get_machine_type(machine_type_name)
|
107
|
+
raise "Unable to find machine type named #{machine_type_name} in region #{@compute.default_zone}" if machine_type.nil?
|
108
|
+
|
109
|
+
# Find the image to use to create the new VM.
|
110
|
+
# Either `image` or `family` must be set in the configuration. Accepted formats
|
111
|
+
# for the image and family:
|
112
|
+
# - {project}/{image}
|
113
|
+
# - {project}/{family}
|
114
|
+
# - {image}
|
115
|
+
# - {family}
|
116
|
+
#
|
117
|
+
# If a {project} is not specified, default to the project provided in the
|
118
|
+
# BEAKER_gce_project environment variable
|
90
119
|
if host[:image]
|
91
|
-
|
92
|
-
|
93
|
-
|
120
|
+
image_selector = host[:image]
|
121
|
+
# Do we have a project name?
|
122
|
+
if %r{/}.match?(image_selector)
|
123
|
+
image_project, image_name = image_selector.split('/')[0..1]
|
124
|
+
else
|
125
|
+
image_project = @gce_helper.options[:gce_project]
|
126
|
+
image_name = image_selector
|
127
|
+
end
|
128
|
+
img = @gce_helper.get_image(image_project, image_name)
|
129
|
+
raise "Unable to find image #{image_name} from project #{image_project}" if img.nil?
|
130
|
+
elsif host[:family]
|
131
|
+
image_selector = host[:family]
|
132
|
+
# Do we have a project name?
|
133
|
+
if %r{/}.match?(image_selector)
|
134
|
+
image_project, family_name = image_selector.split('/')
|
135
|
+
else
|
136
|
+
image_project = @gce_helper.options[:gce_project]
|
137
|
+
family_name = image_selector
|
138
|
+
end
|
139
|
+
img = @gce_helper.get_latest_image_from_family(image_project, family_name)
|
140
|
+
raise "Unable to find image in family #{family_name} from project #{image_project}" if img.nil?
|
94
141
|
else
|
95
|
-
raise('You must specify either :image or :
|
142
|
+
raise('You must specify either :image or :family')
|
96
143
|
end
|
97
144
|
|
98
|
-
img = @gce_helper.get_latest_image(gplatform, start, attempts)
|
99
|
-
|
100
145
|
unique_host_id = test_group_identifier + generate_host_name
|
101
146
|
|
102
|
-
host['
|
103
|
-
|
104
|
-
|
147
|
+
boot_size = host['volume_size'] || img.disk_size_gb
|
148
|
+
|
149
|
+
# The boot disk is created as part of the instance creation
|
150
|
+
# TODO: Allow creation of other disks
|
151
|
+
# disk = @gce_helper.create_disk(host["diskname"], img, size)
|
152
|
+
# @logger.debug("Created Google Compute disk for #{host.name}: #{host["diskname"]}")
|
105
153
|
|
106
154
|
# create new host name
|
107
155
|
host['vmhostname'] = unique_host_id
|
108
|
-
|
109
|
-
|
156
|
+
|
157
|
+
# add a new instance of the image
|
158
|
+
operation = @gce_helper.create_instance(host['vmhostname'], img, machine_type, boot_size)
|
159
|
+
unless operation.error.nil?
|
160
|
+
raise "Unable to create Google Compute Instance #{host.name}: [#{operation.error.errors[0].code}] #{operation.error.errors[0].message}"
|
161
|
+
end
|
110
162
|
@logger.debug("Created Google Compute instance for #{host.name}: #{host['vmhostname']}")
|
163
|
+
instance = @gce_helper.get_instance(host['vmhostname'])
|
111
164
|
|
112
165
|
# add metadata to instance, if there is any to set
|
113
|
-
mdata = format_metadata
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
166
|
+
# mdata = format_metadata
|
167
|
+
# TODO: Set a configuration option for this to allow disabeling oslogin
|
168
|
+
mdata = [
|
169
|
+
{
|
170
|
+
key: 'ssh-keys',
|
171
|
+
value: "google_compute:#{File.read(find_google_ssh_public_key).strip}"
|
172
|
+
},
|
173
|
+
# For now oslogin needs to be disabled as there's no way to log in as root and it would
|
174
|
+
# take too much work on beaker to add sudo support to everything
|
175
|
+
{
|
176
|
+
key: 'enable-oslogin',
|
177
|
+
value: 'FALSE'
|
178
|
+
},
|
179
|
+
]
|
180
|
+
next if mdata.empty?
|
181
|
+
# Add the metadata to the host
|
182
|
+
@gce_helper.set_metadata_on_instance(host['vmhostname'], mdata)
|
183
|
+
@logger.debug("Added tags to Google Compute instance #{host.name}: #{host['vmhostname']}")
|
184
|
+
|
185
|
+
host['ip'] = instance.network_interfaces[0].access_configs[0].nat_ip
|
186
|
+
|
187
|
+
# Add the new host to the firewall
|
188
|
+
@gce_helper.add_firewall_tag(@firewall, host['vmhostname'])
|
189
|
+
|
190
|
+
if host['disable_root_ssh'] == true
|
191
|
+
@logger.info('Not enabling root ssh as disable_root_ssh is true')
|
192
|
+
else
|
120
193
|
|
121
|
-
|
122
|
-
|
194
|
+
# # configure ssh
|
195
|
+
default_user = host['user']
|
123
196
|
|
124
|
-
|
125
|
-
|
126
|
-
host['user'] = 'google_compute'
|
197
|
+
# TODO: Pull this out into a configuration option or something
|
198
|
+
host['user'] = 'google_compute'
|
127
199
|
|
128
|
-
|
129
|
-
|
130
|
-
host['user'] = default_user
|
200
|
+
# Set the ssh private key we need to use
|
201
|
+
host.options['ssh']['keys'] = [find_google_ssh_private_key]
|
131
202
|
|
132
|
-
|
133
|
-
|
203
|
+
copy_ssh_to_root(host, @options)
|
204
|
+
enable_root_login(host, @options)
|
205
|
+
host['user'] = default_user
|
206
|
+
|
207
|
+
# shut down connection, will reconnect on next exec
|
208
|
+
host.close
|
209
|
+
end
|
134
210
|
|
135
211
|
@logger.debug("Instance ready: #{host['vmhostname']} for #{host.name}}")
|
136
212
|
end
|
@@ -138,70 +214,13 @@ module Beaker
|
|
138
214
|
|
139
215
|
# Shutdown and destroy virtual machines in the Google Compute Engine,
|
140
216
|
# including their associated disks and firewall rules
|
141
|
-
def cleanup
|
142
|
-
|
143
|
-
start = Time.now
|
144
|
-
|
145
|
-
@gce_helper.delete_firewall(@firewall, start, attempts)
|
217
|
+
def cleanup
|
218
|
+
@gce_helper.delete_firewall(@firewall)
|
146
219
|
|
147
220
|
@hosts.each do |host|
|
148
|
-
|
221
|
+
# TODO: Delete any other disks attached during the instance creation
|
222
|
+
@gce_helper.delete_instance(host['vmhostname'])
|
149
223
|
@logger.debug("Deleted Google Compute instance #{host['vmhostname']} for #{host.name}")
|
150
|
-
@gce_helper.delete_disk(host['diskname'], start, attempts)
|
151
|
-
@logger.debug("Deleted Google Compute disk #{host['diskname']} for #{host.name}")
|
152
|
-
end
|
153
|
-
|
154
|
-
end
|
155
|
-
|
156
|
-
# Shutdown and destroy Google Compute instances (including their associated
|
157
|
-
# disks and firewall rules) that have been alive longer than ZOMBIE hours.
|
158
|
-
def kill_zombies(max_age = ZOMBIE)
|
159
|
-
now = start = Time.now
|
160
|
-
attempts = @options[:timeout].to_i / SLEEPWAIT
|
161
|
-
|
162
|
-
# get rid of old instances
|
163
|
-
instances = @gce_helper.list_instances(start, attempts)
|
164
|
-
if instances
|
165
|
-
instances.each do |instance|
|
166
|
-
created = Time.parse(instance['creationTimestamp'])
|
167
|
-
alive = (now - created )/60/60
|
168
|
-
if alive >= max_age
|
169
|
-
#kill it with fire!
|
170
|
-
@logger.debug("Deleting zombie instance #{instance['name']}")
|
171
|
-
@gce_helper.delete_instance( instance['name'], start, attempts )
|
172
|
-
end
|
173
|
-
end
|
174
|
-
else
|
175
|
-
@logger.debug("No zombie instances found")
|
176
|
-
end
|
177
|
-
|
178
|
-
# get rid of old disks
|
179
|
-
disks = @gce_helper.list_disks(start, attempts)
|
180
|
-
if disks
|
181
|
-
disks.each do |disk|
|
182
|
-
created = Time.parse(disk['creationTimestamp'])
|
183
|
-
alive = (now - created )/60/60
|
184
|
-
if alive >= max_age
|
185
|
-
|
186
|
-
# kill it with fire!
|
187
|
-
@logger.debug("Deleting zombie disk #{disk['name']}")
|
188
|
-
@gce_helper.delete_disk( disk['name'], start, attempts )
|
189
|
-
end
|
190
|
-
end
|
191
|
-
else
|
192
|
-
@logger.debug("No zombie disks found")
|
193
|
-
end
|
194
|
-
|
195
|
-
# get rid of non-default firewalls
|
196
|
-
firewalls = @gce_helper.list_firewalls( start, attempts)
|
197
|
-
|
198
|
-
if firewalls && !firewalls.empty?
|
199
|
-
firewalls.each do |firewall|
|
200
|
-
@logger.debug("Deleting non-default firewall #{firewall['name']}")
|
201
|
-
@gce_helper.delete_firewall( firewall['name'], start, attempts )
|
202
|
-
end
|
203
|
-
else
|
204
|
-
@logger.debug("No zombie firewalls found")
|
205
224
|
end
|
206
225
|
end
|
207
226
|
end
|