cem_acpt 0.2.5 → 0.6.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/spec.yml +38 -0
  3. data/Gemfile +4 -3
  4. data/Gemfile.lock +85 -56
  5. data/README.md +144 -83
  6. data/cem_acpt.gemspec +8 -7
  7. data/exe/cem_acpt +41 -7
  8. data/lib/cem_acpt/config.rb +345 -0
  9. data/lib/cem_acpt/core_extensions.rb +17 -61
  10. data/lib/cem_acpt/goss/api/action_response.rb +175 -0
  11. data/lib/cem_acpt/goss/api.rb +83 -0
  12. data/lib/cem_acpt/goss.rb +8 -0
  13. data/lib/cem_acpt/image_name_builder.rb +0 -9
  14. data/lib/cem_acpt/logging/formatter.rb +97 -0
  15. data/lib/cem_acpt/logging.rb +168 -142
  16. data/lib/cem_acpt/platform/base.rb +26 -37
  17. data/lib/cem_acpt/platform/gcp.rb +48 -62
  18. data/lib/cem_acpt/platform.rb +30 -28
  19. data/lib/cem_acpt/provision/terraform/linux.rb +47 -0
  20. data/lib/cem_acpt/provision/terraform/os_data.rb +72 -0
  21. data/lib/cem_acpt/provision/terraform/windows.rb +22 -0
  22. data/lib/cem_acpt/provision/terraform.rb +193 -0
  23. data/lib/cem_acpt/provision.rb +20 -0
  24. data/lib/cem_acpt/puppet_helpers.rb +0 -1
  25. data/lib/cem_acpt/test_data.rb +23 -13
  26. data/lib/cem_acpt/test_runner/log_formatter/goss_action_response.rb +104 -0
  27. data/lib/cem_acpt/test_runner/log_formatter.rb +10 -0
  28. data/lib/cem_acpt/test_runner.rb +170 -3
  29. data/lib/cem_acpt/utils/puppet.rb +29 -0
  30. data/lib/cem_acpt/utils/ssh.rb +197 -0
  31. data/lib/cem_acpt/utils/terminal.rb +27 -0
  32. data/lib/cem_acpt/utils.rb +4 -138
  33. data/lib/cem_acpt/version.rb +1 -1
  34. data/lib/cem_acpt.rb +70 -20
  35. data/lib/terraform/gcp/linux/goss/puppet_idempotent.yaml +10 -0
  36. data/lib/terraform/gcp/linux/goss/puppet_noop.yaml +12 -0
  37. data/lib/terraform/gcp/linux/main.tf +191 -0
  38. data/lib/terraform/gcp/linux/systemd/goss-acpt.service +8 -0
  39. data/lib/terraform/gcp/linux/systemd/goss-idempotent.service +8 -0
  40. data/lib/terraform/gcp/linux/systemd/goss-noop.service +8 -0
  41. data/lib/terraform/gcp/windows/.keep +0 -0
  42. data/sample_config.yaml +22 -21
  43. metadata +88 -56
  44. data/lib/cem_acpt/bootstrap/bootstrapper.rb +0 -206
  45. data/lib/cem_acpt/bootstrap/operating_system/rhel_family.rb +0 -129
  46. data/lib/cem_acpt/bootstrap/operating_system.rb +0 -17
  47. data/lib/cem_acpt/bootstrap.rb +0 -12
  48. data/lib/cem_acpt/context.rb +0 -153
  49. data/lib/cem_acpt/platform/base/cmd.rb +0 -71
  50. data/lib/cem_acpt/platform/gcp/cmd.rb +0 -345
  51. data/lib/cem_acpt/platform/gcp/compute.rb +0 -332
  52. data/lib/cem_acpt/platform/vmpooler.rb +0 -24
  53. data/lib/cem_acpt/rspec_utils.rb +0 -242
  54. data/lib/cem_acpt/shared_objects.rb +0 -537
  55. data/lib/cem_acpt/spec_helper_acceptance.rb +0 -184
  56. data/lib/cem_acpt/test_runner/run_handler.rb +0 -187
  57. data/lib/cem_acpt/test_runner/runner.rb +0 -210
  58. data/lib/cem_acpt/test_runner/runner_result.rb +0 -103
