cem_acpt 0.6.4 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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 'core_extensions'
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::CoreExtensions::ExtendedHash
23
+ using CemAcpt::CoreExt::ExtendedHash
24
24
 
25
25
  # Initializes the ImageNameBuilder.
26
26
  # @param config [CemAcpt::Config] The config to use.
@@ -126,9 +126,11 @@ module CemAcpt
126
126
  # Wraps the given block in a Github Actions group if in CI mode
127
127
  # @param name [String] the name of the group
128
128
  def with_ci_group(name)
129
- return yield unless @ci_mode
130
-
131
- self.<< "::group::#{name}\n"
129
+ if @ci_mode
130
+ self.<< "::group::#{name}\n"
131
+ else
132
+ info(name)
133
+ end
132
134
  yield
133
135
  ensure
134
136
  self.<< "::endgroup::\n" if @ci_mode
@@ -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
- attr_reader :test_data, :node_name
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, 'common_data must be implemented by the specific platform module'
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
- machine_type: gcp_machine_type,
23
- image: image_name,
24
- disk_size: gcp_disk_size,
25
- test_name: @test_data[:test_name],
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-small')
65
+ @gcp_machine_type ||= (@config.get('node_data.machine_type') || 'e2-medium')
66
66
  end
67
67
 
68
68
  def gcp_disk_size
@@ -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 << new_platform_object(platform, config, single_test_data, **run_data.reject { |k, _| k == :test_data })
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
- def get(platform)
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 new_platform_object(platform, config, single_test_data, **run_data)
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(CemAcpt::Platform::Base) do
107
+ Class.new(baseklass) do
96
108
  require_relative "platform/#{platform}"
97
109
  include Platform
98
110
  end,
@@ -14,7 +14,7 @@ module CemAcpt
14
14
  DEFAULT_VARS_FILE = 'testvars.json'
15
15
  include CemAcpt::Logging
16
16
 
17
- attr_reader :environment, :working_dir
17
+ attr_reader :environment, :working_dir, :module_package_path, :private_key, :public_key
18
18
 
19
19
  def initialize(config, provision_data)
20
20
  @config = config
@@ -22,11 +22,14 @@ module CemAcpt
22
22
  @backend = new_backend(@provision_data[:test_data].first[:test_name])
23
23
  @environment = new_environment(@config)
24
24
  @working_dir = nil
25
+ @module_package_path = nil
26
+ @private_key = nil
27
+ @public_key = nil
25
28
  end
26
29
 
27
- def provision
30
+ def provision(reuse_working_dir: false)
28
31
  logger.info('Terraform') { 'Provisioning nodes...' }
29
- @working_dir = new_working_dir
32
+ @working_dir = new_working_dir unless reuse_working_dir
30
33
  validate_working_dir!
31
34
  save_vars_to_file!(formatted_vars) # Easier to reuse nodes this way
32
35
 
@@ -42,6 +45,9 @@ module CemAcpt
42
45
  logger.verbose('Terraform') { "Deleting old working directory #{working_dir}" }
43
46
  FileUtils.rm_rf(working_dir)
44
47
  @working_dir = nil
48
+ @module_package_path = nil
49
+ @private_key = nil
50
+ @public_key = nil
45
51
  end
46
52
 
47
53
  def show
@@ -150,13 +156,24 @@ module CemAcpt
150
156
  logger.verbose('Terraform') { "Base directory defined as #{base_dir}" }
151
157
  @backend.base_provision_directory = base_dir
152
158
  logger.verbose('Terraform') { 'Base directory set in backend' }
153
- work_dir = File.join(@config.get('terraform.dir'), "test_#{Time.now.to_i.to_s}")
159
+ work_dir = File.join(@config.get('terraform.dir'), "test_#{Time.now.to_i}")
154
160
  logger.verbose('Terraform') { "Working directory defined as #{work_dir}" }
155
161
  logger.verbose('Terraform') { "Copying backend provision directory #{@backend.provision_directory} to working directory" }
156
162
  FileUtils.cp_r(@backend.provision_directory, work_dir)
157
163
  logger.verbose('Terraform') { "Copied provision directory #{@backend.provision_directory} to #{work_dir}" }
158
164
  FileUtils.cp(@provision_data[:module_package_path], work_dir)
165
+ @module_package_path = File.join(work_dir, File.basename(@provision_data[:module_package_path]))
159
166
  logger.verbose('Terraform') { "Copied module package #{@provision_data[:module_package_path]} to #{work_dir}" }
167
+ if File.exist?(@provision_data[:private_key])
168
+ FileUtils.cp(@provision_data[:private_key], work_dir)
169
+ @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}" }
171
+ end
172
+ if File.exist?(@provision_data[:public_key])
173
+ FileUtils.cp(@provision_data[:public_key], work_dir)
174
+ @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}" }
176
+ end
160
177
  work_dir
161
178
  rescue StandardError => e
162
179
  logger.error('Terraform') { 'Error creating working directory' }
@@ -204,9 +221,9 @@ module CemAcpt
204
221
  def formatted_vars
205
222
  @provision_data[:nodes].first.platform_data.merge(
206
223
  {
207
- puppet_module_package: @provision_data[:module_package_path],
208
- private_key: @provision_data[:private_key],
209
- public_key: @provision_data[:public_key],
224
+ puppet_module_package: @module_package_path,
225
+ private_key: @private_key,
226
+ public_key: @public_key,
210
227
  node_data: provision_node_data,
211
228
  }
212
229
  )
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'pathname'
4
- require_relative 'core_extensions'
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::CoreExtensions::ExtendedHash
21
+ using CemAcpt::CoreExt::ExtendedHash
22
22
 
23
23
  attr_reader :acpt_test_dir, :acceptance_tests
24
24