cem_acpt 0.7.2 → 0.8.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 (37) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +3 -1
  3. data/Gemfile.lock +42 -14
  4. data/README.md +8 -0
  5. data/cem_acpt.gemspec +3 -1
  6. data/exe/cem_acpt_image +0 -0
  7. data/lib/cem_acpt/action_result.rb +85 -0
  8. data/lib/cem_acpt/cli.rb +46 -22
  9. data/lib/cem_acpt/config/base.rb +27 -4
  10. data/lib/cem_acpt/config/cem_acpt.rb +0 -17
  11. data/lib/cem_acpt/config/cem_acpt_image.rb +2 -17
  12. data/lib/cem_acpt/goss/api.rb +8 -2
  13. data/lib/cem_acpt/image_builder.rb +82 -64
  14. data/lib/cem_acpt/logging.rb +41 -30
  15. data/lib/cem_acpt/platform.rb +4 -5
  16. data/lib/cem_acpt/provision/terraform/linux.rb +17 -1
  17. data/lib/cem_acpt/provision/terraform/terraform_cmd.rb +181 -0
  18. data/lib/cem_acpt/provision/terraform/windows.rb +9 -0
  19. data/lib/cem_acpt/provision/terraform.rb +34 -47
  20. data/lib/cem_acpt/provision.rb +1 -1
  21. data/lib/cem_acpt/puppet_helpers.rb +1 -1
  22. data/lib/cem_acpt/test_data.rb +3 -3
  23. data/lib/cem_acpt/test_runner/log_formatter/error_formatter.rb +33 -0
  24. data/lib/cem_acpt/test_runner/log_formatter.rb +10 -1
  25. data/lib/cem_acpt/test_runner.rb +151 -52
  26. data/lib/cem_acpt/utils/ssh.rb +2 -2
  27. data/lib/cem_acpt/utils/terminal.rb +1 -5
  28. data/lib/cem_acpt/utils/winrm_runner.rb +160 -0
  29. data/lib/cem_acpt/utils.rb +41 -1
  30. data/lib/cem_acpt/version.rb +1 -1
  31. data/lib/cem_acpt.rb +51 -21
  32. data/lib/terraform/gcp/linux/goss/puppet_idempotent.yaml +2 -2
  33. data/lib/terraform/gcp/linux/goss/puppet_noop.yaml +1 -1
  34. data/lib/terraform/gcp/windows/goss/puppet_idempotent.yaml +9 -0
  35. data/lib/terraform/gcp/windows/goss/puppet_noop.yaml +12 -0
  36. data/lib/terraform/gcp/windows/main.tf +115 -0
  37. metadata +49 -8
@@ -1,11 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'securerandom'
4
+ require_relative 'action_result'
3
5
  require_relative 'goss'
4
6
  require_relative 'logging'
5
7
  require_relative 'platform'
6
8
  require_relative 'provision'
7
9
  require_relative 'test_data'
8
10
  require_relative 'utils'
11
+ require_relative 'utils/winrm_runner'
9
12
  require_relative 'version'
10
13
  require_relative 'test_runner/log_formatter'
11
14
 
@@ -17,14 +20,16 @@ module CemAcpt
17
20
  include CemAcpt::Logging
18
21
 
19
22
  attr_reader :duration, :exit_code
23
+ attr_accessor :run_data # This is opened up mainly for windows use.
20
24
 
21
25
  def initialize(config)
22
26
  @config = config
23
27
  @run_data = {}
24
28
  @duration = 0
25
29
  @exit_code = 0
26
- @results = nil
30
+ @results = []
27
31
  @http_statuses = []
32
+ @provisioned = false
28
33
  end
29
34
 
30
35
  def inspect
@@ -38,40 +43,66 @@ module CemAcpt
38
43
  def run
39
44
  @run_data = {}
40
45
  @start_time = Time.now
