cem_acpt 0.2.5 → 0.6.0

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 +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
+ }