cem_acpt 0.7.3 → 0.8.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
@@ -2,11 +2,11 @@
|
|
2
2
|
|
3
3
|
require 'fileutils'
|
4
4
|
require 'json'
|
5
|
-
require 'ruby-terraform'
|
6
5
|
require_relative 'logging'
|
7
6
|
require_relative 'platform'
|
8
7
|
require_relative 'utils'
|
9
8
|
require_relative 'version'
|
9
|
+
require_relative 'provision/terraform/terraform_cmd'
|
10
10
|
require_relative 'image_builder/exec'
|
11
11
|
require_relative 'image_builder/provision_commands'
|
12
12
|
|
@@ -18,11 +18,11 @@ module CemAcpt
|
|
18
18
|
|
19
19
|
builder = TerraformBuilder.new(config)
|
20
20
|
builder.run
|
21
|
-
logger.info('ImageBuilder') { "Image builder finished after #{builder.duration} seconds" }
|
21
|
+
logger.info('CemAcpt::ImageBuilder') { "Image builder finished after #{builder.duration} seconds" }
|
22
22
|
if builder.exit_code.zero?
|
23
|
-
logger.info('ImageBuilder') { 'Image builder finished successfully' }
|
23
|
+
logger.info('CemAcpt::ImageBuilder') { 'Image builder finished successfully' }
|
24
24
|
else
|
25
|
-
logger.error('ImageBuilder') { "Image builder finished with exit code #{builder.exit_code}" }
|
25
|
+
logger.error('CemAcpt::ImageBuilder') { "Image builder finished with exit code #{builder.exit_code}" }
|
26
26
|
end
|
27
27
|
exit builder.exit_code
|
28
28
|
end
|
@@ -55,42 +55,41 @@ module CemAcpt
|
|
55
55
|
|
56
56
|
def run
|
57
57
|
@start_time = Time.now
|
58
|
-
logger.
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
end
|
84
|
-
@exit_code = 0
|
58
|
+
logger.start_ci_group("CemAcptImage v#{CemAcpt::VERSION} run started at #{@start_time}")
|
59
|
+
@all_tfvars = new_tfvars(@config)
|
60
|
+
@linux_tfvars, @windows_tfvars = divide_tfvars_by_os(@all_tfvars)
|
61
|
+
image_types = []
|
62
|
+
image_types << [@linux_tfvars, 'linux'] unless no_linux?
|
63
|
+
image_types << [@windows_tfvars, 'windows'] unless no_windows?
|
64
|
+
return dry_run(image_types) if @config.get('dry_run')
|
65
|
+
|
66
|
+
@working_dir = new_working_dir
|
67
|
+
logger.info('CemAcpt::ImageBuilder') { "Using working directory: #{@working_dir}..." }
|
68
|
+
keep_terminal_alive
|
69
|
+
save_vars_to_file!('linux', @linux_tfvars)
|
70
|
+
save_vars_to_file!('windows', @windows_tfvars)
|
71
|
+
terraform_init
|
72
|
+
image_types.each do |tfvars, os_str|
|
73
|
+
terraform_plan(os_str, tfvars, DEFAULT_PLAN_NAME)
|
74
|
+
begin
|
75
|
+
terraform_apply(os_str, DEFAULT_PLAN_NAME)
|
76
|
+
output = JSON.parse(terraform_output(os_str, 'node-data', json: true))
|
77
|
+
output.each do |instance_name, data|
|
78
|
+
logger.info('CemAcpt::ImageBuilder') { "Stopping instance #{instance_name}..." }
|
79
|
+
@exec.run('compute', 'instances', 'stop', instance_name)
|
80
|
+
unless @config.get('no_build_images')
|
81
|
+
deprecate_old_images_in_family(data['image_family'])
|
82
|
+
create_image_from_disk(data['disk_link'], image_name_from_image_family(data['image_family']), data['image_family'])
|
85
83
|
end
|
86
|
-
|
87
|
-
terraform_destroy(os_str, tfvars) unless @config.get('no_destroy_nodes')
|
84
|
+
@exit_code = 0
|
88
85
|
end
|
86
|
+
ensure
|
87
|
+
terraform_destroy(os_str, tfvars) unless @config.get('no_destroy_nodes')
|
89
88
|
end
|
90
89
|
end
|
91
90
|
rescue StandardError => e
|
92
|
-
logger.error('
|
93
|
-
logger.verbose('
|
91
|
+
logger.error('CemAcpt::ImageBuilder') { "Image builder failed with error: #{e}" }
|
92
|
+
logger.verbose('CemAcpt::ImageBuilder') { e.backtrace.join("\n") }
|
94
93
|
@exit_code = 1
|
95
94
|
ensure
|
96
95
|
if @start_time
|
@@ -98,6 +97,7 @@ module CemAcpt
|
|
98
97
|
else
|
99
98
|
@duration = 0
|
100
99
|
end
|
100
|
+
logger.end_ci_group
|
101
101
|
end
|
102
102
|
|
103
103
|
private
|
@@ -109,21 +109,21 @@ module CemAcpt
|
|
109
109
|
end
|
110
110
|
|
111
111
|
def deprecate_old_images_in_family(image_family)
|
112
|
-
logger.info('
|
112
|
+
logger.info('CemAcpt::ImageBuilder') { "Deprecating old images in family #{image_family}..." }
|
113
113
|
images = @exec.run('compute', 'images', 'list', '--project', @config.get('platform.project'), '--filter', "family:#{image_family}")
|
114
114
|
images.each do |image|
|
115
115
|
next unless image['status'] == 'READY'
|
116
116
|
|
117
|
-
logger.info('
|
117
|
+
logger.info('CemAcpt::ImageBuilder') { "Deprecating image #{image['name']}..." }
|
118
118
|
@exec.run('compute', 'images', 'deprecate', image['name'], '--state', 'DEPRECATED', '--delete-in', '1d', '--project', @config.get('platform.project'))
|
119
119
|
end
|
120
120
|
end
|
121
121
|
|
122
122
|
def create_image_from_disk(disk_link, image_name, image_family)
|
123
|
-
logger.info('
|
123
|
+
logger.info('CemAcpt::ImageBuilder') { "Creating image from disk #{disk_link}..." }
|
124
124
|
@exec.run('compute', 'images', 'create', image_name, '--family', image_family,
|
125
125
|
'--source-disk', disk_link, '--project', @config.get('platform.project'))
|
126
|
-
logger.info('
|
126
|
+
logger.info('CemAcpt::ImageBuilder') { "Image #{image_name} created for family #{image_family}"}
|
127
127
|
end
|
128
128
|
|
129
129
|
def no_windows?
|
@@ -135,30 +135,22 @@ module CemAcpt
|
|
135
135
|
end
|
136
136
|
|
137
137
|
def dry_run(image_types)
|
138
|
-
logger.info('
|
138
|
+
logger.info('CemAcpt::ImageBuilder') { 'Dry run mode enabled. No images will be built.' }
|
139
139
|
image_types.each do |tfvars, os_str|
|
140
|
-
logger.info('
|
141
|
-
logger.info('
|
140
|
+
logger.info('CemAcpt::ImageBuilder') { "Dry run for #{os_str}..." }
|
141
|
+
logger.info('CemAcpt::ImageBuilder') { "Terraform vars:\n#{JSON.pretty_generate(tfvars)}" }
|
142
142
|
end
|
143
143
|
@exit_code = 0
|
144
144
|
end
|
145
145
|
|
146
146
|
def terraform
|
147
|
-
|
148
|
-
|
149
|
-
RubyTerraform.configure do |c|
|
150
|
-
c.logger = logger
|
151
|
-
c.stdout = logger
|
152
|
-
c.stderr = logger
|
153
|
-
end
|
154
|
-
@terraform = RubyTerraform
|
155
|
-
@terraform
|
147
|
+
@terraform ||= CemAcpt::Provision::TerraformCmd.new(@image_terraform_dir, @environment)
|
156
148
|
end
|
157
149
|
|
158
150
|
def new_environment(config)
|
159
151
|
env = (config.get('terraform.environment') || {})
|
160
152
|
env['CLOUDSDK_PYTHON_SITEPACKAGES'] = '1' # This is needed for gcloud to use numpy
|
161
|
-
logger.verbose('ImageBuilder') { "Using environment:\n#{JSON.pretty_generate(env)}" }
|
153
|
+
logger.verbose('CemAcpt::ImageBuilder') { "Using environment:\n#{JSON.pretty_generate(env)}" }
|
162
154
|
env
|
163
155
|
end
|
164
156
|
|
@@ -203,7 +195,7 @@ module CemAcpt
|
|
203
195
|
return unless block_given?
|
204
196
|
|
205
197
|
if no_os_str?(os_str)
|
206
|
-
logger.debug('ImageBuilder') { "Skipping #{os_str} because no_#{os_str} is true" }
|
198
|
+
logger.debug('CemAcpt::ImageBuilder') { "Skipping #{os_str} because no_#{os_str} is true" }
|
207
199
|
return
|
208
200
|
end
|
209
201
|
|
@@ -216,7 +208,7 @@ module CemAcpt
|
|
216
208
|
def terraform_init
|
217
209
|
raise 'Cannot initialize Terraform, both no_linux and no_windows are true' if no_linux? && no_windows?
|
218
210
|
|
219
|
-
logger.debug('ImageBuilder') { 'Initializing Terraform' }
|
211
|
+
logger.debug('CemAcpt::ImageBuilder') { 'Initializing Terraform' }
|
220
212
|
in_os_dir('linux') do
|
221
213
|
terraform.init({ input: false, no_color: true })
|
222
214
|
end
|
@@ -227,37 +219,37 @@ module CemAcpt
|
|
227
219
|
|
228
220
|
def terraform_plan(os_dir, tfvars, plan_name)
|
229
221
|
in_os_dir(os_dir) do
|
230
|
-
logger.debug('ImageBuilder') { "Creating Terraform plan #{plan_name} for #{os_dir}" }
|
231
|
-
logger.verbose('ImageBuilder') { "Using vars:\n#{JSON.pretty_generate(tfvars)}" }
|
222
|
+
logger.debug('CemAcpt::ImageBuilder') { "Creating Terraform plan #{plan_name} for #{os_dir}" }
|
223
|
+
logger.verbose('CemAcpt::ImageBuilder') { "Using vars:\n#{JSON.pretty_generate(tfvars)}" }
|
232
224
|
terraform.plan({ input: false, no_color: true, plan: plan_name, vars: tfvars }, { environment: environment })
|
233
225
|
end
|
234
226
|
end
|
235
227
|
|
236
228
|
def terraform_apply(os_dir, plan_name)
|
237
229
|
in_os_dir(os_dir) do
|
238
|
-
logger.debug('ImageBuilder') { "Applying Terraform plan #{plan_name} for #{os_dir}" }
|
230
|
+
logger.debug('CemAcpt::ImageBuilder') { "Applying Terraform plan #{plan_name} for #{os_dir}" }
|
239
231
|
terraform.apply({ input: false, no_color: true, plan: plan_name }, { environment: environment })
|
240
232
|
end
|
241
233
|
end
|
242
234
|
|
243
235
|
def terraform_output(os_dir, output_name, json: true)
|
244
236
|
in_os_dir(os_dir) do
|
245
|
-
logger.debug('ImageBuilder') { "Getting Terraform output #{output_name} for #{os_dir}" }
|
237
|
+
logger.debug('CemAcpt::ImageBuilder') { "Getting Terraform output #{output_name} for #{os_dir}" }
|
246
238
|
terraform.output({ no_color: true, json: json, name: output_name }, { environment: environment })
|
247
239
|
end
|
248
240
|
end
|
249
241
|
|
250
242
|
def terraform_destroy(os_dir, tfvars)
|
251
243
|
in_os_dir(os_dir) do
|
252
|
-
logger.debug('ImageBuilder') { "Destroying Terraform resources for #{os_dir}" }
|
253
|
-
logger.verbose('ImageBuilder') { "Using vars:\n#{JSON.pretty_generate(tfvars)}" }
|
244
|
+
logger.debug('CemAcpt::ImageBuilder') { "Destroying Terraform resources for #{os_dir}" }
|
245
|
+
logger.verbose('CemAcpt::ImageBuilder') { "Using vars:\n#{JSON.pretty_generate(tfvars)}" }
|
254
246
|
terraform.destroy({ auto_approve: true, input: false, no_color: true, vars: tfvars }, { environment: environment })
|
255
247
|
end
|
256
248
|
end
|
257
249
|
|
258
250
|
def terraform_show(os_dir)
|
259
251
|
in_os_dir(os_dir) do
|
260
|
-
logger.debug('ImageBuilder') { "Showing Terraform state for #{os_dir}" }
|
252
|
+
logger.debug('CemAcpt::ImageBuilder') { "Showing Terraform state for #{os_dir}" }
|
261
253
|
terraform.show({ no_color: true }, { environment: environment })
|
262
254
|
end
|
263
255
|
end
|
@@ -288,7 +280,7 @@ module CemAcpt
|
|
288
280
|
windows_image: windows_image?(image[:os]),
|
289
281
|
)
|
290
282
|
end
|
291
|
-
logger.verbose('ImageBuilder') { "All Terraform variables:\n#{JSON.pretty_generate(tfvars)}" }
|
283
|
+
logger.verbose('CemAcpt::ImageBuilder') { "All Terraform variables:\n#{JSON.pretty_generate(tfvars)}" }
|
292
284
|
tfvars
|
293
285
|
end
|
294
286
|
|
@@ -299,8 +291,8 @@ module CemAcpt
|
|
299
291
|
linux_tfvars[:node_data] = linux_node_data
|
300
292
|
windows_tfvars = tfvars.dup
|
301
293
|
windows_tfvars[:node_data] = windows_node_data
|
302
|
-
logger.verbose('ImageBuilder') { "Linux Terraform variables:\n#{JSON.pretty_generate(linux_tfvars)}" }
|
303
|
-
logger.verbose('ImageBuilder') { "Windows Terraform variables:\n#{JSON.pretty_generate(windows_tfvars)}" }
|
294
|
+
logger.verbose('CemAcpt::ImageBuilder') { "Linux Terraform variables:\n#{JSON.pretty_generate(linux_tfvars)}" }
|
295
|
+
logger.verbose('CemAcpt::ImageBuilder') { "Windows Terraform variables:\n#{JSON.pretty_generate(windows_tfvars)}" }
|
304
296
|
[linux_tfvars, windows_tfvars]
|
305
297
|
end
|
306
298
|
|
@@ -309,10 +301,10 @@ module CemAcpt
|
|
309
301
|
end
|
310
302
|
|
311
303
|
def save_vars_to_file!(prefix, vars)
|
312
|
-
logger.debug('ImageBuilder') { "Saving vars to file #{File.join(@working_dir, "#{prefix}_#{DEFAULT_VARS_FILE}")}" }
|
304
|
+
logger.debug('CemAcpt::ImageBuilder') { "Saving vars to file #{File.join(@working_dir, "#{prefix}_#{DEFAULT_VARS_FILE}")}" }
|
313
305
|
File.write(File.join(@working_dir, "#{prefix}_#{DEFAULT_VARS_FILE}"), vars.to_json)
|
314
306
|
rescue StandardError => e
|
315
|
-
logger.error('ImageBuilder') { 'Error saving vars to file' }
|
307
|
+
logger.error('CemAcpt::ImageBuilder') { 'Error saving vars to file' }
|
316
308
|
raise e
|
317
309
|
end
|
318
310
|
|
@@ -320,25 +312,25 @@ module CemAcpt
|
|
320
312
|
unless Dir.exist?(@image_terraform_dir)
|
321
313
|
raise "Image Terraform directory #{@image_terraform_dir} does not exist"
|
322
314
|
end
|
323
|
-
logger.debug('ImageBuilder') { "Creating new working directory in #{@image_terraform_dir}" }
|
315
|
+
logger.debug('CemAcpt::ImageBuilder') { "Creating new working directory in #{@image_terraform_dir}" }
|
324
316
|
working_dir = File.join(@image_terraform_dir, "image_builder_#{Time.now.to_i}")
|
325
317
|
FileUtils.mkdir_p(working_dir)
|
326
|
-
logger.verbose('ImageBuilder') { "Created working directory #{working_dir}" }
|
318
|
+
logger.verbose('CemAcpt::ImageBuilder') { "Created working directory #{working_dir}" }
|
327
319
|
FileUtils.cp_r(File.join(@image_terraform_dir, 'linux'), working_dir)
|
328
320
|
FileUtils.cp_r(File.join(@image_terraform_dir, 'windows'), working_dir)
|
329
|
-
logger.verbose('ImageBuilder') { "Copied Terraform files from #{@image_terraform_dir}/(linux|windows) to #{working_dir}" }
|
321
|
+
logger.verbose('CemAcpt::ImageBuilder') { "Copied Terraform files from #{@image_terraform_dir}/(linux|windows) to #{working_dir}" }
|
330
322
|
if File.exist?(@all_tfvars[:private_key])
|
331
323
|
FileUtils.cp(@all_tfvars[:private_key], working_dir)
|
332
324
|
@private_key = File.join(working_dir, File.basename(@all_tfvars[:private_key]))
|
333
|
-
logger.verbose('ImageBuilder') { "Copied private key #{@all_tfvars[:private_key]} to #{working_dir}" }
|
325
|
+
logger.verbose('CemAcpt::ImageBuilder') { "Copied private key #{@all_tfvars[:private_key]} to #{working_dir}" }
|
334
326
|
end
|
335
327
|
if File.exist?(@all_tfvars[:public_key])
|
336
328
|
FileUtils.cp(@all_tfvars[:public_key], working_dir)
|
337
329
|
@public_key = File.join(working_dir, File.basename(@all_tfvars[:public_key]))
|
338
|
-
logger.verbose('ImageBuilder') { "Copied public key #{@all_tfvars[:public_key]} to #{working_dir}" }
|
330
|
+
logger.verbose('CemAcpt::ImageBuilder') { "Copied public key #{@all_tfvars[:public_key]} to #{working_dir}" }
|
339
331
|
end
|
340
332
|
|
341
|
-
logger.verbose('ImageBuilder') { "Content of #{working_dir}:\n#{Dir.glob(File.join(working_dir, '*')).join("\n")}" }
|
333
|
+
logger.verbose('CemAcpt::ImageBuilder') { "Content of #{working_dir}:\n#{Dir.glob(File.join(working_dir, '*')).join("\n")}" }
|
342
334
|
working_dir
|
343
335
|
end
|
344
336
|
end
|
data/lib/cem_acpt/logging.rb
CHANGED
@@ -13,7 +13,7 @@ module CemAcpt
|
|
13
13
|
'error' => ::Logger::ERROR,
|
14
14
|
'fatal' => ::Logger::FATAL,
|
15
15
|
'unknown' => ::Logger::UNKNOWN,
|
16
|
-
}
|
16
|
+
}.freeze
|
17
17
|
|
18
18
|
# Delegator class for when you want to log to multiple devices
|
19
19
|
# at the same time, such as STDOUT and a file.
|
@@ -28,34 +28,39 @@ module CemAcpt
|
|
28
28
|
def self.from_logger_configs(configs)
|
29
29
|
return if configs.nil? || configs.empty?
|
30
30
|
|
31
|
-
|
31
|
+
logger_instances = configs.map do |config|
|
32
32
|
CemAcpt::Logging::Logger.new(config[:logdev],
|
33
33
|
config[:shift_age],
|
34
34
|
config[:shift_size],
|
35
35
|
**config.reject { |k, _| [:logdev, :shift_age, :shift_size].include?(k) })
|
36
36
|
end
|
37
|
-
new(*
|
37
|
+
new(*logger_instances)
|
38
38
|
end
|
39
39
|
|
40
|
-
def initialize(*
|
41
|
-
@loggers =
|
40
|
+
def initialize(*logger_instances)
|
41
|
+
@loggers = logger_instances
|
42
42
|
end
|
43
43
|
|
44
44
|
def logger_configs
|
45
|
-
|
45
|
+
loggers.map(&:config_hash)
|
46
46
|
end
|
47
47
|
|
48
48
|
def method_missing(m, *args, &block)
|
49
|
-
if
|
50
|
-
|
49
|
+
if loggers.all? { |l| l.respond_to?(m) }
|
50
|
+
loggers.map { |l| l.send(m, *args, &block) }
|
51
51
|
else
|
52
52
|
super
|
53
53
|
end
|
54
54
|
end
|
55
55
|
|
56
56
|
def respond_to_missing?(m, include_private = false)
|
57
|
-
|
57
|
+
loggers.all? { |l| l.respond_to?(m) } || super
|
58
58
|
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
# Basically just for making testing easier
|
63
|
+
attr_reader :loggers
|
59
64
|
end
|
60
65
|
|
61
66
|
# Delagator class for the standard Ruby Logger class.
|
@@ -89,55 +94,63 @@ module CemAcpt
|
|
89
94
|
end
|
90
95
|
|
91
96
|
def debug(progname = nil, &block)
|
92
|
-
|
93
|
-
return
|
97
|
+
severity = 'debug'
|
98
|
+
return log_trap_context(severity, progname, &block) if log_trap_context?(severity)
|
99
|
+
return log_ci_mode(severity, progname, &block) if log_ci_mode?(severity)
|
94
100
|
|
95
101
|
super
|
96
102
|
end
|
97
103
|
|
98
104
|
def info(progname = nil, &block)
|
99
|
-
|
100
|
-
return
|
105
|
+
severity = 'info'
|
106
|
+
return log_trap_context(severity, progname, &block) if log_trap_context?(severity)
|
107
|
+
return log_ci_mode(severity, progname, &block) if log_ci_mode?(severity)
|
101
108
|
|
102
109
|
super
|
103
110
|
end
|
104
111
|
|
105
112
|
def warn(progname = nil, &block)
|
106
|
-
|
107
|
-
return
|
113
|
+
severity = 'warn'
|
114
|
+
return log_trap_context(severity, progname, &block) if log_trap_context?(severity)
|
115
|
+
return log_ci_mode(severity, progname, &block) if log_ci_mode?(severity)
|
108
116
|
|
109
117
|
super
|
110
118
|
end
|
111
119
|
|
112
120
|
def error(progname = nil, &block)
|
113
|
-
|
114
|
-
return
|
121
|
+
severity = 'error'
|
122
|
+
return log_trap_context(severity, progname, &block) if log_trap_context?(severity)
|
123
|
+
return log_ci_mode(severity, progname, &block) if log_ci_mode?(severity)
|
115
124
|
|
116
125
|
super
|
117
126
|
end
|
118
127
|
|
119
128
|
def fatal(progname = nil, &block)
|
120
|
-
|
121
|
-
return
|
129
|
+
severity = 'fatal'
|
130
|
+
return log_trap_context(severity, progname, &block) if log_trap_context?(severity)
|
131
|
+
return log_ci_mode(severity, progname, &block) if log_ci_mode?(severity)
|
122
132
|
|
123
133
|
super
|
124
134
|
end
|
125
135
|
|
126
|
-
|
127
|
-
# @param name [String] the name of the group
|
128
|
-
def with_ci_group(name)
|
136
|
+
def start_ci_group(name)
|
129
137
|
if @ci_mode
|
130
138
|
self.<< "::group::#{name}\n"
|
131
139
|
else
|
132
140
|
info(name)
|
133
141
|
end
|
134
|
-
|
135
|
-
|
142
|
+
end
|
143
|
+
|
144
|
+
def end_ci_group
|
136
145
|
self.<< "::endgroup::\n" if @ci_mode
|
137
146
|
end
|
138
147
|
|
139
148
|
private
|
140
149
|
|
150
|
+
def log_trap_context?(severity)
|
151
|
+
@trap_context && level <= LEVEL_MAP[severity]
|
152
|
+
end
|
153
|
+
|
141
154
|
# Can't use the standard Logger methods when in trap context
|
142
155
|
# because they don't work. So we have to write directly to the
|
143
156
|
# raw logdev. Unlike the standard Logger methods, there is no
|
@@ -145,8 +158,6 @@ module CemAcpt
|
|
145
158
|
# This is necessary in the trap context because using a Mutex
|
146
159
|
# lock in a trap context will cause a deadlock.
|
147
160
|
def log_trap_context(severity, progname = nil, &block)
|
148
|
-
return false unless @trap_context && level <= LEVEL_MAP[severity]
|
149
|
-
|
150
161
|
if @raw_logdev.respond_to?(:puts)
|
151
162
|
@raw_logdev.puts(ci_format(severity, false, progname, &block))
|
152
163
|
elsif @raw_logdev.respond_to?(:write)
|
@@ -155,7 +166,10 @@ module CemAcpt
|
|
155
166
|
# Default to logging to STDERR
|
156
167
|
$stderr.puts(ci_format(severity, false, progname, &block))
|
157
168
|
end
|
158
|
-
|
169
|
+
end
|
170
|
+
|
171
|
+
def log_ci_mode?(severity)
|
172
|
+
@ci_mode && level <= LEVEL_MAP[severity]
|
159
173
|
end
|
160
174
|
|
161
175
|
# CI mode uses << instead of the standard Logger methods
|
@@ -164,10 +178,7 @@ module CemAcpt
|
|
164
178
|
# with a standard Logger, the CI mode messages are still
|
165
179
|
# formatted correctly.
|
166
180
|
def log_ci_mode(severity, progname = nil, &block)
|
167
|
-
return false unless @ci_mode && level <= LEVEL_MAP[severity]
|
168
|
-
|
169
181
|
self.<<(ci_format(severity, true, progname, &block))
|
170
|
-
true
|
171
182
|
end
|
172
183
|
|
173
184
|
# Formats the log message for CI mode, translating the severity
|
data/lib/cem_acpt/platform.rb
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'concurrent-ruby'
|
4
3
|
require_relative 'logging'
|
5
4
|
|
6
5
|
# CemAcpt::Platform manages creating and configring platform specific objects
|
@@ -25,7 +24,7 @@ module CemAcpt::Platform
|
|
25
24
|
raise Error, 'run_data must include a :test_data key' unless run_data.key?(:test_data)
|
26
25
|
raise Error, 'run_data[:test_data] must be an Array' unless run_data[:test_data].is_a?(Array)
|
27
26
|
|
28
|
-
logger.info "Using #{platform} for #{run_data[:test_data].length} tests..."
|
27
|
+
logger.info('CemAcpt::Platform') { "Using #{platform} for #{run_data[:test_data].length} tests..." }
|
29
28
|
run_data[:test_data].dup.each_with_object([]) do |single_test_data, ary|
|
30
29
|
ary << new_test_platform_object(platform, config, single_test_data, **run_data.reject { |k, _| k == :test_data })
|
31
30
|
end
|
@@ -53,7 +52,7 @@ module CemAcpt::Platform
|
|
53
52
|
File.basename(file, '.rb') unless file.end_with?('base.rb')
|
54
53
|
end
|
55
54
|
@platforms.compact!
|
56
|
-
logger.debug "Discovered platform(s): #{@platforms}"
|
55
|
+
logger.debug('CemAcpt::Platform') { "Discovered platform(s): #{@platforms}" }
|
57
56
|
@platforms
|
58
57
|
end
|
59
58
|
|
@@ -83,7 +82,7 @@ module CemAcpt::Platform
|
|
83
82
|
require_relative 'platform/base'
|
84
83
|
# If the class has already been defined, we can just use it.
|
85
84
|
if Object.const_defined?(class_name)
|
86
|
-
logger.debug "Using existing platform class #{class_name}"
|
85
|
+
logger.debug('CemAcpt::Platform') { "Using existing platform class #{class_name}" }
|
87
86
|
klass = Object.const_get(class_name)
|
88
87
|
else
|
89
88
|
# Otherwise, we need to create the class. We do this by setting
|
@@ -110,7 +109,7 @@ module CemAcpt::Platform
|
|
110
109
|
end,
|
111
110
|
)
|
112
111
|
end
|
113
|
-
logger.debug "Using platform class: #{klass.inspect}"
|
112
|
+
logger.debug('CemAcpt::Platform') { "Using platform class: #{klass.inspect}" }
|
114
113
|
klass
|
115
114
|
end
|
116
115
|
end
|
@@ -53,8 +53,8 @@ module CemAcpt
|
|
53
53
|
'--logdest',
|
54
54
|
'console,/opt/cem_acpt/provision_apply.log',
|
55
55
|
]
|
56
|
-
cmd << '--debug' if @config.debug?
|
57
|
-
cmd << '--verbose' if @config.verbose?
|
56
|
+
cmd << '--debug' if @config.debug? && !@config.get('puppet.no_debug')
|
57
|
+
cmd << '--verbose' if @config.verbose? && !@config.get('puppet.no_verbose')
|
58
58
|
cmd << "#{destination_provision_directory}/#{puppet_manifest_file}"
|
59
59
|
cmd.join(' ')
|
60
60
|
end
|
@@ -0,0 +1,154 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
require 'open3'
|
5
|
+
require 'stringio'
|
6
|
+
require_relative '../../logging'
|
7
|
+
require_relative '../../utils/shell'
|
8
|
+
|
9
|
+
module CemAcpt
|
10
|
+
module Provision
|
11
|
+
# Stand-in for ruby-terraform because ruby-terraform doesn't work with Ruby 3
|
12
|
+
class TerraformCmd
|
13
|
+
include CemAcpt::Logging
|
14
|
+
|
15
|
+
attr_accessor :working_dir
|
16
|
+
attr_reader :bin_path
|
17
|
+
|
18
|
+
def initialize(working_dir = nil, environment = {})
|
19
|
+
@working_dir = working_dir
|
20
|
+
@environment = environment
|
21
|
+
@bin_path = which_terraform
|
22
|
+
end
|
23
|
+
|
24
|
+
def inspect
|
25
|
+
to_s
|
26
|
+
end
|
27
|
+
|
28
|
+
def to_s
|
29
|
+
"#<#{self.class.name}:0x#{object_id.to_s(16)}>"
|
30
|
+
end
|
31
|
+
|
32
|
+
def environment=(env)
|
33
|
+
raise ArgumentError, 'environment must be a Hash' unless env.is_a?(Hash)
|
34
|
+
|
35
|
+
@environment = env
|
36
|
+
end
|
37
|
+
|
38
|
+
def environment(env = {})
|
39
|
+
raise ArgumentError, 'additional environment must be a Hash' unless env.is_a?(Hash)
|
40
|
+
|
41
|
+
if env.key?(:environment) && env[:environment].is_a?(Hash)
|
42
|
+
env = env[:environment]
|
43
|
+
end
|
44
|
+
@environment.merge(env)
|
45
|
+
end
|
46
|
+
|
47
|
+
def init(opts = {}, env = {})
|
48
|
+
run_cmd('init', opts, env)
|
49
|
+
end
|
50
|
+
|
51
|
+
def plan(opts = {}, env = {})
|
52
|
+
plan = extract_arg!(opts, :plan, required: true)
|
53
|
+
opts[:out] = plan
|
54
|
+
run_cmd('plan', opts, env)
|
55
|
+
end
|
56
|
+
|
57
|
+
def apply(opts = {}, env = {})
|
58
|
+
plan = extract_arg!(opts, :plan, required: true)
|
59
|
+
run_cmd('apply', opts, env, suffix: plan)
|
60
|
+
end
|
61
|
+
|
62
|
+
def destroy(opts = {}, env = {})
|
63
|
+
run_cmd('destroy', opts, env)
|
64
|
+
end
|
65
|
+
|
66
|
+
def show(opts = {}, env = {})
|
67
|
+
run_cmd('show', opts, env)
|
68
|
+
end
|
69
|
+
|
70
|
+
def output(opts = {}, env = {})
|
71
|
+
name = extract_arg!(opts, :name, required: true)
|
72
|
+
run_cmd('output', opts, env, suffix: name)
|
73
|
+
end
|
74
|
+
|
75
|
+
private
|
76
|
+
|
77
|
+
def extract_arg!(opts, key, required: false)
|
78
|
+
key = key.to_sym
|
79
|
+
raise ArgumentError, "option #{key} is required" if required && !opts.key?(key) && (opts[key].nil? || opts[key].empty?)
|
80
|
+
|
81
|
+
opts.delete(key)
|
82
|
+
end
|
83
|
+
|
84
|
+
def run_cmd(cmd, opts = {}, env = {}, suffix: '')
|
85
|
+
cmd = format_cmd(cmd, opts, suffix)
|
86
|
+
env = environment(env)
|
87
|
+
logger.debug('CemAcpt::Provision::TerraformCmd') { "Running command \"#{cmd}\" with environment \"#{env}\"" }
|
88
|
+
CemAcpt::Utils::Shell.run_cmd(cmd, env, output: logger)
|
89
|
+
end
|
90
|
+
|
91
|
+
def chdir(opts = {})
|
92
|
+
[extract_arg!(opts, :chdir), working_dir, Dir.pwd].each do |d|
|
93
|
+
next if d.nil? || d.empty?
|
94
|
+
|
95
|
+
d = File.expand_path(d)
|
96
|
+
return "-chdir=#{d}" if File.directory?(d)
|
97
|
+
|
98
|
+
logger.warn('CemAcpt::Provision::TerraformCmd') { "Directory #{d} does not exist, using next..." }
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def which_terraform
|
103
|
+
path = CemAcpt::Utils::Shell.which('terraform')
|
104
|
+
raise 'terraform not found in PATH, make sure it is installed' if path.nil?
|
105
|
+
|
106
|
+
path
|
107
|
+
end
|
108
|
+
|
109
|
+
def format_cmd(cmd, opts = {}, suffix = '')
|
110
|
+
formatted = [bin_path, chdir(opts), cmd]
|
111
|
+
opts_s = opts_string(opts)
|
112
|
+
formatted << opts_s unless opts_s.empty?
|
113
|
+
formatted << suffix unless suffix.empty?
|
114
|
+
formatted.join(' ')
|
115
|
+
end
|
116
|
+
|
117
|
+
def opts_string(opts = {})
|
118
|
+
formatted = opts.map do |k, v|
|
119
|
+
next if k.nil? || k.empty?
|
120
|
+
|
121
|
+
k = k.to_s
|
122
|
+
k.tr!('_', '-')
|
123
|
+
if k == 'vars'
|
124
|
+
v.map { |vk, vv| "-var '#{vk}=#{vv}'" }.join(' ')
|
125
|
+
elsif %w[input lock refresh].include?(k) # These are boolean flags with values
|
126
|
+
"-#{k}=#{v}"
|
127
|
+
elsif v.nil? || (v.respond_to?(:empty) && v.empty?) || v.is_a?(TrueClass)
|
128
|
+
"-#{k}"
|
129
|
+
else
|
130
|
+
case v
|
131
|
+
when Array, Hash
|
132
|
+
"-#{k}='#{v.to_json}'"
|
133
|
+
else
|
134
|
+
"-#{k}=#{v}"
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
formatted.join(' ')
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
# IO monkey patch for non-blocking readline
|
144
|
+
class ::IO
|
145
|
+
def readline_nonblock
|
146
|
+
rlnb = []
|
147
|
+
while (ch = read_nonblock(1))
|
148
|
+
rlnb << ch
|
149
|
+
break if ch == "\n"
|
150
|
+
end
|
151
|
+
rlnb.join
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|