cem_acpt 0.7.3 → 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.
- checksums.yaml +4 -4
- data/.rubocop.yml +3 -1
- data/Gemfile.lock +42 -14
- data/README.md +8 -0
- data/cem_acpt.gemspec +3 -1
- data/exe/cem_acpt_image +0 -0
- data/lib/cem_acpt/action_result.rb +85 -0
- data/lib/cem_acpt/cli.rb +42 -22
- data/lib/cem_acpt/config/base.rb +4 -2
- data/lib/cem_acpt/goss/api.rb +8 -2
- data/lib/cem_acpt/image_builder.rb +64 -72
- data/lib/cem_acpt/logging.rb +41 -30
- data/lib/cem_acpt/platform.rb +4 -5
- data/lib/cem_acpt/provision/terraform/linux.rb +2 -2
- data/lib/cem_acpt/provision/terraform/terraform_cmd.rb +181 -0
- data/lib/cem_acpt/provision/terraform/windows.rb +9 -0
- data/lib/cem_acpt/provision/terraform.rb +34 -47
- data/lib/cem_acpt/provision.rb +1 -1
- data/lib/cem_acpt/puppet_helpers.rb +1 -1
- data/lib/cem_acpt/test_data.rb +3 -3
- data/lib/cem_acpt/test_runner/log_formatter/error_formatter.rb +33 -0
- data/lib/cem_acpt/test_runner/log_formatter.rb +10 -1
- data/lib/cem_acpt/test_runner.rb +151 -52
- data/lib/cem_acpt/utils/ssh.rb +2 -2
- data/lib/cem_acpt/utils/terminal.rb +1 -5
- data/lib/cem_acpt/utils/winrm_runner.rb +160 -0
- data/lib/cem_acpt/utils.rb +41 -1
- data/lib/cem_acpt/version.rb +1 -1
- data/lib/cem_acpt.rb +51 -21
- data/lib/terraform/gcp/windows/main.tf +26 -0
- metadata +46 -8
data/lib/cem_acpt/test_runner.rb
CHANGED
@@ -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 =
|
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
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
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
|
-
|
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.
|
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.
|
134
|
-
|
135
|
-
|
136
|
-
|
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
|
-
|
141
|
-
|
142
|
-
CemAcpt::
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
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
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
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
|
data/lib/cem_acpt/utils/ssh.rb
CHANGED
@@ -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
|
-
|
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
|
data/lib/cem_acpt/utils.rb
CHANGED
@@ -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
|
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
|
data/lib/cem_acpt/version.rb
CHANGED