@@ -1,144 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'open3'
4
- require 'securerandom'
3
+ require_relative 'utils/puppet'
4
+ require_relative 'utils/ssh'
5
+ require_relative 'utils/terminal'
5
6
 
6
7
  module CemAcpt
7
8
  # Utility methods and modules for CemAcpt.
8
- module Utils
9
- def self.os
10
- case RbConfig::CONFIG['host_os']
11
- when /mswin|msys|mingw|cygwin|bccwin|wince|emc/
12
- :windows
13
- else
14
- :nix
15
- end
16
- end
17
-
18
- # File-related utilities
19
- module File
20
- def self.set_permissions(permission, *file_paths)
21
- file_paths.map { |p| ::File.chmod(permission, p) }
22
- end
23
- end
24
-
25
- # Puppet-related utilities
26
- module Puppet
27
- DEFAULT_PUPPET_PATH = {
28
- nix: '/opt/puppetlabs/bin/puppet',
29
- windows: 'C:/Program Files/Puppet Labs/Puppet/bin/puppet.bat',
30
- }.freeze
31
-
32
- # Finds and returns the Puppet executable
33
- def self.puppet_executable
34
- this_os = CemAcpt::Utils.os
35
- if ::File.file?(DEFAULT_PUPPET_PATH[this_os]) && ::File.executable?(DEFAULT_PUPPET_PATH[this_os])
36
- return DEFAULT_PUPPET_PATH[this_os]
37
- end
38
-
39
- file_name = 'puppet'
40
- if this_os == :windows
41
- exts = ENV['PATHEXT'] ? ".{#{ENV['PATHEXT'].tr(';', ',').tr('.', '').downcase}}" : '.{exe,com,bat}'
42
- file_name = "#{file_name}#{exts}"
43
- end
44
- ENV['PATH'].split(::File::PATH_SEPARATOR).each do |path|
45
- if ::File.file?(::File.join(path, file_name)) && ::File.executable?(::File.join(path, file_name))
46
- return ::File.join(path, file_name)
47
- end
48
- end
49
- raise 'Could not find Puppet executable! Is Puppet installed?'
50
- end
51
-
52
- # Builds a Puppet module package.
53
- # @param module_dir [String] Path to the module directory. If target_dir
54
- # is specified as a relative path, it will be relative to the module dir.
55
- # @param target_dir [String] Path to the target directory where the package
56
- # will be built. This defaults to the relative path 'pkg/'.
57
- # @param should_log [Boolean] Whether or not to log the build process.
58
- # @return [String] Path to the built package.
59
- def self.build_module_package(module_dir, target_dir = nil, should_log: false)
60
- require 'puppet/modulebuilder'
61
- require 'fileutils'
62
-
63
- builder_logger = should_log ? logger : nil
64
- builder = ::Puppet::Modulebuilder::Builder.new(::File.expand_path(module_dir), target_dir, builder_logger)
65
-
66
- # Validates module metadata by raising exception if invalid
67
- _metadata = builder.metadata
68
-
69
- # Builds the module package
70
- builder.build
71
- end
72
- end
73
-
74
- # Node-related utilities
75
- module Node
76
- def self.random_instance_name(prefix: 'cem-acpt-', length: 24)
77
- rand_length = length - prefix.length
78
- "#{prefix}#{::SecureRandom.hex(rand_length)}"
79
- end
80
- end
81
-
82
- # SSH-related utilities
83
- module SSH
84
- def self.ssh_keygen
85
- bin_path = `#{ENV['SHELL']} -c 'command -v ssh-keygen'`.chomp
86
- raise 'Cannot find ssh-keygen! Install it and verify PATH' unless bin_path
87
-
88
- bin_path
89
- rescue StandardError => e
90
- raise "Cannot find ssh-keygen! Install it and verify PATH. Orignal error: #{e}"
91
- end
92
-
93
- def self.default_keydir
94
- ssh_dir = ::File.join(ENV['HOME'], '.ssh')
95
- raise "SSH directory at #{ssh_dir} does not exist" unless ::File.directory?(ssh_dir)
96
-
97
- ssh_dir
98
- end
99
-
100
- def self.ephemeral_ssh_key(type: 'rsa', bits: '4096', comment: nil, keydir: default_keydir)
101
- raise ArgumentError, 'keydir does not exist' unless ::File.directory?(keydir)
102
-
103
- keyfile = ::File.join(keydir, 'acpt_test_key')
104
- keygen_cmd = [ssh_keygen, "-t #{type}", "-b #{bits}", "-f #{keyfile}", '-N ""']
105
- keygen_cmd << "-C \"#{comment}\"" if comment
106
- _, stderr, status = Open3.capture3(keygen_cmd.join(' '))
107
- raise "Failed to generate ephemeral SSH key: #{stderr}" unless status.success?
108
-
109
- [keyfile, "#{keyfile}.pub"]
110
- end
111
-
112
- def self.acpt_known_hosts(keydir: default_keydir, file_name: 'acpt_known_hosts', overwrite: true)
113
- kh_file = ::File.join(keydir, file_name)
114
- ::File.open(kh_file, 'w') { |f| f.write("\n") } unless ::File.exist?(kh_file) && !overwrite
115
- kh_file
116
- end
117
-
118
- def self.set_ssh_file_permissions(priv_key, pub_key, known_hosts)
119
- CemAcpt::Utils::File.set_permissions(0o600, priv_key, pub_key, known_hosts)
120
- end
121
- end
122
-
123
- # Terminal-related utilities
124
- module Terminal
125
- def self.keep_terminal_alive
126
- require 'concurrent-ruby'
127
- executor = Concurrent::SingleThreadExecutor.new
128
- executor.post do
129
- loop do
130
- $stdout.print(".\r")
131
- sleep(1)
132
- $stdout.print("..\r")
133
- sleep(1)
134
- $stdout.print("...\r")
135
- sleep(1)
136
- $stdout.print(" \r")
137
- sleep(1)
138
- end
139
- end
140
- executor
141
- end
142
- end
143
- end
9
+ module Utils; end
144
10
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CemAcpt
4
- VERSION = '0.2.5'
4
+ VERSION = '0.6.1'
5
5
  end
