opswalrus 1.0.5 → 1.0.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +8 -8
- data/build.ops +40 -0
- data/lib/opswalrus/app.rb +18 -54
- data/lib/opswalrus/bundler.rb +19 -12
- data/lib/opswalrus/git.rb +22 -0
- data/lib/opswalrus/host.rb +27 -13
- data/lib/opswalrus/interaction_handlers.rb +85 -0
- data/lib/opswalrus/local_pty_backend.rb +12 -2
- data/lib/opswalrus/operation_runner.rb +1 -1
- data/lib/opswalrus/ops_file.rb +21 -34
- data/lib/opswalrus/ops_file_script.rb +33 -17
- data/lib/opswalrus/package_file.rb +11 -2
- data/lib/opswalrus/runtime_environment.rb +53 -2
- data/lib/opswalrus/version.rb +1 -1
- data/lib/opswalrus/walrus_lang.rb +1 -1
- data/opswalrus.gemspec +8 -8
- metadata +4 -5
- data/lib/opswalrus/bootstrap_linux_host1.sh +0 -12
- data/lib/opswalrus/bootstrap_linux_host2.sh +0 -37
- data/lib/opswalrus/bootstrap_linux_host3.sh +0 -21
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b0c1c00f88ff00a4f7eb735a634ea35e1e8818b44fffdccea7a2347bac78e199
|
4
|
+
data.tar.gz: fe54254076c753cfeaf26906fa0f7475bd11807c0ac70d4067da03653b0f774c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 28244a51a465941731663e9db3901049d36d1fa385a16b4c5ed9254c31fa62ca44012c2d776ef5b6d1d06906c012802b1352e166d72d28b3ec0aa6f9e27073f5
|
7
|
+
data.tar.gz: 39ccb70ca9dc5b2ecf67b1ab29adb7ccc79cb43ad57d256be0deb68b49909bc4c5cd4ddc2871e68aeeb374ed3d0202c365094536e7933f61c3ecf700a2178076
|
data/Gemfile.lock
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
opswalrus (1.0.
|
5
|
-
bcrypt_pbkdf
|
6
|
-
citrus
|
7
|
-
ed25519
|
8
|
-
git
|
9
|
-
gli
|
10
|
-
rubyzip
|
11
|
-
sshkit
|
4
|
+
opswalrus (1.0.7)
|
5
|
+
bcrypt_pbkdf (~> 1.1)
|
6
|
+
citrus (~> 3.0)
|
7
|
+
ed25519 (~> 1.3)
|
8
|
+
git (~> 1.18)
|
9
|
+
gli (~> 2.21)
|
10
|
+
rubyzip (~> 2.3)
|
11
|
+
sshkit (~> 1.21)
|
12
12
|
|
13
13
|
GEM
|
14
14
|
remote: https://rubygems.org/
|
data/build.ops
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
params:
|
2
|
+
version: string
|
3
|
+
|
4
|
+
imports:
|
5
|
+
core: "https://github.com/opswalrus/core.git"
|
6
|
+
...
|
7
|
+
|
8
|
+
version = params.version
|
9
|
+
|
10
|
+
exit 1, "version parameter must be specified" unless version
|
11
|
+
|
12
|
+
template = <<TEMPLATE
|
13
|
+
module OpsWalrus
|
14
|
+
VERSION = "{{ version }}"
|
15
|
+
end
|
16
|
+
TEMPLATE
|
17
|
+
|
18
|
+
puts "Write version.rb for version #{version}"
|
19
|
+
core.template.write(path: "./lib/opswalrus/version.rb", template: template, variables: {version: version})
|
20
|
+
|
21
|
+
sh("Build gem") { 'gem build opswalrus.gemspec' }
|
22
|
+
bw_status_output = sh("Check whether Bitwarden is locked or not") { 'bw status' }
|
23
|
+
# the status command currently exhibits an error in which it emits 'mac failed.' some number of times, so we need to filter that out
|
24
|
+
# see:
|
25
|
+
# - https://community.bitwarden.com/t/what-does-mac-failed-mean-exactly/29208
|
26
|
+
# - https://github.com/bitwarden/cli/issues/88
|
27
|
+
# - https://github.com/vwxyzjn/portwarden/issues/22
|
28
|
+
# ❯ bw status
|
29
|
+
# mac failed.
|
30
|
+
# {"serverUrl":"...","lastSync":"2023-08-17T19:14:09.384Z","userEmail":"...","userId":"...","status":"locked"}
|
31
|
+
bw_status_output = bw_status_output.gsub('mac failed.', '').strip
|
32
|
+
bw_status_json = bw_status_output.parse_json
|
33
|
+
|
34
|
+
if bw_status_json['status'] != 'unlocked'
|
35
|
+
exit 0, "Bitwarden is not unlocked. Please unlock bitwarden with: bw unlock"
|
36
|
+
end
|
37
|
+
|
38
|
+
totp = sh("Get Rubygems OTP") { 'bw get totp Rubygems' }
|
39
|
+
sh("Push gem", input: {"You have enabled multi-factor authentication. Please enter OTP code." => "#{totp}\n"}) { 'gem push opswalrus-{{ version }}.gem' }
|
40
|
+
sh("Build docker image") { 'docker build -t opswalrus/ops:{{ version }} .' }
|
data/lib/opswalrus/app.rb
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
require "citrus"
|
2
|
-
require "git"
|
3
2
|
require "io/console"
|
4
3
|
require "json"
|
5
4
|
require "random/formatter"
|
@@ -8,27 +7,14 @@ require "socket"
|
|
8
7
|
require "stringio"
|
9
8
|
require "yaml"
|
10
9
|
require "pathname"
|
10
|
+
require_relative "patches"
|
11
|
+
require_relative "git"
|
11
12
|
require_relative "host"
|
12
13
|
require_relative "hosts_file"
|
13
14
|
require_relative "operation_runner"
|
14
15
|
require_relative "bundler"
|
15
16
|
require_relative "package_file"
|
16
17
|
|
17
|
-
class String
|
18
|
-
def escape_single_quotes
|
19
|
-
gsub("'"){"\\'"}
|
20
|
-
end
|
21
|
-
|
22
|
-
def to_pathname
|
23
|
-
Pathname.new(self)
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
|
-
class Pathname
|
28
|
-
def to_pathname
|
29
|
-
self
|
30
|
-
end
|
31
|
-
end
|
32
18
|
|
33
19
|
module OpsWalrus
|
34
20
|
class Error < StandardError
|
@@ -131,6 +117,7 @@ module OpsWalrus
|
|
131
117
|
def prompt_sudo_password
|
132
118
|
password = IO::console.getpass(LOCAL_SUDO_PASSWORD_PROMPT)
|
133
119
|
set_sudo_password(password)
|
120
|
+
# puts "sudo password = |#{password}|"
|
134
121
|
nil
|
135
122
|
end
|
136
123
|
|
@@ -158,24 +145,11 @@ module OpsWalrus
|
|
158
145
|
ops_file = OpsFile.new(self, ops_file_path)
|
159
146
|
|
160
147
|
# if the ops file is part of a package, then set the package directory as the app's pwd
|
161
|
-
# puts "run1: #{ops_file.ops_file_path}"
|
162
148
|
if ops_file.package_file && ops_file.package_file.dirname.to_s !~ /#{Bundler::BUNDLE_DIR}/
|
163
|
-
|
164
|
-
set_pwd(ops_file.package_file.dirname)
|
165
|
-
rebased_ops_file_relative_path = ops_file.ops_file_path.relative_path_from(ops_file.package_file.dirname)
|
166
|
-
# note: rebased_ops_file_relative_path is a relative path that is relative to ops_file.package_file.dirname
|
167
|
-
# puts "rebased path: #{rebased_ops_file_relative_path}"
|
168
|
-
absolute_ops_file_path = ops_file.package_file.dirname.join(rebased_ops_file_relative_path)
|
169
|
-
# puts "absolute path: #{absolute_ops_file_path}"
|
170
|
-
ops_file = OpsFile.new(self, absolute_ops_file_path)
|
149
|
+
ops_file = set_pwd_to_ops_file_package_directory(ops_file)
|
171
150
|
end
|
172
|
-
# puts "run2: #{ops_file.ops_file_path}"
|
173
151
|
|
174
152
|
op = OperationRunner.new(self, ops_file)
|
175
|
-
# if op.requires_sudo?
|
176
|
-
# prompt_sudo_password unless sudo_password
|
177
|
-
# end
|
178
|
-
# exit_status, out, err, script_output_structure = op.run(operation_kv_args, params_json_hash: @params, verbose: @verbose)
|
179
153
|
result = op.run(operation_kv_args, params_json_hash: @params, verbose: @verbose)
|
180
154
|
exit_status = result.exit_status
|
181
155
|
|
@@ -183,14 +157,7 @@ module OpsWalrus
|
|
183
157
|
puts "Op exit_status"
|
184
158
|
puts exit_status
|
185
159
|
|
186
|
-
# puts "Op stdout"
|
187
|
-
# puts out
|
188
|
-
|
189
|
-
# puts "Op stderr"
|
190
|
-
# puts err
|
191
|
-
|
192
160
|
puts "Op output"
|
193
|
-
# puts script_output_structure ? JSON.pretty_generate(script_output_structure) : nil.inspect
|
194
161
|
puts JSON.pretty_generate(result.value)
|
195
162
|
end
|
196
163
|
|
@@ -203,6 +170,19 @@ module OpsWalrus
|
|
203
170
|
FileUtils.remove_entry(tmp_dir) if tmp_dir
|
204
171
|
end
|
205
172
|
|
173
|
+
# sets the App's pwd to the ops file's package directory and
|
174
|
+
# returns a new OpsFile that points at the revised pathname with considered as relative to the package file's directory
|
175
|
+
def set_pwd_to_ops_file_package_directory(ops_file)
|
176
|
+
# puts "set pwd: #{ops_file.package_file.dirname}"
|
177
|
+
set_pwd(ops_file.package_file.dirname)
|
178
|
+
rebased_ops_file_relative_path = ops_file.ops_file_path.relative_path_from(ops_file.package_file.dirname)
|
179
|
+
# note: rebased_ops_file_relative_path is a relative path that is relative to ops_file.package_file.dirname
|
180
|
+
# puts "rebased path: #{rebased_ops_file_relative_path}"
|
181
|
+
absolute_ops_file_path = ops_file.package_file.dirname.join(rebased_ops_file_relative_path)
|
182
|
+
# puts "absolute path: #{absolute_ops_file_path}"
|
183
|
+
OpsFile.new(self, absolute_ops_file_path)
|
184
|
+
end
|
185
|
+
|
206
186
|
# package_operation_and_args can take one of the following forms:
|
207
187
|
# - ["github.com/davidkellis/my-package", "operation1", "arg1:val1", "arg2:val2", "arg3:val3"]
|
208
188
|
# - ["foo.zip", "foo/myfile.ops", "arg1:val1", "arg2:val2", "arg3:val3"]
|
@@ -244,7 +224,7 @@ module OpsWalrus
|
|
244
224
|
|
245
225
|
# operation_kv_args = package_operation_and_args
|
246
226
|
[ops_file_path, operation_kv_args, tmp_dir]
|
247
|
-
when repo_url =
|
227
|
+
when repo_url = Git.repo?(package_or_ops_file_reference)
|
248
228
|
destination_package_path = bundler.download_git_package(repo_url)
|
249
229
|
|
250
230
|
ops_file_path, operation_kv_args = find_entry_point_ops_file_in_dir(destination_package_path, package_operation_and_args)
|
@@ -301,22 +281,6 @@ module OpsWalrus
|
|
301
281
|
[ops_file_path, operation_kv_args]
|
302
282
|
end
|
303
283
|
|
304
|
-
# git_repo?("davidkellis/arborist") -> "https://github.com/davidkellis/arborist"
|
305
|
-
# returns the repo URL
|
306
|
-
def git_repo?(repo_reference)
|
307
|
-
candidate_repo_references = [
|
308
|
-
repo_reference,
|
309
|
-
repo_reference =~ /(\.(com|net|org|dev|io|local))\// && "https://#{repo_reference}",
|
310
|
-
repo_reference !~ /github\.com\// && repo_reference =~ /^[a-z\d](?:[a-z\d]|-(?=[a-z\d])){0,38}\/([\w\.@\:\-~]+)$/i && "https://github.com/#{repo_reference}" # this regex is from https://www.npmjs.com/package/github-username-regex and https://www.debuggex.com/r/H4kRw1G0YPyBFjfm
|
311
|
-
].compact
|
312
|
-
working_repo_reference = candidate_repo_references.find {|reference| Git.ls_remote(reference) rescue nil }
|
313
|
-
working_repo_reference
|
314
|
-
end
|
315
|
-
|
316
|
-
# def is_dir_git_repo?(dir_path)
|
317
|
-
# Git.ls_remote(reference) rescue nil
|
318
|
-
# end
|
319
|
-
|
320
284
|
def bundle_status
|
321
285
|
end
|
322
286
|
|
data/lib/opswalrus/bundler.rb
CHANGED
@@ -22,8 +22,8 @@ module OpsWalrus
|
|
22
22
|
@bundle_dir
|
23
23
|
end
|
24
24
|
|
25
|
-
def
|
26
|
-
FileUtils.mkdir_p(@bundle_dir)
|
25
|
+
def ensure_pwd_bundle_directory_exists
|
26
|
+
FileUtils.mkdir_p(@bundle_dir) unless @bundle_dir.exist?
|
27
27
|
end
|
28
28
|
|
29
29
|
# # returns the OpsFile within the bundle directory that represents the given ops_file (which is outside of the bundle directory)
|
@@ -50,7 +50,7 @@ module OpsWalrus
|
|
50
50
|
# end
|
51
51
|
|
52
52
|
def update
|
53
|
-
|
53
|
+
ensure_pwd_bundle_directory_exists
|
54
54
|
|
55
55
|
package_yaml_files = pwd.glob("./**/package.yaml") - pwd.glob("./**/#{BUNDLE_DIR}/**/package.yaml")
|
56
56
|
package_files_within_pwd = package_yaml_files.map {|path| PackageFile.new(path.realpath) }
|
@@ -83,7 +83,7 @@ module OpsWalrus
|
|
83
83
|
|
84
84
|
# returns the self_pkg directory within the bundle directory
|
85
85
|
# def include_directory_in_bundle_as_self_pkg(dirname = pwd)
|
86
|
-
#
|
86
|
+
# ensure_pwd_bundle_directory_exists
|
87
87
|
|
88
88
|
# destination_package_path = @bundle_dir.join("self_pkg")
|
89
89
|
|
@@ -103,7 +103,7 @@ module OpsWalrus
|
|
103
103
|
# This method downloads a package_url that is a dependency referenced in the specified package_file
|
104
104
|
# returns the destination directory that the package was downloaded to
|
105
105
|
def download_package(package_file, package_reference)
|
106
|
-
|
106
|
+
ensure_pwd_bundle_directory_exists
|
107
107
|
|
108
108
|
local_name = package_reference.local_name
|
109
109
|
package_url = package_reference.package_uri
|
@@ -144,19 +144,26 @@ module OpsWalrus
|
|
144
144
|
end
|
145
145
|
|
146
146
|
def download_git_package(package_url, version = nil, destination_package_path = nil)
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
147
|
+
ensure_pwd_bundle_directory_exists
|
148
|
+
|
149
|
+
destination_package_path ||= dynamic_package_path_for_git_package(package_url, version)
|
150
|
+
|
151
|
+
return destination_package_path if destination_package_path.exist?
|
152
|
+
|
152
153
|
if version
|
153
|
-
Git.clone(package_url, destination_package_path, branch: version, config: ['submodule.recurse=true'])
|
154
|
+
::Git.clone(package_url, destination_package_path, branch: version, config: ['submodule.recurse=true'])
|
154
155
|
else
|
155
|
-
Git.clone(package_url, destination_package_path, config: ['submodule.recurse=true'])
|
156
|
+
::Git.clone(package_url, destination_package_path, config: ['submodule.recurse=true'])
|
156
157
|
end
|
158
|
+
|
157
159
|
destination_package_path
|
158
160
|
end
|
159
161
|
|
162
|
+
def dynamic_package_path_for_git_package(package_url, version = nil)
|
163
|
+
package_reference_dirname = sanitize_path(package_url)
|
164
|
+
bundle_dir.join(package_reference_dirname)
|
165
|
+
end
|
166
|
+
|
160
167
|
def sanitize_path(path)
|
161
168
|
# found this at https://apidock.com/rails/v5.2.3/ActiveStorage/Filename/sanitized
|
162
169
|
path.encode(Encoding::UTF_8, invalid: :replace, undef: :replace, replace: "�").strip.tr("\u{202E}%$|:;/\t\r\n\\", "-")
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require "git"
|
2
|
+
|
3
|
+
module OpsWalrus
|
4
|
+
class Git
|
5
|
+
# repo?("davidkellis/arborist") -> "https://github.com/davidkellis/arborist"
|
6
|
+
# returns the repo URL or directory path
|
7
|
+
def self.repo?(repo_reference)
|
8
|
+
if Dir.exist?(repo_reference)
|
9
|
+
::Git.ls_remote(repo_reference) && repo_reference rescue nil
|
10
|
+
else
|
11
|
+
candidate_repo_references = [
|
12
|
+
repo_reference,
|
13
|
+
repo_reference =~ /(\.(com|net|org|dev|io|local))\// && "https://#{repo_reference}",
|
14
|
+
repo_reference !~ /github\.com\// && repo_reference =~ /^[a-z\d](?:[a-z\d]|-(?=[a-z\d])){0,38}\/([\w\.@\:\-~]+)$/i && "https://github.com/#{repo_reference}" # this regex is from https://www.npmjs.com/package/github-username-regex and https://www.debuggex.com/r/H4kRw1G0YPyBFjfm
|
15
|
+
].compact
|
16
|
+
working_repo_reference = candidate_repo_references.find {|reference| ::Git.ls_remote(reference) rescue nil }
|
17
|
+
working_repo_reference
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
data/lib/opswalrus/host.rb
CHANGED
@@ -7,18 +7,18 @@ module OpsWalrus
|
|
7
7
|
|
8
8
|
module HostDSL
|
9
9
|
# returns the stdout from the command
|
10
|
-
def sh(desc_or_cmd = nil, cmd = nil,
|
11
|
-
out, err, status = *shell!(desc_or_cmd, cmd, block,
|
10
|
+
def sh(desc_or_cmd = nil, cmd = nil, input: nil, &block)
|
11
|
+
out, err, status = *shell!(desc_or_cmd, cmd, block, input: input)
|
12
12
|
out
|
13
13
|
end
|
14
14
|
|
15
15
|
# returns the tuple: [stdout, stderr, exit_status]
|
16
|
-
def shell(desc_or_cmd = nil, cmd = nil,
|
17
|
-
shell!(desc_or_cmd, cmd, block,
|
16
|
+
def shell(desc_or_cmd = nil, cmd = nil, input: nil, &block)
|
17
|
+
shell!(desc_or_cmd, cmd, block, input: input)
|
18
18
|
end
|
19
19
|
|
20
20
|
# returns the tuple: [stdout, stderr, exit_status]
|
21
|
-
def shell!(desc_or_cmd = nil, cmd = nil, block = nil,
|
21
|
+
def shell!(desc_or_cmd = nil, cmd = nil, block = nil, input: nil)
|
22
22
|
# description = nil
|
23
23
|
|
24
24
|
return ["", "", 0] if !desc_or_cmd && !cmd && !block # we were told to do nothing; like hitting enter at the bash prompt; we can do nothing successfully
|
@@ -27,6 +27,10 @@ module OpsWalrus
|
|
27
27
|
cmd = block.call if block
|
28
28
|
cmd ||= desc_or_cmd
|
29
29
|
|
30
|
+
cmd = WalrusLang.render(cmd, block.binding) if block && cmd =~ /{{.*}}/
|
31
|
+
|
32
|
+
#cmd = Shellwords.escape(cmd)
|
33
|
+
|
30
34
|
if self.alias
|
31
35
|
print "[#{self.alias} | #{host}] "
|
32
36
|
else
|
@@ -35,16 +39,13 @@ module OpsWalrus
|
|
35
39
|
print "#{description}: " if description
|
36
40
|
puts cmd
|
37
41
|
|
38
|
-
cmd = WalrusLang.render(cmd, block.binding) if block && cmd =~ /{{.*}}/
|
39
42
|
return unless cmd && !cmd.strip.empty?
|
40
43
|
|
41
|
-
#cmd = Shellwords.escape(cmd)
|
42
|
-
|
43
44
|
# puts "shell: #{cmd}"
|
44
45
|
# puts "shell: #{cmd.inspect}"
|
45
46
|
# puts "sudo_password: #{sudo_password}"
|
46
47
|
|
47
|
-
sshkit_cmd = execute_cmd(cmd)
|
48
|
+
sshkit_cmd = execute_cmd(cmd, input: input)
|
48
49
|
|
49
50
|
[sshkit_cmd.full_stdout, sshkit_cmd.full_stderr, sshkit_cmd.exit_status]
|
50
51
|
end
|
@@ -202,6 +203,10 @@ module OpsWalrus
|
|
202
203
|
})
|
203
204
|
end
|
204
205
|
|
206
|
+
def set_runtime_env(runtime_env)
|
207
|
+
@runtime_env = runtime_env
|
208
|
+
end
|
209
|
+
|
205
210
|
def set_ssh_session_connection(sshkit_backend)
|
206
211
|
@sshkit_backend = sshkit_backend
|
207
212
|
end
|
@@ -211,18 +216,27 @@ module OpsWalrus
|
|
211
216
|
end
|
212
217
|
|
213
218
|
def clear_ssh_session
|
219
|
+
@runtime_env = nil
|
214
220
|
@sshkit_backend = nil
|
215
221
|
@tmp_bundle_root_dir = nil
|
216
222
|
end
|
217
223
|
|
218
|
-
def execute(*args)
|
224
|
+
def execute(*args, input: nil)
|
219
225
|
# puts "interaction handler responds with: #{ssh_password}"
|
220
|
-
@sshkit_backend.capture(*args, interaction_handler: SudoPasswordMapper.new(ssh_password).interaction_handler, verbosity: :info)
|
226
|
+
# @sshkit_backend.capture(*args, interaction_handler: SudoPasswordMapper.new(ssh_password).interaction_handler, verbosity: :info)
|
221
227
|
# @sshkit_backend.capture(*args, interaction_handler: SudoPromptInteractionHandler.new, verbosity: :info)
|
228
|
+
|
229
|
+
@runtime_env.handle_input(input, ssh_password) do |interaction_handler|
|
230
|
+
@sshkit_backend.capture(*args, interaction_handler: interaction_handler, verbosity: :info)
|
231
|
+
end
|
232
|
+
|
222
233
|
end
|
223
234
|
|
224
|
-
def execute_cmd(*args)
|
225
|
-
@sshkit_backend.execute_cmd(*args, interaction_handler: SudoPasswordMapper.new(ssh_password).interaction_handler, verbosity: :info)
|
235
|
+
def execute_cmd(*args, input: nil)
|
236
|
+
# @sshkit_backend.execute_cmd(*args, interaction_handler: SudoPasswordMapper.new(ssh_password).interaction_handler, verbosity: :info)
|
237
|
+
@runtime_env.handle_input(input, ssh_password) do |interaction_handler|
|
238
|
+
@sshkit_backend.execute_cmd(*args, interaction_handler: interaction_handler, verbosity: :info)
|
239
|
+
end
|
226
240
|
end
|
227
241
|
|
228
242
|
def upload(local_path_or_io, remote_path)
|
@@ -2,6 +2,91 @@ require 'sshkit'
|
|
2
2
|
|
3
3
|
module OpsWalrus
|
4
4
|
|
5
|
+
class ScopedMappingInteractionHandler
|
6
|
+
attr_accessor :input_mappings # Hash[ String | Regex => String ]
|
7
|
+
|
8
|
+
def initialize(mapping, log_level = nil)
|
9
|
+
@log_level = log_level
|
10
|
+
@input_mappings = mapping
|
11
|
+
end
|
12
|
+
|
13
|
+
# temporarily adds a sudo password mapping to the interaction handler while the given block is being evaluated
|
14
|
+
# when the given block returns, then the temporary mapping is removed from the interaction handler
|
15
|
+
# def with_sudo_password(password, &block)
|
16
|
+
# with_mapping({
|
17
|
+
# /\[sudo\] password for .*?:\s*/ => "#{password}\n",
|
18
|
+
# App::LOCAL_SUDO_PASSWORD_PROMPT => "#{password}\n",
|
19
|
+
# # /\s+/ => nil, # unnecessary
|
20
|
+
# }, &block)
|
21
|
+
# end
|
22
|
+
|
23
|
+
# sudo_password : String
|
24
|
+
def mapping_for_sudo_password(sudo_password)
|
25
|
+
{
|
26
|
+
/\[sudo\] password for .*?:\s*/ => "#{sudo_password}\n",
|
27
|
+
App::LOCAL_SUDO_PASSWORD_PROMPT => "#{sudo_password}\n",
|
28
|
+
# /\s+/ => nil, # unnecessary
|
29
|
+
}
|
30
|
+
end
|
31
|
+
|
32
|
+
# temporarily adds the specified input mapping to the interaction handler while the given block is being evaluated
|
33
|
+
# when the given block returns, then the temporary mapping is removed from the interaction handler
|
34
|
+
#
|
35
|
+
# mapping : Hash[ String | Regex => String ]
|
36
|
+
def with_mapping(mapping, sudo_password = nil)
|
37
|
+
mapping ||= {}
|
38
|
+
|
39
|
+
raise ArgumentError.new("mapping must be a Hash") unless mapping.is_a?(Hash)
|
40
|
+
|
41
|
+
if sudo_password
|
42
|
+
mapping.merge!(mapping_for_sudo_password(sudo_password))
|
43
|
+
end
|
44
|
+
|
45
|
+
if mapping.empty?
|
46
|
+
yield self
|
47
|
+
else
|
48
|
+
yield ScopedMappingInteractionHandler.new(@input_mappings.merge(mapping), @log_level)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# adds the specified input mapping to the interaction handler
|
53
|
+
#
|
54
|
+
# mapping : Hash[ String | Regex => String ]
|
55
|
+
def add_mapping(mapping)
|
56
|
+
@input_mappings.merge!(mapping)
|
57
|
+
end
|
58
|
+
|
59
|
+
def on_data(_command, stream_name, data, channel)
|
60
|
+
log("Looking up response for #{stream_name} message #{data.inspect}")
|
61
|
+
|
62
|
+
response_data = begin
|
63
|
+
first_matching_key_value_pair = @input_mappings.find {|k, _v| k === data }
|
64
|
+
first_matching_key_value_pair&.last
|
65
|
+
end
|
66
|
+
|
67
|
+
if response_data.nil?
|
68
|
+
log("Unable to find interaction handler mapping for #{stream_name}: #{data.inspect} so no response was sent")
|
69
|
+
else
|
70
|
+
log("Sending #{response_data.inspect}")
|
71
|
+
if channel.respond_to?(:send_data) # Net SSH Channel
|
72
|
+
channel.send_data(response_data)
|
73
|
+
elsif channel.respond_to?(:write) # Local IO
|
74
|
+
channel.write(response_data)
|
75
|
+
else
|
76
|
+
raise "Unable to write response data to channel #{channel.inspect} - does not support '#send_data' or '#write'"
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
private
|
82
|
+
|
83
|
+
def log(message)
|
84
|
+
# puts message
|
85
|
+
SSHKit.config.output.send(@log_level, message) unless @log_level.nil?
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
|
5
90
|
class PasswdInteractionHandler
|
6
91
|
def on_data(command, stream_name, data, channel)
|
7
92
|
# puts data
|
@@ -16,12 +16,16 @@ module SSHKit
|
|
16
16
|
cmd.started = Time.now
|
17
17
|
PTY.spawn(cmd.to_command) do |stdout, stdin, pid|
|
18
18
|
stdout_thread = Thread.new do
|
19
|
+
# debug_log = StringIO.new
|
19
20
|
buffer = ""
|
20
21
|
partial_buffer = ""
|
21
22
|
while !stdout.closed?
|
23
|
+
# debug_log.puts "!!!\nbuffer=#{buffer}|EOL|\npartial=#{partial_buffer}|EOL|"
|
22
24
|
# puts "9" * 80
|
23
25
|
begin
|
24
|
-
|
26
|
+
# partial_buffer = ""
|
27
|
+
# stdout.read_nonblock(4096, partial_buffer)
|
28
|
+
partial_buffer = stdout.read_nonblock(4096)
|
25
29
|
buffer << partial_buffer
|
26
30
|
# puts "nonblocking1. buffer=#{buffer} partial_buffer=#{partial_buffer}"
|
27
31
|
buffer = handle_data_for_stdout(output, cmd, buffer, stdin, false)
|
@@ -48,6 +52,10 @@ module SSHKit
|
|
48
52
|
end
|
49
53
|
end
|
50
54
|
# puts "end!"
|
55
|
+
# debug_log.puts "!!!\nbuffer=#{buffer}|EOL|\npartial=#{partial_buffer}|EOL|"
|
56
|
+
|
57
|
+
# puts "*" * 80
|
58
|
+
# puts debug_log.string
|
51
59
|
|
52
60
|
end
|
53
61
|
stdout_thread.join
|
@@ -59,12 +67,14 @@ module SSHKit
|
|
59
67
|
|
60
68
|
# returns [complete lines, new buffer]
|
61
69
|
def split_buffer(buffer)
|
62
|
-
lines = buffer.split(/(\r\n)
|
70
|
+
lines = buffer.split(/(\r\n)|\r|\n/)
|
63
71
|
buffer = lines.pop
|
64
72
|
[lines, buffer]
|
65
73
|
end
|
66
74
|
|
67
75
|
def handle_data_for_stdout(output, cmd, buffer, stdin, is_blocked)
|
76
|
+
# puts "handling data for stdout: #{buffer}"
|
77
|
+
|
68
78
|
# we're blocked on reading, so let's process the buffer
|
69
79
|
lines, buffer = split_buffer(buffer)
|
70
80
|
lines.each do |line|
|
data/lib/opswalrus/ops_file.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'pathname'
|
2
2
|
require 'yaml'
|
3
|
+
require_relative 'git'
|
3
4
|
require_relative 'ops_file_script'
|
4
5
|
|
5
6
|
module OpsWalrus
|
@@ -91,12 +92,16 @@ module OpsWalrus
|
|
91
92
|
imports_hash.map do |local_name, yaml_import_reference|
|
92
93
|
local_name = local_name.to_s
|
93
94
|
import_reference = case yaml_import_reference
|
95
|
+
|
96
|
+
# when the imports line says:
|
97
|
+
# imports:
|
98
|
+
# my_package: my_package
|
94
99
|
in String => import_str
|
95
100
|
case
|
96
101
|
when package_reference = package_file&.dependency(import_str) # package dependency reference
|
97
102
|
# in this context, import_str is the local package name documented in the package's dependencies
|
98
103
|
PackageDependencyReference.new(local_name, package_reference)
|
99
|
-
when import_str.to_pathname.exist?
|
104
|
+
when import_str.to_pathname.exist? # path reference
|
100
105
|
path = import_str.to_pathname
|
101
106
|
case
|
102
107
|
when path.directory?
|
@@ -106,13 +111,26 @@ module OpsWalrus
|
|
106
111
|
else
|
107
112
|
raise Error, "Unknown import reference: #{local_name} -> #{import_str.inspect}"
|
108
113
|
end
|
114
|
+
when Git.repo?(import_str) # ops file has imported an ad-hoc git repo
|
115
|
+
package_uri = import_str
|
116
|
+
destination_package_path = app.bundler.dynamic_package_path_for_git_package(package_uri)
|
117
|
+
# puts "DynamicPackageImportReference: #{local_name} -> #{destination_package_path}"
|
118
|
+
DynamicPackageImportReference.new(local_name, DynamicPackageReference.new(local_name, package_uri, nil))
|
119
|
+
else
|
120
|
+
raise Error, "Unknown import reference: #{local_name}: #{yaml_import_reference.inspect}"
|
109
121
|
end
|
110
|
-
|
122
|
+
|
123
|
+
# when the imports line says:
|
124
|
+
# imports:
|
125
|
+
# my_package:
|
126
|
+
# url: https://...
|
127
|
+
# version: 2.1
|
128
|
+
# in Hash => package_defn
|
111
129
|
# url = package_defn["url"]
|
112
130
|
# version = package_defn["version"]
|
113
131
|
# PackageReference.new(local_name, url, version&.to_s)
|
114
132
|
else
|
115
|
-
raise Error, "Unknown
|
133
|
+
raise Error, "Unknown import reference: #{local_name}: #{yaml_import_reference.inspect}"
|
116
134
|
end
|
117
135
|
[local_name, import_reference]
|
118
136
|
end.to_h
|
@@ -205,36 +223,5 @@ module OpsWalrus
|
|
205
223
|
dirname.glob("*").select(&:directory?)
|
206
224
|
end
|
207
225
|
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
def supporting_library_include_dir_and_require_lib
|
216
|
-
if Dir.exist?(ops_file_helper_library_directory)
|
217
|
-
[ops_file_helper_library_directory, ops_file_helper_library_basename]
|
218
|
-
elsif File.exist?(ops_file_sibling_helper_library_file)
|
219
|
-
[dirname, ops_file_helper_library_basename]
|
220
|
-
else
|
221
|
-
[nil, nil]
|
222
|
-
end
|
223
|
-
end
|
224
|
-
|
225
|
-
def ops_file_helper_library_basename
|
226
|
-
basename.sub_ext(".rb")
|
227
|
-
end
|
228
|
-
|
229
|
-
# "/home/david/sync/projects/ops/ops/core/host/info.ops" => "/home/david/sync/projects/ops/ops/core/host/info"
|
230
|
-
def ops_file_helper_library_directory
|
231
|
-
File.join(dirname, basename)
|
232
|
-
end
|
233
|
-
|
234
|
-
# "/home/david/sync/projects/ops/ops/core/host/info.ops" => "/home/david/sync/projects/ops/ops/core/host/info.rb"
|
235
|
-
def ops_file_sibling_helper_library_file
|
236
|
-
"#{ops_file_helper_library_directory}.rb"
|
237
|
-
end
|
238
|
-
|
239
226
|
end
|
240
227
|
end
|
@@ -20,6 +20,9 @@ module OpsWalrus
|
|
20
20
|
def [](index, *args, **kwargs, &block)
|
21
21
|
@obj.method(:[]).call(index, *args, **kwargs, &block)
|
22
22
|
end
|
23
|
+
def respond_to_missing?(method, *)
|
24
|
+
@obj.is_a?(Hash) && @obj.respond_to?(method)
|
25
|
+
end
|
23
26
|
def method_missing(name, *args, **kwargs, &block)
|
24
27
|
case @obj
|
25
28
|
when Array
|
@@ -118,13 +121,15 @@ module OpsWalrus
|
|
118
121
|
# bootstrap_shell_script = BootstrapLinuxHostShellScript
|
119
122
|
# on sshkit_hosts do |sshkit_host|
|
120
123
|
SSHKit::Coordinator.new(sshkit_hosts).each(in: kwargs[:in] || :parallel) do |sshkit_host|
|
121
|
-
host = sshkit_host_to_ops_host_map[sshkit_host]
|
122
124
|
|
125
|
+
# in this context, self is an instance of one of the subclasses of SSHKit::Backend::Abstract, e.g. SSHKit::Backend::Netssh
|
126
|
+
|
127
|
+
host = sshkit_host_to_ops_host_map[sshkit_host]
|
123
128
|
# puts "#{host.alias} / #{host}:"
|
124
129
|
|
125
130
|
begin
|
126
|
-
|
127
|
-
host.set_ssh_session_connection(self)
|
131
|
+
host.set_runtime_env(runtime_env)
|
132
|
+
host.set_ssh_session_connection(self) # self is an instance of one of the subclasses of SSHKit::Backend::Abstract, e.g. SSHKit::Backend::Netssh
|
128
133
|
|
129
134
|
# copy over bootstrap shell script
|
130
135
|
# io = StringIO.new(bootstrap_shell_script)
|
@@ -196,11 +201,14 @@ module OpsWalrus
|
|
196
201
|
@runtime_env.app.inventory(tags)
|
197
202
|
end
|
198
203
|
|
199
|
-
def exit(exit_status)
|
204
|
+
def exit(exit_status, message = nil)
|
205
|
+
if message
|
206
|
+
puts message
|
207
|
+
end
|
200
208
|
result = if exit_status == 0
|
201
|
-
Success.new(nil)
|
209
|
+
Invocation::Success.new(nil)
|
202
210
|
else
|
203
|
-
Error.new(nil, exit_status)
|
211
|
+
Invocation::Error.new(nil, exit_status)
|
204
212
|
end
|
205
213
|
throw :exit_now, result
|
206
214
|
end
|
@@ -214,6 +222,9 @@ module OpsWalrus
|
|
214
222
|
end
|
215
223
|
end
|
216
224
|
|
225
|
+
# currently, import may only be used to import a package that is referenced in the script's package file
|
226
|
+
# I may decide to extend this to work with dynamic package references
|
227
|
+
#
|
217
228
|
# local_package_name is the local package name defined for the package dependency that is attempting to be referenced
|
218
229
|
def import(local_package_name)
|
219
230
|
local_package_name = local_package_name.to_s
|
@@ -234,18 +245,18 @@ module OpsWalrus
|
|
234
245
|
end
|
235
246
|
|
236
247
|
# returns the stdout from the command
|
237
|
-
def sh(desc_or_cmd = nil, cmd = nil,
|
238
|
-
out, err, status = *shell!(desc_or_cmd, cmd, block,
|
248
|
+
def sh(desc_or_cmd = nil, cmd = nil, input: nil, &block)
|
249
|
+
out, err, status = *shell!(desc_or_cmd, cmd, block, input: input)
|
239
250
|
out
|
240
251
|
end
|
241
252
|
|
242
253
|
# returns the tuple: [stdout, stderr, exit_status]
|
243
|
-
def shell(desc_or_cmd = nil, cmd = nil,
|
244
|
-
shell!(desc_or_cmd, cmd, block,
|
254
|
+
def shell(desc_or_cmd = nil, cmd = nil, input: nil, &block)
|
255
|
+
shell!(desc_or_cmd, cmd, block, input: input)
|
245
256
|
end
|
246
257
|
|
247
258
|
# returns the tuple: [stdout, stderr, exit_status]
|
248
|
-
def shell!(desc_or_cmd = nil, cmd = nil, block = nil,
|
259
|
+
def shell!(desc_or_cmd = nil, cmd = nil, block = nil, input: nil)
|
249
260
|
# description = nil
|
250
261
|
|
251
262
|
return ["", "", 0] if !desc_or_cmd && !cmd && !block # we were told to do nothing; like hitting enter at the bash prompt; we can do nothing successfully
|
@@ -254,19 +265,20 @@ module OpsWalrus
|
|
254
265
|
cmd = block.call if block
|
255
266
|
cmd ||= desc_or_cmd
|
256
267
|
|
268
|
+
cmd = WalrusLang.render(cmd, block.binding) if block && cmd =~ /{{.*}}/
|
269
|
+
|
270
|
+
#cmd = Shellwords.escape(cmd)
|
271
|
+
|
257
272
|
# puts "shell! self: #{self.inspect}"
|
258
273
|
|
259
274
|
print "[#{@runtime_env.local_hostname}] "
|
260
275
|
print "#{description}: " if description
|
261
276
|
puts cmd
|
262
277
|
|
263
|
-
cmd = WalrusLang.render(cmd, block.binding) if block && cmd =~ /{{.*}}/
|
264
278
|
return unless cmd && !cmd.strip.empty?
|
265
279
|
|
266
|
-
#
|
267
|
-
|
268
|
-
sudo_password = @runtime_env.sudo_password
|
269
|
-
sudo_password &&= sudo_password.gsub(/\n+$/,'') # remove trailing newlines from sudo_password
|
280
|
+
# sudo_password = @runtime_env.sudo_password
|
281
|
+
# sudo_password &&= sudo_password.gsub(/\n+$/,'') # remove trailing newlines from sudo_password
|
270
282
|
|
271
283
|
# puts "shell: #{cmd}"
|
272
284
|
# puts "shell: #{cmd.inspect}"
|
@@ -274,10 +286,14 @@ module OpsWalrus
|
|
274
286
|
|
275
287
|
# sshkit_cmd = SSHKit::Backend::LocalNonBlocking.new {
|
276
288
|
# sshkit_cmd = SSHKit::Backend::LocalPty.new {
|
277
|
-
sshkit_cmd = backend.execute_cmd(cmd, interaction_handler: SudoPasswordMapper.new(sudo_password).interaction_handler, verbosity: :info)
|
289
|
+
# sshkit_cmd = backend.execute_cmd(cmd, interaction_handler: SudoPasswordMapper.new(sudo_password).interaction_handler, verbosity: :info)
|
278
290
|
# execute_cmd(cmd, interaction_handler: SudoPromptInteractionHandler.new, verbosity: :info)
|
279
291
|
# }.run
|
280
292
|
|
293
|
+
sshkit_cmd = @runtime_env.handle_input(input) do |interaction_handler|
|
294
|
+
backend.execute_cmd(cmd, interaction_handler: interaction_handler, verbosity: :info)
|
295
|
+
end
|
296
|
+
|
281
297
|
[sshkit_cmd.full_stdout, sshkit_cmd.full_stderr, sshkit_cmd.exit_status]
|
282
298
|
end
|
283
299
|
|
@@ -5,6 +5,7 @@ require_relative 'bundler'
|
|
5
5
|
|
6
6
|
module OpsWalrus
|
7
7
|
|
8
|
+
# these are static package references defined ahead of time in the package file
|
8
9
|
class PackageReference
|
9
10
|
attr_accessor :local_name
|
10
11
|
attr_accessor :package_uri
|
@@ -23,7 +24,7 @@ module OpsWalrus
|
|
23
24
|
path.encode(Encoding::UTF_8, invalid: :replace, undef: :replace, replace: "�").strip.tr("\u{202E}%$|:;/\t\r\n\\", "-")
|
24
25
|
end
|
25
26
|
|
26
|
-
# important: the
|
27
|
+
# important: the import_resolution_dirname implemented as the local_name is critical because Bundler#download_package downloads
|
27
28
|
# package dependencies to the name that this method returns, which must match the package reference's local name
|
28
29
|
# so that later, when the package is being looked up on the load path (in LoadPath#resolve_import_reference),
|
29
30
|
# the package reference's referenced git repo or file path may not exist or be available, and so the package
|
@@ -32,12 +33,20 @@ module OpsWalrus
|
|
32
33
|
# If this implementation changes, then Bundler#download_package and LoadPath#resolve_import_reference must also
|
33
34
|
# change in order for the three things to reconcile with respect to one another, since all three bits of logic are
|
34
35
|
# what make bundling package dependencies and loading them function properly.
|
35
|
-
def
|
36
|
+
def import_resolution_dirname
|
36
37
|
local_name
|
37
38
|
end
|
38
39
|
|
39
40
|
end
|
40
41
|
|
42
|
+
# these are dynamic package references defined at runtime when an OpsFile's imports are being evaluated.
|
43
|
+
# this will usually be the case when an ops file does not belong to a package
|
44
|
+
class DynamicPackageReference < PackageReference
|
45
|
+
def import_resolution_dirname
|
46
|
+
sanitized_package_uri
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
41
50
|
class PackageFile
|
42
51
|
attr_accessor :package_file_path
|
43
52
|
attr_accessor :yaml
|
@@ -3,6 +3,7 @@ require 'shellwords'
|
|
3
3
|
require 'socket'
|
4
4
|
require 'sshkit'
|
5
5
|
|
6
|
+
require_relative 'interaction_handlers'
|
6
7
|
require_relative 'traversable'
|
7
8
|
require_relative 'walrus_lang'
|
8
9
|
|
@@ -32,6 +33,14 @@ module OpsWalrus
|
|
32
33
|
end
|
33
34
|
end
|
34
35
|
|
36
|
+
class DynamicPackageImportReference < ImportReference
|
37
|
+
attr_accessor :package_reference
|
38
|
+
def initialize(local_name, package_reference)
|
39
|
+
super(local_name)
|
40
|
+
@package_reference = package_reference
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
35
44
|
class OpsFileReference < ImportReference
|
36
45
|
attr_accessor :ops_file_path
|
37
46
|
def initialize(local_name, ops_file_path)
|
@@ -86,6 +95,8 @@ module OpsWalrus
|
|
86
95
|
@dir = dir
|
87
96
|
@root_namespace = build_symbol_resolution_tree(@dir)
|
88
97
|
@path_map = build_path_map(@root_namespace)
|
98
|
+
|
99
|
+
@dynamic_package_additions_memo = {}
|
89
100
|
end
|
90
101
|
|
91
102
|
# returns a tree of Namespace -> {Namespace* -> {Namespace* -> ..., OpsFile*}, OpsFile*}
|
@@ -129,6 +140,19 @@ module OpsWalrus
|
|
129
140
|
path_map
|
130
141
|
end
|
131
142
|
|
143
|
+
def dynamically_add_new_package_dir(new_package_dir)
|
144
|
+
# patch the symbol resolution (namespace) tree
|
145
|
+
dir_basename = new_package_dir.basename
|
146
|
+
unless @root_namespace.resolve_symbol(dir_basename)
|
147
|
+
new_child_namespace = build_symbol_resolution_tree(new_package_dir)
|
148
|
+
@root_namespace.add(dir_basename, new_child_namespace)
|
149
|
+
|
150
|
+
# patch the path map
|
151
|
+
new_partial_path_map = build_path_map(new_child_namespace)
|
152
|
+
@path_map.merge!(new_partial_path_map)
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
132
156
|
# returns a Namespace
|
133
157
|
def lookup_namespace(ops_file)
|
134
158
|
@path_map[ops_file.dirname]
|
@@ -144,7 +168,16 @@ module OpsWalrus
|
|
144
168
|
case import_reference
|
145
169
|
when PackageDependencyReference
|
146
170
|
# puts "root namespace: #{@root_namespace.symbol_table}"
|
147
|
-
@root_namespace.resolve_symbol(import_reference.package_reference.
|
171
|
+
@root_namespace.resolve_symbol(import_reference.package_reference.import_resolution_dirname) # returns the Namespace associated with the bundled package dirname (i.e. the local name)
|
172
|
+
when DynamicPackageImportReference
|
173
|
+
dynamic_package_reference = import_reference.package_reference
|
174
|
+
@dynamic_package_additions_memo[dynamic_package_reference] ||= begin
|
175
|
+
# puts "Downloading dynamic package: #{dynamic_package_reference.inspect}"
|
176
|
+
dynamically_added_package_dir = @runtime_env.app.bundler.download_git_package(dynamic_package_reference.package_uri, dynamic_package_reference.version)
|
177
|
+
dynamically_add_new_package_dir(dynamically_added_package_dir)
|
178
|
+
dynamically_added_package_dir
|
179
|
+
end
|
180
|
+
@root_namespace.resolve_symbol(import_reference.package_reference.import_resolution_dirname) # returns the Namespace associated with the bundled package dirname (i.e. the sanitized package uri)
|
148
181
|
when DirectoryReference
|
149
182
|
@path_map[import_reference.dirname]
|
150
183
|
when OpsFileReference
|
@@ -164,9 +197,27 @@ module OpsWalrus
|
|
164
197
|
@bundle_load_path = LoadPath.new(self, @app.bundle_dir)
|
165
198
|
@app_load_path = LoadPath.new(self, @app.pwd)
|
166
199
|
|
200
|
+
@interaction_handler = ScopedMappingInteractionHandler.new({
|
201
|
+
/\[sudo\] password for .*?:\s*/ => "#{sudo_password}\n",
|
202
|
+
})
|
203
|
+
|
167
204
|
configure_sshkit
|
168
205
|
end
|
169
206
|
|
207
|
+
# def with_sudo_password(password, &block)
|
208
|
+
# @interaction_handler.with_sudo_password(password, &block)
|
209
|
+
# end
|
210
|
+
|
211
|
+
# input_mapping : Hash[ String | Regex => String ]
|
212
|
+
# sudo_password : String
|
213
|
+
def handle_input(input_mapping, sudo_password = nil, &block)
|
214
|
+
@interaction_handler.with_mapping(input_mapping, sudo_password, &block)
|
215
|
+
end
|
216
|
+
|
217
|
+
# def handle_input_with_sudo_password(input_mapping, password, &block)
|
218
|
+
# @interaction_handler.with_mapping(input_mapping, password, &block)
|
219
|
+
# end
|
220
|
+
|
170
221
|
# configure sshkit globally
|
171
222
|
def configure_sshkit
|
172
223
|
SSHKit.config.use_format :blackhole
|
@@ -244,7 +295,7 @@ module OpsWalrus
|
|
244
295
|
case import_reference
|
245
296
|
|
246
297
|
# we know we're dealing with a package dependency reference, so we want to do the lookup in the bundle load path, where package dependencies live
|
247
|
-
when PackageDependencyReference
|
298
|
+
when PackageDependencyReference, DynamicPackageImportReference
|
248
299
|
@bundle_load_path.resolve_import_reference(origin_ops_file, import_reference)
|
249
300
|
|
250
301
|
# we know we're dealing with a directory reference or OpsFile reference outside of the bundle dir, so we want to do the lookup in the app load path
|
data/lib/opswalrus/version.rb
CHANGED
@@ -57,7 +57,7 @@ module WalrusLang
|
|
57
57
|
|
58
58
|
# binding_obj : Binding | Hash
|
59
59
|
def self.render(template, binding_obj)
|
60
|
-
binding_obj = binding_obj.to_binding if binding_obj.
|
60
|
+
binding_obj = binding_obj.to_binding if binding_obj.respond_to?(:to_binding)
|
61
61
|
ast = WalrusLang::Parser.parse(template)
|
62
62
|
ast.render(binding_obj)
|
63
63
|
end
|
data/opswalrus.gemspec
CHANGED
@@ -11,10 +11,10 @@ Gem::Specification.new do |spec|
|
|
11
11
|
spec.summary = "opswalrus is a tool that runs scripts against a fleet of hosts"
|
12
12
|
spec.description = "opswalrus is a tool that runs scripts against a fleet of hosts hosts. It's kind of like Ansible, but aims to be simpler to use."
|
13
13
|
spec.homepage = "https://github.com/opswalrus/opswalrus"
|
14
|
-
spec.license =
|
14
|
+
spec.license = "EPL-2.0"
|
15
15
|
spec.required_ruby_version = ">= 2.6.0"
|
16
16
|
|
17
|
-
# spec.metadata["allowed_push_host"] = "TODO: Set to your gem server
|
17
|
+
# spec.metadata["allowed_push_host"] = "TODO: Set to your gem server - https://example.com"
|
18
18
|
|
19
19
|
spec.metadata["homepage_uri"] = spec.homepage
|
20
20
|
spec.metadata["source_code_uri"] = "https://github.com/opswalrus/opswalrus"
|
@@ -33,13 +33,13 @@ Gem::Specification.new do |spec|
|
|
33
33
|
|
34
34
|
# gem dependencies
|
35
35
|
spec.add_dependency "citrus", "~> 3.0"
|
36
|
-
spec.add_dependency "gli",
|
37
|
-
spec.add_dependency "git",
|
38
|
-
spec.add_dependency "rubyzip",
|
36
|
+
spec.add_dependency "gli", "~> 2.21"
|
37
|
+
spec.add_dependency "git", "~> 1.18"
|
38
|
+
spec.add_dependency "rubyzip", "~> 2.3"
|
39
39
|
|
40
|
-
spec.add_dependency "bcrypt_pbkdf",
|
41
|
-
spec.add_dependency "ed25519",
|
42
|
-
spec.add_dependency "sshkit",
|
40
|
+
spec.add_dependency "bcrypt_pbkdf", "~> 1.1"
|
41
|
+
spec.add_dependency "ed25519", "~> 1.3"
|
42
|
+
spec.add_dependency "sshkit", "~> 1.21"
|
43
43
|
|
44
44
|
# For more information and examples about making a new gem, check out our
|
45
45
|
# guide at: https://bundler.io/guides/creating_gem.html
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: opswalrus
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.7
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- David Ellis
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-08-
|
11
|
+
date: 2023-08-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: citrus
|
@@ -124,15 +124,14 @@ files:
|
|
124
124
|
- LICENSE
|
125
125
|
- README.md
|
126
126
|
- Rakefile
|
127
|
+
- build.ops
|
127
128
|
- exe/ops
|
128
129
|
- lib/opswalrus.rb
|
129
130
|
- lib/opswalrus/app.rb
|
130
131
|
- lib/opswalrus/bootstrap.sh
|
131
|
-
- lib/opswalrus/bootstrap_linux_host1.sh
|
132
|
-
- lib/opswalrus/bootstrap_linux_host2.sh
|
133
|
-
- lib/opswalrus/bootstrap_linux_host3.sh
|
134
132
|
- lib/opswalrus/bundler.rb
|
135
133
|
- lib/opswalrus/cli.rb
|
134
|
+
- lib/opswalrus/git.rb
|
136
135
|
- lib/opswalrus/host.rb
|
137
136
|
- lib/opswalrus/hosts_file.rb
|
138
137
|
- lib/opswalrus/interaction_handlers.rb
|
@@ -1,12 +0,0 @@
|
|
1
|
-
#!/usr/bin/env bash
|
2
|
-
|
3
|
-
# update package list
|
4
|
-
sudo apt update -y
|
5
|
-
|
6
|
-
# update OS
|
7
|
-
# sudo DEBIAN_FRONTEND=noninteractive apt-get -o Dpkg::Options::="--force-confold" -o Dpkg::Options::="--force-confdef" dist-upgrade -q -y --allow-downgrades --allow-remove-essential --allow-change-held-packages
|
8
|
-
|
9
|
-
if [ -f /var/run/reboot-required ]; then
|
10
|
-
echo 'A system reboot is required!'
|
11
|
-
sudo reboot
|
12
|
-
fi
|
@@ -1,37 +0,0 @@
|
|
1
|
-
#!/usr/bin/env bash
|
2
|
-
|
3
|
-
# there are probably some services that need restarting because they're using old libraries, so we'll just do the easy thing and reboot
|
4
|
-
sudo DEBIAN_FRONTEND=noninteractive apt install -yq needrestart
|
5
|
-
|
6
|
-
# install basic development tools
|
7
|
-
sudo DEBIAN_FRONTEND=noninteractive apt install -yq build-essential
|
8
|
-
|
9
|
-
# install ruby dependencies
|
10
|
-
sudo DEBIAN_FRONTEND=noninteractive apt install -yq autoconf patch rustc libssl-dev libyaml-dev libreadline6-dev zlib1g-dev libgmp-dev libncurses5-dev libffi-dev libgdbm6 libgdbm-dev libdb-dev uuid-dev
|
11
|
-
|
12
|
-
# restart services that need it
|
13
|
-
sudo needrestart -q -r a
|
14
|
-
|
15
|
-
# vagrant@ubuntu-jammy:~$ sudo needrestart -q -r a
|
16
|
-
# systemctl restart unattended-upgrades.service
|
17
|
-
|
18
|
-
# vagrant@ubuntu-jammy:~$ sudo needrestart -r l
|
19
|
-
# Scanning processes...
|
20
|
-
# Scanning candidates...
|
21
|
-
# Scanning linux images...
|
22
|
-
#
|
23
|
-
# Running kernel seems to be up-to-date.
|
24
|
-
#
|
25
|
-
# Services to be restarted:
|
26
|
-
#
|
27
|
-
# Service restarts being deferred:
|
28
|
-
# systemctl restart unattended-upgrades.service
|
29
|
-
#
|
30
|
-
# No containers need to be restarted.
|
31
|
-
#
|
32
|
-
# No user sessions are running outdated binaries.
|
33
|
-
#
|
34
|
-
# No VM guests are running outdated hypervisor (qemu) binaries on this host.
|
35
|
-
|
36
|
-
# reboot just in case
|
37
|
-
# sudo reboot
|
@@ -1,21 +0,0 @@
|
|
1
|
-
#!/usr/bin/env bash
|
2
|
-
|
3
|
-
# install homebrew
|
4
|
-
NONINTERACTIVE=1 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
|
5
|
-
eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"
|
6
|
-
|
7
|
-
# install gcc, rust, ruby
|
8
|
-
brew install gcc
|
9
|
-
brew install rust
|
10
|
-
brew install ruby
|
11
|
-
|
12
|
-
# brew install rtx
|
13
|
-
# eval "$(rtx activate bash)" # register a shell hook
|
14
|
-
# rtx use -g ruby@3.2 # install ruby via rtx
|
15
|
-
|
16
|
-
# download frum for ruby version management
|
17
|
-
# curl -L -o frum.tar.gz https://github.com/TaKO8Ki/frum/releases/download/v0.1.2/frum-v0.1.2-x86_64-unknown-linux-musl.tar.gz
|
18
|
-
# tar -zxf frum.tar.gz
|
19
|
-
# mv frum-v0.1.2-x86_64-unknown-linux-musl/frum ~/bin
|
20
|
-
# chmod 755 ~/bin/frum
|
21
|
-
|