cem_acpt 0.6.5 → 0.7.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.
@@ -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.
@@ -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,
@@ -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.to_s}")
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)
@@ -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
 
@@ -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
- config.get('actions.only'),
61
- config.get('actions.except'))
59
+ config.get('actions.only'),
60
+ config.get('actions.except'))
62
61
  end
63
62
  end
64
63
  ensure
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CemAcpt
4
- VERSION = '0.6.5'
4
+ VERSION = '0.7.1'
5
5
  end