data/lib/cem_acpt.rb CHANGED
@@ -1,34 +1,84 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CemAcpt
4
+ require_relative 'cem_acpt/config'
4
5
  require_relative 'cem_acpt/logging'
6
+ require_relative 'cem_acpt/test_runner'
5
7
  require_relative 'cem_acpt/version'
6
- require_relative 'cem_acpt/spec_helper_acceptance'
7
8
 
8
9
  class << self
9
10
  include CemAcpt::Logging
10
- end
11
11
 
12
- def self.run(params)
13
- require_relative 'cem_acpt/context'
12
+ attr_reader :config
13
+
14
+ def version(as_str: false)
15
+ return VERSION unless as_str
16
+
17
+ "cem_acpt v#{VERSION}"
18
+ end
19
+
20
+ def print_config(options, format: :yaml)
21
+ config = new_config(options)
22
+ if format == :explain
23
+ puts config.explain
24
+ return
25
+ end
26
+ puts config.send("to_#{format}".to_sym)
27
+ end
28
+
29
+ def run(options)
30
+ # Set up config, logger, and helper
31
+ @config = new_config(options)
32
+ initialize_logger!
33
+ runner = new_runner
34
+
35
+ # Set up signal handlers
36
+ Signal.trap('INT') do
37
+ @trap_context = true
38
+ logger.trap_context = @trap_context
39
+ logger.fatal('Signal Handler') { 'Received interrupt signal. Cleaning up test suite...' }
40
+ runner.clean_up(@trap_context)
41
+ logger.fatal('Signal Handler') { 'Exiting due to interrupt signal' }
42
+ exit 1
43
+ end
14
44
 