41
- logger.with_ci_group("CemAcpt v#{CemAcpt::VERSION} run started at #{@start_time}") do
42
- logger.info('CemAcpt') { "Using module directory: #{config.get('module_dir')}..." }
43
- Dir.chdir(config.get('module_dir')) do
44
- keep_terminal_alive
45
- @run_data[:private_key], @run_data[:public_key], @run_data[:known_hosts] = new_ephemeral_ssh_keys
46
- logger.info('CemAcpt') { 'Created ephemeral SSH key pair...' }
47
- @run_data[:module_package_path] = build_module_package
48
- logger.info('CemAcpt') { "Created module package: #{@run_data[:module_package_path]}..." }
49
- @run_data[:test_data] = new_test_data
50
- logger.info('CemAcpt') { 'Created test data...' }
51
- logger.verbose('CemAcpt') { "Test data: #{@run_data[:test_data]}" }
52
- @run_data[:nodes] = new_node_data
53
- logger.info('CemAcpt') { 'Created node data...' }
54
- logger.verbose('CemAcpt') { "Node data: #{@run_data[:nodes]}" }
55
- @instance_names_ips = provision_test_nodes
56
- logger.info('CemAcpt') { 'Provisioned test nodes...' }
57
- logger.debug('CemAcpt') { "Instance names and IPs: #{@instance_names_ips}" }
58
- @results = run_tests(@instance_names_ips.map { |_, v| v['ip'] },
59
- config.get('actions.only'),
60
- config.get('actions.except'))
46
+ module_dir = config.get('module_dir')
47
+ @old_dir = Dir.pwd
48
+ Dir.chdir(module_dir)
49
+ logger.start_ci_group("CemAcpt v#{CemAcpt::VERSION} run started at #{@start_time}")
50
+ logger.info('CemAcpt::TestRunner') { "Using module directory: #{module_dir}..." }
51
+ keep_terminal_alive
52
+ @run_data[:private_key], @run_data[:public_key], @run_data[:known_hosts] = new_ephemeral_ssh_keys
53
+ logger.info('CemAcpt::TestRunner') { 'Created ephemeral SSH key pair...' }
54
+ @run_data[:module_package_path] = build_module_package
55
+ logger.info('CemAcpt::TestRunner') { "Created module package: #{@run_data[:module_package_path]}..." }
56
+ @run_data[:test_data] = new_test_data
57
+ logger.info('CemAcpt::TestRunner') { 'Created test data...' }
58
+ logger.verbose('CemAcpt::TestRunner') { "Test data: #{@run_data[:test_data]}" }
59
+ @run_data[:nodes] = new_node_data
60
+ logger.info('CemAcpt::TestRunner') { 'Created node data...' }
61
+ logger.verbose('CemAcpt::TestRunner') { "Node data: #{@run_data[:nodes]}" }
62
+ @instance_names_ips = provision_test_nodes
63
+ logger.info('CemAcpt::TestRunner') { "Instance names and IPs class: #{@instance_names_ips.class}" }
64
+ @provisioned = true
65
+ logger.info('CemAcpt::TestRunner') { 'Provisioned test nodes...' }
66
+ logger.debug('CemAcpt::TestRunner') { "Instance names and IPs: #{@instance_names_ips}" }
67
+ # Verifying that we're running on windows nodes or not
68
+ if config.get('tests').first.include? 'windows'
69
+ logger.info('CemAcpt') { 'Running on windows nodes...' }
70
+ upload_module_to_bucket
71
+
72
+ @instance_names_ips.each do |k, v|
73
+ # Login_info here is basically a super charged version of a hash from
74
+ # instance_names_ips. It contains the username, password, and ip of the
75
+ # windows node, as well as the test name that will be run on that node.
76
+ login_info = CemAcpt::Utils.get_windows_login_info(k, v)
77
+ win_node = CemAcpt::Utils::WinRMRunner::WinNode.new(login_info, @run_data[:module_package_path].split('/').last)
78
+ win_node.run
61
79
  end
62
80
  end
81
+ @results = run_tests(@instance_names_ips.map { |_, v| v['ip'] },
82
+ config.get('actions.only'),
83
+ config.get('actions.except'))
84
+ rescue StandardError => e
85
+ logger.error('CemAcpt::TestRunner') { 'Run failed due to error...' }
86
+ @results << ActionResult.new(e)
63
87
  ensure
64
88
  clean_up
65
89
  process_test_results
90
+ Dir.chdir(@old_dir) if @old_dir
91
+ @results
66
92
  end
67
93
 
68
94
  def clean_up(trap_context = false)
95
+ logger.end_ci_group
69
96
  kill_keep_terminal_alive unless trap_context
97
+ cleanup_bucket # Clean up bucket if we're testing the cem_windows module
70
98
 
71
99
  return no_destroy if config.get('no_destroy_nodes')
72
100
 
73
101
  clean_ephemeral_ssh_keys
74
- destroy_test_nodes
102
+ destroy_test_nodes if @provisioned && !@destroyed
103
+ rescue StandardError => e
104
+ logger.verbose('CemAcpt::TestRunner') { "Error cleaning up: #{e}" }
105
+ logger.verbose('CemAcpt::TestRunner') { e.backtrace.join("\n") }
75
106
  end
