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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f2539a6c12df56b28e4ede4dce5da549496fd1a8ebe7076da780b410bf7d5c58
|
4
|
+
data.tar.gz: 5fe4e01b0c307583fbe204dd893f05dce4361ab60c8d84ea9c5b5f11e806621f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 71d1fe8bf2935bded271ddf40fbb274d7c9748598dff84ff2ca5dd356c8ceb79137093ebefc33c8ee9270400b3afe2c4736578962af180a273dd9127600f542a
|
7
|
+
data.tar.gz: 1d09872c69652ac64e262c06474cc3fb08d0b37b6c548127fea91cd81f3b8e7fa3a79211adac934ae087f40113e5388f328cef17809c04e47f82d0d5fda0918f
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
CemAcpt is an acceptance testing library / command line application for running acceptance tests for the CEM modules.
|
4
4
|
|
5
|
-
CemAcpt uses [Terraform](#terraform) for provisioning test nodes, and [Goss](#goss) to execute acceptance tests.
|
5
|
+
CemAcpt uses [Terraform](#terraform) for provisioning test nodes, and [Goss](#goss) to execute acceptance tests. For provisioning nodes in GCP, CemAcpt also uses the [gcloud CLI](https://cloud.google.com/cli).
|
6
6
|
|
7
7
|
## Installation
|
8
8
|
|
@@ -16,7 +16,7 @@ gem install cem_acpt
|
|
16
16
|
|
17
17
|
### Quickstart
|
18
18
|
|
19
|
-
Make sure Terraform
|
19
|
+
Make sure Terraform and the gcloud CLI are installed and in your PATH. Instructions for installing Terraform can be found [here](https://learn.hashicorp.com/tutorials/terraform/install-cli) and instructions for installing the gcloud CLI can be found [here](https://cloud.google.com/sdk/docs/install).Then, navigate to the root of the `cem_linux` module and run the following command:
|
20
20
|
|
21
21
|
```bash
|
22
22
|
cem_acpt --config ./cem_acpt_config.yaml
|
@@ -26,6 +26,7 @@ cem_acpt --config ./cem_acpt_config.yaml
|
|
26
26
|
|
27
27
|
```bash
|
28
28
|
cem_acpt -h | --help
|
29
|
+
cem_acpt_image -h | --help
|
29
30
|
```
|
30
31
|
|
31
32
|
> If you do not delete Gemfile.lock before running `bundle install`, you may encounter dependency version errors. Just delete Gemfile.lock and run `bundle install` again to get past these.
|
@@ -209,3 +210,41 @@ Much like `name_pattern_vars`, specifying the `image_name_builder` top-level key
|
|
209
210
|
- Once node is provisioned, make HTTP get requests to the Goss server endpoints to run the tests
|
210
211
|
- Destroy the test nodes
|
211
212
|
- Report the results of the tests
|
213
|
+
|
214
|
+
## Generating acceptance test node images
|
215
|
+
|
216
|
+
CemAcpt provides the command `cem_acpt_image` that allows you to generate new acceptance test node images that are then used with the `cem_acpt` command. This is useful for when you want to test a new version of Puppet or a new OS.
|
217
|
+
|
218
|
+
### Configuring the image builder
|
219
|
+
|
220
|
+
Images are built according to the entries in the `images` config key. Each entry in the `images` hash should follow the following format:
|
221
|
+
|
222
|
+
```yaml
|
223
|
+
images:
|
224
|
+
<image family name>:
|
225
|
+
os: <os key string (rhel, alma, etc.)>
|
226
|
+
os_major_version: <os major version integer>
|
227
|
+
puppet_version: <puppet major version integer>
|
228
|
+
base_image: <base image name / family string>
|
229
|
+
provision_commands: <array of commands to run on the image>
|
230
|
+
```
|
231
|
+
|
232
|
+
For example, the following config would build an image for Puppet 7 on RHEL 8:
|
233
|
+
|
234
|
+
```yaml
|
235
|
+
images:
|
236
|
+
cem-acpt-rhel-8-puppet7-firewalld:
|
237
|
+
os: rhel
|
238
|
+
os_major_version: 8
|
239
|
+
puppet_version: 7
|
240
|
+
base_image: 'rhel-cloud/rhel-8'
|
241
|
+
provision_commands:
|
242
|
+
- 'systemctl enable firewalld'
|
243
|
+
- 'systemctl start firewalld'
|
244
|
+
- 'firewall-cmd --permanent --add-service=ssh'
|
245
|
+
- 'firewall-cmd --reload'
|
246
|
+
- 'useradd testuser1'
|
247
|
+
- "echo 'testuser1:P@s5W-rd$' | chpasswd"
|
248
|
+
```
|
249
|
+
|
250
|
+
See [sample_config.yaml](sample_config.yaml) for a more complete example.
|
data/exe/cem_acpt
CHANGED
@@ -1,118 +1,15 @@
|
|
1
|
-
#!/usr/bin/env
|
1
|
+
#!/usr/bin/env ruby
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
-
require 'deep_merge'
|
5
|
-
require 'json'
|
6
|
-
require 'optparse'
|
7
|
-
require 'yaml'
|
8
4
|
require 'cem_acpt'
|
5
|
+
require 'cem_acpt/cli'
|
9
6
|
|
10
|
-
options =
|
11
|
-
parser = OptionParser.new do |opts|
|
12
|
-
opts.banner = 'Usage: cem_acpt [options]'
|
13
|
-
|
14
|
-
opts.on('-h', '--help', 'Show this help message') do
|
15
|
-
puts opts
|
16
|
-
exit 0
|
17
|
-
end
|
18
|
-
|
19
|
-
opts.on('-a', '--only-actions ACTIONS', 'Set actions. Example: -a "acpt,noop"') do |a|
|
20
|
-
options[:actions] ||= {}
|
21
|
-
options[:actions][:only] = a.split(',')
|
22
|
-
end
|
23
|
-
|
24
|
-
opts.on('-A', '--except-actions ACTIONS', 'Set excluded actions. Example: -A "noop,idempotent"') do |a|
|
25
|
-
options[:actions] ||= {}
|
26
|
-
options[:actions][:except] = a.split(',')
|
27
|
-
end
|
28
|
-
|
29
|
-
opts.on('-t', '--tests TESTS', 'Set tests. Example: -t "test1,test2"') do |t|
|
30
|
-
options[:tests] = t.split(',')
|
31
|
-
end
|
32
|
-
|
33
|
-
opts.on('-D', '--debug', 'Enable debug logging') do
|
34
|
-
options[:log_level] = 'debug'
|
35
|
-
end
|
36
|
-
|
37
|
-
opts.on('-L', '--log-file FILE', 'Log to FILE') do |file|
|
38
|
-
options[:log_file] = file
|
39
|
-
end
|
40
|
-
|
41
|
-
opts.on('-O', '--options OPTS', 'Set options. Example: -P "param1=value1,param2=value2"') do |o|
|
42
|
-
params = o.split(',').map { |s| s.split('=') }.to_h
|
43
|
-
params.transform_keys(&:to_sym).each do |k, v|
|
44
|
-
options[k] = v
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
|
-
opts.on('-p', '--platform PLATFORM', 'Set platform. Example: -p "gcp"') do |p|
|
49
|
-
options[:platform] = p
|
50
|
-
end
|
51
|
-
|
52
|
-
opts.on('-m', '--module-dir DIR', 'Set module directory. Example: -m "/tmp/module"') do |m|
|
53
|
-
options[:module_dir] = m
|
54
|
-
end
|
55
|
-
|
56
|
-
opts.on('-c', '--config-file FILE', 'Set config file. Example: -c "/tmp/config.yaml"') do |c|
|
57
|
-
options[:config_file] = c
|
58
|
-
end
|
59
|
-
|
60
|
-
opts.on('-I', '--CI', 'Run in CI mode') do
|
61
|
-
options[:ci_mode] = true
|
62
|
-
options[:log_format] = 'github_action'
|
63
|
-
end
|
64
|
-
|
65
|
-
opts.on('-E', '--no-destroy-nodes', 'Do not destroy nodes') do
|
66
|
-
options[:no_destroy_nodes] = true
|
67
|
-
end
|
68
|
-
|
69
|
-
opts.on('-q', '--quiet', 'Do not log to stdout') do
|
70
|
-
options[:quiet] = true
|
71
|
-
end
|
72
|
-
|
73
|
-
opts.on('-v', '--verbose', 'Enables verbose logging mode') do
|
74
|
-
options[:verbose] = true
|
75
|
-
end
|
76
|
-
|
77
|
-
opts.on('-S', '--no-epehemeral-ssh-key', 'Do not generate an ephemeral SSH key for test suites') do
|
78
|
-
options[:no_ephemeral_ssh_key] = true
|
79
|
-
end
|
80
|
-
|
81
|
-
opts.on('-V', '--version', 'Show the cem_acpt version') do
|
82
|
-
puts CemAcpt.version(as_str: true)
|
83
|
-
exit 0
|
84
|
-
end
|
85
|
-
|
86
|
-
opts.on('-Y', '--print-yaml-config', 'Loads and prints the config as YAML. Other specified options will be added to the config.') do
|
87
|
-
options[:print_yaml_config] = true
|
88
|
-
end
|
89
|
-
|
90
|
-
opts.on('-X', '--explain-config', 'Loads and prints the config explanation. Other specified options will be added to the config.') do
|
91
|
-
options[:explain_config] = true
|
92
|
-
end
|
93
|
-
|
94
|
-
# NOT IMPLEMENTED
|
95
|
-
# opts.on('-R', '--reuse-nodes', 'Reuses test nodes if compatible node inventory exists') do
|
96
|
-
# options[:reuse_nodes] = true
|
97
|
-
# end
|
98
|
-
end
|
99
|
-
|
100
|
-
parser.parse!
|
101
|
-
if options[:print_yaml_config]
|
102
|
-
options.delete(:print_yaml_config)
|
103
|
-
puts CemAcpt.print_config(options, format: :yaml)
|
104
|
-
exit 0
|
105
|
-
end
|
106
|
-
if options[:explain_config]
|
107
|
-
options.delete(:explain_config)
|
108
|
-
puts CemAcpt.print_config(options, format: :explain)
|
109
|
-
exit 0
|
110
|
-
end
|
7
|
+
command, options = CemAcpt::CLI.parse_opts_for(:cem_acpt)
|
111
8
|
# Set CLI defaults
|
112
9
|
options[:module_dir] = Dir.pwd unless options[:module_dir]
|
113
10
|
if (options[:log_level] == 'debug' || options[:verbose]) && !options[:quiet]
|
114
|
-
puts '
|
115
|
-
puts "
|
11
|
+
puts '############################### RUNNING ###############################'
|
12
|
+
puts "Command #{File.basename(__FILE__)}:#{command} using options from command line: #{options}"
|
116
13
|
puts '#######################################################################'
|
117
14
|
end
|
118
|
-
CemAcpt.run(options)
|
15
|
+
CemAcpt.run(command, File.basename(__FILE__).to_sym, options)
|
data/exe/cem_acpt_image
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'cem_acpt'
|
5
|
+
require 'cem_acpt/cli'
|
6
|
+
|
7
|
+
command, options = CemAcpt::Cli.parse_opts_for(:cem_acpt_image)
|
8
|
+
# Set CLI defaults
|
9
|
+
options[:module_dir] = Dir.pwd unless options[:module_dir]
|
10
|
+
if (options[:log_level] == 'debug' || options[:verbose]) && !options[:quiet]
|
11
|
+
puts '############################### RUNNING ###############################'
|
12
|
+
puts "Command #{File.basename(__FILE__)}:#{command} using options from command line: #{options}"
|
13
|
+
puts '#######################################################################'
|
14
|
+
end
|
15
|
+
CemAcpt.run(command, File.basename(__FILE__).to_sym, options)
|
data/lib/cem_acpt/cli.rb
ADDED
@@ -0,0 +1,115 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'optparse'
|
4
|
+
|
5
|
+
module CemAcpt
|
6
|
+
module Cli
|
7
|
+
def self.parse_opts_for(command)
|
8
|
+
cmd = command
|
9
|
+
options = {}
|
10
|
+
parser = OptionParser.new do |opts|
|
11
|
+
opts.banner = "Usage: #{command} [options]"
|
12
|
+
|
13
|
+
opts.on('-h', '--help', 'Show this help message') do
|
14
|
+
puts opts
|
15
|
+
exit 0
|
16
|
+
end
|
17
|
+
|
18
|
+
# Command specific options
|
19
|
+
case command
|
20
|
+
when :cem_acpt
|
21
|
+
opts.on('-m', '--module-dir DIR', 'Set module directory. Example: -m "/tmp/module"') do |m|
|
22
|
+
options[:module_dir] = m
|
23
|
+
end
|
24
|
+
|
25
|
+
opts.on('-a', '--only-actions ACTIONS', 'Set actions. Example: -a "acpt,noop"') do |a|
|
26
|
+
options[:actions] ||= {}
|
27
|
+
options[:actions][:only] = a.split(',')
|
28
|
+
end
|
29
|
+
|
30
|
+
opts.on('-A', '--except-actions ACTIONS', 'Set excluded actions. Example: -A "noop,idempotent"') do |a|
|
31
|
+
options[:actions] ||= {}
|
32
|
+
options[:actions][:except] = a.split(',')
|
33
|
+
end
|
34
|
+
|
35
|
+
opts.on('-t', '--tests TESTS', 'Set tests. Example: -t "test1,test2"') do |t|
|
36
|
+
options[:tests] = t.split(',')
|
37
|
+
end
|
38
|
+
when :cem_acpt_image
|
39
|
+
opts.on('-U', '--no-linux', 'Do not build Linux images') do
|
40
|
+
options[:cem_acpt_image] ||= {}
|
41
|
+
options[:cem_acpt_image][:no_linux] = true
|
42
|
+
end
|
43
|
+
|
44
|
+
opts.on('-W', '--no-windows', 'Do not build Windows images') do
|
45
|
+
options[:cem_acpt_image] ||= {}
|
46
|
+
options[:cem_acpt_image][:no_windows] = true
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
opts.on('-D', '--debug', 'Enable debug logging') do
|
51
|
+
options[:log_level] = 'debug'
|
52
|
+
end
|
53
|
+
|
54
|
+
opts.on('-L', '--log-file FILE', 'Log to FILE') do |file|
|
55
|
+
options[:log_file] = file
|
56
|
+
end
|
57
|
+
|
58
|
+
opts.on('-O', '--options OPTS', 'Set options. Example: -P "param1=value1,param2=value2"') do |o|
|
59
|
+
params = o.split(',').map { |s| s.split('=') }.to_h
|
60
|
+
params.transform_keys(&:to_sym).each do |k, v|
|
61
|
+
options[k] = v
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
opts.on('-p', '--platform PLATFORM', 'Set platform. Example: -p "gcp"') do |p|
|
66
|
+
options[:platform] = p
|
67
|
+
end
|
68
|
+
|
69
|
+
opts.on('-c', '--config-file FILE', 'Set config file. Example: -c "/tmp/config.yaml"') do |c|
|
70
|
+
options[:config_file] = c
|
71
|
+
end
|
72
|
+
|
73
|
+
opts.on('-I', '--CI', 'Run in CI mode (modifies logging for Github Actions output)') do
|
74
|
+
options[:ci_mode] = true
|
75
|
+
options[:log_format] = 'github_action'
|
76
|
+
end
|
77
|
+
|
78
|
+
opts.on('-E', '--no-destroy-nodes', 'Do not destroy nodes') do
|
79
|
+
options[:no_destroy_nodes] = true
|
80
|
+
end
|
81
|
+
|
82
|
+
opts.on('-q', '--quiet', 'Do not log to stdout') do
|
83
|
+
options[:quiet] = true
|
84
|
+
end
|
85
|
+
|
86
|
+
opts.on('-v', '--verbose', 'Enables verbose logging mode') do
|
87
|
+
options[:verbose] = true
|
88
|
+
end
|
89
|
+
|
90
|
+
opts.on('-S', '--no-epehemeral-ssh-key', 'Do not generate an ephemeral SSH key for test suites') do
|
91
|
+
options[:no_ephemeral_ssh_key] = true
|
92
|
+
end
|
93
|
+
|
94
|
+
opts.on('-V', '--version', 'Show the cem_acpt version') do
|
95
|
+
cmd = :version
|
96
|
+
end
|
97
|
+
|
98
|
+
opts.on('-Y', '--print-yaml-config', 'Loads and prints the config as YAML. Other specified options will be added to the config.') do
|
99
|
+
cmd = :print_yaml_config
|
100
|
+
end
|
101
|
+
|
102
|
+
opts.on('-X', '--explain-config', 'Loads and prints the config explanation. Other specified options will be added to the config.') do
|
103
|
+
cmd = :print_explain_config
|
104
|
+
end
|
105
|
+
|
106
|
+
# NOT IMPLEMENTED
|
107
|
+
# opts.on('-R', '--reuse-nodes', 'Reuses test nodes if compatible node inventory exists') do
|
108
|
+
# options[:reuse_nodes] = true
|
109
|
+
# end
|
110
|
+
end
|
111
|
+
parser.parse!
|
112
|
+
[cmd, options]
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
@@ -0,0 +1,347 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'digest'
|
4
|
+
require 'fileutils'
|
5
|
+
require 'json'
|
6
|
+
require 'yaml'
|
7
|
+
require_relative '../core_ext'
|
8
|
+
require_relative '../version'
|
9
|
+
|
10
|
+
# Module contains the CemAcptConfig::Base class which serves as a base class for different configs.
|
11
|
+
module CemAcpt
|
12
|
+
module Config
|
13
|
+
using CemAcpt::CoreExt::ExtendedHash
|
14
|
+
|
15
|
+
# Base class for other config classes
|
16
|
+
# Child classes should implement the following methods:
|
17
|
+
# - valid_keys - provide an array of valid top-level keys for the config as symbols
|
18
|
+
# - defaults - provide a hash of default values for the config
|
19
|
+
# - env_var_prefix - provide a string to prefix environment variables with (defaults to 'CEM_ACPT').
|
20
|
+
# This will be converted to uppercase, have all non-alphanumeric characters replaced with
|
21
|
+
# underscores, and be joined with the key name with an underscore to form the environment
|
22
|
+
# variable name.
|
23
|
+
# However, they can override any of the methods in this class.
|
24
|
+
class Base
|
25
|
+
attr_reader :config, :env_vars
|
26
|
+
|
27
|
+
def initialize(opts: {}, config_file: nil, load_user_config: true)
|
28
|
+
@load_user_config = load_user_config
|
29
|
+
load(opts: opts, config_file: config_file)
|
30
|
+
end
|
31
|
+
|
32
|
+
# @return [String] The prefix for environment variables
|
33
|
+
def env_var_prefix
|
34
|
+
'CEM_ACPT'
|
35
|
+
end
|
36
|
+
|
37
|
+
# @return [Array<Symbol>] Valid top-level keys for the config
|
38
|
+
def valid_keys
|
39
|
+
[]
|
40
|
+
end
|
41
|
+
|
42
|
+
# @return [Hash] The default configuration
|
43
|
+
def defaults
|
44
|
+
{}
|
45
|
+
end
|
46
|
+
|
47
|
+
# Load the configuration from the environment variables, config file, and opts
|
48
|
+
# The order of precedence is:
|
49
|
+
# 1. environment variables
|
50
|
+
# 2. user config file (config.yaml in user_config_dir)
|
51
|
+
# 3. specified config file (if it exists)
|
52
|
+
# 4. opts
|
53
|
+
# 5. static options (set in this class)
|
54
|
+
# @param opts [Hash] The options to load
|
55
|
+
# @param config_file [String] The config file to load
|
56
|
+
# @return [self] This object with the config loaded
|
57
|
+
def load(opts: {}, config_file: nil)
|
58
|
+
create_config_dirs!
|
59
|
+
init_config!(opts: opts, config_file: config_file)
|
60
|
+
add_env_vars!(@config)
|
61
|
+
@config.merge!(user_config) if user_config && @load_user_config
|
62
|
+
@config.merge!(config_from_file) if config_from_file
|
63
|
+
@config.merge!(@options) if @options
|
64
|
+
add_static_options!(@config)
|
65
|
+
@config.format! # Symbolize keys of all hashes
|
66
|
+
validate_config!
|
67
|
+
# Freeze the config so it can't be modified
|
68
|
+
# This helps with thread safety and deterministic behavior
|
69
|
+
@config.freeze
|
70
|
+
self
|
71
|
+
end
|
72
|
+
alias to_h config
|
73
|
+
|
74
|
+
# Returns a string representation of how the config was loaded
|
75
|
+
def explain
|
76
|
+
explanation = {}
|
77
|
+
%i[defaults env_vars user_config config_from_file options].each do |source|
|
78
|
+
source_vals = send(source).dup
|
79
|
+
next if source_vals.nil? || source_vals.empty?
|
80
|
+
|
81
|
+
# The loop below will overwrite the value of explanation[key] if the same key is found in multiple sources
|
82
|
+
# This is intentional, as the last source to set the value is the one that should be used
|
83
|
+
source_vals.each do |key, value|
|
84
|
+
explanation[key] = source if @config.dget(key.to_s) == value
|
85
|
+
end
|
86
|
+
end
|
87
|
+
explained = explanation.each_with_object([]) do |(key, value), ary|
|
88
|
+
ary << "Key '#{key}' from source '#{value}'"
|
89
|
+
end
|
90
|
+
explained.join("\n")
|
91
|
+
end
|
92
|
+
|
93
|
+
def [](key)
|
94
|
+
if key.is_a?(Symbol)
|
95
|
+
@config[key].dup
|
96
|
+
elsif key.is_a?(String)
|
97
|
+
@config.dget(key).dup
|
98
|
+
else
|
99
|
+
raise ArgumentError, "Invalid key type '#{key.class}'"
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def get(dot_key)
|
104
|
+
@config.dget(dot_key).dup
|
105
|
+
end
|
106
|
+
alias dget get
|
107
|
+
|
108
|
+
def has?(dot_key)
|
109
|
+
!!get(dot_key)
|
110
|
+
end
|
111
|
+
|
112
|
+
def empty?
|
113
|
+
@config.empty?
|
114
|
+
end
|
115
|
+
|
116
|
+
def ci_mode?
|
117
|
+
!!get('ci_mode') || !!(ENV['GITHUB_ACTIONS'] || ENV['CI'])
|
118
|
+
end
|
119
|
+
alias ci? ci_mode?
|
120
|
+
|
121
|
+
def debug_mode?
|
122
|
+
get('log_level') == 'debug'
|
123
|
+
end
|
124
|
+
alias debug? debug_mode?
|
125
|
+
|
126
|
+
def verbose_mode?
|
127
|
+
!!get('verbose')
|
128
|
+
end
|
129
|
+
alias verbose? verbose_mode?
|
130
|
+
|
131
|
+
def quiet_mode?
|
132
|
+
!!get('quiet')
|
133
|
+
end
|
134
|
+
alias quiet? quiet_mode?
|
135
|
+
|
136
|
+
def to_yaml
|
137
|
+
@config.to_yaml
|
138
|
+
end
|
139
|
+
|
140
|
+
def to_json(*args)
|
141
|
+
@config.to_json(*args)
|
142
|
+
end
|
143
|
+
|
144
|
+
private
|
145
|
+
|
146
|
+
attr_reader :options
|
147
|
+
|
148
|
+
def user_config_dir
|
149
|
+
@user_config_dir ||= File.join(Dir.home, '.cem_acpt')
|
150
|
+
end
|
151
|
+
|
152
|
+
def user_config_file
|
153
|
+
@user_config_file ||= File.join(user_config_dir, 'config.yaml')
|
154
|
+
end
|
155
|
+
|
156
|
+
def terraform_dir
|
157
|
+
@terraform_dir ||= File.join(user_config_dir, 'terraform')
|
158
|
+
end
|
159
|
+
|
160
|
+
def module_terraform_checksum_file
|
161
|
+
@module_terraform_checksum_file ||= File.join(user_config_dir, 'terraform_checksum.txt')
|
162
|
+
end
|
163
|
+
|
164
|
+
def module_terraform_checksum
|
165
|
+
@module_terraform_checksum ||= new_module_terraform_checksum
|
166
|
+
end
|
167
|
+
|
168
|
+
def valid_env_var?(env_var)
|
169
|
+
env_var.start_with?('CEM_ACPT_') && ENV[env_var]
|
170
|
+
end
|
171
|
+
|
172
|
+
def env_var_to_dot_key(env_var)
|
173
|
+
env_var.sub('CEM_ACPT_', '').gsub(%r{__}, '.').downcase
|
174
|
+
end
|
175
|
+
|
176
|
+
def add_static_options!(config)
|
177
|
+
config.dset('user_config.dir', user_config_dir)
|
178
|
+
config.dset('user_config.file', user_config_file)
|
179
|
+
config.dset('provisioner', 'terraform')
|
180
|
+
config.dset('terraform.dir', terraform_dir)
|
181
|
+
set_third_party_env_vars!(config)
|
182
|
+
end
|
183
|
+
|
184
|
+
# Certain environment variables are used by other tools like GitHub Actions
|
185
|
+
# This method sets the relative config values for those environment variables.
|
186
|
+
# This is the last step in composing the config, so it will override any
|
187
|
+
# values set by the user.
|
188
|
+
def set_third_party_env_vars!(config)
|
189
|
+
if ENV['RUNNER_DEBUG'] == '1'
|
190
|
+
config.dset('log_level', 'debug')
|
191
|
+
config.dset('verbose', true)
|
192
|
+
end
|
193
|
+
if ENV['GITHUB_ACTIONS'] == 'true' || ENV['CI'] == 'true'
|
194
|
+
config.dset('ci_mode', true)
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
# Used to source the config during loading of config files.
|
199
|
+
# Because config may not be fully loaded yet, this method
|
200
|
+
# checks the options hash first, then the current config,
|
201
|
+
# then the environment variables for the given key
|
202
|
+
# @param key [String] The key to find in dot notation
|
203
|
+
# @return [Any] The value of the key
|
204
|
+
def find_option(key)
|
205
|
+
return @options.dget(key) if @options.dget(key)
|
206
|
+
return @config.dget(key) if @config.dget(key)
|
207
|
+
ENV.each do |k, v|
|
208
|
+
next unless valid_env_var?(k)
|
209
|
+
|
210
|
+
return v if env_var_to_dot_key(k) == key
|
211
|
+
end
|
212
|
+
nil
|
213
|
+
end
|
214
|
+
|
215
|
+
def init_config!(opts: {}, config_file: nil)
|
216
|
+
# Blank out the config
|
217
|
+
@user_config = {}
|
218
|
+
@config_from_file = {}
|
219
|
+
@options = {}
|
220
|
+
@config = defaults.dup
|
221
|
+
# Set the parameterized defaults
|
222
|
+
config_file = ENV["#{env_var_prefix}_CONFIG_FILE"] if config_file.nil?
|
223
|
+
@config.dset('config_file', config_file) if config_file
|
224
|
+
@options = opts || {}
|
225
|
+
end
|
226
|
+
|
227
|
+
def add_env_vars!(config)
|
228
|
+
@env_vars = {}
|
229
|
+
# First load known environment variables into their respective config keys
|
230
|
+
# Then load any environment variables that start with CEM_ACPT_<known key> into their respective config keys
|
231
|
+
ENV.each do |env_var, value|
|
232
|
+
next unless valid_env_var?(env_var)
|
233
|
+
|
234
|
+
key = env_var_to_dot_key(env_var) # Convert CEM_ACPT_<key> to <dotkey>
|
235
|
+
next unless valid_keys.include?(key.split('.').first.to_sym) # Skip if the key is not a known config key
|
236
|
+
@env_vars[key] = value
|
237
|
+
config.dset(key, value)
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
def user_config
|
242
|
+
return @user_config unless @user_config.nil? || @user_config.empty?
|
243
|
+
|
244
|
+
@user_config = if user_config_file && File.exist?(user_config_file)
|
245
|
+
load_config_file(user_config_file)
|
246
|
+
else
|
247
|
+
{}
|
248
|
+
end
|
249
|
+
|
250
|
+
@user_config
|
251
|
+
end
|
252
|
+
|
253
|
+
# def config_from_file
|
254
|
+
# {}
|
255
|
+
# end
|
256
|
+
|
257
|
+
def load_config_file(config_file)
|
258
|
+
return {} if config_file.nil? || config_file.empty? || !File.exist?(File.expand_path(config_file))
|
259
|
+
|
260
|
+
loaded = load_yaml(config_file)
|
261
|
+
loaded.format!
|
262
|
+
loaded
|
263
|
+
end
|
264
|
+
|
265
|
+
def config_from_file
|
266
|
+
return @config_from_file unless @config_from_file.nil? || @config_from_file.empty?
|
267
|
+
|
268
|
+
conf_file = find_option('config_file')
|
269
|
+
return {} if conf_file.nil? || conf_file.empty?
|
270
|
+
|
271
|
+
unless conf_file
|
272
|
+
warn "Invalid config_file type '#{conf_file.class}'. Must be a String."
|
273
|
+
return {}
|
274
|
+
end
|
275
|
+
|
276
|
+
mod_dir = find_option('module_dir')
|
277
|
+
if mod_dir && File.exist?(File.join(mod_dir, conf_file))
|
278
|
+
@config_from_file = load_config_file(File.join(mod_dir, conf_file))
|
279
|
+
elsif File.exist?(File.expand_path(conf_file))
|
280
|
+
@config_from_file = load_config_file(File.expand_path(conf_file))
|
281
|
+
else
|
282
|
+
err_msg = [
|
283
|
+
"Config file '#{File.expand_path(conf_file)}' does not exist.",
|
284
|
+
]
|
285
|
+
err_msg << "Config file '#{File.join(mod_dir, conf_file)}' does not exist." if mod_dir
|
286
|
+
raise err_msg.join("\n")
|
287
|
+
end
|
288
|
+
|
289
|
+
@config_from_file
|
290
|
+
end
|
291
|
+
|
292
|
+
def load_yaml(config_file)
|
293
|
+
if YAML.respond_to?(:safe_load_file) # Ruby 3.0+
|
294
|
+
YAML.safe_load_file(File.expand_path(config_file), permitted_classes: [Regexp])
|
295
|
+
else
|
296
|
+
YAML.safe_load(File.read(File.expand_path(config_file)), permitted_classes: [Regexp])
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
def validate_config!
|
301
|
+
return unless @config && !valid_keys.empty?
|
302
|
+
|
303
|
+
@config.each do |key, _value|
|
304
|
+
warn "Config key '#{key}' is not usable with this command" unless valid_keys.include?(key.to_sym)
|
305
|
+
end
|
306
|
+
end
|
307
|
+
|
308
|
+
def create_config_dirs!
|
309
|
+
FileUtils.mkdir_p(user_config_dir)
|
310
|
+
create_terraform_dir!
|
311
|
+
end
|
312
|
+
|
313
|
+
def create_terraform_dir!
|
314
|
+
raise 'Cannot create terraform dir without a user config dir' unless Dir.exist? user_config_dir
|
315
|
+
|
316
|
+
if File.exist?(module_terraform_checksum_file)
|
317
|
+
checksum = File.read(module_terraform_checksum_file).strip
|
318
|
+
return if checksum == module_terraform_checksum
|
319
|
+
end
|
320
|
+
module_terraform_dir = File.expand_path(File.join(__dir__, '..', '..', 'terraform'))
|
321
|
+
FileUtils.rm_rf(File.join(user_config_dir, 'terraform'))
|
322
|
+
FileUtils.cp_r(module_terraform_dir, user_config_dir)
|
323
|
+
raise 'Failed to copy terraform dir to local config dir' unless Dir.exist?(File.join(user_config_dir, 'terraform'))
|
324
|
+
|
325
|
+
@module_terraform_checksum = new_module_terraform_checksum
|
326
|
+
File.write(module_terraform_checksum_file, module_terraform_checksum)
|
327
|
+
end
|
328
|
+
|
329
|
+
def new_module_terraform_checksum
|
330
|
+
sha256 = Digest::SHA256.new
|
331
|
+
sha256 << ::CemAcpt::VERSION # This ensures the checksum changes when the gem version changes
|
332
|
+
path = File.join(File.expand_path(File.join(__dir__, '..', '..', 'terraform')), '**', '*')
|
333
|
+
files_and_dirs = Dir.glob(path)
|
334
|
+
raise "Failed to find terraform files at path #{path}" if files_and_dirs.empty?
|
335
|
+
|
336
|
+
files_and_dirs.each do |file|
|
337
|
+
sha256 << if File.directory?(file)
|
338
|
+
File.basename(file)
|
339
|
+
else
|
340
|
+
File.read(file)
|
341
|
+
end
|
342
|
+
end
|
343
|
+
sha256.hexdigest
|
344
|
+
end
|
345
|
+
end
|
346
|
+
end
|
347
|
+
end
|