15
- log_level = params[:log_level]
16
- log_formatter = params[:log_format] ? new_log_formatter(params[:log_format]) : nil
17
- logdevs = [$stdout]
18
- if params[:log_file] && params[:quiet]
19
- logdevs = [params[:log_file]]
20
- elsif params[:log_file] && !params[:quiet]
21
- logdevs << params[:log_file]
45
+ # Run the test suite
46
+ runner.run
47
+
48
+ exit runner.exit_code
49
+ end
50
+
51
+ private
52
+
53
+ def new_config(options)
54
+ CemAcpt::Config.new(opts: options, config_file: options[:config_file])
22
55
  end
23
- if (params[:CI] || ENV['CI'] || ENV['GITHUB_ACTION']) && !logdevs.include?($stdout)
24
- logdevs << $stdout
56
+
57
+ def new_runner
58
+ CemAcpt::TestRunner::Runner.new(@config)
59
+ end
60
+
61
+ def initialize_logger!
62
+ raise 'Config must be loaded before logger can be initialized' if config.nil? || config.empty?
63
+
64
+ log_formatter = config.get('log_format')&.to_sym || :text
65
+ logdevs = [$stdout]
66
+ # If log_file is set, and quiet is set, only log to the file
67
+ if config.get('log_file') && config.get('quiet')
68
+ logdevs = [config.get('log_file')]
69
+ elsif config.get('log_file') && !config.get('quiet')
70
+ logdevs << config.get('log_file')
71
+ end
72
+ if config.ci_mode? && !logdevs.include?($stdout)
73
+ logdevs << $stdout
74
+ end
75
+ new_log = new_logger(
76
+ *logdevs,
77
+ level: config.get('log_level'),
78
+ formatter: log_formatter,
79
+ )
80
+ new_log.set_verbose(!!config.get('verbose'))
81
+ new_log
25
82
  end
26
- new_logger(
27
- *logdevs,
28
- level: log_level,
29
- formatter: log_formatter,
30
- )
31
- exit_code = CemAcpt::Context.with(params, &:run)
32
- exit exit_code
33
83
  end
34
84
  end
