cem_acpt 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|