cem_acpt 0.2.5 → 0.6.1
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/spec.yml +38 -0
- data/Gemfile +4 -3
- data/Gemfile.lock +85 -56
- data/README.md +144 -83
- data/cem_acpt.gemspec +8 -7
- data/exe/cem_acpt +41 -7
- data/lib/cem_acpt/config.rb +345 -0
- data/lib/cem_acpt/core_extensions.rb +17 -61
- data/lib/cem_acpt/goss/api/action_response.rb +175 -0
- data/lib/cem_acpt/goss/api.rb +83 -0
- data/lib/cem_acpt/goss.rb +8 -0
- data/lib/cem_acpt/image_name_builder.rb +0 -9
- data/lib/cem_acpt/logging/formatter.rb +97 -0
- data/lib/cem_acpt/logging.rb +168 -142
- data/lib/cem_acpt/platform/base.rb +26 -37
- data/lib/cem_acpt/platform/gcp.rb +48 -62
- data/lib/cem_acpt/platform.rb +30 -28
- data/lib/cem_acpt/provision/terraform/linux.rb +47 -0
- data/lib/cem_acpt/provision/terraform/os_data.rb +72 -0
- data/lib/cem_acpt/provision/terraform/windows.rb +22 -0
- data/lib/cem_acpt/provision/terraform.rb +193 -0
- data/lib/cem_acpt/provision.rb +20 -0
- data/lib/cem_acpt/puppet_helpers.rb +0 -1
- data/lib/cem_acpt/test_data.rb +23 -13
- data/lib/cem_acpt/test_runner/log_formatter/goss_action_response.rb +104 -0
- data/lib/cem_acpt/test_runner/log_formatter.rb +10 -0
- data/lib/cem_acpt/test_runner.rb +170 -3
- data/lib/cem_acpt/utils/puppet.rb +29 -0
- data/lib/cem_acpt/utils/ssh.rb +197 -0
- data/lib/cem_acpt/utils/terminal.rb +27 -0
- data/lib/cem_acpt/utils.rb +4 -138
- data/lib/cem_acpt/version.rb +1 -1
- data/lib/cem_acpt.rb +70 -20
- data/lib/terraform/gcp/linux/goss/puppet_idempotent.yaml +10 -0
- data/lib/terraform/gcp/linux/goss/puppet_noop.yaml +12 -0
- data/lib/terraform/gcp/linux/main.tf +191 -0
- data/lib/terraform/gcp/linux/systemd/goss-acpt.service +8 -0
- data/lib/terraform/gcp/linux/systemd/goss-idempotent.service +8 -0
- data/lib/terraform/gcp/linux/systemd/goss-noop.service +8 -0
- data/lib/terraform/gcp/windows/.keep +0 -0
- data/sample_config.yaml +22 -21
- metadata +88 -56
- data/lib/cem_acpt/bootstrap/bootstrapper.rb +0 -206
- data/lib/cem_acpt/bootstrap/operating_system/rhel_family.rb +0 -129
- data/lib/cem_acpt/bootstrap/operating_system.rb +0 -17
- data/lib/cem_acpt/bootstrap.rb +0 -12
- data/lib/cem_acpt/context.rb +0 -153
- data/lib/cem_acpt/platform/base/cmd.rb +0 -71
- data/lib/cem_acpt/platform/gcp/cmd.rb +0 -345
- data/lib/cem_acpt/platform/gcp/compute.rb +0 -332
- data/lib/cem_acpt/platform/vmpooler.rb +0 -24
- data/lib/cem_acpt/rspec_utils.rb +0 -242
- data/lib/cem_acpt/shared_objects.rb +0 -537
- data/lib/cem_acpt/spec_helper_acceptance.rb +0 -184
- data/lib/cem_acpt/test_runner/run_handler.rb +0 -187
- data/lib/cem_acpt/test_runner/runner.rb +0 -210
- data/lib/cem_acpt/test_runner/runner_result.rb +0 -103
@@ -1,85 +1,71 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'json'
|
4
|
+
|
3
5
|
# GCP platform implementation
|
4
6
|
module Platform
|
5
|
-
|
6
|
-
|
7
|
+
def platform_data
|
8
|
+
{
|
9
|
+
username: gcp_username,
|
10
|
+
credentials_file: gcp_credentials_file,
|
11
|
+
project: gcp_project,
|
12
|
+
region: gcp_region,
|
13
|
+
zone: gcp_zone,
|
14
|
+
subnetwork: gcp_subnetwork,
|
15
|
+
private_key: gcp_private_key,
|
16
|
+
public_key: gcp_public_key,
|
17
|
+
}
|
18
|
+
end
|
7
19
|
|
8
|
-
|
9
|
-
|
10
|
-
|
20
|
+
def node_data
|
21
|
+
{
|
22
|
+
machine_type: gcp_machine_type,
|
23
|
+
image: image_name,
|
24
|
+
disk_size: gcp_disk_size,
|
25
|
+
test_name: @test_data[:test_name],
|
26
|
+
}
|
11
27
|
end
|
12
28
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
@instance = CemAcpt::Platform::Gcp::VM.new(
|
19
|
-
node_name,
|
20
|
-
components: creation_params,
|
21
|
-
)
|
22
|
-
@instance.configure!
|
23
|
-
logger.debug("Creating with command: #{@instance.send(:create_cmd)}")
|
24
|
-
@instance.create
|
29
|
+
def gcp_username
|
30
|
+
return @gcp_username if @gcp_username
|
31
|
+
|
32
|
+
@gcp_username = @config.get('platform.username')
|
33
|
+
@gcp_username ||= JSON.parse(`gcloud compute os-login describe-profile --format json`.chomp)['posixAccounts'].first['username']
|
25
34
|
end
|
26
35
|
|
27
|
-
|
28
|
-
|
29
|
-
@instance.destroy
|
36
|
+
def gcp_credentials_file
|
37
|
+
@gcp_credentials_file ||= (@config.get('platform.credentials_file') || File.join(Dir.home, '.config', 'gcloud', 'application_default_credentials.json'))
|
30
38
|
end
|
31
39
|
|
32
|
-
|
33
|
-
|
34
|
-
logger.debug("Checking if #{node_name} is ready...")
|
35
|
-
@instance.ready?
|
40
|
+
def gcp_project
|
41
|
+
@gcp_project ||= (@config.get('platform.project') || `gcloud config get-value project`.chomp)
|
36
42
|
end
|
37
43
|
|
38
|
-
|
39
|
-
|
40
|
-
def run_tests(&block)
|
41
|
-
logger.debug("Running tests for #{node_name}...")
|
42
|
-
block.call @instance.cmd.env
|
44
|
+
def gcp_region
|
45
|
+
@gcp_region ||= (@config.get('platform.region') || `gcloud config get-value compute/region`.chomp)
|
43
46
|
end
|
44
47
|
|
45
|
-
|
46
|
-
|
47
|
-
remote_path = remote_path.nil? ? File.join('/tmp', File.basename(module_pkg_path)) : remote_path
|
48
|
-
logger.info("Uploading module package #{module_pkg_path} to #{remote_path} on #{node_name} and installing it...")
|
49
|
-
logger.debug("Using puppet path: #{puppet_path}")
|
50
|
-
@instance.install_puppet_module_package(module_pkg_path, remote_path, puppet_path)
|
51
|
-
logger.info("Module package #{module_pkg_path} installed on #{node_name}")
|
48
|
+
def gcp_zone
|
49
|
+
@gcp_zone ||= (@config.get('platform.zone') || `gcloud config get-value compute/zone`.chomp)
|
52
50
|
end
|
53
51
|
|
54
|
-
|
55
|
-
|
56
|
-
base.extend(SpecMethods)
|
52
|
+
def gcp_subnetwork
|
53
|
+
@gcp_subnetwork ||= (@config.get('platform.subnetwork') || 'default')
|
57
54
|
end
|
58
55
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
# @return [CemAcpt::Platform::Gcp::Cmd]
|
63
|
-
def command_provider
|
64
|
-
CemAcpt::Platform::Gcp::Cmd.new(out_format: 'json')
|
65
|
-
end
|
56
|
+
def gcp_private_key
|
57
|
+
@gcp_private_key ||= (@test_data[:private_key] || File.join(Dir.home, '.ssh', 'google_compute_engine'))
|
58
|
+
end
|
66
59
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
end
|
60
|
+
def gcp_public_key
|
61
|
+
@gcp_public_key ||= (@test_data[:public_key] || File.join(Dir.home, '.ssh', 'google_compute_engine.pub'))
|
62
|
+
end
|
63
|
+
|
64
|
+
def gcp_machine_type
|
65
|
+
@gcp_machine_type ||= (@config.get('node_data.machine_type') || 'e2-small')
|
66
|
+
end
|
75
67
|
|
76
|
-
|
77
|
-
|
78
|
-
# @param command [String] the command to run
|
79
|
-
# @param opts [Hash] options to pass to the run_shell command
|
80
|
-
# @return [String] the output of the run_shell command
|
81
|
-
def run_shell(instance_name, cmd, opts = {})
|
82
|
-
command_provider.run_shell(instance_name, cmd, opts)
|
83
|
-
end
|
68
|
+
def gcp_disk_size
|
69
|
+
@gcp_disk_size ||= (@config.get('node_data.disk_size') || 40)
|
84
70
|
end
|
85
71
|
end
|
data/lib/cem_acpt/platform.rb
CHANGED
@@ -1,12 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'concurrent-ruby'
|
4
|
+
require_relative 'logging'
|
4
5
|
|
5
6
|
# CemAcpt::Platform manages creating and configring platform specific objects
|
6
7
|
# for the acceptance test suites.
|
7
8
|
module CemAcpt::Platform
|
8
|
-
require_relative 'logging'
|
9
|
-
|
10
9
|
class Error < StandardError; end
|
11
10
|
|
12
11
|
PLATFORM_DIR = File.expand_path(File.join(__dir__, 'platform'))
|
@@ -19,16 +18,15 @@ module CemAcpt::Platform
|
|
19
18
|
# @param platform [String] the name of the platform
|
20
19
|
# @param config [CemAcpt::Config] the config object
|
21
20
|
# @param test_data [Hash] the test data
|
22
|
-
|
23
|
-
def use(platform, config, test_data, local_port_allocator)
|
21
|
+
def use(platform, config, test_data)
|
24
22
|
raise Error, "Platform #{platform} is not supported" unless platforms.include?(platform)
|
25
23
|
raise Error, 'test_data must be an Array' unless test_data.is_a?(Array)
|
26
24
|
|
27
25
|
logger.info "Using #{platform} for #{test_data.length} tests..."
|
28
26
|
test_data.each_with_object([]) do |single_test_data, ary|
|
29
|
-
local_port = local_port_allocator.allocate
|
30
|
-
logger.debug("Allocated local port #{local_port} for test #{single_test_data[:test_name]}")
|
31
|
-
ary << new_platform_object(platform, config, single_test_data
|
27
|
+
#local_port = local_port_allocator.allocate
|
28
|
+
#logger.debug("Allocated local port #{local_port} for test #{single_test_data[:test_name]}")
|
29
|
+
ary << new_platform_object(platform, config, single_test_data)
|
32
30
|
end
|
33
31
|
end
|
34
32
|
|
@@ -40,6 +38,22 @@ module CemAcpt::Platform
|
|
40
38
|
platform_class(platform)
|
41
39
|
end
|
42
40
|
|
41
|
+
# Returns an array of the names of the supported platforms.
|
42
|
+
# Supported platforms are discovered by looking for files in the
|
43
|
+
# platform directory, and platform names are the basename (no extension)
|
44
|
+
# of the files. We deliberately exclude the base class, as it is not
|
45
|
+
# a platform.
|
46
|
+
def platforms
|
47
|
+
return @platforms if defined?(@platforms)
|
48
|
+
|
49
|
+
@platforms = Dir.glob(File.join(PLATFORM_DIR, '*.rb')).map do |file|
|
50
|
+
File.basename(file, '.rb') unless file.end_with?('base.rb')
|
51
|
+
end
|
52
|
+
@platforms.compact!
|
53
|
+
logger.debug "Discovered platform(s): #{@platforms}"
|
54
|
+
@platforms
|
55
|
+
end
|
56
|
+
|
43
57
|
private
|
44
58
|
|
45
59
|
# Dynamically creates a new platform class if it doesn't exist
|
@@ -48,10 +62,10 @@ module CemAcpt::Platform
|
|
48
62
|
# @param config [CemAcpt::Config] the config object.
|
49
63
|
# @param single_test_data [Hash] the test data for a single test.
|
50
64
|
# @return [CemAcpt::Platform::Base] an initialized platform class.
|
51
|
-
def new_platform_object(platform, config, single_test_data
|
65
|
+
def new_platform_object(platform, config, single_test_data)
|
52
66
|
raise Error, 'single_test_data must be a Hash' unless single_test_data.is_a?(Hash)
|
53
67
|
|
54
|
-
platform_class(platform).new(config, single_test_data
|
68
|
+
platform_class(platform).new(config, single_test_data)
|
55
69
|
end
|
56
70
|
|
57
71
|
# Creates a new platform-specific Class object for the given platform.
|
@@ -59,12 +73,14 @@ module CemAcpt::Platform
|
|
59
73
|
# @param platform [String] the name of the platform.
|
60
74
|
# @return [Class] the platform-specific Object.
|
61
75
|
def platform_class(platform)
|
76
|
+
class_name = platform.capitalize
|
62
77
|
# We require the platform base class here so that we can use it as
|
63
78
|
# a parent class for the platform-specific class.
|
64
79
|
require_relative 'platform/base'
|
65
80
|
# If the class has already been defined, we can just use it.
|
66
|
-
if Object.const_defined?(
|
67
|
-
|
81
|
+
if Object.const_defined?(class_name)
|
82
|
+
logger.debug "Using existing platform class #{class_name}"
|
83
|
+
klass = Object.const_get(class_name)
|
68
84
|
else
|
69
85
|
# Otherwise, we need to create the class. We do this by setting
|
70
86
|
# a new constant with the name of the platform capitalized, and
|
@@ -73,31 +89,17 @@ module CemAcpt::Platform
|
|
73
89
|
# include and extend our class with the Platform module from the file,
|
74
90
|
# include Logging and Concurrent::Async, and finally call the
|
75
91
|
# initialize method on the class.
|
92
|
+
logger.debug "Creating platform class #{class_name}"
|
76
93
|
klass = Object.const_set(
|
77
|
-
|
94
|
+
class_name,
|
78
95
|
Class.new(CemAcpt::Platform::Base) do
|
79
96
|
require_relative "platform/#{platform}"
|
80
97
|
include Platform
|
81
98
|
end,
|
82
99
|
)
|
83
100
|
end
|
101
|
+
logger.debug "Using platform class: #{klass.inspect}"
|
84
102
|
klass
|
85
103
|
end
|
86
|
-
|
87
|
-
# Returns an array of the names of the supported platforms.
|
88
|
-
# Supported platforms are discovered by looking for files in the
|
89
|
-
# platform directory, and platform names are the basename (no extension)
|
90
|
-
# of the files. We deliberately exclude the base class, as it is not
|
91
|
-
# a platform.
|
92
|
-
def platforms
|
93
|
-
return @platforms if defined?(@platforms)
|
94
|
-
|
95
|
-
@platforms = Dir.glob(File.join(PLATFORM_DIR, '*.rb')).map do |file|
|
96
|
-
File.basename(file, '.rb') unless file.end_with?('base.rb')
|
97
|
-
end
|
98
|
-
@platforms.compact!
|
99
|
-
logger.debug "Discovered platform(s): #{@platforms}"
|
100
|
-
@platforms
|
101
|
-
end
|
102
104
|
end
|
103
105
|
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'os_data'
|
4
|
+
|
5
|
+
module CemAcpt
|
6
|
+
module Provision
|
7
|
+
# Class provides methods for gathering provision data for Linux nodes
|
8
|
+
class Linux < OsData
|
9
|
+
def self.valid_names
|
10
|
+
%w[centos rhel oel alma]
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.valid_versions
|
14
|
+
%w[7, 8]
|
15
|
+
end
|
16
|
+
|
17
|
+
def systemd_files
|
18
|
+
Dir.glob(File.join(provision_directory, 'systemd', '*.service')).map { |f| File.basename(f) }
|
19
|
+
end
|
20
|
+
|
21
|
+
def puppet_bin_path
|
22
|
+
'/opt/puppetlabs/puppet/bin/puppet'
|
23
|
+
end
|
24
|
+
|
25
|
+
def destination_provision_directory
|
26
|
+
'/opt/cem_acpt'
|
27
|
+
end
|
28
|
+
|
29
|
+
def provision_commands
|
30
|
+
commands = [
|
31
|
+
"sudo /opt/puppetlabs/puppet/bin/puppet module install #{destination_provision_directory}/#{remote_module_package_name}",
|
32
|
+
'curl -fsSL https://goss.rocks/install | sudo sh',
|
33
|
+
]
|
34
|
+
unless systemd_files.empty?
|
35
|
+
systemd_files.each do |file|
|
36
|
+
commands << "sudo cp /opt/cem_acpt/systemd/#{file} /etc/systemd/system/#{file}"
|
37
|
+
end
|
38
|
+
commands << 'sudo systemctl daemon-reload'
|
39
|
+
systemd_files.each do |file|
|
40
|
+
commands << "sudo systemctl start #{file} && sudo systemctl enable #{file}"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
commands << "sudo #{puppet_bin_path} apply #{destination_provision_directory}/#{puppet_manifest_file}"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../../logging'
|
4
|
+
|
5
|
+
module CemAcpt
|
6
|
+
module Provision
|
7
|
+
# Base class for OS-specific provisioning data
|
8
|
+
class OsData
|
9
|
+
include CemAcpt::Logging
|
10
|
+
extend CemAcpt::Logging
|
11
|
+
|
12
|
+
def self.use_for?(test_name)
|
13
|
+
name_ver = test_name.match(%r{^\w+_(\w+)-(\d+).*})
|
14
|
+
return false unless name_ver && name_ver.length == 3
|
15
|
+
|
16
|
+
if valid_versions.include?(name_ver[2]) || valid_versions.include?(name_ver[2].to_s)
|
17
|
+
return true if valid_names.include?(name_ver[1])
|
18
|
+
end
|
19
|
+
|
20
|
+
false
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.valid_names
|
24
|
+
raise NotImplementedError
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.valid_versions
|
28
|
+
raise NotImplementedError
|
29
|
+
end
|
30
|
+
|
31
|
+
attr_accessor :base_provision_directory
|
32
|
+
|
33
|
+
def initialize(config, provision_data)
|
34
|
+
@config = config
|
35
|
+
@provision_data = provision_data
|
36
|
+
@base_provision_directory = @config.get('terraform.dir')
|
37
|
+
end
|
38
|
+
|
39
|
+
def puppet_bin_path
|
40
|
+
raise NotImplementedError
|
41
|
+
end
|
42
|
+
|
43
|
+
def puppet_manifest_file
|
44
|
+
'manifest.pp'
|
45
|
+
end
|
46
|
+
|
47
|
+
def remote_module_package_name
|
48
|
+
'puppet-module.tar.gz'
|
49
|
+
end
|
50
|
+
|
51
|
+
def implementation_name
|
52
|
+
self.class.to_s.downcase.split('::').last
|
53
|
+
end
|
54
|
+
|
55
|
+
def provision_directory
|
56
|
+
File.join(base_provision_directory, implementation_name)
|
57
|
+
end
|
58
|
+
|
59
|
+
def destination_provision_directory
|
60
|
+
raise NotImplementedError
|
61
|
+
end
|
62
|
+
|
63
|
+
def provision_commands
|
64
|
+
raise NotImplementedError
|
65
|
+
end
|
66
|
+
|
67
|
+
def goss_files
|
68
|
+
Dir.glob(File.join(provision_directory, 'goss', '*.yaml')).map { |f| File.basename(f) }
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'os_data'
|
4
|
+
|
5
|
+
module CemAcpt
|
6
|
+
module Provision
|
7
|
+
# Class provides methods for gathering provision data for Windows nodes
|
8
|
+
class Windows < OsData
|
9
|
+
def self.valid_names
|
10
|
+
%w[windows]
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.valid_versions
|
14
|
+
%w[2016 2019 2022]
|
15
|
+
end
|
16
|
+
|
17
|
+
def puppet_bin_path
|
18
|
+
'C:/Program Files/Puppet Labs/Puppet/bin/puppet.bat'
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,193 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'fileutils'
|
4
|
+
require 'json'
|
5
|
+
require 'ruby-terraform'
|
6
|
+
require_relative '../logging'
|
7
|
+
require_relative 'terraform/linux'
|
8
|
+
require_relative 'terraform/windows'
|
9
|
+
|
10
|
+
module CemAcpt
|
11
|
+
module Provision
|
12
|
+
class Terraform
|
13
|
+
DEFAULT_PLAN_NAME = 'testplan.tfplan'
|
14
|
+
include CemAcpt::Logging
|
15
|
+
|
16
|
+
attr_reader :environment, :working_dir
|
17
|
+
|
18
|
+
def initialize(config, provision_data)
|
19
|
+
@config = config
|
20
|
+
@provision_data = provision_data
|
21
|
+
@backend = new_backend(@provision_data[:test_data].first[:test_name])
|
22
|
+
@environment = new_environment(@config)
|
23
|
+
@working_dir = nil
|
24
|
+
end
|
25
|
+
|
26
|
+
def provision
|
27
|
+
logger.info('Terraform') { 'Provisioning nodes...' }
|
28
|
+
@working_dir = new_working_dir
|
29
|
+
validate_working_dir!
|
30
|
+
|
31
|
+
terraform_configure_logging
|
32
|
+
terraform_init
|
33
|
+
terraform_plan(formatted_vars, DEFAULT_PLAN_NAME)
|
34
|
+
terraform_apply(DEFAULT_PLAN_NAME)
|
35
|
+
JSON.parse(terraform_output('instance_name_ip', json: true))
|
36
|
+
end
|
37
|
+
|
38
|
+
def destroy
|
39
|
+
terraform_destroy(formatted_vars)
|
40
|
+
logger.verbose('Terraform') { "Deleting old working directory #{working_dir}" }
|
41
|
+
FileUtils.rm_rf(working_dir)
|
42
|
+
@working_dir = nil
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def terraform
|
48
|
+
@terraform ||= RubyTerraform
|
49
|
+
end
|
50
|
+
|
51
|
+
def terraform_configure_logging
|
52
|
+
terraform.configure do |c|
|
53
|
+
c.logger = logger
|
54
|
+
c.stdout = c.logger
|
55
|
+
c.stderr = c.logger
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def terraform_init
|
60
|
+
logger.debug('Terraform') { 'Initializing Terraform' }
|
61
|
+
terraform.init({ chdir: working_dir, input: false, no_color: true }, { environment: environment })
|
62
|
+
end
|
63
|
+
|
64
|
+
def terraform_plan(vars, plan_name = DEFAULT_PLAN_NAME)
|
65
|
+
logger.debug('Terraform') { "Creating Terraform plan '#{plan_name}'" }
|
66
|
+
logger.verbose('Terraform') { "Using vars:\n#{JSON.pretty_generate(vars)}" }
|
67
|
+
terraform.plan(
|
68
|
+
{
|
69
|
+
chdir: working_dir,
|
70
|
+
input: false,
|
71
|
+
no_color: true,
|
72
|
+
plan: plan_name,
|
73
|
+
vars: vars,
|
74
|
+
},
|
75
|
+
{
|
76
|
+
environment: environment,
|
77
|
+
},
|
78
|
+
)
|
79
|
+
end
|
80
|
+
|
81
|
+
def terraform_apply(plan_name = DEFAULT_PLAN_NAME)
|
82
|
+
logger.debug('Terraform') { "Running Terraform apply with the plan #{plan_name}" }
|
83
|
+
terraform.apply({ chdir: working_dir, input: false, no_color: true, plan: plan_name }, { environment: environment })
|
84
|
+
end
|
85
|
+
|
86
|
+
def terraform_output(name, json: true)
|
87
|
+
logger.debug('Terraform') { "Getting Terraform output #{name}" }
|
88
|
+
terraform.output({ chdir: working_dir, no_color: true, json: json, name: name }, { environment: environment })
|
89
|
+
end
|
90
|
+
|
91
|
+
def terraform_destroy(vars)
|
92
|
+
logger.debug('Terraform') { 'Destroying Terraform resources' }
|
93
|
+
terraform.destroy(
|
94
|
+
{
|
95
|
+
chdir: working_dir,
|
96
|
+
auto_approve: true,
|
97
|
+
input: false,
|
98
|
+
no_color: true,
|
99
|
+
vars: vars,
|
100
|
+
},
|
101
|
+
{
|
102
|
+
environment: environment,
|
103
|
+
},
|
104
|
+
)
|
105
|
+
end
|
106
|
+
|
107
|
+
def new_backend(test_name)
|
108
|
+
if CemAcpt::Provision::Linux.use_for?(test_name)
|
109
|
+
logger.info('Terraform') { 'Using Linux backend' }
|
110
|
+
logger.verbose('Terraform') { "Creating backend with provision_data:\n#{JSON.pretty_generate(@provision_data)}" }
|
111
|
+
CemAcpt::Provision::Linux.new(@config, @provision_data)
|
112
|
+
elsif CemAcpt::Provision::Windows.use_for?(test_name)
|
113
|
+
logger.info('Terraform') { 'Using Windows backend' }
|
114
|
+
logger.verbose('Terraform') { "Creating backend with provision_data:\n#{JSON.pretty_generate(@provision_data)}" }
|
115
|
+
CemAcpt::Provision::Windows.new(@config, @provision_data)
|
116
|
+
else
|
117
|
+
err_msg = [
|
118
|
+
"Test name #{test_name} does not match any known OS.",
|
119
|
+
"Known OSes are: #{CemAcpt::Provision::Linux.valid_names.join(', ')}",
|
120
|
+
"and #{CemAcpt::Provision::Windows.valid_names.join(', ')}.",
|
121
|
+
"Known versions are: #{CemAcpt::Provision::Linux.valid_versions.join(', ')}",
|
122
|
+
", and #{CemAcpt::Provision::Windows.valid_versions.join(', ')}."
|
123
|
+
].join(' ')
|
124
|
+
logger.error('Terraform') { err_msg }
|
125
|
+
raise ArgumentError, err_msg
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def new_environment(config)
|
130
|
+
env = (config.get('terraform.environment') || {})
|
131
|
+
env['CLOUDSDK_PYTHON_SITEPACKAGES'] = '1' # This is needed for gcloud to use numpy
|
132
|
+
logger.verbose('Terraform') { "Using environment:\n#{JSON.pretty_generate(env)}" }
|
133
|
+
env
|
134
|
+
end
|
135
|
+
|
136
|
+
def new_working_dir
|
137
|
+
logger.debug('Terraform') { "Creating new working directory" }
|
138
|
+
base_dir = File.join(@config.get('terraform.dir'), @config.get('platform.name'))
|
139
|
+
logger.verbose('Terraform') { "Base directory defined as #{base_dir}" }
|
140
|
+
@backend.base_provision_directory = base_dir
|
141
|
+
logger.verbose('Terraform') { 'Base directory set in backend' }
|
142
|
+
work_dir = File.join(@config.get('terraform.dir'), "test_#{Time.now.to_i.to_s}")
|
143
|
+
logger.verbose('Terraform') { "Working directory defined as #{work_dir}" }
|
144
|
+
logger.verbose('Terraform') { "Copying backend provision directory #{@backend.provision_directory} to working directory" }
|
145
|
+
FileUtils.cp_r(@backend.provision_directory, work_dir)
|
146
|
+
logger.verbose('Terraform') { "Copied provision directory #{@backend.provision_directory} to #{work_dir}" }
|
147
|
+
FileUtils.cp(@provision_data[:module_package_path], work_dir)
|
148
|
+
logger.verbose('Terraform') { "Copied module package #{@provision_data[:module_package_path]} to #{work_dir}" }
|
149
|
+
work_dir
|
150
|
+
rescue StandardError => e
|
151
|
+
logger.error('Terraform') { 'Error creating working directory' }
|
152
|
+
raise e
|
153
|
+
end
|
154
|
+
|
155
|
+
def validate_working_dir!
|
156
|
+
logger.debug('Terraform') { "Validating working directory #{working_dir}" }
|
157
|
+
logger.verbose('Terraform') { "Content of #{working_dir}:\n#{Dir.glob(File.join(working_dir, '*')).join("\n")}" }
|
158
|
+
raise "Terraform working directory #{working_dir} does not exist" unless File.directory?(working_dir)
|
159
|
+
raise "Terraform working directory #{working_dir} does not contain a Terraform file" unless Dir.glob(File.join(working_dir, '*.tf')).any?
|
160
|
+
logger.info('Terraform') { "Using working directory: #{working_dir}" }
|
161
|
+
rescue StandardError => e
|
162
|
+
logger.error('Terraform') { 'Error validating working directory' }
|
163
|
+
raise e
|
164
|
+
end
|
165
|
+
|
166
|
+
def provision_node_data
|
167
|
+
node_data = @provision_data[:nodes].each_with_object({}) do |node, h|
|
168
|
+
h[node.node_name] = node.node_data.merge({
|
169
|
+
goss_file: node.test_data[:goss_file],
|
170
|
+
puppet_manifest: node.test_data[:puppet_manifest],
|
171
|
+
provision_dir_source: @backend.provision_directory,
|
172
|
+
provision_dir_dest: @backend.destination_provision_directory,
|
173
|
+
provision_commands: @backend.provision_commands,
|
174
|
+
})
|
175
|
+
end
|
176
|
+
node_data.to_json
|
177
|
+
rescue StandardError => e
|
178
|
+
logger.error('Terraform') { 'Error creating node data' }
|
179
|
+
raise e
|
180
|
+
end
|
181
|
+
|
182
|
+
def formatted_vars
|
183
|
+
@provision_data[:nodes].first.platform_data.merge({
|
184
|
+
puppet_module_package: @provision_data[:module_package_path],
|
185
|
+
node_data: provision_node_data,
|
186
|
+
})
|
187
|
+
rescue StandardError => e
|
188
|
+
logger.error('Terraform') { 'Error creating formatted vars' }
|
189
|
+
raise e
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'logging'
|
4
|
+
require_relative 'provision/terraform'
|
5
|
+
|
6
|
+
module CemAcpt
|
7
|
+
module Provision
|
8
|
+
include CemAcpt::Logging
|
9
|
+
|
10
|
+
def self.new_provisioner(config, provision_data)
|
11
|
+
case config.get('provisioner')
|
12
|
+
when 'terraform'
|
13
|
+
logger.debug('Provision') { 'Using Terraform provisioner' }
|
14
|
+
CemAcpt::Provision::Terraform.new(config, provision_data)
|
15
|
+
else
|
16
|
+
raise ArgumentError, "Unknown provisioner #{config.get('provisioner')}"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -28,7 +28,6 @@ module CemAcpt
|
|
28
28
|
|
29
29
|
# Validates module metadata by raising exception if invalid
|
30
30
|
_metadata = builder.metadata
|
31
|
-
logger.debug("Metadata for module #{builder.release_name} is valid")
|
32
31
|
|
33
32
|
# Builds the module package
|
34
33
|
logger.info("Building module package for #{builder.release_name}")
|