@@ -0,0 +1,10 @@
1
+ command:
2
+ puppet_idempotent:
3
+ exec: 'sudo /opt/puppetlabs/puppet/bin/puppet apply --verbose --detailed-exitcodes /opt/cem_acpt/manifest.pp'
4
+ timeout: 300000 # 5 mins in miliseconds
5
+ stdout:
6
+ - "Applied catalog in"
7
+ stderr:
8
+ - ""
9
+ exit-status: 0
10
+
@@ -0,0 +1,12 @@
1
+ command:
2
+ puppet_noop:
3
+ exec: 'sudo /opt/puppetlabs/puppet/bin/puppet apply --verbose --detailed-exitcodes --noop /opt/cem_acpt/manifest.pp'
4
+ timeout: 300000 # 5 mins in miliseconds
5
+ stdout:
6
+ - "Applied catalog in"
7
+ stderr:
8
+ - ""
9
+ exit-status:
10
+ or:
11
+ - 0
12
+ - 2
@@ -0,0 +1,191 @@
1
+ terraform {
2
+ required_providers {
3
+ google = {
4
+ source = "hashicorp/google"
5
+ version = "4.59.0"
6
+ }
7
+ }
8
+ }
9
+
10
+ variable "credentials_file" {
11
+ type = string
12
+ }
13
+
14
+ variable "project" {
15
+ type = string
16
+ }
17
+
18
+ variable "region" {
19
+ type = string
20
+ }
21
+
22
+ variable "zone" {
23
+ type = string
24
+ }
25
+
26
+ variable "subnetwork" {
27
+ type = string
28
+ }
29
+
30
+ variable "username" {
31
+ type = string
32
+ }
33
+
34
+ variable "private_key" {
35
+ type = string
36
+ sensitive = true
37
+ }
38
+
39
+ variable "public_key" {
40
+ type = string
41
+ }
42
+
43
+ variable "puppet_module_package" {
44
+ type = string
45
+ }
46
+
47
+ variable "node_data" {
48
+ type = map(object({
49
+ machine_type = string
50
+ image = string
51
+ disk_size = number
52
+ test_name = string
53
+ goss_file = string
54
+ puppet_manifest = string
55
+ provision_dir_source = string
56
+ provision_dir_dest = string
57
+ provision_commands = list(string)
58
+ }))
59
+ }
60
+
61
+ provider "google" {
62
+ credentials = file(var.credentials_file)
63
+ project = var.project
64
+ region = var.region
65
+ zone = var.zone
66
+ }
67
+
68
+ resource "google_compute_instance" "acpt-test-node" {
69
+ provider = google
70
+ for_each = var.node_data
71
+ name = each.key
72
+ machine_type = each.value.machine_type
73
+
74
+ boot_disk {
75
+ initialize_params {
76
+ image = each.value.image
77
+ size = each.value.disk_size
78
+ type = "pd-standard"
79
+ }
80
+ }
81
+
82
+ scheduling {
83
+ preemptible = true
84
+ automatic_restart = false
85
+ provisioning_model = "SPOT"
86
+ instance_termination_action = "DELETE"
87
+ }
88
+
89
+ network_interface {
90
+ subnetwork = var.subnetwork
91
+ access_config {
92
+ network_tier = "STANDARD"
93
+ }
94
+ }
95
+
96
+ provisioner "remote-exec" {
97
+ connection {
98
+ type = "ssh"
99
+ user = "${var.username}"
100
+ timeout = "5m"
101
+ host = self.network_interface.0.access_config.0.nat_ip
102
+ private_key = "${file(var.private_key)}"
103
+ agent = false
104
+ }
105
+ inline = [
106
+ "sudo mkdir -p ${each.value.provision_dir_dest}",
107
+ "sudo chown -R ${var.username}:${var.username} ${each.value.provision_dir_dest}"
108
+ ]
109
+ }
110
+
111
+ provisioner "file" {
112
+ source = "${each.value.provision_dir_source}/"
113
+ destination = each.value.provision_dir_dest
114
+ connection {
115
+ type = "ssh"
116
+ user = "${var.username}"
117
+ timeout = "2m"
118
+ host = self.network_interface.0.access_config.0.nat_ip
119
+ private_key = "${file(var.private_key)}"
120
+ agent = false
121
+ }
122
+ }
123
+
124
+ provisioner "file" {
125
+ source = var.puppet_module_package
126
+ destination = "${each.value.provision_dir_dest}/puppet-module.tar.gz"
127
+ connection {
128
+ type = "ssh"
129
+ user = "${var.username}"
130
+ timeout = "2m"
131
+ host = self.network_interface.0.access_config.0.nat_ip
132
+ private_key = "${file(var.private_key)}"
133
+ agent = false
134
+ }
135
+ }
136
+
137
+ provisioner "file" {
138
+ source = each.value.goss_file
139
+ destination = "${each.value.provision_dir_dest}/goss.yaml"
140
+ connection {
141
+ type = "ssh"
142
+ user = "${var.username}"
143
+ timeout = "2m"
144
+ host = self.network_interface.0.access_config.0.nat_ip
145
+ private_key = "${file(var.private_key)}"
146
+ agent = false
147
+ }
148
+ }
149
+
150
+ provisioner "file" {
151
+ source = each.value.puppet_manifest
152
+ destination = "${each.value.provision_dir_dest}/manifest.pp"
153
+ connection {
154
+ type = "ssh"
155
+ user = "${var.username}"
156
+ timeout = "2m"
157
+ host = self.network_interface.0.access_config.0.nat_ip
158
+ private_key = "${file(var.private_key)}"
159
+ agent = false
160
+ }
161
+ }
162
+
163
+ provisioner "remote-exec" {
164
+ connection {
165
+ type = "ssh"
166
+ user = "${var.username}"
167
+ timeout = "5m"
168
+ host = self.network_interface.0.access_config.0.nat_ip
169
+ private_key = "${file(var.private_key)}"
170
+ agent = false
171
+ }
172
+ inline = each.value.provision_commands
173
+ }
174
+
175
+ metadata = {
176
+ oslogin = "TRUE"
177
+ ssh-keys = "${var.username}:${file(var.public_key)}"
178
+ cem-acpt-test = each.value.test_name
179
+ }
180
+
181
+ tags = [ "cem-acpt-test-node" ]
182
+ }
183
+
184
+ output "instance_name_ip" {
185
+ value = {
186
+ for k, v in google_compute_instance.acpt-test-node : v.name => {
187
+ ip = v.network_interface.0.access_config.0.nat_ip,
188
+ test_name = v.metadata.cem-acpt-test,
189
+ }
190
+ }
191
+ }
@@ -0,0 +1,8 @@
1
+ [Unit]
2
+ Description=unit file for goss server acpt endpoint service
3
+
4
+ [Service]
5
+ ExecStart=/usr/local/bin/goss -g /opt/cem_acpt/goss.yaml serve -f json --endpoint "/acpt" --cache "10m"
6
+
7
+ [Install]
8
+ WantedBy=multi-user.target
@@ -0,0 +1,8 @@
1
+ [Unit]
2
+ Description=unit file for goss server acpt endpoint service
3
+
4
+ [Service]
5
+ ExecStart=/usr/local/bin/goss -g /opt/cem_acpt/goss/puppet_idempotent.yaml serve -f json -l ":8081" --endpoint "/idempotent" --cache "10m"
6
+
7
+ [Install]
8
+ WantedBy=multi-user.target
@@ -0,0 +1,8 @@
1
+ [Unit]
2
+ Description=unit file for goss server acpt endpoint service
3
+
4
+ [Service]
5
+ ExecStart=/usr/local/bin/goss -g /opt/cem_acpt/goss/puppet_noop.yaml serve -f json -l ":8082" --endpoint "/noop" --cache "10m"
6
+
7
+ [Install]
8
+ WantedBy=multi-user.target
File without changes
data/sample_config.yaml CHANGED
@@ -1,7 +1,6 @@
1
1
  test_data:
