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.
- 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,90 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base'
|
4
|
+
|
5
|
+
module CemAcpt
|
6
|
+
module Config
|
7
|
+
# Holds the configuration for cem_acpt
|
8
|
+
class CemAcpt < Base
|
9
|
+
VALID_KEYS = %i[
|
10
|
+
actions
|
11
|
+
ci_mode
|
12
|
+
config_file
|
13
|
+
image_name_builder
|
14
|
+
log_level
|
15
|
+
log_file
|
16
|
+
log_format
|
17
|
+
module_dir
|
18
|
+
node_data
|
19
|
+
no_destroy_nodes
|
20
|
+
no_ephemeral_ssh_key
|
21
|
+
platform
|
22
|
+
provisioner
|
23
|
+
quiet
|
24
|
+
terraform
|
25
|
+
test_data
|
26
|
+
tests
|
27
|
+
user_config
|
28
|
+
verbose
|
29
|
+
].freeze
|
30
|
+
|
31
|
+
def valid_keys
|
32
|
+
VALID_KEYS
|
33
|
+
end
|
34
|
+
|
35
|
+
# The default configuration
|
36
|
+
def defaults
|
37
|
+
{
|
38
|
+
actions: {},
|
39
|
+
ci_mode: false,
|
40
|
+
config_file: nil,
|
41
|
+
image_name_builder: {
|
42
|
+
character_substitutions: ['_', '-'],
|
43
|
+
parts: ['cem-acpt', '$image_fam', '$collection', '$firewall'],
|
44
|
+
join_with: '-',
|
45
|
+
},
|
46
|
+
log_level: 'info',
|
47
|
+
log_file: nil,
|
48
|
+
log_format: 'text',
|
49
|
+
module_dir: Dir.pwd,
|
50
|
+
node_data: {},
|
51
|
+
no_ephemeral_ssh_key: false,
|
52
|
+
platform: {
|
53
|
+
name: 'gcp',
|
54
|
+
},
|
55
|
+
quiet: false,
|
56
|
+
test_data: {
|
57
|
+
for_each: {
|
58
|
+
collection: %w[puppet7],
|
59
|
+
},
|
60
|
+
vars: {},
|
61
|
+
name_pattern_vars: %r{^(?<framework>[a-z]+)_(?<image_fam>[a-z0-9-]+)_(?<firewall>[a-z]+)_(?<framework_vars>[-_a-z0-9]+)$},
|
62
|
+
vars_post_processing: {
|
63
|
+
new_vars: [
|
64
|
+
{
|
65
|
+
name: 'profile',
|
66
|
+
string_split: {
|
67
|
+
from: 'framework_vars',
|
68
|
+
using: '_',
|
69
|
+
part: 0,
|
70
|
+
},
|
71
|
+
},
|
72
|
+
{
|
73
|
+
name: 'level',
|
74
|
+
string_split: {
|
75
|
+
from: 'framework_vars',
|
76
|
+
using: '_',
|
77
|
+
part: 1,
|
78
|
+
},
|
79
|
+
},
|
80
|
+
],
|
81
|
+
delete_vars: %w[framework_vars],
|
82
|
+
},
|
83
|
+
},
|
84
|
+
tests: [],
|
85
|
+
verbose: false,
|
86
|
+
}
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base'
|
4
|
+
|
5
|
+
module CemAcpt
|
6
|
+
module Config
|
7
|
+
# Holds the configuration for cem_acpt_image
|
8
|
+
class CemAcptImage < Base
|
9
|
+
VALID_KEYS = %i[
|
10
|
+
ci_mode
|
11
|
+
config_file
|
12
|
+
images
|
13
|
+
log_level
|
14
|
+
log_file
|
15
|
+
log_format
|
16
|
+
no_destroy_nodes
|
17
|
+
no_ephemeral_ssh_key
|
18
|
+
platform
|
19
|
+
provisioner
|
20
|
+
quiet
|
21
|
+
terraform
|
22
|
+
user_config
|
23
|
+
verbose
|
24
|
+
].freeze
|
25
|
+
|
26
|
+
def valid_keys
|
27
|
+
VALID_KEYS
|
28
|
+
end
|
29
|
+
|
30
|
+
def env_var_prefix
|
31
|
+
'CEM_ACPT_IMAGE'
|
32
|
+
end
|
33
|
+
|
34
|
+
# The default configuration
|
35
|
+
def defaults
|
36
|
+
{
|
37
|
+
ci_mode: false,
|
38
|
+
config_file: nil,
|
39
|
+
images: {},
|
40
|
+
log_level: 'info',
|
41
|
+
log_file: nil,
|
42
|
+
log_format: 'text',
|
43
|
+
no_ephemeral_ssh_key: false,
|
44
|
+
platform: {
|
45
|
+
name: 'gcp',
|
46
|
+
},
|
47
|
+
quiet: false,
|
48
|
+
verbose: false,
|
49
|
+
}
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
data/lib/cem_acpt/config.rb
CHANGED
@@ -1,381 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
require 'json'
|
5
|
-
require 'yaml'
|
6
|
-
require_relative 'core_extensions'
|
7
|
-
|
3
|
+
# Module contains the CemAcptConfig::Base class which serves as a base class for different configs.
|
8
4
|
module CemAcpt
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
class Config
|
13
|
-
KEYS = %i[
|
14
|
-
actions
|
15
|
-
ci_mode
|
16
|
-
config_file
|
17
|
-
image_name_builder
|
18
|
-
log_level
|
19
|
-
log_file
|
20
|
-
log_format
|
21
|
-
module_dir
|
22
|
-
node_data
|
23
|
-
no_destroy_nodes
|
24
|
-
no_ephemeral_ssh_key
|
25
|
-
platform
|
26
|
-
provisioner
|
27
|
-
quiet
|
28
|
-
terraform
|
29
|
-
test_data
|
30
|
-
tests
|
31
|
-
user_config
|
32
|
-
verbose
|
33
|
-
].freeze
|
34
|
-
|
35
|
-
attr_reader :config, :env_vars
|
36
|
-
|
37
|
-
def initialize(opts: {}, config_file: nil, load_user_config: true)
|
38
|
-
@load_user_config = load_user_config
|
39
|
-
load(opts: opts, config_file: config_file)
|
40
|
-
end
|
41
|
-
|
42
|
-
# The default configuration
|
43
|
-
def defaults
|
44
|
-
{
|
45
|
-
actions: {},
|
46
|
-
ci_mode: false,
|
47
|
-
config_file: nil,
|
48
|
-
image_name_builder: {
|
49
|
-
character_substitutions: ['_', '-'],
|
50
|
-
parts: ['cem-acpt', '$image_fam', '$collection', '$firewall'],
|
51
|
-
join_with: '-',
|
52
|
-
},
|
53
|
-
log_level: 'info',
|
54
|
-
log_file: nil,
|
55
|
-
log_format: 'text',
|
56
|
-
module_dir: Dir.pwd,
|
57
|
-
node_data: {},
|
58
|
-
no_ephemeral_ssh_key: false,
|
59
|
-
platform: {
|
60
|
-
name: 'gcp',
|
61
|
-
},
|
62
|
-
quiet: false,
|
63
|
-
test_data: {
|
64
|
-
for_each: {
|
65
|
-
collection: %w[puppet7],
|
66
|
-
},
|
67
|
-
vars: {},
|
68
|
-
name_pattern_vars: %r{^(?<framework>[a-z]+)_(?<image_fam>[a-z0-9-]+)_(?<firewall>[a-z]+)_(?<framework_vars>[-_a-z0-9]+)$},
|
69
|
-
vars_post_processing: {
|
70
|
-
new_vars: [
|
71
|
-
{
|
72
|
-
name: 'profile',
|
73
|
-
string_split: {
|
74
|
-
from: 'framework_vars',
|
75
|
-
using: '_',
|
76
|
-
part: 0,
|
77
|
-
},
|
78
|
-
},
|
79
|
-
{
|
80
|
-
name: 'level',
|
81
|
-
string_split: {
|
82
|
-
from: 'framework_vars',
|
83
|
-
using: '_',
|
84
|
-
part: 1,
|
85
|
-
},
|
86
|
-
},
|
87
|
-
],
|
88
|
-
delete_vars: %w[framework_vars],
|
89
|
-
},
|
90
|
-
},
|
91
|
-
tests: [],
|
92
|
-
verbose: false,
|
93
|
-
}
|
94
|
-
end
|
95
|
-
|
96
|
-
# Load the configuration from the environment variables, config file, and opts
|
97
|
-
# The order of precedence is:
|
98
|
-
# 1. environment variables
|
99
|
-
# 2. user config file (config.yaml in user_config_dir)
|
100
|
-
# 3. specified config file (if it exists)
|
101
|
-
# 4. opts
|
102
|
-
# 5. static options (set in this class)
|
103
|
-
# @param opts [Hash] The options to load
|
104
|
-
# @param config_file [String] The config file to load
|
105
|
-
# @return [self] This object with the config loaded
|
106
|
-
def load(opts: {}, config_file: nil)
|
107
|
-
create_config_dirs!
|
108
|
-
init_config!(opts: opts, config_file: config_file)
|
109
|
-
add_env_vars!(@config)
|
110
|
-
@config.merge!(user_config) if user_config && @load_user_config
|
111
|
-
@config.merge!(config_from_file) if config_from_file
|
112
|
-
@config.merge!(@options) if @options
|
113
|
-
add_static_options!(@config)
|
114
|
-
@config.format! # Symbolize keys of all hashes
|
115
|
-
validate_config!
|
116
|
-
# Freeze the config so it can't be modified
|
117
|
-
# This helps with thread safety and deterministic behavior
|
118
|
-
@config.freeze
|
119
|
-
self
|
120
|
-
end
|
121
|
-
alias to_h config
|
122
|
-
|
123
|
-
def explain
|
124
|
-
explanation = {}
|
125
|
-
%i[defaults env_vars user_config config_from_file options].each do |source|
|
126
|
-
source_vals = send(source).dup
|
127
|
-
next if source_vals.nil? || source_vals.empty?
|
128
|
-
|
129
|
-
# The loop below will overwrite the value of explanation[key] if the same key is found in multiple sources
|
130
|
-
# This is intentional, as the last source to set the value is the one that should be used
|
131
|
-
source_vals.each do |key, value|
|
132
|
-
explanation[key] = source if @config.dget(key.to_s) == value
|
133
|
-
end
|
134
|
-
end
|
135
|
-
explained = explanation.each_with_object([]) do |(key, value), ary|
|
136
|
-
ary << "Key '#{key}' from source '#{value}'"
|
137
|
-
end
|
138
|
-
explained.join("\n")
|
139
|
-
end
|
140
|
-
|
141
|
-
def [](key)
|
142
|
-
if key.is_a?(Symbol)
|
143
|
-
@config[key].dup
|
144
|
-
elsif key.is_a?(String)
|
145
|
-
@config.dget(key).dup
|
146
|
-
else
|
147
|
-
raise ArgumentError, "Invalid key type '#{key.class}'"
|
148
|
-
end
|
149
|
-
end
|
150
|
-
|
151
|
-
def get(dot_key)
|
152
|
-
@config.dget(dot_key).dup
|
153
|
-
end
|
154
|
-
alias dget get
|
155
|
-
|
156
|
-
def has?(dot_key)
|
157
|
-
!!get(dot_key)
|
158
|
-
end
|
159
|
-
|
160
|
-
def empty?
|
161
|
-
@config.empty?
|
162
|
-
end
|
163
|
-
|
164
|
-
def ci_mode?
|
165
|
-
!!get('ci_mode') || !!(ENV['GITHUB_ACTIONS'] || ENV['CI'])
|
166
|
-
end
|
167
|
-
alias ci? ci_mode?
|
168
|
-
|
169
|
-
def debug_mode?
|
170
|
-
get('log_level') == 'debug'
|
171
|
-
end
|
172
|
-
alias debug? debug_mode?
|
173
|
-
|
174
|
-
def verbose_mode?
|
175
|
-
!!get('verbose')
|
176
|
-
end
|
177
|
-
alias verbose? verbose_mode?
|
178
|
-
|
179
|
-
def quiet_mode?
|
180
|
-
!!get('quiet')
|
181
|
-
end
|
182
|
-
alias quiet? quiet_mode?
|
183
|
-
|
184
|
-
def to_yaml
|
185
|
-
@config.to_yaml
|
186
|
-
end
|
187
|
-
|
188
|
-
def to_json(*args)
|
189
|
-
@config.to_json(*args)
|
190
|
-
end
|
191
|
-
|
192
|
-
private
|
193
|
-
|
194
|
-
attr_reader :options
|
195
|
-
|
196
|
-
def user_config_dir
|
197
|
-
@user_config_dir ||= File.join(Dir.home, '.cem_acpt')
|
198
|
-
end
|
199
|
-
|
200
|
-
def user_config_file
|
201
|
-
@user_config_file ||= File.join(user_config_dir, 'config.yaml')
|
202
|
-
end
|
203
|
-
|
204
|
-
def terraform_dir
|
205
|
-
@terraform_dir ||= File.join(user_config_dir, 'terraform')
|
206
|
-
end
|
207
|
-
|
208
|
-
def module_terraform_checksum_file
|
209
|
-
@module_terraform_checksum_file ||= File.join(user_config_dir, 'terraform_checksum.txt')
|
210
|
-
end
|
211
|
-
|
212
|
-
def module_terraform_checksum
|
213
|
-
@module_terraform_checksum ||= new_module_terraform_checksum
|
214
|
-
end
|
215
|
-
|
216
|
-
def valid_env_var?(env_var)
|
217
|
-
env_var.start_with?('CEM_ACPT_') && ENV[env_var]
|
218
|
-
end
|
219
|
-
|
220
|
-
def env_var_to_dot_key(env_var)
|
221
|
-
env_var.sub('CEM_ACPT_', '').gsub(%r{__}, '.').downcase
|
222
|
-
end
|
223
|
-
|
224
|
-
def add_static_options!(config)
|
225
|
-
config.dset('user_config.dir', user_config_dir)
|
226
|
-
config.dset('user_config.file', user_config_file)
|
227
|
-
config.dset('provisioner', 'terraform')
|
228
|
-
config.dset('terraform.dir', terraform_dir)
|
229
|
-
set_third_party_env_vars!(config)
|
230
|
-
end
|
231
|
-
|
232
|
-
# Certain environment variables are used by other tools like GitHub Actions
|
233
|
-
# This method sets the relative config values for those environment variables.
|
234
|
-
# This is the last step in composing the config, so it will override any
|
235
|
-
# values set by the user.
|
236
|
-
def set_third_party_env_vars!(config)
|
237
|
-
if ENV['RUNNER_DEBUG'] == '1'
|
238
|
-
config.dset('log_level', 'debug')
|
239
|
-
config.dset('verbose', true)
|
240
|
-
end
|
241
|
-
if ENV['GITHUB_ACTIONS'] == 'true' || ENV['CI'] == 'true'
|
242
|
-
config.dset('ci_mode', true)
|
243
|
-
end
|
244
|
-
end
|
245
|
-
|
246
|
-
# Used to source the config during loading of config files.
|
247
|
-
# Because config may not be fully loaded yet, this method
|
248
|
-
# checks the options hash first, then the current config,
|
249
|
-
# then the environment variables for the given key
|
250
|
-
# @param key [String] The key to find in dot notation
|
251
|
-
# @return [Any] The value of the key
|
252
|
-
def find_option(key)
|
253
|
-
return @options.dget(key) if @options.dget(key)
|
254
|
-
return @config.dget(key) if @config.dget(key)
|
255
|
-
ENV.each do |k, v|
|
256
|
-
next unless valid_env_var?(k)
|
257
|
-
|
258
|
-
return v if env_var_to_dot_key(k) == key
|
259
|
-
end
|
260
|
-
nil
|
261
|
-
end
|
262
|
-
|
263
|
-
def init_config!(opts: {}, config_file: nil)
|
264
|
-
# Blank out the config
|
265
|
-
@user_config = {}
|
266
|
-
@config_from_file = {}
|
267
|
-
@options = {}
|
268
|
-
@config = defaults.dup
|
269
|
-
# Set the parameterized defaults
|
270
|
-
config_file = ENV['CEM_ACPT_CONFIG_FILE'] if config_file.nil?
|
271
|
-
@config.dset('config_file', config_file) if config_file
|
272
|
-
@options = opts || {}
|
273
|
-
end
|
274
|
-
|
275
|
-
def add_env_vars!(config)
|
276
|
-
@env_vars = {}
|
277
|
-
# First load known environment variables into their respective config keys
|
278
|
-
# Then load any environment variables that start with CEM_ACPT_<known key> into their respective config keys
|
279
|
-
ENV.each do |env_var, value|
|
280
|
-
next unless valid_env_var?(env_var)
|
281
|
-
|
282
|
-
key = env_var_to_dot_key(env_var) # Convert CEM_ACPT_<key> to <dotkey>
|
283
|
-
next unless KEYS.include?(key.split('.').first.to_sym) # Skip if the key is not a known config key
|
284
|
-
@env_vars[key] = value
|
285
|
-
config.dset(key, value)
|
286
|
-
end
|
287
|
-
end
|
288
|
-
|
289
|
-
def user_config
|
290
|
-
return @user_config unless @user_config.nil? || @user_config.empty?
|
291
|
-
|
292
|
-
@user_config = if user_config_file && File.exist?(user_config_file)
|
293
|
-
load_config_file(user_config_file)
|
294
|
-
else
|
295
|
-
{}
|
296
|
-
end
|
297
|
-
|
298
|
-
@user_config
|
299
|
-
end
|
300
|
-
|
301
|
-
def config_from_file
|
302
|
-
return @config_from_file unless @config_from_file.nil? || @config_from_file.empty?
|
303
|
-
|
304
|
-
conf_file = find_option('config_file')
|
305
|
-
return {} if conf_file.nil? || conf_file.empty?
|
306
|
-
|
307
|
-
unless conf_file
|
308
|
-
warn "Invalid config_file type '#{conf_file.class}'. Must be a String."
|
309
|
-
return {}
|
310
|
-
end
|
311
|
-
|
312
|
-
mod_dir = find_option('module_dir')
|
313
|
-
if mod_dir && File.exist?(File.join(mod_dir, conf_file))
|
314
|
-
@config_from_file = load_config_file(File.join(mod_dir, conf_file))
|
315
|
-
elsif File.exist?(File.expand_path(conf_file))
|
316
|
-
@config_from_file = load_config_file(File.expand_path(conf_file))
|
317
|
-
else
|
318
|
-
err_msg = [
|
319
|
-
"Config file '#{File.expand_path(conf_file)}' does not exist.",
|
320
|
-
]
|
321
|
-
err_msg << "Config file '#{File.join(mod_dir, conf_file)}' does not exist." if mod_dir
|
322
|
-
raise err_msg.join("\n")
|
323
|
-
end
|
324
|
-
|
325
|
-
@config_from_file
|
326
|
-
end
|
327
|
-
|
328
|
-
def load_config_file(config_file)
|
329
|
-
return {} if config_file.nil? || config_file.empty? || !File.exist?(File.expand_path(config_file))
|
330
|
-
|
331
|
-
loaded = load_yaml(config_file)
|
332
|
-
loaded.format!
|
333
|
-
loaded
|
334
|
-
end
|
335
|
-
|
336
|
-
def load_yaml(config_file)
|
337
|
-
if YAML.respond_to?(:safe_load_file) # Ruby 3.0+
|
338
|
-
YAML.safe_load_file(File.expand_path(config_file), permitted_classes: [Regexp])
|
339
|
-
else
|
340
|
-
YAML.safe_load(File.read(File.expand_path(config_file)), permitted_classes: [Regexp])
|
341
|
-
end
|
342
|
-
end
|
343
|
-
|
344
|
-
def validate_config!
|
345
|
-
@config.each do |key, _value|
|
346
|
-
warn "Unknown config key: #{key}" unless KEYS.include?(key)
|
347
|
-
end
|
348
|
-
end
|
349
|
-
|
350
|
-
def create_config_dirs!
|
351
|
-
FileUtils.mkdir_p(user_config_dir)
|
352
|
-
create_terraform_dir!
|
353
|
-
end
|
354
|
-
|
355
|
-
def create_terraform_dir!
|
356
|
-
raise 'Cannot create terraform dir without a user config dir' unless Dir.exist? user_config_dir
|
357
|
-
|
358
|
-
if File.exist?(module_terraform_checksum_file)
|
359
|
-
checksum = File.read(module_terraform_checksum_file).strip
|
360
|
-
return if checksum == module_terraform_checksum
|
361
|
-
end
|
362
|
-
FileUtils.rm_rf(File.join(user_config_dir, 'terraform'))
|
363
|
-
FileUtils.cp_r(File.expand_path(File.join(__dir__, '..', 'terraform')), user_config_dir)
|
364
|
-
@module_terraform_checksum = new_module_terraform_checksum
|
365
|
-
File.write(module_terraform_checksum_file, module_terraform_checksum)
|
366
|
-
end
|
367
|
-
|
368
|
-
def new_module_terraform_checksum
|
369
|
-
sha256 = Digest::SHA256.new
|
370
|
-
files_and_dirs = Dir.glob(File.join(__dir__, '..', 'terraform', '**', '*'))
|
371
|
-
files_and_dirs.each do |file|
|
372
|
-
sha256 << if File.directory?(file)
|
373
|
-
File.basename(file)
|
374
|
-
else
|
375
|
-
File.read(file)
|
376
|
-
end
|
377
|
-
end
|
378
|
-
sha256.hexdigest
|
379
|
-
end
|
5
|
+
module Config
|
6
|
+
require_relative 'config/cem_acpt'
|
7
|
+
require_relative 'config/cem_acpt_image'
|
380
8
|
end
|
381
9
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# This module holds extensions and refinements to Ruby and the Ruby stdlib
|
4
|
-
module CemAcpt::
|
4
|
+
module CemAcpt::CoreExt
|
5
5
|
# Refines the Hash class with some convenience methods.
|
6
6
|
# Must call `using CemAcpt::CoreExtensions::HashExtensions`
|
7
7
|
# before these methods will be available.
|
@@ -15,6 +15,8 @@ module CemAcpt::CoreExtensions
|
|
15
15
|
transform_values! do |value|
|
16
16
|
if value.is_a?(Hash)
|
17
17
|
value.format!
|
18
|
+
elsif value.is_a?(Array)
|
19
|
+
value.map { |v| v.is_a?(Hash) ? v.format! : v }
|
18
20
|
else
|
19
21
|
value
|
20
22
|
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
require 'open3'
|
5
|
+
require_relative '../logging'
|
6
|
+
|
7
|
+
module CemAcpt
|
8
|
+
module ImageBuilder
|
9
|
+
module Exec
|
10
|
+
def self.new_exec(config)
|
11
|
+
executor = config.get('exec') || 'gcloud'
|
12
|
+
case executor
|
13
|
+
when 'gcloud'
|
14
|
+
CemAcpt::ImageBuilder::Exec::Gcloud.new(config)
|
15
|
+
else
|
16
|
+
raise ArgumentError, "Unknown exec #{executor}"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class Gcloud
|
21
|
+
include CemAcpt::Logging
|
22
|
+
|
23
|
+
def initialize(config)
|
24
|
+
@config = config
|
25
|
+
verify_gcloud!
|
26
|
+
end
|
27
|
+
|
28
|
+
# Run a gcloud command
|
29
|
+
# @raise [RuntimeError] if the command fails
|
30
|
+
# @return [Hash] JSON output of the command
|
31
|
+
def run(*command)
|
32
|
+
formatted = format_command(*command)
|
33
|
+
logger.debug('Exec') { "Running command: #{formatted}" }
|
34
|
+
gcloud(formatted)
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def verify_gcloud!
|
40
|
+
return if system('gcloud --version')
|
41
|
+
|
42
|
+
raise 'gcloud not found in PATH'
|
43
|
+
end
|
44
|
+
|
45
|
+
def format_command(*command)
|
46
|
+
command.unshift('gcloud')
|
47
|
+
command.push('--format=json')
|
48
|
+
command.join(' ')
|
49
|
+
end
|
50
|
+
|
51
|
+
def gcloud(formatted_command)
|
52
|
+
stdout, stderr, status = Open3.capture3(formatted_command)
|
53
|
+
raise "gcloud command failed: #{stderr}" unless status.success?
|
54
|
+
|
55
|
+
JSON.parse(stdout)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CemAcpt
|
4
|
+
module ImageBuilder
|
5
|
+
# Holds methods for determining the provision commands for each supported OS / Puppet version
|
6
|
+
module ProvisionCommands
|
7
|
+
class EnterpriseLinuxFamily
|
8
|
+
def initialize(config, image_name, base_image, os_major_version, puppet_version)
|
9
|
+
@config = config
|
10
|
+
@image_name = image_name
|
11
|
+
@base_image = base_image
|
12
|
+
@os_major_version = os_major_version
|
13
|
+
@puppet_version = puppet_version
|
14
|
+
end
|
15
|
+
|
16
|
+
def default_provision_commands
|
17
|
+
[enable_puppet_repository_command, install_puppet_agent_command]
|
18
|
+
end
|
19
|
+
|
20
|
+
def provision_commands
|
21
|
+
commands_from_config = @config.get("images.#{@image_name}.provision_commands") || []
|
22
|
+
(default_provision_commands + commands_from_config).compact
|
23
|
+
end
|
24
|
+
alias to_a provision_commands
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def package_manager
|
29
|
+
@package_manager ||= @os_major_version.to_i >= 8 ? 'dnf' : 'yum'
|
30
|
+
end
|
31
|
+
|
32
|
+
def package_manager_utils
|
33
|
+
@package_manager_utils ||= @os_major_version.to_i >= 8 ? 'dnf-utils' : 'yum-utils'
|
34
|
+
end
|
35
|
+
|
36
|
+
def upgrade_packages_command
|
37
|
+
"sudo #{package_manager} upgrade -y"
|
38
|
+
end
|
39
|
+
|
40
|
+
def install_package_manager_utils_command
|
41
|
+
"sudo #{package_manager} install -y #{package_manager_utils}"
|
42
|
+
end
|
43
|
+
|
44
|
+
def puppet_platform_repository_url
|
45
|
+
"https://yum.puppet.com/puppet#{@puppet_version}-release-el-#{@os_major_version}.noarch.rpm"
|
46
|
+
end
|
47
|
+
|
48
|
+
def enable_puppet_repository_command
|
49
|
+
"sudo rpm -U #{puppet_platform_repository_url}"
|
50
|
+
end
|
51
|
+
|
52
|
+
def install_puppet_agent_command
|
53
|
+
"sudo #{package_manager} install -y puppet-agent"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
class WindowsFamily
|
58
|
+
def initialize(config, image_name, base_image, os_major_version, puppet_version)
|
59
|
+
@config = config
|
60
|
+
@image_name = image_name
|
61
|
+
@base_image = base_image
|
62
|
+
@os_major_version = os_major_version
|
63
|
+
@puppet_version = puppet_version
|
64
|
+
end
|
65
|
+
|
66
|
+
def default_provision_commands
|
67
|
+
[]
|
68
|
+
end
|
69
|
+
|
70
|
+
def provision_commands
|
71
|
+
commands_from_config = @config.get("images.#{@image_name}.provision_commands") || []
|
72
|
+
(default_provision_commands + commands_from_config).compact
|
73
|
+
end
|
74
|
+
alias to_a provision_commands
|
75
|
+
end
|
76
|
+
|
77
|
+
class << self
|
78
|
+
# Map of OS to class that holds the provision commands for that OS
|
79
|
+
OS_CLASS_MAP = {
|
80
|
+
'centos' => 'EnterpriseLinuxFamily',
|
81
|
+
'rhel' => 'EnterpriseLinuxFamily',
|
82
|
+
'alma' => 'EnterpriseLinuxFamily',
|
83
|
+
'windows' => 'WindowsFamily',
|
84
|
+
}.freeze
|
85
|
+
|
86
|
+
def provision_commands(config, image_name:, base_image:, os:, os_major_version:, puppet_version:)
|
87
|
+
os_major_version = os_major_version.to_s
|
88
|
+
puppet_version = puppet_version.to_s
|
89
|
+
cmd_klass = OS_CLASS_MAP[os.to_s]
|
90
|
+
raise "Unsupported OS: #{os}" unless cmd_klass
|
91
|
+
|
92
|
+
const_get(cmd_klass).new(config, image_name, base_image, os_major_version, puppet_version).provision_commands
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|