cem_acpt 0.2.6-universal-java-17

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +18 -0
  3. data/.rspec +3 -0
  4. data/CODEOWNERS +1 -0
  5. data/Gemfile +9 -0
  6. data/Gemfile.lock +93 -0
  7. data/README.md +150 -0
  8. data/Rakefile +6 -0
  9. data/bin/console +14 -0
  10. data/bin/setup +8 -0
  11. data/cem_acpt.gemspec +39 -0
  12. data/exe/cem_acpt +84 -0
  13. data/lib/cem_acpt/bootstrap/bootstrapper.rb +206 -0
  14. data/lib/cem_acpt/bootstrap/operating_system/rhel_family.rb +129 -0
  15. data/lib/cem_acpt/bootstrap/operating_system.rb +17 -0
  16. data/lib/cem_acpt/bootstrap.rb +12 -0
  17. data/lib/cem_acpt/context.rb +153 -0
  18. data/lib/cem_acpt/core_extensions.rb +108 -0
  19. data/lib/cem_acpt/image_name_builder.rb +104 -0
  20. data/lib/cem_acpt/logging.rb +351 -0
  21. data/lib/cem_acpt/platform/base/cmd.rb +71 -0
  22. data/lib/cem_acpt/platform/base.rb +78 -0
  23. data/lib/cem_acpt/platform/gcp/cmd.rb +345 -0
  24. data/lib/cem_acpt/platform/gcp/compute.rb +332 -0
  25. data/lib/cem_acpt/platform/gcp.rb +85 -0
  26. data/lib/cem_acpt/platform/vmpooler.rb +24 -0
  27. data/lib/cem_acpt/platform.rb +103 -0
  28. data/lib/cem_acpt/puppet_helpers.rb +39 -0
  29. data/lib/cem_acpt/rspec_utils.rb +242 -0
  30. data/lib/cem_acpt/shared_objects.rb +537 -0
  31. data/lib/cem_acpt/spec_helper_acceptance.rb +184 -0
  32. data/lib/cem_acpt/test_data.rb +146 -0
  33. data/lib/cem_acpt/test_runner/run_handler.rb +187 -0
  34. data/lib/cem_acpt/test_runner/runner.rb +210 -0
  35. data/lib/cem_acpt/test_runner/runner_result.rb +103 -0
  36. data/lib/cem_acpt/test_runner.rb +10 -0
  37. data/lib/cem_acpt/utils.rb +144 -0
  38. data/lib/cem_acpt/version.rb +5 -0
  39. data/lib/cem_acpt.rb +34 -0
  40. data/sample_config.yaml +58 -0
  41. metadata +218 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 0a59b5c56bf7b9c5bf417fc95eb7afe0557b84635098cd5de92e2649d53b4cdd