2
2
  for_each:
3
3
  collection:
4
- - puppet6
5
4
  - puppet7
6
5
  vars:
7
6
  test_static_var: 'static_var_value'
@@ -21,24 +20,24 @@ test_data:
21
20
  delete_vars:
22
21
  - 'framework_vars'
23
22
 
24
- platform: gcp
23
+ platform:
24
+ name: gcp
25
+ project: 'team-sse'
26
+ region: 'us-west1'
27
+ zone: 'us-west1-b'
28
+ subnetwork: 'cem-acpt-subnet'
29
+
30
+ # terraform:
31
+ # environment:
32
+ # TF_LOG: 'DEBUG'
33
+
34
+ actions:
35
+ only:
36
+ - 'acpt'
25
37
 
26
38
  node_data:
27
39
  machine_type: 'e2-small'
28
- project:
29
- name: 'some-project'
30
- zone: 'us-west1-b'
31
- disk:
32
- name: 'disk-1'
33
- size: 20
34
- type: 'pd-standard'
35
- network_interface:
36
- tier: 'STANDARD'
37
- subnetwork: 'some-subnet'
38
- metadata:
39
- ephemeral_ssh_key:
40
- lifetime: 2
41
- keydir: '/tmp/acpt_test_keys'
40
+ disk_size: 40
42
41
 
43
42
  image_name_builder:
44
43
  character_substitutions:
@@ -48,11 +47,13 @@ image_name_builder:
48
47
  - '$image_fam'
49
48
  - '$collection'
50
49
  - '$firewall'
51
- join_with: '-'
50
+ join_with: '-'
52
51
 
53
52
  tests:
54
- - cis_rhel-7_firewalld_server_1
55
- - cis_rhel-7_iptables_server_1
56
- - cis_rhel-8_firewalld_server_1
53
+ # - cis_rhel-7_firewalld_server_2
57
54
  - cis_rhel-8_firewalld_server_2
58
- - cis_rhel-8_iptables_server_1
55
+ # - cis_oel-7_firewalld_server_2
56
+ # - cis_oel-8_firewalld_server_2
57
+ # - cis_alma-8_firewalld_server_2
58
+ # - stig_rhel-7_firewalld_public_3
59
+ # - stig_rhel-8_firewalld_public_3