cem_acpt 0.1.0 → 0.2.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/.gitignore +7 -0
- data/Gemfile.lock +36 -15
- data/README.md +8 -4
- data/cem_acpt.gemspec +10 -10
- data/exe/cem_acpt +29 -3
- data/lib/cem_acpt/context.rb +132 -39
- data/lib/cem_acpt/core_extensions.rb +9 -12
- data/lib/cem_acpt/logging.rb +177 -19
- data/lib/cem_acpt/platform/base/cmd.rb +8 -2
- data/lib/cem_acpt/platform/gcp/cmd.rb +162 -79
- data/lib/cem_acpt/platform/gcp/compute.rb +6 -1
- data/lib/cem_acpt/platform/gcp.rb +3 -3
- data/lib/cem_acpt/puppet_helpers.rb +1 -0
- data/lib/cem_acpt/rspec_utils.rb +242 -0
- data/lib/cem_acpt/shared_objects.rb +147 -26
- data/lib/cem_acpt/spec_helper_acceptance.rb +21 -13
- data/lib/cem_acpt/test_data.rb +3 -14
- data/lib/cem_acpt/test_runner/run_handler.rb +187 -0
- data/lib/cem_acpt/test_runner/runner.rb +228 -0
- data/lib/cem_acpt/test_runner/runner_result.rb +103 -0
- data/lib/cem_acpt/test_runner.rb +10 -0
- data/lib/cem_acpt/utils.rb +84 -12
- data/lib/cem_acpt/version.rb +1 -1
- data/lib/cem_acpt.rb +18 -11
- metadata +47 -44
- data/.travis.yml +0 -6
- data/lib/cem_acpt/runner.rb +0 -304
data/lib/cem_acpt/logging.rb
CHANGED
@@ -5,8 +5,55 @@ require 'logger'
|
|
5
5
|
module CemAcpt
|
6
6
|
# Logging for CemAcpt
|
7
7
|
module Logging
|
8
|
+
LEVEL_MAP = {
|
9
|
+
'debug' => Logger::DEBUG,
|
10
|
+
'info' => Logger::INFO,
|
11
|
+
'warn' => Logger::WARN,
|
12
|
+
'error' => Logger::ERROR,
|
13
|
+
'fatal' => Logger::FATAL,
|
14
|
+
'unknown' => Logger::UNKNOWN,
|
15
|
+
}
|
16
|
+
|
17
|
+
# Delegator class for when you want to log to multiple devices
|
18
|
+
# at the same time, such as STDOUT and a file.
|
19
|
+
# @param loggers [::Logger] one or more instances of Logger
|
20
|
+
class MultiLogger
|
21
|
+
def initialize(*loggers)
|
22
|
+
@loggers = loggers
|
23
|
+
end
|
24
|
+
|
25
|
+
def method_missing(m, *args, &block)
|
26
|
+
if @loggers.all? { |l| l.respond_to?(m) }
|
27
|
+
@loggers.map { |l| l.send(m, *args, &block) }
|
28
|
+
else
|
29
|
+
super
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def respond_to_missing?(m, include_private = false)
|
34
|
+
@loggers.all? { |l| l.respond_to?(m) } || super
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
8
38
|
class << self
|
9
|
-
|
39
|
+
def new_logger(*logdevs, **configs)
|
40
|
+
new_configs = current_log_config.merge(configs.reject { |_, v| v.nil? })
|
41
|
+
if new_configs.key?(:level) && !LEVEL_MAP.values.include?(new_configs[:level])
|
42
|
+
new_configs[:level] = LEVEL_MAP[new_configs[:level].downcase]
|
43
|
+
end
|
44
|
+
loggers = logdevs.map do |dev|
|
45
|
+
logdev = dev.is_a?(String) ? $stdout : dev
|
46
|
+
logger = Logger.new(
|
47
|
+
logdev,
|
48
|
+
new_configs[:shift_age],
|
49
|
+
new_configs[:shift_size],
|
50
|
+
**new_configs.reject { |k, _| %i[logdev shift_age shift_size].include?(k) },
|
51
|
+
)
|
52
|
+
logger.reopen(dev) if dev.is_a?(String)
|
53
|
+
logger
|
54
|
+
end
|
55
|
+
@logger = CemAcpt::Logging::MultiLogger.new(*loggers)
|
56
|
+
end
|
10
57
|
|
11
58
|
# Exposes a logger instance. Will either use the currently set logger or
|
12
59
|
# create a new one.
|
@@ -29,22 +76,9 @@ module CemAcpt
|
|
29
76
|
# Shortcut method to set logger.level
|
30
77
|
# @param level [String] the log level to set
|
31
78
|
def new_log_level(level)
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
when 'info'
|
36
|
-
@logger.level = Logger::INFO
|
37
|
-
when 'warn'
|
38
|
-
@logger.level = Logger::WARN
|
39
|
-
when 'error'
|
40
|
-
@logger.level = Logger::ERROR
|
41
|
-
when 'fatal'
|
42
|
-
@logger.level = Logger::FATAL
|
43
|
-
when 'unknown'
|
44
|
-
@logger.level = Logger::UNKNOWN
|
45
|
-
else
|
46
|
-
raise "Cannot set log level #{level}: invalid level"
|
47
|
-
end
|
79
|
+
raise ArgumentError, 'Log level not recognized' unless LEVEL_MAP[level.downcase]
|
80
|
+
|
81
|
+
@logger.level = LEVEL_MAP[level.downcase]
|
48
82
|
end
|
49
83
|
|
50
84
|
# Shows the current log format style if set, or the default if not.
|
@@ -64,7 +98,7 @@ module CemAcpt
|
|
64
98
|
@current_log_format = :json
|
65
99
|
proc do |severity, datetime, progname, msg|
|
66
100
|
{
|
67
|
-
timestamp: datetime
|
101
|
+
timestamp: datetime,
|
68
102
|
progname: progname,
|
69
103
|
severity: severity,
|
70
104
|
message: msg,
|
@@ -75,10 +109,22 @@ module CemAcpt
|
|
75
109
|
proc do |severity, _datetime, _progname, msg|
|
76
110
|
"#{severity} - #{msg}\n"
|
77
111
|
end
|
112
|
+
when :github_action
|
113
|
+
@current_log_format = :github_action
|
114
|
+
sev_map = {
|
115
|
+
'DEBUG' => '::debug::',
|
116
|
+
'INFO' => '::notice::',
|
117
|
+
'WARN' => '::warning::',
|
118
|
+
'ERROR' => '::error::',
|
119
|
+
'FATAL' => '::error::',
|
120
|
+
}
|
121
|
+
proc do |severity, _datetime, _progname, msg|
|
122
|
+
"#{sev_map[severity]}{#{msg}}\n"
|
123
|
+
end
|
78
124
|
else
|
79
125
|
@current_log_format = :file
|
80
126
|
proc do |severity, datetime, _progname, msg|
|
81
|
-
"[#{datetime
|
127
|
+
"[#{datetime}] #{severity}: #{msg}\n"
|
82
128
|
end
|
83
129
|
end
|
84
130
|
end
|
@@ -143,6 +189,10 @@ module CemAcpt
|
|
143
189
|
# Provides class method wrappers for logging methods
|
144
190
|
def self.included(base)
|
145
191
|
class << base
|
192
|
+
def new_logger(*logdevs, **configs)
|
193
|
+
CemAcpt::Logging.new_logger(*logdevs, **configs)
|
194
|
+
end
|
195
|
+
|
146
196
|
def logger
|
147
197
|
CemAcpt::Logging.logger
|
148
198
|
end
|
@@ -155,6 +205,14 @@ module CemAcpt
|
|
155
205
|
CemAcpt::Logging.new_log_level(level)
|
156
206
|
end
|
157
207
|
|
208
|
+
def current_log_format
|
209
|
+
CemAcpt::Logging.current_log_format
|
210
|
+
end
|
211
|
+
|
212
|
+
def new_log_formatter(f)
|
213
|
+
CemAcpt::Logging.new_log_formatter(f)
|
214
|
+
end
|
215
|
+
|
158
216
|
def current_log_config
|
159
217
|
CemAcpt::Logging.current_log_config
|
160
218
|
end
|
@@ -165,6 +223,10 @@ module CemAcpt
|
|
165
223
|
end
|
166
224
|
end
|
167
225
|
|
226
|
+
def new_logger(*logdevs, **configs)
|
227
|
+
CemAcpt::Logging.new_logger(*logdevs, **configs)
|
228
|
+
end
|
229
|
+
|
168
230
|
# Exposes the logger instance
|
169
231
|
def logger
|
170
232
|
CemAcpt::Logging.logger
|
@@ -180,6 +242,14 @@ module CemAcpt
|
|
180
242
|
CemAcpt::Logging.new_log_level(level)
|
181
243
|
end
|
182
244
|
|
245
|
+
def current_log_format
|
246
|
+
CemAcpt::Logging.current_log_format
|
247
|
+
end
|
248
|
+
|
249
|
+
def new_log_formatter(f)
|
250
|
+
CemAcpt::Logging.new_log_formatter(f)
|
251
|
+
end
|
252
|
+
|
183
253
|
# Exposes the current log config
|
184
254
|
def current_log_config
|
185
255
|
CemAcpt::Logging.current_log_config
|
@@ -190,4 +260,92 @@ module CemAcpt
|
|
190
260
|
CemAcpt::Logging.new_log_config(logdev: logdev, shift_age: shift_age, shift_size: shift_size, level: level, formatter: formatter, datetime_format: datetime_format)
|
191
261
|
end
|
192
262
|
end
|
263
|
+
|
264
|
+
module LoggingAsync
|
265
|
+
require 'concurrent-ruby'
|
266
|
+
|
267
|
+
class << self
|
268
|
+
def log_write_thread
|
269
|
+
@log_write_thread ||= Concurrent::SingleThreadExecutor.new
|
270
|
+
end
|
271
|
+
|
272
|
+
def async_debug(message, prefix = nil)
|
273
|
+
msg = prefix.nil? || prefix.empty? ? message : "#{prefix} #{message}"
|
274
|
+
CemAcpt::Logging.logger.debug(msg)
|
275
|
+
end
|
276
|
+
|
277
|
+
def async_info(message, prefix = nil)
|
278
|
+
msg = prefix.nil? || prefix.empty? ? message : "#{prefix} #{message}"
|
279
|
+
CemAcpt::Logging.logger.info(msg)
|
280
|
+
end
|
281
|
+
|
282
|
+
def async_warn(message, prefix = nil)
|
283
|
+
msg = prefix.nil? || prefix.empty? ? message : "#{prefix} #{message}"
|
284
|
+
CemAcpt::Logging.logger.warn(msg)
|
285
|
+
end
|
286
|
+
|
287
|
+
def async_error(message, prefix = nil)
|
288
|
+
msg = prefix.nil? || prefix.empty? ? message : "#{prefix} #{message}"
|
289
|
+
CemAcpt::Logging.logger.error(msg)
|
290
|
+
end
|
291
|
+
|
292
|
+
def async_fatal(message, prefix = nil)
|
293
|
+
msg = prefix.nil? || prefix.empty? ? message : "#{prefix} #{message}"
|
294
|
+
CemAcpt::Logging.logger.fatal(msg)
|
295
|
+
end
|
296
|
+
end
|
297
|
+
|
298
|
+
# Provides class method wrappers for logging methods
|
299
|
+
def self.included(base)
|
300
|
+
class << base
|
301
|
+
def log_write_thread
|
302
|
+
CemAcpt::LoggingAsync.log_write_thread
|
303
|
+
end
|
304
|
+
|
305
|
+
def async_debug(message, prefix = nil)
|
306
|
+
CemAcpt::LoggingAsync.async_debug(message, prefix)
|
307
|
+
end
|
308
|
+
|
309
|
+
def async_info(message, prefix = nil)
|
310
|
+
CemAcpt::LoggingAsync.async_info(message, prefix)
|
311
|
+
end
|
312
|
+
|
313
|
+
def async_warn(message, prefix = nil)
|
314
|
+
CemAcpt::LoggingAsync.async_warn(message, prefix)
|
315
|
+
end
|
316
|
+
|
317
|
+
def async_error(message, prefix = nil)
|
318
|
+
CemAcpt::LoggingAsync.async_error(message, prefix)
|
319
|
+
end
|
320
|
+
|
321
|
+
def async_fatal(message, prefix = nil)
|
322
|
+
CemAcpt::LoggingAsync.async_fatal(message, prefix)
|
323
|
+
end
|
324
|
+
end
|
325
|
+
end
|
326
|
+
|
327
|
+
def log_write_thread
|
328
|
+
CemAcpt::LoggingAsync.log_write_thread
|
329
|
+
end
|
330
|
+
|
331
|
+
def async_debug(message, prefix = nil)
|
332
|
+
CemAcpt::LoggingAsync.async_debug(message, prefix)
|
333
|
+
end
|
334
|
+
|
335
|
+
def async_info(message, prefix = nil)
|
336
|
+
CemAcpt::LoggingAsync.async_info(message, prefix)
|
337
|
+
end
|
338
|
+
|
339
|
+
def async_warn(message, prefix = nil)
|
340
|
+
CemAcpt::LoggingAsync.async_warn(message, prefix)
|
341
|
+
end
|
342
|
+
|
343
|
+
def async_error(message, prefix = nil)
|
344
|
+
CemAcpt::LoggingAsync.async_error(message, prefix)
|
345
|
+
end
|
346
|
+
|
347
|
+
def async_fatal(message, prefix = nil)
|
348
|
+
CemAcpt::LoggingAsync.async_fatal(message, prefix)
|
349
|
+
end
|
350
|
+
end
|
193
351
|
end
|
@@ -3,11 +3,17 @@
|
|
3
3
|
module CemAcpt::Platform
|
4
4
|
require_relative File.join(__dir__, '..', '..', 'logging.rb')
|
5
5
|
|
6
|
+
class CmdError < StandardError; end
|
7
|
+
|
6
8
|
# Base class for command providers. Provides an API for subclasses to implement.
|
7
9
|
class CmdBase
|
8
10
|
include CemAcpt::Logging
|
9
11
|
|
10
|
-
|
12
|
+
attr_reader :env
|
13
|
+
|
14
|
+
def initialize(*_args, env: {}, **_kwargs)
|
15
|
+
@env = env
|
16
|
+
end
|
11
17
|
|
12
18
|
def local_exec(*_args, **_kwargs)
|
13
19
|
raise NotImplementedError, '#local_exec must be implemented by a subclass'
|
@@ -56,7 +62,7 @@ module CemAcpt::Platform
|
|
56
62
|
return output
|
57
63
|
rescue StandardError => e
|
58
64
|
last_error = e
|
59
|
-
sleep(
|
65
|
+
sleep(10)
|
60
66
|
end
|
61
67
|
end
|
62
68
|
raise last_error
|
@@ -1,19 +1,18 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'json'
|
4
|
-
require 'open3'
|
5
|
-
require_relative File.join(__dir__, '..', 'base', 'cmd.rb')
|
6
|
-
|
7
3
|
module CemAcpt::Platform::Gcp
|
4
|
+
require 'json'
|
5
|
+
require 'open3'
|
6
|
+
require_relative File.join(__dir__, '..', 'base', 'cmd.rb')
|
7
|
+
|
8
8
|
# This class provides methods to run gcloud commands. It allows for default values to be
|
9
9
|
# set for the project, zone, and user and can also find these values from the local config.
|
10
10
|
# Additionally, this class provides a way to run SSH commands against GCP VMs using IAP.
|
11
11
|
class Cmd < CemAcpt::Platform::CmdBase
|
12
|
-
def initialize(project: nil, zone: nil, out_format: nil, filter: nil, user_name: nil, local_port: nil)
|
13
|
-
super
|
12
|
+
def initialize(project: nil, zone: nil, out_format: nil, filter: nil, user_name: nil, local_port: nil, ssh_key: nil)
|
13
|
+
super(env: { 'CLOUDSDK_PYTHON_SITEPACKAGES' => '1' })
|
14
14
|
require 'net/ssh'
|
15
15
|
require 'net/ssh/proxy/command'
|
16
|
-
require 'net/scp'
|
17
16
|
|
18
17
|
@project = project unless project.nil?
|
19
18
|
@zone = zone unless zone.nil?
|
@@ -21,8 +20,9 @@ module CemAcpt::Platform::Gcp
|
|
21
20
|
@default_filter = filter
|
22
21
|
@user_name = user_name
|
23
22
|
@local_port = local_port
|
24
|
-
|
25
|
-
raise 'gcloud
|
23
|
+
@ssh_key = ssh_key
|
24
|
+
raise CemAcpt::Platform::CmdError, 'gcloud command not available' unless gcloud?
|
25
|
+
raise CemAcpt::Platform::CmdError, 'gcloud is not authenticated' unless authenticated?
|
26
26
|
end
|
27
27
|
|
28
28
|
# Returns either the project name passed in during object initialization
|
@@ -40,25 +40,37 @@ module CemAcpt::Platform::Gcp
|
|
40
40
|
# Returns either the user name passed in during object initialization
|
41
41
|
# or queries the current active, authenticated user from gcloud and returns the name.
|
42
42
|
def user_name
|
43
|
-
@user_name ||= authenticated_user_name
|
43
|
+
@user_name ||= authenticated_user_name
|
44
44
|
end
|
45
45
|
|
46
46
|
# Returns the format string passed in during object initialization or
|
47
47
|
# the default format string.
|
48
48
|
def format(out_format = nil)
|
49
|
-
out_format&.chomp || @default_out_format
|
49
|
+
out_format&.chomp || @default_out_format
|
50
50
|
end
|
51
51
|
|
52
52
|
# Returns the filter string passed in during object initialization or
|
53
53
|
# the default filter string.
|
54
54
|
def filter(out_filter = nil)
|
55
|
-
out_filter&.chomp || @default_filter
|
55
|
+
out_filter&.chomp || @default_filter
|
56
56
|
end
|
57
57
|
|
58
58
|
def local_port
|
59
59
|
@local_port ||= rand(49_512..65_535)
|
60
60
|
end
|
61
61
|
|
62
|
+
def ssh_key
|
63
|
+
return @ssh_key unless @ssh_key.nil?
|
64
|
+
|
65
|
+
if File.exist?(File.join([ENV['HOME'], '.ssh', 'acpt_test_key']))
|
66
|
+
@ssh_key = File.join([ENV['HOME'], '.ssh', 'acpt_test_key'])
|
67
|
+
else
|
68
|
+
logger.debug("Test SSH key not found at #{File.join([ENV['HOME'], '.ssh', 'acpt_test_key'])}, using default")
|
69
|
+
@ssh_key = File.join([ENV['HOME'], '.ssh', 'google_compute_engine'])
|
70
|
+
end
|
71
|
+
@ssh_key
|
72
|
+
end
|
73
|
+
|
62
74
|
# Returns a formatted hash of ssh options to be used with Net::SSH.start.
|
63
75
|
# If you pass in a GCP VM instance name, this method will configure the
|
64
76
|
# IAP tunnel ProxyCommand to use. If you pass in an opts hash, it will
|
@@ -74,21 +86,26 @@ module CemAcpt::Platform::Gcp
|
|
74
86
|
|
75
87
|
# Runs `gcloud` commands locally.
|
76
88
|
def local_exec(command, out_format: 'json', out_filter: nil, project_flag: true)
|
77
|
-
|
78
|
-
|
79
|
-
cmd_parts << "--format=#{format(out_format)}" if format(out_format)
|
80
|
-
cmd_parts << "--filter=\"#{filter(out_filter)}\"" if filter(out_filter)
|
81
|
-
final_command = cmd_parts.join(' ')
|
82
|
-
stdout, stderr, status = Open3.capture3(final_command)
|
89
|
+
final_command = format_gcloud_command(command, out_format: out_format, out_filter: out_filter, project_flag: project_flag)
|
90
|
+
stdout, stderr, status = Open3.capture3(env, final_command)
|
83
91
|
raise "gcloud command '#{final_command}' failed: #{stderr}" unless status.success?
|
84
92
|
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
93
|
+
if format(out_format) == 'json'
|
94
|
+
begin
|
95
|
+
::JSON.parse(stdout)
|
96
|
+
rescue ::JSON::ParserError
|
97
|
+
<<<<<<< HEAD
|
98
|
+
stdout
|
99
|
+
end
|
100
|
+
else
|
101
|
+
stdout
|
102
|
+
=======
|
103
|
+
stdout.chomp
|
104
|
+
end
|
105
|
+
else
|
106
|
+
stdout.chomp
|
107
|
+
>>>>>>> 489757f (Fixes for race conditions)
|
89
108
|
end
|
90
|
-
|
91
|
-
stdout
|
92
109
|
end
|
93
110
|
|
94
111
|
# Stops a GCP VM instance.
|
@@ -112,7 +129,9 @@ module CemAcpt::Platform::Gcp
|
|
112
129
|
# on the given CGP VM via SSH. Using `ssh` does not invoke the gcloud command
|
113
130
|
# and is dependent on the system's SSH configuration.
|
114
131
|
def ssh(instance_name, cmd, ignore_command_errors: false, use_proxy_command: true, opts: {})
|
115
|
-
Net::SSH.start(instance_name, user_name,
|
132
|
+
Net::SSH.start(instance_name, user_name,
|
133
|
+
ssh_opts(instance_name: instance_name, use_proxy_command: use_proxy_command, opts: opts)) do |ssh|
|
134
|
+
logger.debug("Running SSH command '#{cmd}' on instance '#{instance_name}'")
|
116
135
|
result = ssh.exec!(cmd)
|
117
136
|
if result.exitstatus != 0 && !ignore_command_errors
|
118
137
|
abridged_cmd = cmd.length > 100 ? "#{cmd[0..100]}..." : cmd
|
@@ -126,29 +145,45 @@ module CemAcpt::Platform::Gcp
|
|
126
145
|
def scp_upload(instance_name, local, remote, use_proxy_command: true, scp_opts: {}, opts: {})
|
127
146
|
raise "Local file #{local} does not exist" unless File.exist?(local)
|
128
147
|
|
129
|
-
|
130
|
-
|
131
|
-
scp
|
132
|
-
|
148
|
+
cmd = [
|
149
|
+
'compute',
|
150
|
+
'scp',
|
151
|
+
]
|
152
|
+
cmd << '--strict-host-key-checking=no'
|
153
|
+
cmd << "--tunnel-through-iap" if use_proxy_command
|
154
|
+
cmd << "--recurse" if scp_opts[:recurse]
|
155
|
+
cmd << "#{local} #{instance_name}:#{remote}"
|
156
|
+
local_exec(cmd.join(' '), out_format: 'json')
|
133
157
|
end
|
134
158
|
|
135
159
|
# Downloads a file from the specified VM.
|
136
160
|
def scp_download(instance_name, remote, local, use_proxy_command: true, scp_opts: {}, opts: {})
|
137
161
|
raise "Local file #{local} does not exist" unless File.exist?(local)
|
138
162
|
|
139
|
-
|
140
|
-
|
141
|
-
scp
|
142
|
-
|
163
|
+
cmd = [
|
164
|
+
'compute',
|
165
|
+
'scp',
|
166
|
+
]
|
167
|
+
cmd << '--strict-host-key-checking=no'
|
168
|
+
cmd << '--tunnel-through-iap' if use_proxy_command
|
169
|
+
cmd << '--recurse' if scp_opts[:recurse]
|
170
|
+
cmd << "#{instance_name}:#{remote} #{local}"
|
171
|
+
local_exec(cmd.join(' '), out_format: 'json')
|
143
172
|
end
|
144
173
|
|
145
|
-
def ssh_ready?(instance_name,
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
174
|
+
def ssh_ready?(instance_name, opts: {})
|
175
|
+
ssh_options = ssh_opts(instance_name: instance_name, opts: opts)
|
176
|
+
logger.debug("Testing SSH connection to #{instance_name} with options #{ssh_options}")
|
177
|
+
gcloud_ssh(instance_name, 'echo "SSH is ready"')
|
178
|
+
logger.debug('Removing ecdsa & ed25519 host keys from config due to bug in jruby-openssl')
|
179
|
+
gcloud_ssh(instance_name, 'sudo sed -E -i "s;HostKey /etc/ssh/ssh_host_(ecdsa|ed25519)_key;;g" /etc/ssh/sshd_config')
|
180
|
+
logger.debug('Restarting SSH service')
|
181
|
+
gcloud_ssh(instance_name, 'sudo systemctl restart sshd', ignore_command_errors: true)
|
182
|
+
logger.info("SSH connection to #{instance_name} is ready")
|
183
|
+
true
|
184
|
+
rescue StandardError => e
|
185
|
+
logger.debug("SSH connection to #{instance_name} failed: #{e}")
|
186
|
+
false
|
152
187
|
end
|
153
188
|
|
154
189
|
# This function spawns a background thread to run a GCP IAP tunnel, run the given
|
@@ -160,9 +195,7 @@ module CemAcpt::Platform::Gcp
|
|
160
195
|
# @param instance_name [String] The name of the GCP VM instance to connect to.
|
161
196
|
# @param instance_port [Integer] The port to connect to on the GCP VM instance.
|
162
197
|
# @param disable_connection_check [Boolean] If true, the connection check will be disabled.
|
163
|
-
def with_iap_tunnel(instance_name, instance_port = 22, disable_connection_check: false)
|
164
|
-
return unless block_given?
|
165
|
-
|
198
|
+
def with_iap_tunnel(instance_name, instance_port = 22, disable_connection_check: false, &block)
|
166
199
|
cmd = [
|
167
200
|
'compute start-iap-tunnel',
|
168
201
|
"#{instance_name} #{instance_port}",
|
@@ -174,16 +207,12 @@ module CemAcpt::Platform::Gcp
|
|
174
207
|
port: local_port,
|
175
208
|
forward_agent: false,
|
176
209
|
}
|
210
|
+
final_cmd = format_gcloud_command(cmd.join(' '), out_format: nil, project_flag: false)
|
211
|
+
tunnel_pid = Process.spawn(env, final_cmd)
|
177
212
|
begin
|
178
|
-
|
179
|
-
local_exec(cmd.join(' '))
|
180
|
-
end
|
181
|
-
yield ssh_opts(use_proxy_command: false, opts: tunnel_ssh_opts)
|
182
|
-
thread.exit
|
183
|
-
rescue IOError
|
184
|
-
# Ignore errors when killing the thread.
|
213
|
+
block.call(ssh_opts(use_proxy_command: false, opts: tunnel_ssh_opts))
|
185
214
|
ensure
|
186
|
-
|
215
|
+
Process.kill('TERM', tunnel_pid)
|
187
216
|
end
|
188
217
|
end
|
189
218
|
|
@@ -205,47 +234,87 @@ module CemAcpt::Platform::Gcp
|
|
205
234
|
instance_name,
|
206
235
|
command.join(' '),
|
207
236
|
ignore_command_errors: true,
|
208
|
-
use_proxy_command: false,
|
209
237
|
opts: ssh_opts,
|
210
238
|
)
|
211
239
|
end
|
212
240
|
|
213
241
|
def apply_manifest(instance_name, manifest, opts = {})
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
temp_manifest.close
|
219
|
-
begin
|
220
|
-
scp_upload(
|
221
|
-
instance_name,
|
222
|
-
temp_manifest.path,
|
223
|
-
'/tmp/acpt_manifest.pp',
|
224
|
-
opts: opts[:ssh_opts],
|
225
|
-
)
|
226
|
-
ensure
|
227
|
-
temp_manifest.unlink
|
242
|
+
<<<<<<< HEAD
|
243
|
+
keep_manifest_file = opts[:apply][:keep_manifest_file] || false
|
244
|
+
with_temp_manifest_file(manifest, keep_manifest_file: keep_manifest_file) do |tf|
|
245
|
+
upload_temp_manifest(instance_name, tf.path, opts: opts)
|
228
246
|
end
|
229
|
-
apply_cmd = [
|
230
|
-
|
231
|
-
|
232
|
-
|
247
|
+
apply_cmd = ["#{opts[:puppet_path]} apply /tmp/acpt_manifest.pp"]
|
248
|
+
=======
|
249
|
+
unless opts[:apply][:no_upload]
|
250
|
+
with_temp_manifest_file(manifest) do |tf|
|
251
|
+
upload_temp_manifest(instance_name, tf.path, remote_path: '/tmp/acpt_manifest.pp', opts: opts)
|
252
|
+
end
|
253
|
+
end
|
254
|
+
apply_cmd = [opts[:puppet_path], 'apply', '/tmp/acpt_manifest.pp']
|
255
|
+
>>>>>>> 489757f (Fixes for race conditions)
|
233
256
|
apply_cmd << '--trace' if opts[:apply][:trace]
|
234
257
|
apply_cmd << "--hiera_config=#{opts[:apply][:hiera_config]}" if opts[:apply][:hiera_config]
|
235
258
|
apply_cmd << '--debug' if opts[:apply][:debug]
|
236
259
|
apply_cmd << '--noop' if opts[:apply][:noop]
|
237
260
|
apply_cmd << '--detailed-exitcodes' if opts[:apply][:detailed_exitcodes]
|
238
261
|
|
239
|
-
|
262
|
+
run_shell(
|
240
263
|
instance_name,
|
241
264
|
apply_cmd.join(' '),
|
242
|
-
|
243
|
-
opts: opts[:ssh_opts]
|
265
|
+
opts
|
244
266
|
)
|
245
267
|
end
|
246
268
|
|
247
269
|
private
|
248
270
|
|
271
|
+
def format_gcloud_command(command, out_format: 'json', out_filter: nil, project_flag: true)
|
272
|
+
cmd_parts = ['gcloud', command]
|
273
|
+
cmd_parts << "--project=#{project.chomp}" unless !project_flag || project.nil?
|
274
|
+
cmd_parts << "--format=#{format(out_format)}" if format(out_format)
|
275
|
+
cmd_parts << "--filter=\"#{filter(out_filter)}\"" if filter(out_filter)
|
276
|
+
cmd_parts.join(' ')
|
277
|
+
end
|
278
|
+
|
279
|
+
<<<<<<< HEAD
|
280
|
+
def with_temp_manifest_file(manifest, file_name: 'acpt_manifest', keep_manifest_file: false)
|
281
|
+
=======
|
282
|
+
def with_temp_manifest_file(manifest, file_name: 'acpt_manifest')
|
283
|
+
>>>>>>> 489757f (Fixes for race conditions)
|
284
|
+
require 'tempfile'
|
285
|
+
tf = Tempfile.new(file_name)
|
286
|
+
tf.write(manifest)
|
287
|
+
tf.close
|
288
|
+
begin
|
289
|
+
yield tf
|
290
|
+
ensure
|
291
|
+
<<<<<<< HEAD
|
292
|
+
if keep_manifest_file
|
293
|
+
@manifest_file_exists = true
|
294
|
+
else
|
295
|
+
tf.unlink
|
296
|
+
@manifest_file_exists = false
|
297
|
+
end
|
298
|
+
=======
|
299
|
+
tf.unlink
|
300
|
+
>>>>>>> 489757f (Fixes for race conditions)
|
301
|
+
end
|
302
|
+
end
|
303
|
+
|
304
|
+
def upload_temp_manifest(instance_name, local_path, remote_path: '/tmp/acpt_manifest.pp', opts: {})
|
305
|
+
<<<<<<< HEAD
|
306
|
+
scp_upload(instance_name, local_path, remote_path, opts: opts[:ssh_opts]) unless @manifest_file_exists
|
307
|
+
=======
|
308
|
+
scp_upload(instance_name, local_path, remote_path, opts: opts[:ssh_opts]) unless opts[:apply][:no_upload]
|
309
|
+
>>>>>>> 489757f (Fixes for race conditions)
|
310
|
+
end
|
311
|
+
|
312
|
+
def gcloud_ssh(instance_name, command, ignore_command_errors: false)
|
313
|
+
local_exec("compute ssh --ssh-key-file #{ssh_key} --tunnel-through-iap #{instance_name} --command='#{command}'")
|
314
|
+
rescue StandardError => e
|
315
|
+
raise e unless ignore_command_errors
|
316
|
+
end
|
317
|
+
|
249
318
|
def vm_alias(vm)
|
250
319
|
"compute.#{vm_describe(vm)['id']}"
|
251
320
|
end
|
@@ -253,21 +322,35 @@ module CemAcpt::Platform::Gcp
|
|
253
322
|
# Default options for Net::SSH
|
254
323
|
def default_ssh_opts
|
255
324
|
{
|
256
|
-
|
257
|
-
|
325
|
+
<<<<<<< HEAD
|
326
|
+
verify_host_key: :never,
|
327
|
+
keys: [ssh_key],
|
328
|
+
kex: ['diffie-hellman-group-exchange-sha256'], # ecdh algos cause jruby to shit the bed
|
258
329
|
keys_only: true,
|
259
330
|
config: false,
|
331
|
+
=======
|
332
|
+
auth_methods: ['publickey'],
|
333
|
+
>>>>>>> 489757f (Fixes for race conditions)
|
260
334
|
check_host_ip: false,
|
335
|
+
compression: true,
|
336
|
+
config: false,
|
337
|
+
keys: [ssh_key],
|
338
|
+
keys_only: true,
|
339
|
+
kex: ['diffie-hellman-group-exchange-sha256'], # ecdh algos cause jruby to shit the bed
|
261
340
|
non_interactive: true,
|
262
341
|
port: 22,
|
263
342
|
user: user_name,
|
264
|
-
user_known_hosts_file: File.join(ENV['HOME'], '.ssh', '
|
343
|
+
user_known_hosts_file: File.join(ENV['HOME'], '.ssh', 'acpt_test_known_hosts'),
|
344
|
+
<<<<<<< HEAD
|
345
|
+
=======
|
346
|
+
verify_host_key: :never,
|
347
|
+
>>>>>>> 489757f (Fixes for race conditions)
|
265
348
|
}
|
266
349
|
end
|
267
350
|
|
268
351
|
# Returns a Proxy::Command object to use for the SSH option ProxyCommand.
|
269
352
|
# This works in the same way that `gcloud compute ssh --tunnel-through-iap` does.
|
270
|
-
def proxy_command(instance_name, port: 22, quiet: true, verbosity:
|
353
|
+
def proxy_command(instance_name, port: 22, quiet: true, verbosity: 'debug')
|
271
354
|
proxy_command = [
|
272
355
|
'gcloud compute start-iap-tunnel',
|
273
356
|
"#{instance_name} #{port}",
|
@@ -275,7 +358,7 @@ module CemAcpt::Platform::Gcp
|
|
275
358
|
"--project=#{project}",
|
276
359
|
"--zone=#{zone}",
|
277
360
|
]
|
278
|
-
proxy_command << '--no-user-output-enabled --quiet' if quiet
|
361
|
+
#proxy_command << '--no-user-output-enabled --quiet' if quiet
|
279
362
|
proxy_command << "--verbosity=#{verbosity}" unless quiet || verbosity.nil? || verbosity.empty?
|
280
363
|
Net::SSH::Proxy::Command.new(proxy_command.join(' '))
|
281
364
|
end
|
@@ -307,7 +390,7 @@ module CemAcpt::Platform::Gcp
|
|
307
390
|
|
308
391
|
raise 'failed to find authenticated user name' unless uname
|
309
392
|
|
310
|
-
uname.
|
393
|
+
uname.gsub(/[^a-zA-Z0-9_]/, '_')
|
311
394
|
end
|
312
395
|
end
|
313
396
|
end
|