76
107
 
77
108
  private
@@ -93,31 +124,40 @@ module CemAcpt
93
124
 
94
125
  # @return [String] The path to the module package
95
126
  def build_module_package
96
- CemAcpt::Utils::Puppet.build_module_package(config.get('module_dir'))
127
+ if config.get('tests').first.include? 'windows'
128
+ CemAcpt::Utils.package_win_module(config.get('module_dir'))
129
+ else
130
+ CemAcpt::Utils::Puppet.build_module_package(config.get('module_dir'))
131
+ end
97
132
  end
98
133
 
99
134
  # @return [Array<String>] The paths to the ssh private key, public key, and known hosts file
100
135
  def new_ephemeral_ssh_keys
101
136
  return [nil, nil, nil] if config.get('no_ephemeral_ssh_key')
102
137
 
138
+ logger.info('CemAcpt::TestRunner') { 'Creating ephemeral SSH keys...' }
103
139
  CemAcpt::Utils::SSH::Ephemeral.create
104
140
  end
105
141
 
106
142
  def clean_ephemeral_ssh_keys
107
143
  return if config.get('no_ephemeral_ssh_key') || config.get('no_destroy_nodes')
108
144
 
145
+ logger.info('CemAcpt::TestRunner') { 'Cleaning ephemeral SSH keys...' }
109
146
  CemAcpt::Utils::SSH::Ephemeral.clean
110
147
  end
111
148
 
112
149
  def new_test_data
150
+ logger.debug('CemAcpt::TestRunner') { 'Creating new test data...' }
113
151
  CemAcpt::TestData.acceptance_test_data(config)
114
152
  end
115
153
 
116
154
  def new_node_data
155
+ logger.debug('CemAcpt::TestRunner') { 'Creating new node data...' }
117
156
  CemAcpt::Platform.use(config.get('platform.name'), config, @run_data)
118
157
  end
119
158
 
120
159
  def provision_test_nodes
160
+ logger.info('CemAcpt::TestRunner') { 'Provisioning test nodes...' }
121
161
  @provisioner = CemAcpt::Provision.new_provisioner(config, @run_data)
122
162
  @provisioner.provision
123
163
  end
@@ -125,62 +165,121 @@ module CemAcpt
125
165
  def destroy_test_nodes
126
166
  return no_destroy if config.get('no_destroy_nodes')
127
167
 
128
- logger.with_ci_group("CemAcpt v#{CemAcpt::VERSION} run finished at #{Time.now}") { @provisioner&.destroy }
168
+ logger.info('CemAcpt::TestRunner') { 'Destroying test nodes...' }
169
+ logger.start_ci_group("CemAcpt v#{CemAcpt::VERSION} run finished at #{Time.now}")
170
+ @provisioner&.destroy
171
+ @destroyed = true
172
+ ensure
173
+ logger.end_ci_group
129
174
  end
130
175
 
131
176
  def no_destroy
132
- logger.warn('CemAcpt') { 'Not destroying test nodes...' }
133
- logger.with_ci_group("CemAcpt v#{CemAcpt::VERSION} run finished at #{Time.now}") do
134
- @provisioner&.show
135
- logger.info('CemAcpt') { "Test SSH Keys:\n Private Key: #{@run_data[:private_key]}\n Public Key:#{@run_data[:public_key]}" }
136
- end
177
+ logger.warn('CemAcpt::TestRunner') { 'Not destroying test nodes...' }
178
+ logger.start_ci_group("CemAcpt v#{CemAcpt::VERSION} run finished at #{Time.now}")
179
+ @provisioner&.show
180
+ logger.info('CemAcpt') { "Test SSH Keys:\n Private Key: #{@run_data[:private_key]}\n Public Key:#{@run_data[:public_key]}" }
181
+ ensure
182
+ logger.end_ci_group
137
183
  end
138
184
 
139
185
  def run_tests(hosts, only_actions, except_actions)
140
- only_actions = [] if only_actions.nil?
141
- except_actions = [] if except_actions.nil?
142
- CemAcpt::Goss::Api.run_actions_async(hosts, only: only_actions, except: except_actions)
143
- end
144
-
145
- def result_log_formatter
146
- @result_log_formatter ||= LogFormatter::GossActionResponse.new(config, @instance_names_ips)
186
+ logger.info('CemAcpt::TestRunner') { 'Running tests...' }
187
+ logger.verbose('CemAcpt::TestRunner') { "Hosts: #{hosts}" }
188
+ logger.verbose('CemAcpt::TestRunner') { "Only actions: #{only_actions}" }
189
+ logger.verbose('CemAcpt::TestRunner') { "Except actions: #{except_actions}" }
190
+ api_results = CemAcpt::Goss::Api.run_actions_async(hosts,
191
+ only: only_actions || [],
192
+ except: except_actions || [])
193
+ res = []
194
+ api_results.close unless api_results.closed?
195
+ while (r = api_results.pop)
196
+ res << ActionResult.new(r)
197
+ end
198
+ res
147
199
  end
