cem_acpt 0.6.5 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/README.md +41 -2
- data/exe/cem_acpt +6 -109
- data/exe/cem_acpt_image +15 -0
- data/lib/cem_acpt/cli.rb +115 -0
- data/lib/cem_acpt/config/base.rb +347 -0
- data/lib/cem_acpt/config/cem_acpt.rb +90 -0
- data/lib/cem_acpt/config/cem_acpt_image.rb +53 -0
- data/lib/cem_acpt/config.rb +4 -376
- data/lib/cem_acpt/{core_extensions.rb → core_ext.rb} +3 -1
- data/lib/cem_acpt/image_builder/exec.rb +60 -0
- data/lib/cem_acpt/image_builder/provision_commands.rb +97 -0
- data/lib/cem_acpt/image_builder.rb +308 -0
- data/lib/cem_acpt/image_name_builder.rb +2 -2
- data/lib/cem_acpt/platform/base.rb +48 -25
- data/lib/cem_acpt/platform/gcp.rb +7 -7
- data/lib/cem_acpt/platform.rb +19 -7
- data/lib/cem_acpt/provision/terraform.rb +1 -1
- data/lib/cem_acpt/test_data.rb +2 -2
- data/lib/cem_acpt/test_runner.rb +2 -3
- data/lib/cem_acpt/version.rb +1 -1
- data/lib/cem_acpt.rb +64 -29
- data/lib/terraform/image/gcp/linux/main.tf +112 -0
- data/lib/terraform/image/gcp/windows/.keep +0 -0
- data/sample_config.yaml +89 -2
- metadata +14 -3
@@ -0,0 +1,308 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'fileutils'
|
4
|
+
require 'json'
|
5
|
+
require 'ruby-terraform'
|
6
|
+
require_relative 'logging'
|
7
|
+
require_relative 'platform'
|
8
|
+
require_relative 'utils'
|
9
|
+
require_relative 'version'
|
10
|
+
require_relative 'image_builder/exec'
|
11
|
+
require_relative 'image_builder/provision_commands'
|
12
|
+
|
13
|
+
module CemAcpt
|
14
|
+
# This module contains the classes and methods for building test node images
|
15
|
+
module ImageBuilder
|
16
|
+
def self.build_images(config)
|
17
|
+
TerraformBuilder.new(config).run
|
18
|
+
end
|
19
|
+
|
20
|
+
# This class builds test node images using Terraform
|
21
|
+
# NOTE: There is a huge amount of overlap between this class and
|
22
|
+
# CemAcpt::Provision::Terraform. This isn't ideal, but there are
|
23
|
+
# enough differences that a new class was needed. This should be
|
24
|
+
# refactored in the future.
|
25
|
+
class TerraformBuilder
|
26
|
+
DEFAULT_PLAN_NAME = 'testplan.tfplan'
|
27
|
+
DEFAULT_VARS_FILE = 'imagevars.json'
|
28
|
+
include CemAcpt::Logging
|
29
|
+
|
30
|
+
def initialize(config)
|
31
|
+
@config = config
|
32
|
+
@image_terraform_dir = File.join(config.get('terraform.dir'), 'image', config.get('platform.name'))
|
33
|
+
@exec = CemAcpt::ImageBuilder::Exec.new_exec(@config)
|
34
|
+
@environment = new_environment(@config)
|
35
|
+
@all_tfvars = { node_data: {} }
|
36
|
+
@linux_tfvars = { node_data: {} }
|
37
|
+
@windows_tfvars = { node_data: {} }
|
38
|
+
@duration = 0
|
39
|
+
@exit_code = 0
|
40
|
+
@private_key = nil
|
41
|
+
@public_key = nil
|
42
|
+
end
|
43
|
+
|
44
|
+
def run
|
45
|
+
@start_time = Time.now
|
46
|
+
logger.with_ci_group("CemAcptImage v#{CemAcpt::VERSION} run started at #{@start_time}") do
|
47
|
+
logger.info('CemAcptImage') { "Using working directory: #{@working_dir}..." }
|
48
|
+
keep_terminal_alive
|
49
|
+
@all_tfvars = new_tfvars(@config)
|
50
|
+
@working_dir = new_working_dir
|
51
|
+
@linux_tfvars, @windows_tfvars = divide_tfvars_by_os(@all_tfvars)
|
52
|
+
save_vars_to_file!('linux', @linux_tfvars)
|
53
|
+
save_vars_to_file!('windows', @windows_tfvars)
|
54
|
+
terraform_init
|
55
|
+
image_types = []
|
56
|
+
image_types << [@linux_tfvars, 'linux'] unless no_linux?
|
57
|
+
image_types << [@windows_tfvars, 'windows'] unless no_windows?
|
58
|
+
image_types.each do |tfvars, os_str|
|
59
|
+
terraform_plan(os_str, tfvars, DEFAULT_PLAN_NAME)
|
60
|
+
begin
|
61
|
+
terraform_apply(os_str, DEFAULT_PLAN_NAME)
|
62
|
+
output = JSON.parse(terraform_output(os_str, 'node-data', json: true))
|
63
|
+
output.each do |instance_name, data|
|
64
|
+
logger.info('CemAcptImage') { "Stopping instance #{instance_name}..." }
|
65
|
+
@exec.run('compute', 'instances', 'stop', instance_name)
|
66
|
+
deprecate_old_images_in_family(data['image_family'])
|
67
|
+
create_image_from_disk(data['disk_link'], image_name_from_image_family(data['image_family']), data['image_family'])
|
68
|
+
end
|
69
|
+
ensure
|
70
|
+
terraform_destroy(os_str, tfvars)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
attr_reader :environment
|
79
|
+
|
80
|
+
def image_name_from_image_family(image_family)
|
81
|
+
"#{image_family}-v#{@start_time.to_i}"[0..64]
|
82
|
+
end
|
83
|
+
|
84
|
+
def deprecate_old_images_in_family(image_family)
|
85
|
+
logger.info('CemAcptImage') { "Deprecating old images in family #{image_family}..." }
|
86
|
+
images = @exec.run('compute', 'images', 'list', '--project', @config.get('platform.project'), '--filter', "family:#{image_family}")
|
87
|
+
images.each do |image|
|
88
|
+
next unless image['status'] == 'READY'
|
89
|
+
|
90
|
+
logger.info('CemAcptImage') { "Deprecating image #{image['name']}..." }
|
91
|
+
@exec.run('compute', 'images', 'deprecate', image['name'], '--state', 'DEPRECATED', '--delete-in', '1d', '--project', @config.get('platform.project'))
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def create_image_from_disk(disk_link, image_name, image_family)
|
96
|
+
logger.info('CemAcptImage') { "Creating image from disk #{disk_link}..." }
|
97
|
+
@exec.run('compute', 'images', 'create', image_name, '--family', image_family,
|
98
|
+
'--source-disk', disk_link, '--project', @config.get('platform.project'))
|
99
|
+
logger.info('CemAcptImage') { "Image #{image_name} created for family #{image_family}"}
|
100
|
+
end
|
101
|
+
|
102
|
+
def no_windows?
|
103
|
+
@windows_tfvars[:node_data].empty? || @config.get('cem_acpt_image.no_windows')
|
104
|
+
end
|
105
|
+
|
106
|
+
def no_linux?
|
107
|
+
@linux_tfvars[:node_data].empty? || @config.get('cem_acpt_image.no_linux')
|
108
|
+
end
|
109
|
+
|
110
|
+
def terraform
|
111
|
+
return @terraform if defined?(@terraform)
|
112
|
+
|
113
|
+
RubyTerraform.configure do |c|
|
114
|
+
c.logger = logger
|
115
|
+
c.stdout = logger
|
116
|
+
c.stderr = logger
|
117
|
+
end
|
118
|
+
@terraform = RubyTerraform
|
119
|
+
@terraform
|
120
|
+
end
|
121
|
+
|
122
|
+
def new_environment(config)
|
123
|
+
env = (config.get('terraform.environment') || {})
|
124
|
+
env['CLOUDSDK_PYTHON_SITEPACKAGES'] = '1' # This is needed for gcloud to use numpy
|
125
|
+
logger.verbose('ImageBuilder') { "Using environment:\n#{JSON.pretty_generate(env)}" }
|
126
|
+
env
|
127
|
+
end
|
128
|
+
|
129
|
+
def new_platform(config, **run_data)
|
130
|
+
CemAcpt::Platform.get(config.get('platform.name'), base_type: :base).new(config, **run_data)
|
131
|
+
end
|
132
|
+
|
133
|
+
# @return [Thread] The thread that keeps the terminal alive
|
134
|
+
def keep_terminal_alive
|
135
|
+
return unless @config.ci?
|
136
|
+
|
137
|
+
@keep_terminal_alive ||= CemAcpt::Utils::Terminal.keep_terminal_alive
|
138
|
+
end
|
139
|
+
|
140
|
+
def kill_keep_terminal_alive
|
141
|
+
return if @trap_context
|
142
|
+
|
143
|
+
keep_terminal_alive&.kill
|
144
|
+
end
|
145
|
+
|
146
|
+
# @return [Array<String>] The paths to the ssh private key, public key, and known hosts file
|
147
|
+
def new_ephemeral_ssh_keys
|
148
|
+
return [nil, nil, nil] if @config.get('no_ephemeral_ssh_key')
|
149
|
+
|
150
|
+
CemAcpt::Utils::SSH::Ephemeral.create
|
151
|
+
end
|
152
|
+
|
153
|
+
def clean_ephemeral_ssh_keys
|
154
|
+
return if @config.get('no_ephemeral_ssh_key')
|
155
|
+
|
156
|
+
CemAcpt::Utils::SSH::Ephemeral.clean
|
157
|
+
end
|
158
|
+
|
159
|
+
def no_os_str?(os_str)
|
160
|
+
return true if os_str == 'windows' && no_windows?
|
161
|
+
return true if os_str != 'windows' && no_linux?
|
162
|
+
|
163
|
+
false
|
164
|
+
end
|
165
|
+
|
166
|
+
def in_os_dir(os_str)
|
167
|
+
return unless block_given?
|
168
|
+
|
169
|
+
if no_os_str?(os_str)
|
170
|
+
logger.debug('ImageBuilder') { "Skipping #{os_str} because no_#{os_str} is true" }
|
171
|
+
return
|
172
|
+
end
|
173
|
+
|
174
|
+
os_dir = (os_str == 'windows') ? File.join(@working_dir, 'windows') : File.join(@working_dir, 'linux')
|
175
|
+
Dir.chdir(os_dir) do
|
176
|
+
yield
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
def terraform_init
|
181
|
+
raise 'Cannot initialize Terraform, both no_linux and no_windows are true' if no_linux? && no_windows?
|
182
|
+
|
183
|
+
logger.debug('ImageBuilder') { 'Initializing Terraform' }
|
184
|
+
in_os_dir('linux') do
|
185
|
+
terraform.init({ input: false, no_color: true })
|
186
|
+
end
|
187
|
+
in_os_dir('windows') do
|
188
|
+
terraform.init({ input: false, no_color: true })
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
def terraform_plan(os_dir, tfvars, plan_name)
|
193
|
+
in_os_dir(os_dir) do
|
194
|
+
logger.debug('ImageBuilder') { "Creating Terraform plan #{plan_name} for #{os_dir}" }
|
195
|
+
logger.verbose('ImageBuilder') { "Using vars:\n#{JSON.pretty_generate(tfvars)}" }
|
196
|
+
terraform.plan({ input: false, no_color: true, plan: plan_name, vars: tfvars }, { environment: environment })
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
def terraform_apply(os_dir, plan_name)
|
201
|
+
in_os_dir(os_dir) do
|
202
|
+
logger.debug('ImageBuilder') { "Applying Terraform plan #{plan_name} for #{os_dir}" }
|
203
|
+
terraform.apply({ input: false, no_color: true, plan: plan_name }, { environment: environment })
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
def terraform_output(os_dir, output_name, json: true)
|
208
|
+
in_os_dir(os_dir) do
|
209
|
+
logger.debug('ImageBuilder') { "Getting Terraform output #{output_name} for #{os_dir}" }
|
210
|
+
terraform.output({ no_color: true, json: json, name: output_name }, { environment: environment })
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
def terraform_destroy(os_dir, tfvars)
|
215
|
+
in_os_dir(os_dir) do
|
216
|
+
logger.debug('ImageBuilder') { "Destroying Terraform resources for #{os_dir}" }
|
217
|
+
logger.verbose('ImageBuilder') { "Using vars:\n#{JSON.pretty_generate(tfvars)}" }
|
218
|
+
terraform.destroy({ auto_approve: true, input: false, no_color: true, vars: tfvars }, { environment: environment })
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
def terraform_show(os_dir)
|
223
|
+
in_os_dir(os_dir) do
|
224
|
+
logger.debug('ImageBuilder') { "Showing Terraform state for #{os_dir}" }
|
225
|
+
terraform.show({ no_color: true }, { environment: environment })
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
# @return [Hash] A hash of the Terraform variables to use
|
230
|
+
def new_tfvars(config)
|
231
|
+
tfvars = { node_data: {} }
|
232
|
+
private_key, public_key, _ = new_ephemeral_ssh_keys
|
233
|
+
tfvars[:private_key] = private_key if private_key
|
234
|
+
tfvars[:public_key] = public_key if public_key
|
235
|
+
config.get('images').each do |image_name, image|
|
236
|
+
platform = new_platform(config, **tfvars)
|
237
|
+
tfvars.merge!(platform.platform_data)
|
238
|
+
provision_commands = CemAcpt::ImageBuilder::ProvisionCommands.provision_commands(
|
239
|
+
config,
|
240
|
+
image_name: image_name,
|
241
|
+
base_image: image[:base_image],
|
242
|
+
os: image[:os],
|
243
|
+
os_major_version: image[:os_major_version],
|
244
|
+
puppet_version: image[:puppet_version],
|
245
|
+
)
|
246
|
+
tfvars[:node_data][platform.node_name] = platform.node_data.merge(
|
247
|
+
image_family: image_name,
|
248
|
+
provision_commands: provision_commands,
|
249
|
+
base_image: image[:base_image],
|
250
|
+
windows_image: windows_image?(image[:os]),
|
251
|
+
)
|
252
|
+
end
|
253
|
+
logger.verbose('ImageBuilder') { "All Terraform variables:\n#{JSON.pretty_generate(tfvars)}" }
|
254
|
+
tfvars
|
255
|
+
end
|
256
|
+
|
257
|
+
def divide_tfvars_by_os(tfvars)
|
258
|
+
linux_node_data = tfvars[:node_data].reject { |_, data| data[:windows_image] }
|
259
|
+
windows_node_data = tfvars[:node_data].select { |_, data| data[:windows_image] }
|
260
|
+
linux_tfvars = tfvars.dup
|
261
|
+
linux_tfvars[:node_data] = linux_node_data
|
262
|
+
windows_tfvars = tfvars.dup
|
263
|
+
windows_tfvars[:node_data] = windows_node_data
|
264
|
+
logger.verbose('ImageBuilder') { "Linux Terraform variables:\n#{JSON.pretty_generate(linux_tfvars)}" }
|
265
|
+
logger.verbose('ImageBuilder') { "Windows Terraform variables:\n#{JSON.pretty_generate(windows_tfvars)}" }
|
266
|
+
[linux_tfvars, windows_tfvars]
|
267
|
+
end
|
268
|
+
|
269
|
+
def windows_image?(os_str)
|
270
|
+
os_str == 'windows'
|
271
|
+
end
|
272
|
+
|
273
|
+
def save_vars_to_file!(prefix, vars)
|
274
|
+
logger.debug('ImageBuilder') { "Saving vars to file #{File.join(@working_dir, "#{prefix}_#{DEFAULT_VARS_FILE}")}" }
|
275
|
+
File.write(File.join(@working_dir, "#{prefix}_#{DEFAULT_VARS_FILE}"), vars.to_json)
|
276
|
+
rescue StandardError => e
|
277
|
+
logger.error('ImageBuilder') { 'Error saving vars to file' }
|
278
|
+
raise e
|
279
|
+
end
|
280
|
+
|
281
|
+
def new_working_dir
|
282
|
+
unless Dir.exist?(@image_terraform_dir)
|
283
|
+
raise "Image Terraform directory #{@image_terraform_dir} does not exist"
|
284
|
+
end
|
285
|
+
logger.debug('ImageBuilder') { "Creating new working directory in #{@image_terraform_dir}" }
|
286
|
+
working_dir = File.join(@image_terraform_dir, "image_builder_#{Time.now.to_i}")
|
287
|
+
FileUtils.mkdir_p(working_dir)
|
288
|
+
logger.verbose('ImageBuilder') { "Created working directory #{working_dir}" }
|
289
|
+
FileUtils.cp_r(File.join(@image_terraform_dir, 'linux'), working_dir)
|
290
|
+
FileUtils.cp_r(File.join(@image_terraform_dir, 'windows'), working_dir)
|
291
|
+
logger.verbose('ImageBuilder') { "Copied Terraform files from #{@image_terraform_dir}/(linux|windows) to #{working_dir}" }
|
292
|
+
if File.exist?(@all_tfvars[:private_key])
|
293
|
+
FileUtils.cp(@all_tfvars[:private_key], working_dir)
|
294
|
+
@private_key = File.join(working_dir, File.basename(@all_tfvars[:private_key]))
|
295
|
+
logger.verbose('ImageBuilder') { "Copied private key #{@all_tfvars[:private_key]} to #{working_dir}" }
|
296
|
+
end
|
297
|
+
if File.exist?(@all_tfvars[:public_key])
|
298
|
+
FileUtils.cp(@all_tfvars[:public_key], working_dir)
|
299
|
+
@public_key = File.join(working_dir, File.basename(@all_tfvars[:public_key]))
|
300
|
+
logger.verbose('ImageBuilder') { "Copied public key #{@all_tfvars[:public_key]} to #{working_dir}" }
|
301
|
+
end
|
302
|
+
|
303
|
+
logger.verbose('ImageBuilder') { "Content of #{working_dir}:\n#{Dir.glob(File.join(working_dir, '*')).join("\n")}" }
|
304
|
+
working_dir
|
305
|
+
end
|
306
|
+
end
|
307
|
+
end
|
308
|
+
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module CemAcpt
|
4
|
-
require_relative '
|
4
|
+
require_relative 'core_ext'
|
5
5
|
require_relative 'logging'
|
6
6
|
|
7
7
|
# Dynamically builds an image name based on parameters specified in the
|
@@ -20,7 +20,7 @@ module CemAcpt
|
|
20
20
|
# - 'validation_pattern' - (Optional) A regex pattern to validate the image name against.
|
21
21
|
class ImageNameBuilder
|
22
22
|
include CemAcpt::Logging
|
23
|
-
using CemAcpt::
|
23
|
+
using CemAcpt::CoreExt::ExtendedHash
|
24
24
|
|
25
25
|
# Initializes the ImageNameBuilder.
|
26
26
|
# @param config [CemAcpt::Config] The config to use.
|
@@ -6,33 +6,24 @@ require_relative File.join(__dir__, '..', 'image_name_builder')
|
|
6
6
|
require_relative File.join(__dir__, '..', 'logging')
|
7
7
|
|
8
8
|
module CemAcpt::Platform
|
9
|
-
# Base class for all platform classes. This class provides an API for
|
10
|
-
# interacting with the underlying platform.
|
11
9
|
class Base
|
12
10
|
include CemAcpt::Logging
|
13
11
|
|
14
|
-
|
15
|
-
|
16
|
-
# @param conf [CemAcpt::Config] the config object.
|
17
|
-
# @param single_test_data [Hash] the test data for the current test.
|
18
|
-
# @param run_data [Hash] the run data for the current run minus the test data key.
|
19
|
-
def initialize(config, single_test_data, **run_data)
|
20
|
-
raise ArgumentError, 'single_test_data must be a Hash' unless single_test_data.is_a?(Hash)
|
21
|
-
|
12
|
+
def initialize(config, **run_data)
|
22
13
|
@config = config
|
23
|
-
@test_data = single_test_data
|
24
|
-
@node_name = @test_data[:node_name] || random_node_name
|
25
14
|
@run_data = run_data
|
26
15
|
end
|
27
16
|
|
17
|
+
def node_name
|
18
|
+
@node_name ||= random_node_name
|
19
|
+
end
|
20
|
+
|
21
|
+
def image_name
|
22
|
+
@image_name ||= @run_data[:image_name]
|
23
|
+
end
|
24
|
+
|
28
25
|
def to_h
|
29
|
-
{
|
30
|
-
node_name: node_name,
|
31
|
-
image_name: image_name,
|
32
|
-
test_data: test_data,
|
33
|
-
platform_data: platform_data,
|
34
|
-
node_data: node_data,
|
35
|
-
}
|
26
|
+
{ node_name: node_name, platform_data: platform_data, node_data: node_data }
|
36
27
|
end
|
37
28
|
|
38
29
|
def to_json(*args)
|
@@ -41,7 +32,7 @@ module CemAcpt::Platform
|
|
41
32
|
|
42
33
|
# Data common to all nodes of the same platform.
|
43
34
|
def platform_data
|
44
|
-
raise NotImplementedError, '
|
35
|
+
raise NotImplementedError, 'platform_data must be implemented by the specific platform module'
|
45
36
|
end
|
46
37
|
|
47
38
|
# Data specific to the current node.
|
@@ -49,17 +40,49 @@ module CemAcpt::Platform
|
|
49
40
|
raise NotImplementedError, 'node_data must be implemented by the specific platform module'
|
50
41
|
end
|
51
42
|
|
52
|
-
# Generates or retrieves an image name from the test data.
|
53
|
-
def image_name
|
54
|
-
@image_name ||= (@config.has?('image_name_builder') ? image_name_builder(@config, test_data) : test_data[:image_name])
|
55
|
-
end
|
56
|
-
|
57
43
|
private
|
58
44
|
|
59
45
|
# Generates a random node name.
|
60
46
|
def random_node_name
|
61
47
|
"cem-acpt-#{SecureRandom.hex(12)}"
|
62
48
|
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# Base class for all test node platform classes. This class provides an API for
|
52
|
+
# interacting with the underlying platform.
|
53
|
+
class TestBase < Base
|
54
|
+
attr_reader :test_data
|
55
|
+
|
56
|
+
# @param conf [CemAcpt::Config] the config object.
|
57
|
+
# @param single_test_data [Hash] the test data for the current test.
|
58
|
+
# @param run_data [Hash] the run data for the current run minus the test data key.
|
59
|
+
def initialize(config, single_test_data, **run_data)
|
60
|
+
super(config, **run_data)
|
61
|
+
raise ArgumentError, 'single_test_data must be a Hash' unless single_test_data.is_a?(Hash)
|
62
|
+
|
63
|
+
@test_data = single_test_data
|
64
|
+
end
|
65
|
+
|
66
|
+
def node_name
|
67
|
+
@node_name ||= (@test_data[:node_name] || random_node_name)
|
68
|
+
end
|
69
|
+
|
70
|
+
def to_h
|
71
|
+
{
|
72
|
+
node_name: node_name,
|
73
|
+
image_name: image_name,
|
74
|
+
test_data: test_data,
|
75
|
+
platform_data: platform_data,
|
76
|
+
node_data: node_data,
|
77
|
+
}
|
78
|
+
end
|
79
|
+
|
80
|
+
# Generates or retrieves an image name from the test data.
|
81
|
+
def image_name
|
82
|
+
@image_name ||= (@config.has?('image_name_builder') ? image_name_builder(@config, test_data) : test_data[:image_name])
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
63
86
|
|
64
87
|
# Builds an image name if the config specifies to use the image name builder.
|
65
88
|
def image_name_builder(conf, tdata)
|
@@ -18,12 +18,12 @@ module Platform
|
|
18
18
|
end
|
19
19
|
|
20
20
|
def node_data
|
21
|
-
{
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
21
|
+
nd = {}
|
22
|
+
nd[:machine_type] = gcp_machine_type
|
23
|
+
nd[:disk_size] = gcp_disk_size
|
24
|
+
nd[:image] = image_name if image_name
|
25
|
+
nd[:test_name] = @test_data[:test_name] if @test_data&.key?(:test_name)
|
26
|
+
nd
|
27
27
|
end
|
28
28
|
|
29
29
|
def gcp_username
|
@@ -62,7 +62,7 @@ module Platform
|
|
62
62
|
end
|
63
63
|
|
64
64
|
def gcp_machine_type
|
65
|
-
@gcp_machine_type ||= (@config.get('node_data.machine_type') || 'e2-
|
65
|
+
@gcp_machine_type ||= (@config.get('node_data.machine_type') || 'e2-medium')
|
66
66
|
end
|
67
67
|
|
68
68
|
def gcp_disk_size
|
data/lib/cem_acpt/platform.rb
CHANGED
@@ -9,6 +9,7 @@ module CemAcpt::Platform
|
|
9
9
|
class Error < StandardError; end
|
10
10
|
|
11
11
|
PLATFORM_DIR = File.expand_path(File.join(__dir__, 'platform'))
|
12
|
+
BASE_TYPES = %i[base test].freeze
|
12
13
|
|
13
14
|
class << self
|
14
15
|
include CemAcpt::Logging
|
@@ -26,16 +27,18 @@ module CemAcpt::Platform
|
|
26
27
|
|
27
28
|
logger.info "Using #{platform} for #{run_data[:test_data].length} tests..."
|
28
29
|
run_data[:test_data].dup.each_with_object([]) do |single_test_data, ary|
|
29
|
-
ary <<
|
30
|
+
ary << new_test_platform_object(platform, config, single_test_data, **run_data.reject { |k, _| k == :test_data })
|
30
31
|
end
|
31
32
|
end
|
32
33
|
|
33
34
|
# Returns an un-initialized platform specific Class of the given platform.
|
34
35
|
# @param platform [String] the name of the platform
|
35
|
-
|
36
|
+
# @param base_type [Symbol] the type of base class to use.
|
37
|
+
def get(platform, base_type: :test)
|
36
38
|
raise Error, "Platform #{platform} is not supported" unless platforms.include?(platform)
|
39
|
+
raise Error, "Base type #{base_type} is not supported" unless BASE_TYPES.include?(base_type.to_sym)
|
37
40
|
|
38
|
-
platform_class(platform)
|
41
|
+
platform_class(base_type, platform)
|
39
42
|
end
|
40
43
|
|
41
44
|
# Returns an array of the names of the supported platforms.
|
@@ -62,17 +65,18 @@ module CemAcpt::Platform
|
|
62
65
|
# @param config [CemAcpt::Config] the config object.
|
63
66
|
# @param single_test_data [Hash] the test data for a single test.
|
64
67
|
# @return [CemAcpt::Platform::Base] an initialized platform class.
|
65
|
-
def
|
68
|
+
def new_test_platform_object(platform, config, single_test_data, **run_data)
|
66
69
|
raise Error, 'single_test_data must be a Hash' unless single_test_data.is_a?(Hash)
|
67
70
|
|
68
|
-
platform_class(platform).new(config, single_test_data, **run_data)
|
71
|
+
platform_class(:test, platform).new(config, single_test_data, **run_data)
|
69
72
|
end
|
70
73
|
|
71
74
|
# Creates a new platform-specific Class object for the given platform.
|
72
75
|
# Does not initialize the class.
|
76
|
+
# @param base_type [Symbol] the type of base class to use.
|
73
77
|
# @param platform [String] the name of the platform.
|
74
78
|
# @return [Class] the platform-specific Object.
|
75
|
-
def platform_class(platform)
|
79
|
+
def platform_class(base_type, platform)
|
76
80
|
class_name = platform.capitalize
|
77
81
|
# We require the platform base class here so that we can use it as
|
78
82
|
# a parent class for the platform-specific class.
|
@@ -90,9 +94,17 @@ module CemAcpt::Platform
|
|
90
94
|
# include Logging and Concurrent::Async, and finally call the
|
91
95
|
# initialize method on the class.
|
92
96
|
logger.debug "Creating platform class #{class_name}"
|
97
|
+
baseklass = case base_type.to_sym
|
98
|
+
when :base
|
99
|
+
CemAcpt::Platform::Base
|
100
|
+
when :test
|
101
|
+
CemAcpt::Platform::TestBase
|
102
|
+
else
|
103
|
+
raise Error, "Base type #{base_type} is not supported"
|
104
|
+
end
|
93
105
|
klass = Object.const_set(
|
94
106
|
class_name,
|
95
|
-
Class.new(
|
107
|
+
Class.new(baseklass) do
|
96
108
|
require_relative "platform/#{platform}"
|
97
109
|
include Platform
|
98
110
|
end,
|
@@ -156,7 +156,7 @@ module CemAcpt
|
|
156
156
|
logger.verbose('Terraform') { "Base directory defined as #{base_dir}" }
|
157
157
|
@backend.base_provision_directory = base_dir
|
158
158
|
logger.verbose('Terraform') { 'Base directory set in backend' }
|
159
|
-
work_dir = File.join(@config.get('terraform.dir'), "test_#{Time.now.to_i
|
159
|
+
work_dir = File.join(@config.get('terraform.dir'), "test_#{Time.now.to_i}")
|
160
160
|
logger.verbose('Terraform') { "Working directory defined as #{work_dir}" }
|
161
161
|
logger.verbose('Terraform') { "Copying backend provision directory #{@backend.provision_directory} to working directory" }
|
162
162
|
FileUtils.cp_r(@backend.provision_directory, work_dir)
|
data/lib/cem_acpt/test_data.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'pathname'
|
4
|
-
require_relative '
|
4
|
+
require_relative 'core_ext'
|
5
5
|
require_relative 'logging'
|
6
6
|
|
7
7
|
module CemAcpt
|
@@ -18,7 +18,7 @@ module CemAcpt
|
|
18
18
|
# Fetcher provides the methods for extracting and formatting test data.
|
19
19
|
class Fetcher
|
20
20
|
include CemAcpt::Logging
|
21
|
-
using CemAcpt::
|
21
|
+
using CemAcpt::CoreExt::ExtendedHash
|
22
22
|
|
23
23
|
attr_reader :acpt_test_dir, :acceptance_tests
|
24
24
|
|
data/lib/cem_acpt/test_runner.rb
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative 'core_extensions'
|
4
3
|
require_relative 'goss'
|
5
4
|
require_relative 'logging'
|
6
5
|
require_relative 'platform'
|
@@ -57,8 +56,8 @@ module CemAcpt
|
|
57
56
|
logger.info('CemAcpt') { 'Provisioned test nodes...' }
|
58
57
|
logger.debug('CemAcpt') { "Instance names and IPs: #{@instance_names_ips}" }
|
59
58
|
@results = run_tests(@instance_names_ips.map { |_, v| v['ip'] },
|
60
|
-
|
61
|
-
|
59
|
+
config.get('actions.only'),
|
60
|
+
config.get('actions.except'))
|
62
61
|
end
|
63
62
|
end
|
64
63
|
ensure
|
data/lib/cem_acpt/version.rb
CHANGED