cem_acpt 0.2.5 → 0.6.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.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/spec.yml +30 -0
  3. data/Gemfile +4 -3
  4. data/Gemfile.lock +95 -43
  5. data/README.md +144 -83
  6. data/cem_acpt.gemspec +12 -7
  7. data/exe/cem_acpt +41 -7
  8. data/lib/cem_acpt/config.rb +340 -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 +73 -23
  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 +151 -51
  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
@@ -0,0 +1,197 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'open3'
4
+ require_relative '../logging'
5
+
6
+ module CemAcpt
7
+ module Utils
8
+ # SSH-related utilities
9
+ module SSH
10
+ # Class for generating SSH keys
11
+ class Keygen
12
+ include CemAcpt::Logging
13
+
14
+ DEFAULT_TYPE = 'ed25519'
15
+ DEFAULT_PASSPHRASE = ''
16
+ DEFAULT_ROUNDS = 100
17
+ DEFAULT_BITS = 4096
18
+
19
+ def initialize(key_dir: ::File.join(ENV['HOME'], '.ssh'))
20
+ @key_dir = key_dir
21
+ @bin_path = find_bin_path
22
+ end
23
+
24
+ def exist?(key_name)
25
+ key_paths(key_name, chmod: false)
26
+ true
27
+ rescue StandardError
28
+ false
29
+ end
30
+
31
+ def create(key_name, **options)
32
+ delete(key_name) # Delete existing keys with same name
33
+ cmd = new_keygen_cmd(key_name, **options)
34
+ logger.debug("Creating SSH key with command: #{cmd}")
35
+ _stdout, stderr, status = Open3.capture3(cmd)
36
+ raise "Failed to create SSH key! #{stderr}" unless status.success?
37
+
38
+ key_paths(key_name)
39
+ end
40
+
41
+ def delete(key_name)
42
+ priv_key = key_path(key_name)
43
+ pub_key = key_path(key_name, public_key: true)
44
+ if ::File.file?(priv_key)
45
+ logger.debug("Deleting private key: #{priv_key}")
46
+ ::File.delete(priv_key)
47
+ end
48
+ if ::File.file?(pub_key)
49
+ logger.debug("Deleting public key: #{pub_key}")
50
+ ::File.delete(pub_key)
51
+ end
52
+ end
53
+
54
+ private
55
+
56
+ FIND_BIN_PATH_COMMANDS = [
57
+ "#{ENV['SHELL']} -c 'command -v ssh-keygen'",
58
+ "#{ENV['SHELL']} -c 'which ssh-keygen'",
59
+ ].freeze
60
+
61
+ def find_bin_path(find_cmd = FIND_BIN_PATH_COMMANDS.first)
62
+ bin_path, stderr, status = Open3.capture3(find_cmd)
63
+ raise "Cannot find ssh-keygen with command #{find_cmd}: #{stderr}" unless status.success?
64
+
65
+ bin_path.chomp
66
+ rescue StandardError => e
67
+ return find_bin_path(FIND_BIN_PATH_COMMANDS.last) unless FIND_BIN_PATH_COMMANDS.last == find_cmd
68
+
69
+ raise e
70
+ end
71
+
72
+ def new_keygen_cmd(key_name, **options)
73
+ [
74
+ @bin_path,
75
+ '-o',
76
+ "-t #{options[:type] || DEFAULT_TYPE}",
77
+ "-C '#{options[:comment] || "cem_acpt_#{key_name}"}'",
78
+ "-f #{::File.join(@key_dir, key_name)}",
79
+ "-N '#{options[:passphrase] || DEFAULT_PASSPHRASE}'",
80
+ "-a #{options[:rounds] || DEFAULT_ROUNDS}",
81
+ "-b #{options[:bits] || DEFAULT_BITS}",
82
+ ].join(' ')
83
+ end
84
+
85
+ def key_path(file_name, public_key: false)
86
+ key = ::File.join(@key_dir, file_name)
87
+ public_key ? "#{key}.pub" : key
88
+ end
89
+
90
+ def key_paths(file_name, chmod: true)
91
+ priv_key = key_path(file_name)
92
+ pub_key = key_path(file_name, public_key: true)
93
+ raise "Private key file #{priv_key} does not exist" unless ::File.file?(priv_key)
94
+ raise "Public key file #{pub_key} does not exist" unless ::File.file?(pub_key)
95
+
96
+ ::File.chmod(0o600, priv_key) if chmod
97
+ ::File.chmod(0o600, pub_key) if chmod
98
+ [priv_key, pub_key]
99
+ end
100
+ end
101
+
102
+ def self.ssh_keygen
103
+ bin_path = `#{ENV['SHELL']} -c 'command -v ssh-keygen'`.chomp
104
+ raise 'Cannot find ssh-keygen! Install it and verify PATH' unless bin_path
105
+
106
+ bin_path
107
+ rescue StandardError => e
108
+ raise "Cannot find ssh-keygen! Install it and verify PATH. Orignal error: #{e}"
109
+ end
110
+
111
+ def self.default_keydir
112
+ ssh_dir = ::File.join(ENV['HOME'], '.ssh')
113
+ raise "SSH directory at #{ssh_dir} does not exist" unless ::File.directory?(ssh_dir)
114
+
115
+ ssh_dir
116
+ end
117
+
118
+ def self.file_path(file_name, keydir: default_keydir)
119
+ ::File.join(keydir, file_name)
120
+ end
121
+
122
+ # Takes a file name (not path) and optional SSH key directory and returns the paths
123
+ # to the private key and public key based on the file name given.
124
+ # @param file_name [String] The base name for the keys
125
+ # @param keydir [String] An optional SSH key directory
126
+ def self.key_paths(file_name, keydir: default_keydir)
127
+ [file_path(file_name, keydir: keydir), file_path("#{file_name}.pub", keydir: keydir)]
128
+ end
129
+
130
+ def self.create(key_name, **options)
131
+ keygen = Keygen.new
132
+ keys = keygen.create(key_name, **options)
133
+ keys + ['/dev/null']
134
+ end
135
+
136
+ def self.create_known_hosts(known_hosts, overwrite: true, keydir: default_keydir)
137
+ return nil unless known_hosts
138
+
139
+ kh_file = file_path(known_hosts, keydir: keydir)
140
+ ::File.open(kh_file, 'w') { |f| f.write("\n") } unless ::File.exist?(kh_file) && !overwrite
141
+ kh_file
142
+ end
143
+
144
+ def self.set_ssh_file_permissions(*files)
145
+ files.uniq.compact.map { |p| ::File.chmod(0o600, p) }
146
+ end
147
+
148
+ def self.ephemeral_ssh_key(keydir: default_keydir)
149
+ CemAcpt::Utils::SSH::Ephemeral.create(keydir: keydir)
150
+ end
151
+
152
+ def self.clean_ephemeral_keys
153
+ CemAcpt::Utils::SSH::Ephemeral.clean
154
+ end
155
+
156
+ # Ephemeral SSH key generation and cleanup
157
+ module Ephemeral
158
+ PRIV_KEY = 'acpt_test_key'
159
+ CREATE_OPTS = {
160
+ type: 'ed25519',
161
+ bits: '4096',
162
+ rounds: '100',
163
+ comment: 'Ephemeral for cem_acpt',
164
+ password: '',
165
+ known_hosts: 'acpt_known_hosts',
166
+ overwrite_known_hosts: true,
167
+ }.freeze
168
+
169
+ class << self
170
+ attr_accessor :ephemeral_keydir
171
+ end
172
+
173
+ def self.create(keydir: CemAcpt::Utils::SSH.default_keydir)
174
+ return [false, false, false] if ENV['CEM_ACPT_SSH_PRI_KEY'] # Don't create ephemeral keys if this is set
175
+
176
+ self.ephemeral_keydir = keydir
177
+ @priv_key, @pub_key, @known_hosts = CemAcpt::Utils::SSH.create(PRIV_KEY, keydir: ephemeral_keydir, **CREATE_OPTS)
178
+ [@priv_key, @pub_key, @known_hosts]
179
+ end
180
+
181
+ def self.clean
182
+ return if ENV['CEM_ACPT_SSH_PRI_KEY']
183
+
184
+ [@priv_key, @pub_key, @known_hosts].each_with_object([]) do |f, arr|
185
+ next unless f
186
+
187
+ path = CemAcpt::Utils::SSH.file_path(f, keydir: ephemeral_keydir)
188
+ if ::File.exist?(path)
189
+ ::File.delete(path)
190
+ arr << path
191
+ end
192
+ end
193
+ end
194
+ end
195
+ end
196
+ end
197
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'concurrent-ruby'
4
+
5
+ module CemAcpt
6
+ module Utils
7
+ # Terminal-related utilities
8
+ module Terminal
9
+ def self.keep_terminal_alive
10
+ executor = Concurrent::SingleThreadExecutor.new
11
+ executor.post do
12
+ loop do
13
+ $stdout.print(".\r")
14
+ sleep(1)
15
+ $stdout.print("..\r")
16
+ sleep(1)
17
+ $stdout.print("...\r")
18
+ sleep(1)
19
+ $stdout.print(" \r")
20
+ sleep(1)
21
+ end
22
+ end
23
+ executor
24
+ end
25
+ end
26
+ end
27
+ end
@@ -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.0'
5
5
  end
data/lib/cem_acpt.rb CHANGED
@@ -1,34 +1,84 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module CemAcpt
4
- require_relative 'cem_acpt/logging'
5
- require_relative 'cem_acpt/version'
6
- require_relative 'cem_acpt/spec_helper_acceptance'
3
+ require_relative 'cem_acpt/config'
4
+ require_relative 'cem_acpt/logging'
5
+ require_relative 'cem_acpt/test_runner'
6
+ require_relative 'cem_acpt/version'
7
7
 
8
+ module CemAcpt
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
14
34
 
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]
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
44
+
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
+ }