148
200
 
149
201
  def process_test_results
150
- if @results.nil?
151
- logger.error('CemAcpt') { 'No test results to process' }
202
+ if @results.nil? || @results.empty?
203
+ logger.error('CemAcpt::TestRunner') { 'No test results to process' }
152
204
  @exit_code = 1
153
205
  else
206
+ logger.info('CemAcpt::TestRunner') { "Processing #{@results.size} test result(s)..." }
154
207
  until @results.empty?
155
208
  result = @results.pop
156
209
  @http_statuses << result.http_status
157
210
  log_test_result(result)
158
211
  end
159
212
  if @http_statuses.empty?
160
- logger.error('CemAcpt') { 'No test results to process' }
213
+ logger.error('CemAcpt::TestRunner') { 'No test results to process' }
161
214
  @exit_code = 1
162
215
  else
163
- @exit_code = @http_statuses.any? { |s| s.to_i != 200 } ? 1 : 0
216
+ @exit_code = (@http_statuses.any? { |s| s.to_i != 200 }) ? 1 : 0
164
217
  end
165
218
  end
166
219
  @duration = Time.now - @start_time
167
- logger.info('CemAcpt') { "Test suite finished after ~#{duration.round} seconds." }
220
+ logger.info('CemAcpt::TestRunner') { "Test suite finished after ~#{duration.round} seconds." }
168
221
  end
169
222
 
170
223
  def log_test_result(result)
171
- logger.with_ci_group("Test results for #{result_log_formatter.test_name(result)}") do
172
- logger.info(result_log_formatter.summary(result))
173
- formatted_results = result_log_formatter.results(result)
174
- formatted_results.each do |r|
175
- if r.start_with?('Passed:')
176
- logger.verbose { r }
177
- elsif r.start_with?('Skipped:')
178
- logger.info { r }
179
- else
180
- logger.error { r }
181
- end
224
+ result_log_formatter = LogFormatter.new_formatter(result, config, @instance_names_ips)
225
+ logger.start_ci_group("Test results for #{result_log_formatter.test_name(result)}")
226
+ return log_error_test_result(result_log_formatter, result) if result.error?
227
+
228
+ logger.info(result_log_formatter.summary(result))
229
+ formatted_results = result_log_formatter.results(result)
230
+ formatted_results.each do |r|
231
+ if r.start_with?('Passed:')
232
+ logger.verbose { r }
233
+ elsif r.start_with?('Skipped:')
234
+ logger.info { r }
235
+ else
236
+ logger.error { r }
182
237
  end
183
238
  end
239
+ ensure
240
+ logger.end_ci_group
241
+ end
242
+
243
+ def log_error_test_result(formatter, result)
244
+ logger.fatal { formatter.results(result).join("\n") }
245
+ end
246
+
247
+ # Upload the cem_windows module to the bucket if we're testing the cem_windows module
248
+ # This should only be done once per cem_acpt run. It's important to update the module_package_path
249
+ # in the run_data to reflect the new module path if we do end up changing the module name
250
+ def upload_module_to_bucket
251
+ curr_module_path = @run_data[:module_package_path].split('/')[0..-2].join('/')
252
+ curr_module_name = @run_data[:module_package_path].split('/')[-1]
253
+
254
+ logger.debug('CemAcpt') { 'Checking if there duplicate cem_windows module in the bucket already...' }
255
+ # Checking if the module already exists in the bucket. Gcloud will return an empty string if the module
256
+ # doesn't exist in the bucket.
257
+ attempts = 0
258
+ until `gcloud storage ls gs://win_cem_acpt/#{curr_module_name}`.empty? do
259
+ raise "Failed to rename duplicate module in GCloud" if attempts >= 3
260
+
261
+ logger.debug('CemAcpt') { 'Duplicate cem_windows module found. Renaming cem_windows module...' }
262
+ # Rename the cem_windows module
263
+ curr_module_name = SecureRandom.uuid << curr_module_name
264
+ `mv #{@run_data[:module_package_path]} #{curr_module_path}/#{curr_module_name}`
265
+ # update the module_package_path in the run_data
266
+ @run_data[:module_package_path] = File.join(curr_module_path, curr_module_name)
267
+ attempts += 1
268
+ end
269
+ # Upload the module from the local host to the bucket
270
+ logger.info('CemAcpt') { 'Transporting cem_windows module...' }
271
+ gcloud_cmd = `gcloud storage cp #{@run_data[:module_package_path]} gs://win_cem_acpt`
272
+ logger.debug('CemAcpt') { "Uploaded cem_windows module to bucket: #{gcloud_cmd}" }
273
+ end
274
+
275
+ # We have to clean up our gcp bucket after we're done testing. This will limit the duplicated
276
+ # modules existing in the bucket.
277
+ def cleanup_bucket
278
+ # Cleanup the module from the bucket
279
+ cleanup_cmd = `gcloud storage rm gs://win_cem_acpt/#{@run_data[:module_package_path].split('/')[-1]}`
280
+ logger.debug('CemAcpt') { "Removed module from bucket: #{cleanup_cmd}" }
281
+ rescue StandardError
282
+ logger.info('CemAcpt') { 'No module to clean up in the bucket. You might not be running test for cem_windows' }
184
283
  end
