opswalrus 1.0.16 → 1.0.18
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +18 -1
- data/lib/opswalrus/app.rb +52 -17
- data/lib/opswalrus/bootstrap.sh +1 -0
- data/lib/opswalrus/bundler.rb +18 -34
- data/lib/opswalrus/cli.rb +24 -10
- data/lib/opswalrus/host.rb +26 -12
- data/lib/opswalrus/interaction_handlers.rb +17 -16
- data/lib/opswalrus/invocation.rb +71 -41
- data/lib/opswalrus/local_non_blocking_backend.rb +0 -1
- data/lib/opswalrus/operation_runner.rb +32 -17
- data/lib/opswalrus/ops_file.rb +3 -2
- data/lib/opswalrus/ops_file_script.rb +81 -10
- data/lib/opswalrus/ops_file_script_dsl.rb +13 -70
- data/lib/opswalrus/package_file.rb +11 -11
- data/lib/opswalrus/runtime_environment.rb +21 -41
- data/lib/opswalrus/version.rb +1 -1
- data/lib/opswalrus.rb +1 -1
- data/opswalrus.gemspec +4 -2
- metadata +30 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 27cea4b01102c518b26f6b47e47a6461ff76aac39f4ab0094955aa6413d3e9f7
|
4
|
+
data.tar.gz: ce1b064a905fea231d4d551513a74f33f726cfcfdede0e4a2660acefc17c1b62
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5bd3daea688e85fe18e8f61bb455b238478b8be1f6caf9be2bed096af0d20187155d4882a2fb2732044eaee3f93303f1592b3025fbdfc74775a323007bd1a544
|
7
|
+
data.tar.gz: 027a8423627f1af58966e9064157c72cdfca7aec5e74f04130cd8b563ec23217ee9dc0da9ccf43afdc1b07f39bd2ab69e3d744d7355b9f27ae1536d16180caea
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
opswalrus (1.0.
|
4
|
+
opswalrus (1.0.18)
|
5
5
|
amazing_print (~> 1.5)
|
6
6
|
bcrypt_pbkdf (~> 1.1)
|
7
7
|
citrus (~> 3.0)
|
@@ -9,8 +9,10 @@ PATH
|
|
9
9
|
git (~> 1.18)
|
10
10
|
gli (~> 2.21)
|
11
11
|
ougai (~> 2.0)
|
12
|
+
pastel (~> 0.8)
|
12
13
|
rubyzip (~> 2.3)
|
13
14
|
sshkit (~> 1.21)
|
15
|
+
tty-editor (~> 0.7)
|
14
16
|
|
15
17
|
GEM
|
16
18
|
remote: https://rubygems.org/
|
@@ -32,6 +34,8 @@ GEM
|
|
32
34
|
oj (3.16.0)
|
33
35
|
ougai (2.0.0)
|
34
36
|
oj (~> 3.10)
|
37
|
+
pastel (0.8.0)
|
38
|
+
tty-color (~> 0.5)
|
35
39
|
public_suffix (5.0.3)
|
36
40
|
rake (13.0.6)
|
37
41
|
rchardet (1.8.0)
|
@@ -52,6 +56,19 @@ GEM
|
|
52
56
|
sshkit (1.21.5)
|
53
57
|
net-scp (>= 1.1.2)
|
54
58
|
net-ssh (>= 2.8.0)
|
59
|
+
tty-color (0.6.0)
|
60
|
+
tty-cursor (0.7.1)
|
61
|
+
tty-editor (0.7.0)
|
62
|
+
tty-prompt (~> 0.22)
|
63
|
+
tty-prompt (0.23.1)
|
64
|
+
pastel (~> 0.8)
|
65
|
+
tty-reader (~> 0.8)
|
66
|
+
tty-reader (0.9.0)
|
67
|
+
tty-cursor (~> 0.7)
|
68
|
+
tty-screen (~> 0.8)
|
69
|
+
wisper (~> 2.0)
|
70
|
+
tty-screen (0.8.1)
|
71
|
+
wisper (2.0.1)
|
55
72
|
|
56
73
|
PLATFORMS
|
57
74
|
x86_64-linux
|
data/lib/opswalrus/app.rb
CHANGED
@@ -1,14 +1,16 @@
|
|
1
|
+
require "amazing_print"
|
1
2
|
require "citrus"
|
2
3
|
require "io/console"
|
3
4
|
require "json"
|
4
5
|
# require "logger"
|
5
6
|
require "random/formatter"
|
6
7
|
require "ougai"
|
8
|
+
require "pastel"
|
9
|
+
require "pathname"
|
7
10
|
require "shellwords"
|
8
11
|
require "socket"
|
9
12
|
require "stringio"
|
10
13
|
require "yaml"
|
11
|
-
require "pathname"
|
12
14
|
require_relative "errors"
|
13
15
|
require_relative "patches"
|
14
16
|
require_relative "git"
|
@@ -21,6 +23,8 @@ require_relative "version"
|
|
21
23
|
|
22
24
|
|
23
25
|
module OpsWalrus
|
26
|
+
Style = Pastel.new(enabled: $stdout.tty?)
|
27
|
+
|
24
28
|
class App
|
25
29
|
def self.instance(*args)
|
26
30
|
@instance ||= new(*args)
|
@@ -32,9 +36,14 @@ module OpsWalrus
|
|
32
36
|
attr_reader :local_hostname
|
33
37
|
|
34
38
|
def initialize(pwd = Dir.pwd)
|
35
|
-
@logger = Ougai::Logger.new($stdout
|
39
|
+
@logger = Ougai::Logger.new($stdout) # Logger.new($stdout, level: Logger::INFO)
|
40
|
+
@logger.level = :info # , :trace or 'trace'
|
36
41
|
@logger.formatter = Ougai::Formatters::Readable.new
|
37
42
|
|
43
|
+
# @logger.warn Style.yellow("warn"), foo: "bar", baz: {qux: "quux"}
|
44
|
+
# @logger.info Style.yellow("info"), foo: "bar", baz: {qux: "quux"}
|
45
|
+
# @logger.debug Style.yellow("debug"), foo: "bar", baz: {qux: "quux"}
|
46
|
+
# @logger.trace Style.yellow("trace"), foo: "bar", baz: {qux: "quux"}
|
38
47
|
|
39
48
|
@verbose = false
|
40
49
|
@sudo_user = nil
|
@@ -46,6 +55,7 @@ module OpsWalrus
|
|
46
55
|
@bundler = Bundler.new(@pwd)
|
47
56
|
@local_hostname = "localhost"
|
48
57
|
@mode = :report # :report | :script
|
58
|
+
@dry_run = false
|
49
59
|
end
|
50
60
|
|
51
61
|
def to_s
|
@@ -68,6 +78,14 @@ module OpsWalrus
|
|
68
78
|
@mode == :script
|
69
79
|
end
|
70
80
|
|
81
|
+
def dry_run?
|
82
|
+
@dry_run
|
83
|
+
end
|
84
|
+
|
85
|
+
def dry_run!
|
86
|
+
@dry_run = true
|
87
|
+
end
|
88
|
+
|
71
89
|
def set_local_hostname(hostname)
|
72
90
|
hostname = hostname.strip
|
73
91
|
@local_hostname = hostname.empty? ? "localhost" : hostname
|
@@ -91,37 +109,54 @@ module OpsWalrus
|
|
91
109
|
@bundler.bundle_dir
|
92
110
|
end
|
93
111
|
|
94
|
-
|
95
|
-
|
96
|
-
|
112
|
+
# log_level = :fatal, :error, :warn, :info, :debug, :trace
|
113
|
+
# irb(main):018:0> Ougai::Logger::TRACE
|
114
|
+
# => -1
|
115
|
+
# irb(main):019:0> Ougai::Logger::DEBUG
|
116
|
+
# => 0
|
117
|
+
# irb(main):020:0> Ougai::Logger::INFO
|
118
|
+
# => 1
|
119
|
+
# irb(main):021:0> Ougai::Logger::WARN
|
120
|
+
# => 2
|
121
|
+
# irb(main):022:0> Ougai::Logger::ERROR
|
122
|
+
# => 3
|
123
|
+
# irb(main):023:0> Ougai::Logger::FATAL
|
124
|
+
# => 4
|
125
|
+
def set_log_level(log_level)
|
126
|
+
@logger.level = log_level
|
97
127
|
end
|
98
128
|
|
99
129
|
def verbose?
|
100
|
-
@
|
130
|
+
@logger.level <= 1
|
101
131
|
end
|
102
132
|
|
103
133
|
def debug?
|
104
|
-
@
|
134
|
+
@logger.level <= 0
|
135
|
+
end
|
136
|
+
|
137
|
+
def fatal(*args)
|
138
|
+
@logger.fatal(*args)
|
105
139
|
end
|
106
140
|
|
107
|
-
def error(
|
108
|
-
@logger.error(
|
141
|
+
def error(*args)
|
142
|
+
@logger.error(*args)
|
109
143
|
end
|
110
144
|
|
111
|
-
def warn(
|
112
|
-
@logger.warn(
|
145
|
+
def warn(*args)
|
146
|
+
@logger.warn(*args)
|
113
147
|
end
|
114
148
|
|
115
|
-
def log(
|
116
|
-
@logger.info(
|
149
|
+
def log(*args)
|
150
|
+
@logger.info(*args)
|
117
151
|
end
|
152
|
+
alias_method :info, :log
|
118
153
|
|
119
|
-
def debug(
|
120
|
-
@logger.debug(
|
154
|
+
def debug(*args)
|
155
|
+
@logger.debug(*args)
|
121
156
|
end
|
122
157
|
|
123
|
-
def trace(
|
124
|
-
@logger.trace(
|
158
|
+
def trace(*args)
|
159
|
+
@logger.trace(*args)
|
125
160
|
end
|
126
161
|
|
127
162
|
def set_pwd(pwd)
|
data/lib/opswalrus/bootstrap.sh
CHANGED
@@ -9,6 +9,7 @@ if [ -x "$(command -v /home/linuxbrew/.linuxbrew/bin/brew)" ]; then
|
|
9
9
|
echo 'Ruby is already installed.' >&2
|
10
10
|
|
11
11
|
# make sure the latest opswalrus gem is installed
|
12
|
+
# todo: figure out how to install this differently, so that test versions will work
|
12
13
|
gem install opswalrus
|
13
14
|
|
14
15
|
exit 0
|
data/lib/opswalrus/bundler.rb
CHANGED
@@ -26,6 +26,10 @@ module OpsWalrus
|
|
26
26
|
FileUtils.mkdir_p(@bundle_dir) unless @bundle_dir.exist?
|
27
27
|
end
|
28
28
|
|
29
|
+
def delete_pwd_bundle_directory
|
30
|
+
FileUtils.remove_dir(@bundle_dir) if @bundle_dir.exist?
|
31
|
+
end
|
32
|
+
|
29
33
|
# # returns the OpsFile within the bundle directory that represents the given ops_file (which is outside of the bundle directory)
|
30
34
|
# def build_bundle_for_ops_file(ops_file)
|
31
35
|
# if ops_file.package_file # ops_file is part of larger package
|
@@ -50,6 +54,7 @@ module OpsWalrus
|
|
50
54
|
# end
|
51
55
|
|
52
56
|
def update
|
57
|
+
delete_pwd_bundle_directory
|
53
58
|
ensure_pwd_bundle_directory_exists
|
54
59
|
|
55
60
|
package_yaml_files = pwd.glob("./**/package.yaml") - pwd.glob("./**/#{BUNDLE_DIR}/**/package.yaml")
|
@@ -110,36 +115,16 @@ module OpsWalrus
|
|
110
115
|
version = package_reference.version
|
111
116
|
|
112
117
|
destination_package_path = @bundle_dir.join(package_reference.import_resolution_dirname)
|
113
|
-
|
118
|
+
|
119
|
+
# we return early here under the assumption that an already downloaded package/version combo will not
|
120
|
+
# differ if we download it again multiple times to the same location
|
121
|
+
if destination_package_path.exist?
|
122
|
+
App.instance.log("Skipping #{package_reference} referenced in #{package_file.package_file_path} since it already has been downloaded to #{destination_package_path}")
|
123
|
+
return destination_package_path
|
124
|
+
end
|
125
|
+
# FileUtils.remove_dir(destination_package_path) if destination_package_path.exist?
|
114
126
|
|
115
127
|
download_package_contents(package_file, local_name, package_url, version, destination_package_path)
|
116
|
-
# case
|
117
|
-
# when package_url =~ /\.git/ # git reference
|
118
|
-
# download_git_package(package_url, version, destination_package_path)
|
119
|
-
# when package_url.start_with?("file://") # local path
|
120
|
-
# path = package_url.sub("file://", "")
|
121
|
-
# path = path.to_pathname
|
122
|
-
# package_path_to_download = if path.relative? # relative path
|
123
|
-
# package_file.containing_directory.join(path)
|
124
|
-
# else # absolute path
|
125
|
-
# path.realpath
|
126
|
-
# end
|
127
|
-
|
128
|
-
# raise Error, "Package not found: #{package_path_to_download}" unless package_path_to_download.exist?
|
129
|
-
# FileUtils.cp_r(package_path_to_download, destination_package_path)
|
130
|
-
# when package_url.to_pathname.exist? || package_file.containing_directory.join(package_url).exist? # local path
|
131
|
-
# path = package_url.to_pathname
|
132
|
-
# package_path_to_download = if path.relative? # relative path
|
133
|
-
# package_file.containing_directory.join(path)
|
134
|
-
# else # absolute path
|
135
|
-
# path.realpath
|
136
|
-
# end
|
137
|
-
|
138
|
-
# raise Error, "Package not found: #{package_path_to_download}" unless File.exist?(package_path_to_download)
|
139
|
-
# FileUtils.cp_r(package_path_to_download, destination_package_path)
|
140
|
-
# else # git reference
|
141
|
-
# download_git_package(package_url, version, destination_package_path)
|
142
|
-
# end
|
143
128
|
|
144
129
|
destination_package_path
|
145
130
|
end
|
@@ -147,10 +132,12 @@ module OpsWalrus
|
|
147
132
|
def download_package_contents(package_file, local_name, package_url, version, destination_package_path)
|
148
133
|
package_path = package_url.to_pathname
|
149
134
|
package_path = package_path.to_s.gsub(/^~/, Dir.home).to_pathname
|
135
|
+
App.instance.trace("download_package_contents #{package_path}")
|
150
136
|
if package_path.absolute? && package_path.exist? # absolute path reference
|
151
137
|
return case
|
152
138
|
when package_path.directory?
|
153
139
|
package_path_to_download = package_path.realpath
|
140
|
+
App.instance.debug("Copying #{package_path_to_download} to #{destination_package_path}")
|
154
141
|
FileUtils.cp_r(package_path_to_download, destination_package_path)
|
155
142
|
when package_path.file?
|
156
143
|
raise Error, "Package reference must be a directory, not a file:: #{local_name}: #{package_path}"
|
@@ -163,6 +150,7 @@ module OpsWalrus
|
|
163
150
|
if rebased_path.exist?
|
164
151
|
return case
|
165
152
|
when rebased_path.directory?
|
153
|
+
App.instance.debug("Copying #{package_path_to_download} to #{destination_package_path}")
|
166
154
|
package_path_to_download = rebased_path.realpath
|
167
155
|
FileUtils.cp_r(package_path_to_download, destination_package_path)
|
168
156
|
when rebased_path.file?
|
@@ -174,6 +162,7 @@ module OpsWalrus
|
|
174
162
|
end
|
175
163
|
|
176
164
|
if package_uri = Git.repo?(package_url) # git repo
|
165
|
+
App.instance.debug("Cloning repo #{package_uri} into #{destination_package_path}")
|
177
166
|
download_git_package(package_uri, version, destination_package_path)
|
178
167
|
end
|
179
168
|
end
|
@@ -195,15 +184,10 @@ module OpsWalrus
|
|
195
184
|
end
|
196
185
|
|
197
186
|
def dynamic_package_path_for_git_package(package_url, version = nil)
|
198
|
-
package_reference_dirname =
|
187
|
+
package_reference_dirname = DynamicPackageReference.import_resolution_dirname(package_url, version)
|
199
188
|
bundle_dir.join(package_reference_dirname)
|
200
189
|
end
|
201
190
|
|
202
|
-
def sanitize_path(path)
|
203
|
-
# found this at https://apidock.com/rails/v5.2.3/ActiveStorage/Filename/sanitized
|
204
|
-
path.encode(Encoding::UTF_8, invalid: :replace, undef: :replace, replace: "�").strip.tr("\u{202E}%$|:;/\t\r\n\\", "-")
|
205
|
-
end
|
206
|
-
|
207
191
|
# returns the directory that the zip file is unzipped into
|
208
192
|
def unzip(zip_bundle_file, output_dir = nil)
|
209
193
|
if zip_bundle_file.to_pathname.exist?
|
data/lib/opswalrus/cli.rb
CHANGED
@@ -9,7 +9,7 @@ module OpsWalrus
|
|
9
9
|
|
10
10
|
pre do |global_options, command, options, args|
|
11
11
|
$app = App.instance(Dir.pwd)
|
12
|
-
$app.set_local_hostname(ENV["
|
12
|
+
$app.set_local_hostname(ENV["OPSWALRUS_LOCAL_HOSTNAME"]) if ENV["OPSWALRUS_LOCAL_HOSTNAME"]
|
13
13
|
true
|
14
14
|
end
|
15
15
|
|
@@ -31,6 +31,10 @@ module OpsWalrus
|
|
31
31
|
desc 'Turn on debug mode'
|
32
32
|
switch [:d, :debug]
|
33
33
|
|
34
|
+
switch :noop, desc: "Perform a dry run"
|
35
|
+
switch :dryrun, desc: "Perform a dry run"
|
36
|
+
switch :dry_run, desc: "Perform a dry run"
|
37
|
+
|
34
38
|
flag [:h, :hosts], multiple: true, desc: "Specify the hosts.yaml file"
|
35
39
|
flag [:t, :tags], multiple: true, desc: "Specify a set of tags to filter the hosts by"
|
36
40
|
|
@@ -48,7 +52,8 @@ module OpsWalrus
|
|
48
52
|
hosts = global_options[:hosts]
|
49
53
|
tags = global_options[:tags]
|
50
54
|
|
51
|
-
|
55
|
+
log_level = global_options[:debug] && :trace || global_options[:verbose] && :debug || :info
|
56
|
+
$app.set_log_level(log_level)
|
52
57
|
|
53
58
|
$app.report_inventory(hosts, tags: tags)
|
54
59
|
end
|
@@ -63,28 +68,31 @@ module OpsWalrus
|
|
63
68
|
c.flag [:p, :params], desc: "JSON string that represents the input parameters for the operation. The JSON string must conform to the params schema for the operation."
|
64
69
|
c.switch :script, desc: "Script mode"
|
65
70
|
|
71
|
+
# dry run
|
72
|
+
c.switch :noop, desc: "Perform a dry run"
|
73
|
+
c.switch :dryrun, desc: "Perform a dry run"
|
74
|
+
c.switch :dry_run, desc: "Perform a dry run"
|
75
|
+
|
66
76
|
c.action do |global_options, options, args|
|
77
|
+
log_level = global_options[:debug] && :trace || global_options[:verbose] && :debug || :info
|
78
|
+
$app.set_log_level(log_level)
|
79
|
+
|
67
80
|
hosts = global_options[:hosts] || []
|
68
81
|
tags = global_options[:tags] || []
|
69
82
|
|
70
83
|
$app.set_inventory_hosts(hosts)
|
71
84
|
$app.set_inventory_tags(tags)
|
72
85
|
|
73
|
-
verbose = case
|
74
|
-
when global_options[:debug]
|
75
|
-
2
|
76
|
-
when global_options[:verbose]
|
77
|
-
1
|
78
|
-
end
|
79
|
-
|
80
86
|
user = options[:user]
|
81
87
|
params = options[:params]
|
82
88
|
|
83
|
-
$app.set_verbose(verbose)
|
84
89
|
$app.set_params(params)
|
85
90
|
|
86
91
|
$app.set_sudo_user(user) if user
|
87
92
|
|
93
|
+
dry_run = [:noop, :dryrun, :dry_run].any? {|sym| global_options[sym] || options[sym] }
|
94
|
+
$app.dry_run! if dry_run
|
95
|
+
|
88
96
|
if options[:pass]
|
89
97
|
$app.prompt_sudo_password
|
90
98
|
end
|
@@ -107,6 +115,9 @@ module OpsWalrus
|
|
107
115
|
long_desc 'Download and bundle the latest versions of dependencies for the current package'
|
108
116
|
c.command :update do |update|
|
109
117
|
update.action do |global_options, options, args|
|
118
|
+
log_level = global_options[:debug] && :trace || global_options[:verbose] && :debug || :info
|
119
|
+
$app.set_log_level(log_level)
|
120
|
+
|
110
121
|
$app.bundle_update
|
111
122
|
end
|
112
123
|
end
|
@@ -125,6 +136,9 @@ module OpsWalrus
|
|
125
136
|
unzip.flag [:o, :output], desc: "Specify the output directory"
|
126
137
|
|
127
138
|
unzip.action do |global_options, options, args|
|
139
|
+
log_level = global_options[:debug] && :trace || global_options[:verbose] && :debug || :info
|
140
|
+
$app.set_log_level(log_level)
|
141
|
+
|
128
142
|
output_dir = options[:output]
|
129
143
|
zip_file_path = args.first
|
130
144
|
|
data/lib/opswalrus/host.rb
CHANGED
@@ -18,20 +18,20 @@ module OpsWalrus
|
|
18
18
|
ops_file.local_symbol_table.each do |symbol_name, import_reference|
|
19
19
|
unless methods_defined.include? symbol_name
|
20
20
|
klass.define_method(symbol_name) do |*args, **kwargs, &block|
|
21
|
-
|
21
|
+
App.instance.trace "resolving local symbol table entry: #{symbol_name}"
|
22
22
|
namespace_or_ops_file = @runtime_env.resolve_import_reference(ops_file, import_reference)
|
23
|
-
|
23
|
+
App.instance.trace "namespace_or_ops_file=#{namespace_or_ops_file.to_s}"
|
24
24
|
|
25
25
|
invocation_context = case import_reference
|
26
26
|
# we know we're dealing with a package dependency reference, so we want to run an ops file contained within the bundle directory,
|
27
27
|
# therefore, we want to reference the specified ops file with respect to the bundle dir
|
28
28
|
when PackageDependencyReference
|
29
|
-
RemoteImportInvocationContext.new(@runtime_env, self, namespace_or_ops_file, true)
|
29
|
+
RemoteImportInvocationContext.new(@runtime_env, self, namespace_or_ops_file, true, prompt_for_sudo_password: !!ssh_password)
|
30
30
|
|
31
31
|
# we know we're dealing with a directory reference or OpsFile reference outside of the bundle dir, so we want to reference
|
32
32
|
# the specified ops file with respect to the root directory, and not with respect to the bundle dir
|
33
33
|
when DirectoryReference, OpsFileReference
|
34
|
-
RemoteImportInvocationContext.new(@runtime_env, self, namespace_or_ops_file, false)
|
34
|
+
RemoteImportInvocationContext.new(@runtime_env, self, namespace_or_ops_file, false, prompt_for_sudo_password: !!ssh_password)
|
35
35
|
end
|
36
36
|
|
37
37
|
invocation_context._invoke(*args, **kwargs)
|
@@ -49,7 +49,11 @@ module OpsWalrus
|
|
49
49
|
unless methods_defined.include? symbol_name
|
50
50
|
# puts "2. defining: #{symbol_name}(...)"
|
51
51
|
klass.define_method(symbol_name) do |*args, **kwargs, &block|
|
52
|
-
|
52
|
+
App.instance.trace "resolving implicit import: #{symbol_name}"
|
53
|
+
namespace_or_ops_file = @runtime_env.resolve_sibling_symbol(ops_file, symbol_name)
|
54
|
+
App.instance.trace "namespace_or_ops_file=#{namespace_or_ops_file.to_s}"
|
55
|
+
|
56
|
+
invocation_context = RemoteImportInvocationContext.new(@runtime_env, self, namespace_or_ops_file, false, prompt_for_sudo_password: !!ssh_password)
|
53
57
|
invocation_context._invoke(*args, **kwargs)
|
54
58
|
end
|
55
59
|
methods_defined << symbol_name
|
@@ -107,13 +111,14 @@ module OpsWalrus
|
|
107
111
|
#cmd = Shellwords.escape(cmd)
|
108
112
|
|
109
113
|
if App.instance.report_mode?
|
114
|
+
puts Style.green("*" * 80)
|
110
115
|
if self.alias
|
111
|
-
print "[#{self.alias} | #{host}] "
|
116
|
+
print "[#{Style.blue(self.alias)} | #{Style.blue(host)}] "
|
112
117
|
else
|
113
|
-
print "[#{host}] "
|
118
|
+
print "[#{Style.blue(host)}] "
|
114
119
|
end
|
115
120
|
print "#{description}: " if description
|
116
|
-
puts cmd
|
121
|
+
puts Style.yellow(cmd)
|
117
122
|
end
|
118
123
|
|
119
124
|
return unless cmd && !cmd.strip.empty?
|
@@ -122,9 +127,12 @@ module OpsWalrus
|
|
122
127
|
# puts "shell: #{cmd.inspect}"
|
123
128
|
# puts "sudo_password: #{sudo_password}"
|
124
129
|
|
125
|
-
|
126
|
-
|
127
|
-
|
130
|
+
if App.instance.dry_run?
|
131
|
+
["", "", 0]
|
132
|
+
else
|
133
|
+
sshkit_cmd = execute_cmd(cmd, input: input)
|
134
|
+
[sshkit_cmd.full_stdout, sshkit_cmd.full_stderr, sshkit_cmd.exit_status]
|
135
|
+
end
|
128
136
|
end
|
129
137
|
|
130
138
|
# def init_brew
|
@@ -137,7 +145,13 @@ module OpsWalrus
|
|
137
145
|
# e.g. /home/linuxbrew/.linuxbrew/bin/gem exec -g opswalrus ops run echo.ops args:foo args:bar
|
138
146
|
|
139
147
|
# cmd = "/home/linuxbrew/.linuxbrew/bin/gem exec -g opswalrus ops"
|
140
|
-
|
148
|
+
local_hostname_for_remote_host = if self.alias
|
149
|
+
"#{self.alias} | #{host}"
|
150
|
+
else
|
151
|
+
host
|
152
|
+
end
|
153
|
+
|
154
|
+
cmd = "OPSWALRUS_LOCAL_HOSTNAME='#{local_hostname_for_remote_host}'; /home/linuxbrew/.linuxbrew/bin/gem exec -g opswalrus ops"
|
141
155
|
cmd << " -v" if verbose
|
142
156
|
cmd << " #{ops_command.to_s}"
|
143
157
|
cmd << " #{ops_command_options.to_s}" if ops_command_options
|
@@ -5,6 +5,7 @@ module OpsWalrus
|
|
5
5
|
class ScopedMappingInteractionHandler
|
6
6
|
attr_accessor :input_mappings # Hash[ String | Regex => String ]
|
7
7
|
|
8
|
+
# log_level is one of: :fatal, :error, :warn, :info, :debug, :trace
|
8
9
|
def initialize(mapping, log_level = nil)
|
9
10
|
@log_level = log_level
|
10
11
|
@input_mappings = mapping
|
@@ -21,11 +22,10 @@ module OpsWalrus
|
|
21
22
|
# end
|
22
23
|
|
23
24
|
# sudo_password : String
|
24
|
-
def mapping_for_sudo_password(sudo_password)
|
25
|
+
def self.mapping_for_sudo_password(sudo_password)
|
25
26
|
{
|
26
27
|
/\[sudo\] password for .*?:\s*/ => "#{sudo_password}\n",
|
27
28
|
App::LOCAL_SUDO_PASSWORD_PROMPT => "#{sudo_password}\n",
|
28
|
-
# /\s+/ => nil, # unnecessary
|
29
29
|
}
|
30
30
|
end
|
31
31
|
|
@@ -39,7 +39,7 @@ module OpsWalrus
|
|
39
39
|
raise ArgumentError.new("mapping must be a Hash") unless mapping.is_a?(Hash)
|
40
40
|
|
41
41
|
if sudo_password
|
42
|
-
mapping.merge!(mapping_for_sudo_password(sudo_password))
|
42
|
+
mapping.merge!(ScopedMappingInteractionHandler.mapping_for_sudo_password(sudo_password))
|
43
43
|
end
|
44
44
|
|
45
45
|
if mapping.empty?
|
@@ -57,17 +57,16 @@ module OpsWalrus
|
|
57
57
|
end
|
58
58
|
|
59
59
|
def on_data(_command, stream_name, data, channel)
|
60
|
-
log("Looking up response for #{stream_name} message #{data.inspect}")
|
61
|
-
|
62
60
|
response_data = begin
|
63
61
|
first_matching_key_value_pair = @input_mappings.find {|k, _v| k === data }
|
64
62
|
first_matching_key_value_pair&.last
|
65
63
|
end
|
66
64
|
|
67
65
|
if response_data.nil?
|
68
|
-
|
66
|
+
trace(Style.red("No interaction handler mapping for #{stream_name}: #{data} so no response was sent"))
|
69
67
|
else
|
70
|
-
|
68
|
+
debug(Style.cyan("Handling #{stream_name} message #{data}"))
|
69
|
+
debug(Style.cyan("Sending response #{response_data}"))
|
71
70
|
if channel.respond_to?(:send_data) # Net SSH Channel
|
72
71
|
channel.send_data(response_data)
|
73
72
|
elsif channel.respond_to?(:write) # Local IO
|
@@ -80,16 +79,21 @@ module OpsWalrus
|
|
80
79
|
|
81
80
|
private
|
82
81
|
|
83
|
-
def
|
84
|
-
|
85
|
-
|
82
|
+
def trace(message)
|
83
|
+
App.instance.trace(message)
|
84
|
+
end
|
85
|
+
|
86
|
+
def debug(message)
|
87
|
+
App.instance.debug(message)
|
88
|
+
if [:fatal, :error, :warn, :info, :debug, :trace].include? @log_level
|
89
|
+
SSHKit.config.output.send(@log_level, message)
|
90
|
+
end
|
86
91
|
end
|
87
92
|
|
88
93
|
end
|
89
94
|
|
90
95
|
class PasswdInteractionHandler
|
91
96
|
def on_data(command, stream_name, data, channel)
|
92
|
-
# puts data
|
93
97
|
case data
|
94
98
|
when '(current) UNIX password: '
|
95
99
|
channel.send_data("old_pw\n")
|
@@ -118,8 +122,6 @@ module OpsWalrus
|
|
118
122
|
|
119
123
|
class SudoPromptInteractionHandler
|
120
124
|
def on_data(command, stream_name, data, channel)
|
121
|
-
# puts "0" * 80
|
122
|
-
# puts data.inspect
|
123
125
|
case data
|
124
126
|
when /\[sudo\] password for/
|
125
127
|
if channel.respond_to?(:send_data) # Net::SSH channel
|
@@ -128,12 +130,11 @@ module OpsWalrus
|
|
128
130
|
channel.write("conquer\n")
|
129
131
|
end
|
130
132
|
when /\s+/
|
131
|
-
|
133
|
+
nil
|
132
134
|
else
|
133
135
|
raise "Unexpected prompt: #{data} on stream #{stream_name} and channel #{channel.inspect}"
|
134
|
-
|
136
|
+
end
|
135
137
|
end
|
136
138
|
end
|
137
139
|
|
138
|
-
|
139
140
|
end
|