4
+ data.tar.gz: 2cb8332995cadfd7bcbdc80b728aae8a10d313441e42fbc1cdafdbbe5ca9b697
5
+ SHA512:
6
+ metadata.gz: 6fb1eef1dae934942b004e5b615d847079afa0c887aabf5d3df084c960b73fe03a847c38a17b2019fe43ed90da608f345f9e46454e400c36d6e604a7f136a41c
7
+ data.tar.gz: 2369b28c8d7d3c40692dd4fc6522ad0fbec9e40d4cf33eb1a63c7baada1ec6b28eb87e9a78a79dff8f2474a3767890a02f1c2f5dd848925ac769b9e976ff958e
data/.gitignore ADDED
@@ -0,0 +1,18 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ # rspec failure tracking
11
+ .rspec_status
12
+ test.log
13
+
14
+ # profiling
15
+ profile.txt
16
+
17
+ # local dev
18
+ run_script.rb
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/CODEOWNERS ADDED
@@ -0,0 +1 @@
1
+ * @puppetlabs/abide-team
data/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ source "https://rubygems.org"
2
+
3
+ ruby '2.6.8', engine: 'jruby', engine_version: '>= 9.3.3.0'
4
+
5
+ # Specify your gem's dependencies in cem_acpt.gemspec
6
+ gemspec
7
+
8
+ gem "rake", "~> 12.0"
9
+ gem "rspec", "~> 3.0"
data/Gemfile.lock ADDED
@@ -0,0 +1,93 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ cem_acpt (0.2.6-universal-java-17)
5
+ concurrent-ruby (~> 1.1.9)
6
+ deep_merge (~> 1.2.2)
7
+ ed25519 (>= 1.2, < 2.0)
8
+ net-ssh (~> 7.0.0.beta1)
9
+ puppet-modulebuilder (> 0.0.1)
10
+ rake
11
+ rspec
12
+ serverspec-cem-acpt
13
+
14
+ GEM
15
+ remote: https://rubygems.org/
16
+ specs:
17
+ ast (2.4.2)
18
+ concurrent-ruby (1.1.10)
19
+ deep_merge (1.2.2)
20
+ diff-lcs (1.5.0)
21
+ ed25519 (1.3.0-java)
22
+ minitar (0.9)
23
+ multi_json (1.15.0)
24
+ net-scp (1.2.1)
25
+ net-ssh (>= 2.6.5)
26
+ net-ssh (7.0.1)
27
+ net-telnet (0.1.1)
28
+ parallel (1.22.1)
29
+ parser (3.1.2.0)
30
+ ast (~> 2.4.1)
31
+ pathspec (1.0.0)
32
+ puppet-modulebuilder (0.3.0)
33
+ minitar (~> 0.9)
34
+ pathspec (>= 0.2.1, < 2.0.0)
35
+ rainbow (3.1.1)
36
+ rake (12.3.3)
37
+ regexp_parser (2.5.0)
38
+ rexml (3.2.5)
39
+ rspec (3.11.0)
40
+ rspec-core (~> 3.11.0)
41
+ rspec-expectations (~> 3.11.0)
42
+ rspec-mocks (~> 3.11.0)
43
+ rspec-core (3.11.0)
44
+ rspec-support (~> 3.11.0)
45
+ rspec-expectations (3.11.0)
46
+ diff-lcs (>= 1.2.0, < 2.0)
47
+ rspec-support (~> 3.11.0)
48
+ rspec-its (1.3.0)
49
+ rspec-core (>= 3.0.0)
50
+ rspec-expectations (>= 3.0.0)
51
+ rspec-mocks (3.11.1)
52
+ diff-lcs (>= 1.2.0, < 2.0)
53
+ rspec-support (~> 3.11.0)
54
+ rspec-support (3.11.0)
55
+ rubocop (1.31.0)
56
+ parallel (~> 1.10)
57
+ parser (>= 3.1.0.0)
58
+ rainbow (>= 2.2.2, < 4.0)
59
+ regexp_parser (>= 1.8, < 3.0)
60
+ rexml (>= 3.2.5, < 4.0)
61
+ rubocop-ast (>= 1.18.0, < 2.0)
62
+ ruby-progressbar (~> 1.7)
63
+ unicode-display_width (>= 1.4.0, < 3.0)
64
+ rubocop-ast (1.18.0)
65
+ parser (>= 3.1.1.0)
66
+ ruby-progressbar (1.11.0)
67
+ serverspec-cem-acpt (2.42.0)
68
+ multi_json
69
+ rspec (~> 3.0)
70
+ rspec-its
71
+ specinfra (~> 2.83.1)
72
+ sfl (2.3)
73
+ specinfra (2.83.2)
74
+ net-scp
75
+ net-ssh (>= 2.7)
76
+ net-telnet (= 0.1.1)
77
+ sfl
78
+ unicode-display_width (2.2.0)
79
+
80
+ PLATFORMS
81
+ universal-java-17
82
+
83
+ DEPENDENCIES
84
+ cem_acpt!
85
+ rake (~> 12.0)
86
+ rspec (~> 3.0)
87
+ rubocop
88
+
89
+ RUBY VERSION
90
+ ruby 2.6.8p0 (jruby 9.3.3.0)
91
+
92
+ BUNDLED WITH
93
+ 2.3.15
data/README.md ADDED
@@ -0,0 +1,150 @@
1
+ # CemAcpt
2
+
3
+ CemAcpt is an acceptance testing library / command line application for running acceptance tests against the CEM modules. It is heavily inspired by Puppet Litmus. CemAcpt is fully compatible with Puppet Litmus acceptance tests.
4
+
5
+ CemAcpt was written to facilitate running a single acceptance test file against one or more test nodes concurrently. This is useful when your module supports a lot of different operating systems and modifies low-level components of those operating systems. For CEM, we manage things such as firewall and bootloader configurations which requires us to test against multiple base images of a single operating system (i.e. RHEL 8 with `iptables` and RHEL 8 with `firewalld`). Additionally, we test our module using both Puppet 6 and Puppet 7. As you can see, we now need to test against four test nodes that are all RHEL 8 with slightly different baseline configs. CemAcpt allows us to do so quickly in parallel.
6
+
7
+ Major differences between Puppet Litmus and CemAcpt are:
8
+
9
+ - CemAcpt revolves around mapping a single acceptance test file to one or more test hosts.
10
+ - Test node image names can be dynamically defined by configurable parsing of the acceptance test name.
11
+ - This allows your acceptance test names to define the node it will run against.
12
+ - CemAcpt has it's own CLI for running provisioning of hosts and the test suite and does not use `rake`.
13
+ - CemAcpt runs everything, including provisioning the test node, creating a temporary manifest on the test node, and running the acceptance test on the test node, in parallel. No matter how many test / host combinations there are, running the test suite will only take as long as the longest running single test / host combo.
14
+ - CemAcpt is configurable via a YAML file.
15
+
16
+ ## Concepts
17
+
18
+ CemAcpt is underpinned with a few core concepts. These concepts shape the architecture and functionality of CemAcpt.
19
+
20
+ ### Platforms
21
+
22
+ Platforms are backends that handle provisioning, interacting with, and destroying test nodes. Platforms typically represent something like a cloud provider or hypervisor, but as long as a platform implements the required methods it could do anything.
23
+
24
+ Platforms are created by creating a file in `lib/cem_acpt/platform`. The name of the platform file is significant as it will be capitalized and used as a classname. PLATFORMS MUST implement the methods stipulated by `lib/cem_acpt/platform/base.rb` in a single module called `Platform` because the platform system will dynamically create the platform class from this module. How the methods are implemented is irrelevant as long as they take the correct input and give the correct output.
25
+
26
+ Platforms will be passed all configuration options under the top-level key `node_data`. Node configuration should be generic and should apply to all test nodes regardless of the test being ran against them. For test-specific configurations, use `test data`.
27
+
28
+ See [lib/cem_acpt/platform/gcp.rb](lib/cem_acpt/platform/gcp.rb) for an example of a platform file.
29
+
30
+ ### Tests
31
+
32
+ Each acceptance test should be specified in the config under the top-level key `tests`. These tests SHOULD NOT be paths and should not contain the suffix `_spec.rb`. For example, if you have an acceptance test `spec/acceptance/our_acceptance_test_spec.rb`, the tests config would look like this:
33
+
34
+ ```yaml
35
+ tests:
36
+ - our_acceptance_test
37
+ ```
38
+
39
+ Aside from ways of manipulating test data outlined below, tests are typically matched one-to-one with a test node.
40
+
41
+ ### Test data
42
+
43
+ Test data is a collection of data about a specific test that persists through the entire acceptance test suite lifecycle. Specifically, test data is implemented as an array of hashes with each hash representing a single run through the acceptance test suite lifecycle. What this means is that each item in the test data array is couple with a single test node that is provisioned and a single test run against that node. Test data is used to store values that are test-specific, as opposed to node data which is generic.
44
+
45
+ Test data can be configured using the top-level key `test_data`. There are several supported options for manipulating test data:
46
+
47
+ #### for_each
48
+
49
+ When specified, should be a hash of `key -> Array` pairs. For each of these pairs, a copy of the test data is made for each item in the array, with that item becoming the value of a variable `key`.
50
+
51
+ Example:
52
+
53
+ ```yaml
54
+ test_data:
55
+ for_each:
56
+ collection:
57
+ - puppet6
58
+ - puppet7
59
+
60
+ tests:
61
+ - our_acceptance_test
62
+ ```
63
+
64
+ In the above config, instead of test data consisting of a single hash, it will have two hashes that have the `collection` variable set to `puppet6` and `puppet7` respectively. This means that there will be two test nodes provisioned and two runs of the test `our_acceptance_test`, one run against each node.
65
+
66
+ #### vars
67
+
68
+ Aribitrary key-value pairs that are injected into the test data hashes. Think of these as constants.
69
+
70
+ #### name_pattern_vars
71
+
72
+ A Ruby regex pattern that is matched against the test name with the intent of creating variables from named capture groups. See [sample_config.yaml](sample_config.yaml) for an example.
73
+
74
+ #### vars_post_processing
75
+
76
+ Rules that allow for processing variables after all other test data rules are ran. See [sample_config.yaml](sample_config.yaml) for an example.
77
+
78
+ ### Image name builder
79
+
80
+ Much like `name_pattern_vars`, specifying the `image_name_builder` top-level key in the config allows you to manipulate acceptance test names to create a special test data variable called `image_name`. This is helpful for when you have multiple platform base images and want to use the correct image with the correct test. See [sample_config.yaml](sample_config.yaml) for an example.
81
+
82
+ ## Installation
83
+
84
+ ### Installing from RubyGems
85
+
86
+ To install the gem locally to use as a CLI tool:
87
+
88
+ `gem install cem_acpt --prerelease`
89
+
90
+ ### Installing into a Puppet module
91
+
92
+ Add this line to your module's Gemfile (usually under `group :development`):
93
+
94
+ ```ruby
95
+ gem 'cem_acpt', require: false
96
+ ```
97
+
98
+ Then, add the following to your `spec_helper_acceptance.rb` (replacing the Puppet Litmus configuration lines):
99
+
100
+ ```ruby
101
+ require 'cem_acpt'
102
+
103
+ CemAcpt.configure_spec_helper!
104
+ ```
105
+
106
+ To use CemAcpt in your acceptance tests, you must call the method `initialize_test_environment!` in your acceptance test's `describe` block before doing anything else:
107
+
108
+ ```ruby
109
+ describe 'cem_linux CIS Level 1' do
110
+ initialize_test_environment!
111
+ ...
112
+ ```
113
+
114
+ ## Usage
115
+
116
+ ### RSpec methods
117
+
118
+ CemAcpt enables you to use all ServerSpec methods in your acceptance tests, and adds three new methods that can be used (these methods are used the same as in Litmus):
119
+
120
+ - `apply_manifest(manifest, opts = {})`: Applies a Puppet manifest given as a string.
121
+ - `idempotent_apply(manifest, opts = {})`: Applies a Puppet manifest given as a string twice and fails if the second apply reports changes.
122
+ - `run_shell(command, opts = {})`: Runs a shell command against the test node.
123
+
124
+ ### Config file
125
+
126
+ CemAcpt can use a config file (default path is `./cem_acpt_config.yaml`) to configure it's settings. Below are some of the options available in the config file:
127
+
128
+ - `tests`: An array of test names you want to run. These are assumed to exist in the path `spec/acceptance`. You should leave off the suffix `_spec.rb` from the test file names.
129
+ - `platform`: The backend platform that acceptance tests will run on. Currently, only `gcp` is supported.
130
+ - `test_data`: Configurations for test data. Allows assigning variables to test data either dynamically or statically.
131
+ - `node_data`: Configurations for nodes created by the platform. The available options are platform-dependent.
132
+ - `image_name_builder`: If this key is specified, platforms will be passed a dynamically generated image name based on the configurations under this key.
133
+
134
+ ### Semantic acceptance test names
135
+
136
+ With CemAcpt, acceptance test names can have semantic meaning and influence how your acceptance test nodes are provisioned and how test data is created. The semantic meaning of the test names can be configured in the config file.
137
+
138
+ ## Development
139
+
140
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
141
+
142
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
143
+
144
+ ## Contributing
145
+
146
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/cem_acpt.
147
+
148
+ ## License
149
+
150
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "cem_acpt"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
data/cem_acpt.gemspec ADDED
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lib/cem_acpt/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'cem_acpt'
7
+ spec.version = CemAcpt::VERSION
8
+ spec.authors = ['puppetlabs']
9
+ spec.email = ['abide-team@puppet.com']
10
+
11
+ spec.summary = 'CEM Acceptance Tests'
12
+ spec.description = 'Litmus-like library focusing on CEM Acceptance Tests'
13
+ spec.homepage = 'https://github.com/puppetlabs/cem_acpt'
14
+ spec.license = 'proprietary'
15
+ spec.required_ruby_version = Gem::Requirement.new('>= 2.6.0')
16
+
17
+ spec.metadata['homepage_uri'] = spec.homepage
18
+ spec.metadata['source_code_uri'] = 'https://github.com/puppetlabs/cem_acpt'
19
+ spec.metadata['changelog_uri'] = 'https://github.com/puppetlabs/cem_acpt'
20
+
21
+ # Specify which files should be added to the gem when it is released.
22
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
23
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
24
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
25
+ end
26
+ spec.bindir = 'exe'
27
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
28
+ spec.require_paths = ['lib']
29
+ spec.platform = Gem::Platform.local
30
+ spec.add_runtime_dependency 'concurrent-ruby', '~> 1.1.9'
31
+ spec.add_runtime_dependency 'deep_merge', '~> 1.2.2'
32
+ spec.add_runtime_dependency 'net-ssh', '~> 7.0.0.beta1'
33
+ spec.add_runtime_dependency 'ed25519', '>= 1.2', '< 2.0'
34
+ spec.add_runtime_dependency 'puppet-modulebuilder', '> 0.0.1'
35
+ spec.add_runtime_dependency 'rake'
36
+ spec.add_runtime_dependency 'rspec'
37
+ spec.add_runtime_dependency 'serverspec-cem-acpt'
38
+ spec.add_development_dependency 'rubocop'
39
+ end
data/exe/cem_acpt ADDED
@@ -0,0 +1,84 @@
1
+ #!/usr/bin/env jruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'deep_merge'
5
+ require 'json'
6
+ require 'optparse'
7
+ require 'yaml'
8
+ require 'cem_acpt'
9
+
10
+ options = {}
11
+ parser = OptionParser.new do |opts|
12
+ opts.banner = 'Usage: cem_acpt [options]'
13
+
14
+ opts.on('-D', '--debug', 'Enable debug logging') do
15
+ options[:log_level] = 'debug'
16
+ end
17
+
18
+ opts.on('-L', '--log-file FILE', 'Log to FILE') do |file|
19
+ options[:log_file] = file
20
+ end
21
+
22
+ opts.on('-O', '--options OPTS', 'Set options. Example: -P "param1=value1,param2=value2"') do |o|
23
+ params = o.split(',').map { |s| s.split('=') }.to_h
24
+ params.transform_keys(&:to_sym).each do |k, v|
25
+ options[k] = v
26
+ end
27
+ end
28
+
29
+ opts.on('-p', '--platform PLATFORM', 'Set platform. Example: -p "gcp"') do |p|
30
+ options[:platform] = p
31
+ end
32
+
33
+ opts.on('-m', '--module-dir DIR', 'Set module directory. Example: -m "/tmp/module"') do |m|
34
+ options[:module_dir] = m
35
+ end
36
+
37
+ opts.on('-c', '--config-file FILE', 'Set config file. Example: -c "/tmp/config.yaml"') do |c|
38
+ options[:config_file] = c
39
+ end
40
+
41
+ opts.on('-I', '--CI', 'Run in CI mode') do
42
+ options[:CI] = true
43
+ options[:log_format] = 'github_action'
44
+ end
45
+
46
+ opts.on('-E', '--no-destroy-nodes', 'Do not destroy nodes') do
47
+ options[:no_destroy_nodes] = true
48
+ end
49
+
50
+ opts.on('-q', '--quiet', 'Do not log to stdout') do
51
+ options[:quiet] = true
52
+ end
53
+
54
+ opts.on('-v', '--verbose', 'Enables verbose logging mode') do
55
+ options[:verbose] = true
56
+ end
57
+
58
+ opts.on('-V', '--version', 'Show the cem_acpt version') do
59
+ options[:show_version] = true
60
+ end
61
+
62
+ opts.on('-S', '--no-epehemeral-ssh-key', 'Do not generate an ephemeral SSH key for test suites') do
63
+ options[:no_ephemeral_ssh_key] = true
64
+ end
65
+
66
+ # NOT IMPLEMENTED
67
+ # opts.on('-R', '--reuse-nodes', 'Reuses test nodes if compatible node inventory exists') do
68
+ # options[:reuse_nodes] = true
69
+ # end
70
+ end
71
+
72
+ parser.parse!
73
+ if options[:show_version]
74
+ puts CemAcpt::VERSION
75
+ exit 0
76
+ end
77
+ options[:module_dir] = Dir.pwd unless options[:module_dir]
78
+ options[:platforms] = %w[gcp vmpooler] unless options[:platform]
79
+ if (options[:log_level] == 'debug' || options[:verbose]) && !options[:quiet]
80
+ puts '#################### RUNNING ACCEPTANCE TEST SUITE ####################'
81
+ puts "Using options from command line: #{options}"
82
+ puts '#######################################################################'
83
+ end
84
+ CemAcpt.run(options)
@@ -0,0 +1,206 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'operating_system'
4
+
5
+ # Currently unused in this implementation.
6
+ module CemAcpt::Bootstrap
7
+ # This class runs bootstrapping commands on the VM after it
8
+ # is provisioned. This can include installing repos,
9
+ # installing Puppet, installing other programs,
10
+ # starting services, and running other commands.
11
+ class Bootstrapper
12
+ STATE_ORDER = [:repo, :package, :service, :command, :complete].freeze
13
+ STATE_ACTIONS = {
14
+ repo: :run_repo_installs,
15
+ package: :run_package_installs,
16
+ service: :run_service_starts,
17
+ command: :run_commands,
18
+ }.freeze
19
+ UNIVERSAL_DEPS = [
20
+ 'git',
21
+ 'curl',
22
+ 'puppet-agent',
23
+ ].freeze
24
+ include CemAcpt::Bootstrap::OperatingSystem
25
+
26
+ def initialize(instance_name, instance_image, cmd, collection: 'puppet7', repos: [], packages: [], services: [], commands: [])
27
+ os, major_version = instance_image.split('-')
28
+ use_os(os)
29
+ @instance_name = instance_name.split('.').first
30
+ @cmd_provider = cmd
31
+ @collection = collection
32
+ @os = os
33
+ @major_version = major_version
34
+ @repos = repos
35
+ @packages = packages
36
+ @services = services
37
+ @commands = commands
38
+ @logfile = '/var/log/bootstrap.log'
39
+ @state = 0
40
+ @retries = 0
41
+ @results = {}
42
+ end
43
+
44
+ def ruby_version
45
+ return @ruby_version if defined?(@ruby_version)
46
+
47
+ @ruby_version = determine_ruby_version(@collection)
48
+ @ruby_version
49
+ end
50
+
51
+ def repos
52
+ @repos.push(puppet_agent_repo).uniq
53
+ end
54
+
55
+ def packages
56
+ (@packages + UNIVERSAL_DEPS).uniq
57
+ end
58
+
59
+ def services
60
+ @services.uniq
61
+ end
62
+
63
+ def commands
64
+ @commands + ruby_install_commands
65
+ end
66
+
67
+ def instance_available?
68
+ @instance_available ||= test_connection
69
+ end
70
+
71
+ def run
72
+ raise 'Instance is not available' unless instance_available?
73
+
74
+ @results = {}
75
+ transition_state
76
+ @results
77
+ rescue StandardError => e
78
+ return_results_with_error(e)
79
+ end
80
+
81
+ private
82
+
83
+ def return_results_with_error(error)
84
+ @results[:_error] = {
85
+ error: error,
86
+ details: {
87
+ instance_name: @instance_name,
88
+ instance_image: @instance_image,
89
+ collection: @collection,
90
+ os: @os,
91
+ major_version: @major_version,
92
+ repos: repos,
93
+ packages: packages,
94
+ services: services,
95
+ commands: commands,
96
+ state: @state,
97
+ retries: @retries,
98
+ },
99
+ }
100
+ @results
101
+ end
102
+
103
+ def execute_state_action
104
+ state_sym = STATE_ORDER[@state]
105
+ send(STATE_ACTIONS[state_sym])
106
+ end
107
+
108
+ def transition_state
109
+ raise @last_error if @results.key?(:_error)
110
+ return if @state == STATE_ORDER.length - 1
111
+
112
+ begin
113
+ execute_state_action
114
+ rescue StandardError => e
115
+ @last_error_time = Time.now
116
+ @last_errored_state = @state
117
+ @last_error = e
118
+ handle_state_error
119
+ end
120
+ @state += 1
121
+ transition_state
122
+ end
123
+
124
+ def run_repo_installs
125
+ raise 'Instance is not available' unless instance_available?
126
+
127
+ run_ssh_command_with_formatting(repo_install_cmd(repos), :repo_installs)
128
+ end
129
+
130
+ def run_package_installs
131
+ raise 'Instance is not available' unless instance_available?
132
+
133
+ run_ssh_command_with_formatting(package_install_cmd(packages), :package_installs)
134
+ end
135
+
136
+ def run_service_starts
137
+ raise 'Instance is not available' unless instance_available?
138
+
139
+ run_ssh_command_with_formatting(service_start_cmd(services), :service_starts)
140
+ end
141
+
142
+ def run_commands
143
+ raise 'Instance is not available' unless instance_available?
144
+
145
+ run_ssh_command_with_formatting(commands.join(';'), :command_runs)
146
+ end
147
+
148
+ def handle_state_error
149
+ @retries = 0 unless @last_errored_state == @state
150
+
151
+ raise @last_error unless @retries < 3
152
+
153
+ @retries += 1
154
+ @state = @last_errored_state
155
+ transition_state
156
+ end
157
+
158
+ def run_ssh_command_with_formatting(cmd, results_key, log_file: '/tmp/puppet-bootstrap.log')
159
+ key = results_key.to_sym
160
+ @results[key] = {}
161
+ @results[key][:started] = Time.now
162
+ if cmd.nil? || cmd.empty?
163
+ @results[key][:results] = 'No command(s) to run'
164
+ @results[key][:finished] = Time.now
165
+ return
166
+ end
167
+ cmd = "#{cmd} | tee -a #{log_file}"
168
+ ssh_results = @cmd_provider.ssh(@instance_name, cmd)
169
+ @results[key][:results] = ssh_results.gsub(%r{\s+}, ' ').split('\n')
170
+ @results[key][:finished] = Time.now
171
+ end
172
+
173
+ def verify_command(results_key)
174
+ send("verify_#{results_key}".to_sym)
175
+ end
176
+
177
+ def test_connection
178
+ return true if @cmd_provider.ssh_ready?(@instance_name)
179
+
180
+ raise "Connection test failed for #{@instance_name}"
181
+ end
182
+
183
+ def determine_ruby_version(collection)
184
+ case collection
185
+ when 'puppet7'
186
+ '2.7'
187
+ when 'puppet6'
188
+ '2.5'
189
+ else
190
+ raise "Cannot determine Ruby version, unsupported collection: #{collection}"
191
+ end
192
+ end
193
+
194
+ def ruby_install_commands
195
+ [
196
+ 'curl -sSL https://raw.githubusercontent.com/rvm/rvm/stable/binscripts/rvm-installer -o rvm-installer',
197
+ 'curl -sSL https://raw.githubusercontent.com/rvm/rvm/stable/binscripts/rvm-installer.asc -o rvm-installer.asc',
198
+ 'chmod +x rvm-installer',
199
+ 'gpg --verify rvm-installer.asc rvm-installer && sudo ./rvm-installer',
200
+ "sudo rvm install #{ruby_version} && sudo rvm use #{ruby_version} --default",
201
+ 'gem install bundler',
202
+ '[[ -f ./Gemfile ]] && bundle install',
203
+ ]
204
+ end
205
+ end
206
+ end