185
284
  end
186
285
  end
@@ -31,7 +31,7 @@ module CemAcpt
31
31
  def create(key_name, **options)
32
32
  delete(key_name) # Delete existing keys with same name
33
33
  cmd = new_keygen_cmd(key_name, **options)
34
- logger.debug("Creating SSH key with command: #{cmd}")
34
+ logger.debug('CemAcpt::Utils::SSH::Keygen') { "Creating SSH key with command: #{cmd}" }
35
35
  _stdout, stderr, status = Open3.capture3(cmd)
36
36
  raise "Failed to create SSH key! #{stderr}" unless status.success?
37
37
 
@@ -42,7 +42,7 @@ module CemAcpt
42
42
  priv_key = key_path(key_name)
43
43
  pub_key = key_path(key_name, public_key: true)
44
44
  if ::File.file?(priv_key)
45
- logger.debug("Deleting private key: #{priv_key}")
45
+ logger.debug('CemAcpt::Utils::SSH::Keygen') { "Deleting private key: #{priv_key}" }
46
46
  ::File.delete(priv_key)
47
47
  end
48
48
  if ::File.file?(pub_key)
@@ -1,14 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'concurrent-ruby'
4
-
5
3
  module CemAcpt
6
4
  module Utils
7
5
  # Terminal-related utilities
8
6
  module Terminal
9
7
  def self.keep_terminal_alive
10
- executor = Concurrent::SingleThreadExecutor.new
11
- executor.post do
8
+ Thread.new do
12
9
  loop do
13
10
  $stdout.print(".\r")
14
11
  sleep(1)
@@ -20,7 +17,6 @@ module CemAcpt
20
17
  sleep(1)
21
18
  end
22
19
  end
23
- executor
24
20
  end
25
21
  end
26
22
  end
