cem_acpt 0.7.3 → 0.8.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/.rubocop.yml +3 -1
- data/Gemfile.lock +42 -14
- data/README.md +8 -0
- data/cem_acpt.gemspec +3 -1
- data/exe/cem_acpt_image +0 -0
- data/lib/cem_acpt/action_result.rb +85 -0
- data/lib/cem_acpt/cli.rb +42 -22
- data/lib/cem_acpt/config/base.rb +4 -2
- data/lib/cem_acpt/goss/api.rb +8 -2
- data/lib/cem_acpt/image_builder.rb +64 -72
- data/lib/cem_acpt/logging.rb +41 -30
- data/lib/cem_acpt/platform.rb +4 -5
- data/lib/cem_acpt/provision/terraform/linux.rb +2 -2
- data/lib/cem_acpt/provision/terraform/terraform_cmd.rb +154 -0
- data/lib/cem_acpt/provision/terraform/windows.rb +9 -0
- data/lib/cem_acpt/provision/terraform.rb +41 -48
- data/lib/cem_acpt/provision.rb +1 -1
- data/lib/cem_acpt/puppet_helpers.rb +1 -1
- data/lib/cem_acpt/test_data.rb +3 -3
- data/lib/cem_acpt/test_runner/log_formatter/error_formatter.rb +33 -0
- data/lib/cem_acpt/test_runner/log_formatter.rb +10 -1
- data/lib/cem_acpt/test_runner.rb +151 -72
- data/lib/cem_acpt/utils/shell.rb +90 -0
- data/lib/cem_acpt/utils/ssh.rb +10 -23
- data/lib/cem_acpt/utils/terminal.rb +1 -5
- data/lib/cem_acpt/utils/winrm_runner.rb +160 -0
- data/lib/cem_acpt/utils.rb +59 -1
- data/lib/cem_acpt/version.rb +1 -1
- data/lib/cem_acpt.rb +51 -21
- data/lib/terraform/gcp/windows/main.tf +26 -0
- metadata +47 -8
@@ -6,6 +6,7 @@ module CemAcpt
|
|
6
6
|
module Provision
|
7
7
|
# Class provides methods for gathering provision data for Windows nodes
|
8
8
|
class Windows < OsData
|
9
|
+
# A name that will match with how image names are on GCP
|
9
10
|
def self.valid_names
|
10
11
|
%w[windows]
|
11
12
|
end
|
@@ -17,6 +18,14 @@ module CemAcpt
|
|
17
18
|
def puppet_bin_path
|
18
19
|
'C:/Program Files/Puppet Labs/Puppet/bin/puppet.bat'
|
19
20
|
end
|
21
|
+
|
22
|
+
def destination_provision_directory
|
23
|
+
'C:/cem_acpt'
|
24
|
+
end
|
25
|
+
|
26
|
+
def provision_commands
|
27
|
+
['placeholder']
|
28
|
+
end
|
20
29
|
end
|
21
30
|
end
|
22
31
|
end
|
@@ -2,10 +2,10 @@
|
|
2
2
|
|
3
3
|
require 'fileutils'
|
4
4
|
require 'json'
|
5
|
-
require 'ruby-terraform'
|
6
5
|
require_relative '../logging'
|
7
6
|
require_relative 'terraform/linux'
|
8
7
|
require_relative 'terraform/windows'
|
8
|
+
require_relative 'terraform/terraform_cmd'
|
9
9
|
|
10
10
|
module CemAcpt
|
11
11
|
module Provision
|
@@ -27,22 +27,27 @@ module CemAcpt
|
|
27
27
|
@public_key = nil
|
28
28
|
end
|
29
29
|
|
30
|
+
# @return [Hash] A hash of instance names and IPs
|
30
31
|
def provision(reuse_working_dir: false)
|
31
|
-
logger.info('Terraform') { 'Provisioning nodes...' }
|
32
|
+
logger.info('CemAcpt::Provision::Terraform') { 'Provisioning nodes...' }
|
32
33
|
@working_dir = new_working_dir unless reuse_working_dir
|
33
34
|
validate_working_dir!
|
34
35
|
save_vars_to_file!(formatted_vars) # Easier to reuse nodes this way
|
35
|
-
|
36
|
-
terraform_configure_logging
|
37
36
|
terraform_init
|
38
37
|
terraform_plan(formatted_vars, DEFAULT_PLAN_NAME)
|
39
38
|
terraform_apply(DEFAULT_PLAN_NAME)
|
40
|
-
|
39
|
+
begin
|
40
|
+
output = terraform_output('instance_name_ip', json: true)
|
41
|
+
JSON.parse(output)
|
42
|
+
rescue JSON::ParserError => e
|
43
|
+
logger.error('CemAcpt::Provision::Terraform') { "Error parsing Terraform output: #{output}" }
|
44
|
+
raise e
|
45
|
+
end
|
41
46
|
end
|
42
47
|
|
43
48
|
def destroy
|
44
49
|
terraform_destroy(formatted_vars)
|
45
|
-
logger.verbose('Terraform') { "Deleting old working directory #{working_dir}" }
|
50
|
+
logger.verbose('CemAcpt::Provision::Terraform') { "Deleting old working directory #{working_dir}" }
|
46
51
|
FileUtils.rm_rf(working_dir)
|
47
52
|
@working_dir = nil
|
48
53
|
@module_package_path = nil
|
@@ -57,25 +62,17 @@ module CemAcpt
|
|
57
62
|
private
|
58
63
|
|
59
64
|
def terraform
|
60
|
-
@terraform ||=
|
61
|
-
end
|
62
|
-
|
63
|
-
def terraform_configure_logging
|
64
|
-
terraform.configure do |c|
|
65
|
-
c.logger = logger
|
66
|
-
c.stdout = c.logger
|
67
|
-
c.stderr = c.logger
|
68
|
-
end
|
65
|
+
@terraform ||= CemAcpt::Provision::TerraformCmd.new(working_dir, environment)
|
69
66
|
end
|
70
67
|
|
71
68
|
def terraform_init
|
72
|
-
logger.
|
69
|
+
logger.info('CemAcpt::Provision::Terraform') { 'Initializing Terraform' }
|
73
70
|
terraform.init({ chdir: working_dir, input: false, no_color: true }, { environment: environment })
|
74
71
|
end
|
75
72
|
|
76
73
|
def terraform_plan(vars, plan_name = DEFAULT_PLAN_NAME)
|
77
|
-
logger.
|
78
|
-
logger.verbose('Terraform') { "Using vars:\n#{JSON.pretty_generate(vars)}" }
|
74
|
+
logger.info('CemAcpt::Provision::Terraform') { "Creating Terraform plan '#{plan_name}'" }
|
75
|
+
logger.verbose('CemAcpt::Provision::Terraform') { "Using vars:\n#{JSON.pretty_generate(vars)}" }
|
79
76
|
terraform.plan(
|
80
77
|
{
|
81
78
|
chdir: working_dir,
|
@@ -91,23 +88,24 @@ module CemAcpt
|
|
91
88
|
end
|
92
89
|
|
93
90
|
def terraform_apply(plan_name = DEFAULT_PLAN_NAME)
|
94
|
-
logger.
|
91
|
+
logger.info('CemAcpt::Provision::Terraform') { "Running Terraform apply with the plan #{plan_name}" }
|
95
92
|
terraform.apply({ chdir: working_dir, input: false, no_color: true, plan: plan_name }, { environment: environment })
|
96
93
|
end
|
97
94
|
|
98
95
|
def terraform_output(name, json: true)
|
99
|
-
logger.
|
96
|
+
logger.info('CemAcpt::Provision::Terraform') { "Getting Terraform output #{name}" }
|
100
97
|
terraform.output({ chdir: working_dir, no_color: true, json: json, name: name }, { environment: environment })
|
101
98
|
end
|
102
99
|
|
103
100
|
def terraform_destroy(vars)
|
104
|
-
logger.
|
101
|
+
logger.info('CemAcpt::Provision::Terraform') { 'Destroying Terraform resources...' }
|
102
|
+
logger.verbose('CemAcpt::Provision::Terraform') { "Using vars: #{vars}" }
|
105
103
|
terraform.destroy(
|
106
104
|
{
|
107
105
|
chdir: working_dir,
|
108
106
|
auto_approve: true,
|
109
107
|
input: false,
|
110
|
-
no_color:
|
108
|
+
no_color: nil,
|
111
109
|
vars: vars,
|
112
110
|
},
|
113
111
|
{
|
@@ -117,18 +115,18 @@ module CemAcpt
|
|
117
115
|
end
|
118
116
|
|
119
117
|
def terraform_show
|
120
|
-
logger.
|
118
|
+
logger.info('CemAcpt::Provision::Terraform') { 'Showing Terraform state' }
|
121
119
|
terraform.show({ chdir: working_dir, no_color: true }, { environment: environment })
|
122
120
|
end
|
123
121
|
|
124
122
|
def new_backend(test_name)
|
125
123
|
if CemAcpt::Provision::Linux.use_for?(test_name)
|
126
|
-
logger.info('Terraform') { 'Using Linux backend' }
|
127
|
-
logger.verbose('Terraform') { "Creating backend with provision_data:\n#{JSON.pretty_generate(@provision_data)}" }
|
124
|
+
logger.info('CemAcpt::Provision::Terraform') { 'Using Linux backend' }
|
125
|
+
logger.verbose('CemAcpt::Provision::Terraform') { "Creating backend with provision_data:\n#{JSON.pretty_generate(@provision_data)}" }
|
128
126
|
CemAcpt::Provision::Linux.new(@config, @provision_data)
|
129
127
|
elsif CemAcpt::Provision::Windows.use_for?(test_name)
|
130
|
-
logger.info('Terraform') { 'Using Windows backend' }
|
131
|
-
logger.verbose('Terraform') { "Creating backend with provision_data:\n#{JSON.pretty_generate(@provision_data)}" }
|
128
|
+
logger.info('CemAcpt::Provision::Terraform') { 'Using Windows backend' }
|
129
|
+
logger.verbose('CemAcpt::Provision::Terraform') { "Creating backend with provision_data:\n#{JSON.pretty_generate(@provision_data)}" }
|
132
130
|
CemAcpt::Provision::Windows.new(@config, @provision_data)
|
133
131
|
else
|
134
132
|
err_msg = [
|
@@ -136,9 +134,8 @@ module CemAcpt
|
|
136
134
|
"Known OSes are: #{CemAcpt::Provision::Linux.valid_names.join(', ')}",
|
137
135
|
"and #{CemAcpt::Provision::Windows.valid_names.join(', ')}.",
|
138
136
|
"Known versions are: #{CemAcpt::Provision::Linux.valid_versions.join(', ')}",
|
139
|
-
", and #{CemAcpt::Provision::Windows.valid_versions.join(', ')}."
|
137
|
+
", and #{CemAcpt::Provision::Windows.valid_versions.join(', ')}.",
|
140
138
|
].join(' ')
|
141
|
-
logger.error('Terraform') { err_msg }
|
142
139
|
raise ArgumentError, err_msg
|
143
140
|
end
|
144
141
|
end
|
@@ -146,57 +143,55 @@ module CemAcpt
|
|
146
143
|
def new_environment(config)
|
147
144
|
env = (config.get('terraform.environment') || {})
|
148
145
|
env['CLOUDSDK_PYTHON_SITEPACKAGES'] = '1' # This is needed for gcloud to use numpy
|
149
|
-
logger.verbose('Terraform') { "Using environment:\n#{JSON.pretty_generate(env)}" }
|
146
|
+
logger.verbose('CemAcpt::Provision::Terraform') { "Using environment:\n#{JSON.pretty_generate(env)}" }
|
150
147
|
env
|
151
148
|
end
|
152
149
|
|
153
150
|
def new_working_dir
|
154
|
-
logger.debug('Terraform') { 'Creating new working directory' }
|
151
|
+
logger.debug('CemAcpt::Provision::Terraform') { 'Creating new working directory' }
|
155
152
|
base_dir = File.join(@config.get('terraform.dir'), @config.get('platform.name'))
|
156
|
-
logger.verbose('Terraform') { "Base directory defined as #{base_dir}" }
|
153
|
+
logger.verbose('CemAcpt::Provision::Terraform') { "Base directory defined as #{base_dir}" }
|
157
154
|
@backend.base_provision_directory = base_dir
|
158
|
-
logger.verbose('Terraform') { 'Base directory set in backend' }
|
155
|
+
logger.verbose('CemAcpt::Provision::Terraform') { 'Base directory set in backend' }
|
159
156
|
work_dir = File.join(@config.get('terraform.dir'), "test_#{Time.now.to_i}")
|
160
|
-
logger.verbose('Terraform') { "Working directory defined as #{work_dir}" }
|
161
|
-
logger.verbose('Terraform') { "Copying backend provision directory #{@backend.provision_directory} to working directory" }
|
157
|
+
logger.verbose('CemAcpt::Provision::Terraform') { "Working directory defined as #{work_dir}" }
|
158
|
+
logger.verbose('CemAcpt::Provision::Terraform') { "Copying backend provision directory #{@backend.provision_directory} to working directory" }
|
162
159
|
FileUtils.cp_r(@backend.provision_directory, work_dir)
|
163
|
-
logger.verbose('Terraform') { "Copied provision directory #{@backend.provision_directory} to #{work_dir}" }
|
160
|
+
logger.verbose('CemAcpt::Provision::Terraform') { "Copied provision directory #{@backend.provision_directory} to #{work_dir}" }
|
164
161
|
FileUtils.cp(@provision_data[:module_package_path], work_dir)
|
165
162
|
@module_package_path = File.join(work_dir, File.basename(@provision_data[:module_package_path]))
|
166
|
-
logger.verbose('Terraform') { "Copied module package #{@provision_data[:module_package_path]} to #{work_dir}" }
|
163
|
+
logger.verbose('CemAcpt::Provision::Terraform') { "Copied module package #{@provision_data[:module_package_path]} to #{work_dir}" }
|
167
164
|
if File.exist?(@provision_data[:private_key])
|
168
165
|
FileUtils.cp(@provision_data[:private_key], work_dir)
|
169
166
|
@private_key = File.join(work_dir, File.basename(@provision_data[:private_key]))
|
170
|
-
logger.verbose('Terraform') { "Copied private key #{@provision_data[:private_key]} to #{work_dir}" }
|
167
|
+
logger.verbose('CemAcpt::Provision::Terraform') { "Copied private key #{@provision_data[:private_key]} to #{work_dir}" }
|
171
168
|
end
|
172
169
|
if File.exist?(@provision_data[:public_key])
|
173
170
|
FileUtils.cp(@provision_data[:public_key], work_dir)
|
174
171
|
@public_key = File.join(work_dir, File.basename(@provision_data[:public_key]))
|
175
|
-
logger.verbose('Terraform') { "Copied public key #{@provision_data[:public_key]} to #{work_dir}" }
|
172
|
+
logger.verbose('CemAcpt::Provision::Terraform') { "Copied public key #{@provision_data[:public_key]} to #{work_dir}" }
|
176
173
|
end
|
177
174
|
work_dir
|
178
175
|
rescue StandardError => e
|
179
|
-
logger.error('Terraform') { 'Error creating working directory' }
|
176
|
+
logger.error('CemAcpt::Provision::Terraform') { 'Error creating working directory' }
|
180
177
|
raise e
|
181
178
|
end
|
182
179
|
|
183
180
|
def validate_working_dir!
|
184
|
-
logger.debug('Terraform') { "Validating working directory #{working_dir}" }
|
185
|
-
logger.verbose('Terraform') { "Content of #{working_dir}:\n#{Dir.glob(File.join(working_dir, '*')).join("\n")}" }
|
181
|
+
logger.debug('CemAcpt::Provision::Terraform') { "Validating working directory #{working_dir}" }
|
182
|
+
logger.verbose('CemAcpt::Provision::Terraform') { "Content of #{working_dir}:\n#{Dir.glob(File.join(working_dir, '*')).join("\n")}" }
|
186
183
|
raise "Terraform working directory #{working_dir} does not exist" unless File.directory?(working_dir)
|
187
184
|
raise "Terraform working directory #{working_dir} does not contain a Terraform file" unless Dir.glob(File.join(working_dir, '*.tf')).any?
|
188
185
|
|
189
|
-
logger.info('Terraform') { "Using working directory: #{working_dir}" }
|
186
|
+
logger.info('CemAcpt::Provision::Terraform') { "Using working directory: #{working_dir}" }
|
190
187
|
rescue StandardError => e
|
191
|
-
logger.error('Terraform') { 'Error validating working directory' }
|
192
188
|
raise e
|
193
189
|
end
|
194
190
|
|
195
191
|
def save_vars_to_file!(vars)
|
196
|
-
logger.debug('Terraform') { "Saving vars to file #{File.join(working_dir, DEFAULT_VARS_FILE)}" }
|
192
|
+
logger.debug('CemAcpt::Provision::Terraform') { "Saving vars to file #{File.join(working_dir, DEFAULT_VARS_FILE)}" }
|
197
193
|
File.write(File.join(working_dir, DEFAULT_VARS_FILE), vars.to_json)
|
198
194
|
rescue StandardError => e
|
199
|
-
logger.error('Terraform') { 'Error saving vars to file' }
|
200
195
|
raise e
|
201
196
|
end
|
202
197
|
|
@@ -214,7 +209,6 @@ module CemAcpt
|
|
214
209
|
end
|
215
210
|
node_data.to_json
|
216
211
|
rescue StandardError => e
|
217
|
-
logger.error('Terraform') { 'Error creating node data' }
|
218
212
|
raise e
|
219
213
|
end
|
220
214
|
|
@@ -228,7 +222,6 @@ module CemAcpt
|
|
228
222
|
}
|
229
223
|
)
|
230
224
|
rescue StandardError => e
|
231
|
-
logger.error('Terraform') { 'Error creating formatted vars' }
|
232
225
|
raise e
|
233
226
|
end
|
234
227
|
end
|
data/lib/cem_acpt/provision.rb
CHANGED
@@ -10,7 +10,7 @@ module CemAcpt
|
|
10
10
|
def self.new_provisioner(config, provision_data)
|
11
11
|
case config.get('provisioner')
|
12
12
|
when 'terraform'
|
13
|
-
logger.debug('Provision') { 'Using Terraform provisioner' }
|
13
|
+
logger.debug('CemAcpt::Provision') { 'Using Terraform provisioner' }
|
14
14
|
CemAcpt::Provision::Terraform.new(config, provision_data)
|
15
15
|
else
|
16
16
|
raise ArgumentError, "Unknown provisioner #{config.get('provisioner')}"
|
@@ -30,7 +30,7 @@ module CemAcpt
|
|
30
30
|
_metadata = builder.metadata
|
31
31
|
|
32
32
|
# Builds the module package
|
33
|
-
logger.info("Building module package for #{builder.release_name}"
|
33
|
+
logger.info('CemAcpt::PuppetHelpers') { "Building module package for #{builder.release_name}" }
|
34
34
|
builder.build
|
35
35
|
end
|
36
36
|
end
|
data/lib/cem_acpt/test_data.rb
CHANGED
@@ -33,7 +33,7 @@ module CemAcpt
|
|
33
33
|
# Extracts, formats, and returns a test data hash.
|
34
34
|
# @return [Array<Hash>] an array of test data hashes
|
35
35
|
def acceptance_test_data
|
36
|
-
logger.info 'Gathering acceptance test data...'
|
36
|
+
logger.info('CemAcpt::TestData') { 'Gathering acceptance test data...' }
|
37
37
|
raise "No 'tests' entry found in config" unless @config.has?('tests')
|
38
38
|
|
39
39
|
@config.get('tests').each_with_object([]) do |test_name, a|
|
@@ -45,7 +45,7 @@ module CemAcpt
|
|
45
45
|
raise "Goss file not found for test #{test_name}" unless File.exist?(goss_file)
|
46
46
|
raise "Puppet manifest not found for test #{test_name}" unless File.exist?(puppet_manifest)
|
47
47
|
|
48
|
-
logger.debug("Complete test directory found for test #{test_name}: #{test_dir}"
|
48
|
+
logger.debug('CemAcpt::TestData') { "Complete test directory found for test #{test_name}: #{test_dir}" }
|
49
49
|
test_data = {
|
50
50
|
test_name: test_name,
|
51
51
|
test_dir: File.expand_path(test_dir),
|
@@ -71,7 +71,7 @@ module CemAcpt
|
|
71
71
|
@acceptance_tests = acpt_test_dir.children.select { |f| f.directory? && File.exist?(File.join(f, 'goss.yaml')) }.map(&:to_s)
|
72
72
|
raise 'No acceptance tests found' if @acceptance_tests.empty?
|
73
73
|
|
74
|
-
logger.info "Found #{@acceptance_tests.size} acceptance tests"
|
74
|
+
logger.info('CemAcpt') { "Found #{@acceptance_tests.size} acceptance tests" }
|
75
75
|
end
|
76
76
|
|
77
77
|
# Processes a for_each statement in the test data config.
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CemAcpt
|
4
|
+
module TestRunner
|
5
|
+
module LogFormatter
|
6
|
+
class ErrorFormatter
|
7
|
+
def inspect
|
8
|
+
to_s
|
9
|
+
end
|
10
|
+
|
11
|
+
def to_s
|
12
|
+
"#<#{self.class.name}:0x#{object_id.to_s(16)}>"
|
13
|
+
end
|
14
|
+
|
15
|
+
def summary(response)
|
16
|
+
"Error: #{response.summary}"
|
17
|
+
end
|
18
|
+
|
19
|
+
def results(response)
|
20
|
+
[response.summary, response.results.join("\n")]
|
21
|
+
end
|
22
|
+
|
23
|
+
def host_name(response)
|
24
|
+
"Error: #{response.error.class.name}"
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_name(response)
|
28
|
+
"Error: #{response.error.class.name}"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -1,10 +1,19 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative 'log_formatter/goss_action_response'
|
4
|
+
require_relative 'log_formatter/error_formatter'
|
4
5
|
|
5
6
|
module CemAcpt
|
6
7
|
module TestRunner
|
7
8
|
# Holds classes for formatting test runner results
|
8
|
-
module LogFormatter
|
9
|
+
module LogFormatter
|
10
|
+
def self.new_formatter(result, *args, **kwargs)
|
11
|
+
if result.error?
|
12
|
+
ErrorFormatter.new
|
13
|
+
else
|
14
|
+
GossActionResponse.new(*args)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
9
18
|
end
|
10
19
|
end
|