@@ -0,0 +1,160 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'winrm'
4
+ require_relative '../logging'
5
+
6
+ module CemAcpt
7
+ module Utils
8
+ module WinRMRunner
9
+ class WinRMExecutionError < StandardError; end
10
+
11
+ # A wrapper class for executing commands on a windows node
12
+ # Provides error handling and logging on each command being executed
13
+ class RunnerWrapper
14
+ include CemAcpt::Logging
15
+
16
+ def initialize(shell)
17
+ @shell = shell
18
+ end
19
+
20
+ def run(command)
21
+ output = @shell.run(command) do |stdout, stderr|
22
+ puts stdout
23
+ puts stderr
24
+ end
25
+
26
+ # At any point, if the exit code is not 0, we will raise an error and stop the test
27
+ # Because if one command fails, it will effect the rest of the test.
28
+ if output.exitcode != 0
29
+ raise WinRMExecutionError, "Command #{command} failed with exit code #{output.exitcode}. Error: #{output.stderr}"
30
+ end
31
+ end
32
+ end
33
+
34
+ # A class for setting up windows nodes for testing
35
+ # Applies puppet manifest on nodes and installs the cem_windows module
36
+ class WinNode
37
+ include CemAcpt::Logging
38
+
39
+ attr_reader :mod_name
40
+
41
+ def initialize(login_info, mod_name)
42
+ @login_info = login_info
43
+ @mod_name = mod_name
44
+ end
45
+
46
+ def run_winrm
47
+ logger.info('CemAcpt') { 'Setting up Windows nodes for test...' }
48
+
49
+ cem_windows_mod_dir = "C:\\ProgramData\\PuppetLabs\\code\\environments\\production\\modules\\cem_windows"
50
+
51
+ @login_info.each do |node_name, node_info|
52
+ username = node_info['username']
53
+ password = node_info['password']
54
+ test_name = node_info['test_name']
55
+ ip = node_info['ip']
56
+
57
+ opts = {
58
+ endpoint: "https://#{ip}:5986/wsman",
59
+ user: username,
60
+ password: password,
61
+ transport: :ssl,
62
+ no_ssl_peer_verification: true,
63
+ retry_limit: 5,
64
+ retry_delay: 20,
65
+ }
66
+
67
+ conn = WinRM::Connection.new(opts)
68
+ conn.shell(:powershell) do |shell|
69
+ # Instantiate the wrapper class
70
+ winrm_runner = RunnerWrapper.new(shell)
71
+ # The below steps for enabling long paths, installing Puppet and make cem_acpt directory will be a part of making the custom image.
72
+ # Enable long paths
73
+ logger.debug('CemAcpt') { 'Enabling long paths...' }
74
+ winrm_runner.run('Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\FileSystem" -Name "LongPathsEnabled" -Value 1')
75
+ # Download Puppet and Install it
76
+ logger.info('CemAcpt') { 'Downloading and installing Puppet...' }
77
+ winrm_runner.run('Invoke-WebRequest -URI http://downloads.puppetlabs.com/windows/puppet7/puppet-agent-7.25.0-x64.msi -OutFile "C:\puppet-agent-7.25.0-x64.msi"')
78
+ winrm_runner.run("Start-process msiexec.exe -Wait -ArgumentList '/I C:\\puppet-agent-7.25.0-x64.msi PUPPET_AGENT_STARTUP_MODE=Disabled /qn /norestart'")
79
+ # Create our cem_acpt parent directory
80
+ logger.debug('CemAcpt') { 'Creating cem_acpt directory and downloading necessary files...' }
81
+ winrm_runner.run("mkdir C:\\cem_acpt")
82
+ # Download the cem_windows module from the bucket to cem_acpt directory
83
+ winrm_runner.run("gcloud storage cp gs://win_cem_acpt/#{@mod_name} C:\\cem_acpt")
84
+ # Install the cem_windows module
85
+ logger.info('CemAcpt') { 'Installing cem_windows module...' }
86
+ winrm_runner.run("Start-Process -FilePath 'C:\\Program Files\\Puppet Labs\\Puppet\\bin\\puppet.bat' -ArgumentList 'module install C:\\cem_acpt\\#{@mod_name}' -Wait -NoNewWindow")
87
+ # Download Goss and set it to alpha mode since it's on alpha for windows
88
+ logger.info('CemAcpt') { 'Downloading Goss...' }
89
+ winrm_runner.run("Invoke-WebRequest -URI https://github.com/goss-org/goss/releases/download/v0.3.23/goss-alpha-windows-amd64.exe -OutFile 'C:\\goss.exe'")
90
+ # Since we already installed the cem_windows module, we can unpack it to get the goss.yaml and the manifest.pp in spec/acceptance dir
91
+ # Due to the pain in the ass of trying to parse a string that is yaml dumped in ruby through powershell, I'm just gonna create the yaml
92
+ # in the cem_windows module itself and then move them to the directory at C:/cem_acpt for ease of access and use. The location for these two
93
+ # files will be at spec/files
94
+ winrm_runner.run("Move-Item -Path #{cem_windows_mod_dir}\\spec\\files\\puppet_idempotent.yaml -Destination C:\\cem_acpt")
95
+ winrm_runner.run("Move-Item -Path #{cem_windows_mod_dir}\\spec\\files\\puppet_noop.yaml -Destination C:\\cem_acpt")
96
+ # Grab the goss.yaml and the manifest.pp from the cem_windows module using the test_name from run_data and move to top level of C:/cem_acpt/
97
+ winrm_runner.run("Move-Item -Path #{cem_windows_mod_dir}\\spec\\acceptance\\#{test_name}\\goss.yaml -Destination C:\\cem_acpt")
98
+ winrm_runner.run("Move-Item -Path #{cem_windows_mod_dir}\\spec\\acceptance\\#{test_name}\\manifest.pp -Destination C:\\cem_acpt")
99
+ # Open up firewall ports
100
+ logger.debug('CemAcpt') { 'Opening up firewall ports...' }
101
+ winrm_runner.run('netsh advfirewall firewall add rule name="Goss in" dir=in action=allow protocol=TCP localport=8080')
102
+ winrm_runner.run('netsh advfirewall firewall add rule name="Idempotent in" dir=in action=allow protocol=TCP localport=8081')
103
+ winrm_runner.run('netsh advfirewall firewall add rule name="Noop in" dir=in action=allow protocol=TCP localport=8082')
104
+ winrm_runner.run('netsh advfirewall firewall add rule name="Goss out" dir=out action=allow protocol=TCP localport=8080')
105
+ winrm_runner.run('netsh advfirewall firewall add rule name="Idempotent out" dir=out action=allow protocol=TCP localport=8081')
106
+ winrm_runner.run('netsh advfirewall firewall add rule name="Noop out" dir=out action=allow protocol=TCP localport=8082')
107
+ logger.info('CemAcpt') { 'Setting up NSSM and starting Goss services...' }
108
+ # Download NSSM into cem_acpt directory
109
+ # NSSM is a tool that allows us to create services on windows without having to create a Windows service project.
110
+ # NSSM works with almost any executable. In our case, it will work with goss.exe
111
+ winrm_runner.run("Invoke-WebRequest -URI http://nssm.cc/ci/nssm-2.24-101-g897c7ad.zip -OutFile 'C:\\cem_acpt\\nssm-2.24.zip'")
112
+ # Extract NSSM into cem_acpt directory
113
+ winrm_runner.run("Expand-Archive -Path C:\\cem_acpt\\nssm-2.24.zip -DestinationPath C:\\cem_acpt")
114
+ winrm_runner.run("Rename-Item -Path C:\\cem_acpt\\nssm-2.24-101-g897c7ad -NewName nssm-2.24")
115
+ # Install NSSM Goss server and start it
116
+ winrm_runner.run('C:\\cem_acpt\\nssm-2.24\\win64\\nssm.exe install goss-server C:\\goss.exe')
117
+ winrm_runner.run('C:\\cem_acpt\\nssm-2.24\\win64\\nssm.exe set goss-server AppStdout C:\\cem_acpt\\goss-server.log')
118
+ winrm_runner.run('C:\\cem_acpt\\nssm-2.24\\win64\\nssm.exe set goss-server AppStderr C:\\cem_acpt\\goss-server-stderr.log')
119
+ winrm_runner.run('C:\\cem_acpt\\nssm-2.24\\win64\\nssm.exe set goss-server AppParameters "--use-alpha=1 -g C:\\cem_acpt\\goss.yaml serve -f json --endpoint /acpt --cache 10m"')
120
+ # There is a slight issue with starting through nssm, if the process took a bit too long, it will send an error but the process will still be running.
121
+ # winrm_runner.run(shell, 'C:\\cem_acpt\\nssm-2.24\\win64\\nssm.exe start goss-server')
122
+ winrm_runner.run('$GossServer= Get-Service "goss-server"')
123
+ winrm_runner.run('$GossServer.Start()')
124
+ winrm_runner.run('$WaitForGossServer = New-TimeSpan -Seconds 5')
125
+ winrm_runner.run('$GossServer.WaitForStatus([System.ServiceProcess.ServiceControllerStatus]::Running, $WaitForGossServer)')
126
+ # Install NSSM Idempotent server and start it
127
+ winrm_runner.run('C:\\cem_acpt\\nssm-2.24\\win64\\nssm.exe install idempotent-server C:\\goss.exe')
128
+ winrm_runner.run('C:\\cem_acpt\\nssm-2.24\\win64\\nssm.exe set idempotent-server AppStdout C:\\cem_acpt\\idempotent-server.log')
129
+ winrm_runner.run('C:\\cem_acpt\\nssm-2.24\\win64\\nssm.exe set idempotent-server AppStderr C:\\cem_acpt\\idempotent-server-stderr.log')
130
+ winrm_runner.run('C:\\cem_acpt\\nssm-2.24\\win64\\nssm.exe set idempotent-server AppParameters "--use-alpha=1 -g C:\\cem_acpt\\puppet_idempotent.yaml serve -f json -l :8081 --endpoint /idempotent --cache 10m"')
131
+ # winrm_runner.run(shell, 'C:\\cem_acpt\\nssm-2.24\\win64\\nssm.exe start idempotent-server')
132
+ winrm_runner.run('$IdmServer= Get-Service "idempotent-server"')
133
+ winrm_runner.run('$IdmServer.Start()')
134
+ winrm_runner.run('$WaitForIdmServer = New-TimeSpan -Seconds 5')
135
+ winrm_runner.run('$IdmServer.WaitForStatus([System.ServiceProcess.ServiceControllerStatus]::Running, $WaitForIdmServer)')
136
+ # Install NSSM Noop server and start it
137
+ winrm_runner.run('C:\\cem_acpt\\nssm-2.24\\win64\\nssm.exe install noop-server C:\\goss.exe')
138
+ winrm_runner.run('C:\\cem_acpt\\nssm-2.24\\win64\\nssm.exe set noop-server AppStdout C:\\cem_acpt\\noop-server.log')
139
+ winrm_runner.run('C:\\cem_acpt\\nssm-2.24\\win64\\nssm.exe set noop-server AppStderr C:\\cem_acpt\\noop-server-stderr.log')
140
+ winrm_runner.run('C:\\cem_acpt\\nssm-2.24\\win64\\nssm.exe set noop-server AppParameters "--use-alpha=1 -g C:\\cem_acpt\\puppet_noop.yaml serve -f json -l :8082 --endpoint /noop --cache 10m"')
141
+ # winrm_runner.run(shell, 'C:\\cem_acpt\\nssm-2.24\\win64\\nssm.exe start noop-server')
142
+ winrm_runner.run('$NoopServer= Get-Service "noop-server"')
143
+ winrm_runner.run('$NoopServer.Start()')
144
+ winrm_runner.run('$WaitForNoopServer = New-TimeSpan -Seconds 5')
145
+ winrm_runner.run('$NoopServer.WaitForStatus([System.ServiceProcess.ServiceControllerStatus]::Running, $WaitForNoopServer)')
146
+ # Run puppet apply
147
+ logger.info('CemAcpt') { 'Running puppet apply...' }
148
+ winrm_runner.run("Start-Process -FilePath 'C:\\Program Files\\Puppet Labs\\Puppet\\bin\\puppet.bat' -ArgumentList 'apply --verbose C:\\cem_acpt\\manifest.pp' -Wait -NoNewWindow -PassThru")
149
+ logger.info('CemAcpt') { 'Puppet apply finished...' }
150
+ end
151
+ end
152
+ end
153
+
154
+ def run
155
+ run_winrm
156
+ end
157
+ end
158
+ end
159
+ end
160
+ end
@@ -3,8 +3,48 @@
3
3
  require_relative 'utils/puppet'
4
4
  require_relative 'utils/ssh'
5
5
  require_relative 'utils/terminal'
6
+ require_relative 'logging'
6
7
 
7
8
  module CemAcpt
8
9
  # Utility methods and modules for CemAcpt.
9
- module Utils; end
10
+ module Utils
11
+ class << self
12
+ include CemAcpt::Logging
13
+
14
+ def package_win_module(module_dir)
15
+ # Path to the package file
16
+ package_file = File.join(module_dir, 'puppetlabs-cem_windows.tar.gz')
17
+
18
+ # Remove the old package file if it exists
19
+ FileUtils.rm_f(package_file)
20
+ `cd #{module_dir} && touch puppetlabs-cem_windows.tar.gz && tar -czf puppetlabs-cem_windows.tar.gz --exclude=puppetlabs-cem_windows.tar.gz *`
21
+ logger.info('CemAcpt') { "Windows module packaged at #{package_file}" }
22
+ return package_file
23
+ end
24
+
25
+ def reset_password_readiness_polling(instance_name)
26
+ result = %x(echo Y | gcloud compute reset-windows-password #{instance_name} --zone=us-west1-b)
27
+ while result.empty?
28
+ logger.info('CemAcpt') { "Waiting for instance #{instance_name} to be ready for password reset..." }
29
+ sleep 10
30
+ result = %x(echo Y | gcloud compute reset-windows-password #{instance_name} --zone=us-west1-b)
31
+ end
32
+ return result
33
+ end
34
+
35
+ def get_windows_login_info(instance_name, hash_of_instance)
36
+ password_and_username = {}
37
+ password_and_username[instance_name] = {}
38
+ info = reset_password_readiness_polling(instance_name).split(/\r?\n/)[1..2]
39
+ info.each do |line|
40
+ key_val = line.split(' ')
41
+ password_and_username[instance_name][key_val[0].strip.delete(':')] = key_val[1].strip
42
+ end
43
+
44
+ password_and_username[instance_name]['ip'] = hash_of_instance['ip']
45
+ password_and_username[instance_name]['test_name'] = hash_of_instance['test_name']
46
+ password_and_username
47
+ end
48
+ end
49
+ end
10
50
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CemAcpt
4
- VERSION = '0.7.2'
4
+ VERSION = '0.8.0'
5
5
  end