opswalrus 1.0.13 → 1.0.15
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Dockerfile +2 -2
- data/Gemfile.lock +7 -1
- data/README.md +6 -4
- data/build.ops +2 -1
- data/lib/opswalrus/app.rb +81 -27
- data/lib/opswalrus/errors.rb +7 -0
- data/lib/opswalrus/host.rb +4 -0
- data/lib/opswalrus/invocation.rb +1 -442
- data/lib/opswalrus/operation_runner.rb +12 -12
- data/lib/opswalrus/ops_file_script.rb +7 -22
- data/lib/opswalrus/package_file.rb +11 -0
- data/lib/opswalrus/runtime_environment.rb +62 -11
- data/lib/opswalrus/version.rb +1 -1
- data/opswalrus.gemspec +2 -0
- metadata +30 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 89bf0215bfa03656ade838eeecc4e8a9026c71290d65499d7df9ef9f93e482ec
|
4
|
+
data.tar.gz: b434b04092d90139ce02b0f55b3c54f7027cc84d0190ed68d9d9baacf04d4c62
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c2e19399736ff4e3f46a391484342c8411cfc4c29727f0104e3a5cd46313fd68da036ca41927036351cd9d74b9d3f7f7c50dcb61cc559174e40d8e0bd4c86c64
|
7
|
+
data.tar.gz: 4bddab84402b4a89d80bdda60df5eeab729d3b22a0b99f28fa4730c5ab7de051acba633b36848a7cd63648c0aea6230ffb86d97db81f67c6349282142ccf7844
|
data/Dockerfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,12 +1,14 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
opswalrus (1.0.
|
4
|
+
opswalrus (1.0.15)
|
5
|
+
amazing_print (~> 1.5)
|
5
6
|
bcrypt_pbkdf (~> 1.1)
|
6
7
|
citrus (~> 3.0)
|
7
8
|
ed25519 (~> 1.3)
|
8
9
|
git (~> 1.18)
|
9
10
|
gli (~> 2.21)
|
11
|
+
ougai (~> 2.0)
|
10
12
|
rubyzip (~> 2.3)
|
11
13
|
sshkit (~> 1.21)
|
12
14
|
|
@@ -15,6 +17,7 @@ GEM
|
|
15
17
|
specs:
|
16
18
|
addressable (2.8.5)
|
17
19
|
public_suffix (>= 2.0.2, < 6.0)
|
20
|
+
amazing_print (1.5.0)
|
18
21
|
bcrypt_pbkdf (1.1.0)
|
19
22
|
citrus (3.0.2)
|
20
23
|
diff-lcs (1.5.0)
|
@@ -26,6 +29,9 @@ GEM
|
|
26
29
|
net-scp (4.0.0)
|
27
30
|
net-ssh (>= 2.6.5, < 8.0.0)
|
28
31
|
net-ssh (7.2.0)
|
32
|
+
oj (3.16.0)
|
33
|
+
ougai (2.0.0)
|
34
|
+
oj (~> 3.10)
|
29
35
|
public_suffix (5.0.3)
|
30
36
|
rake (13.0.6)
|
31
37
|
rchardet (1.8.0)
|
data/README.md
CHANGED
@@ -11,17 +11,19 @@ You have two options:
|
|
11
11
|
## Rubygems install
|
12
12
|
|
13
13
|
```shell
|
14
|
-
gem install opswalrus
|
14
|
+
❯ gem install opswalrus
|
15
15
|
|
16
|
-
ops version
|
16
|
+
❯ ops version
|
17
|
+
1.0.13
|
17
18
|
```
|
18
19
|
|
19
20
|
## Docker install
|
20
21
|
|
21
22
|
```shell
|
22
|
-
alias ops='docker run --rm -it -v $HOME/.ssh:/root/.ssh -v /var/run/docker.sock:/var/run/docker.sock -v ${PWD}/:/workdir ghcr.io/opswalrus/ops'
|
23
|
+
❯ alias ops='docker run --rm -it -v $HOME/.ssh:/root/.ssh -v /var/run/docker.sock:/var/run/docker.sock -v ${PWD}/:/workdir ghcr.io/opswalrus/ops'
|
23
24
|
|
24
|
-
ops version
|
25
|
+
❯ ops version
|
26
|
+
1.0.13
|
25
27
|
```
|
26
28
|
|
27
29
|
# Examples
|
data/build.ops
CHANGED
@@ -51,4 +51,5 @@ end
|
|
51
51
|
|
52
52
|
totp = sh("Get Rubygems OTP") { 'bw get totp Rubygems' }
|
53
53
|
sh("Push gem", input: {"You have enabled multi-factor authentication. Please enter OTP code." => "#{totp}\n"}) { 'gem push opswalrus-{{ version }}.gem' }
|
54
|
-
sh("Build docker image") { 'docker build -t opswalrus/ops:{{ version }} .' }
|
54
|
+
sh("Build docker image") { 'docker build -t ghcr.io/opswalrus/ops:latest -t ghcr.io/opswalrus/ops:{{ version }} -t opswalrus/ops:latest -t opswalrus/ops:{{ version }} .' }
|
55
|
+
sh("Push docker image to ghcr.io/opswalrus/ops") { 'docker push ghcr.io/opswalrus/ops:latest' }
|
data/lib/opswalrus/app.rb
CHANGED
@@ -1,12 +1,15 @@
|
|
1
1
|
require "citrus"
|
2
2
|
require "io/console"
|
3
3
|
require "json"
|
4
|
+
require "logger"
|
4
5
|
require "random/formatter"
|
6
|
+
require "ougai"
|
5
7
|
require "shellwords"
|
6
8
|
require "socket"
|
7
9
|
require "stringio"
|
8
10
|
require "yaml"
|
9
11
|
require "pathname"
|
12
|
+
require_relative "errors"
|
10
13
|
require_relative "patches"
|
11
14
|
require_relative "git"
|
12
15
|
require_relative "host"
|
@@ -18,9 +21,6 @@ require_relative "version"
|
|
18
21
|
|
19
22
|
|
20
23
|
module OpsWalrus
|
21
|
-
class Error < StandardError
|
22
|
-
end
|
23
|
-
|
24
24
|
class App
|
25
25
|
def self.instance(*args)
|
26
26
|
@instance ||= new(*args)
|
@@ -32,6 +32,10 @@ module OpsWalrus
|
|
32
32
|
attr_reader :local_hostname
|
33
33
|
|
34
34
|
def initialize(pwd = Dir.pwd)
|
35
|
+
@logger = Ougai::Logger.new($stdout, level: Logger::INFO) # Logger.new($stdout, level: Logger::INFO)
|
36
|
+
@logger.formatter = Ougai::Formatters::Readable.new
|
37
|
+
|
38
|
+
|
35
39
|
@verbose = false
|
36
40
|
@sudo_user = nil
|
37
41
|
@sudo_password = nil
|
@@ -89,6 +93,7 @@ module OpsWalrus
|
|
89
93
|
|
90
94
|
def set_verbose(verbose)
|
91
95
|
@verbose = verbose
|
96
|
+
@logger.debug! if verbose?
|
92
97
|
end
|
93
98
|
|
94
99
|
def verbose?
|
@@ -99,6 +104,26 @@ module OpsWalrus
|
|
99
104
|
@verbose == 2
|
100
105
|
end
|
101
106
|
|
107
|
+
def error(msg)
|
108
|
+
@logger.error(msg)
|
109
|
+
end
|
110
|
+
|
111
|
+
def warn(msg)
|
112
|
+
@logger.warn(msg)
|
113
|
+
end
|
114
|
+
|
115
|
+
def log(msg)
|
116
|
+
@logger.info(msg)
|
117
|
+
end
|
118
|
+
|
119
|
+
def debug(msg)
|
120
|
+
@logger.debug(msg)
|
121
|
+
end
|
122
|
+
|
123
|
+
def trace(msg)
|
124
|
+
@logger.trace(msg)
|
125
|
+
end
|
126
|
+
|
102
127
|
def set_pwd(pwd)
|
103
128
|
@pwd = pwd.to_pathname
|
104
129
|
@bundler = Bundler.new(@pwd)
|
@@ -147,13 +172,9 @@ module OpsWalrus
|
|
147
172
|
def run(package_operation_and_args)
|
148
173
|
return 0 if package_operation_and_args.empty?
|
149
174
|
|
150
|
-
ops_file_path, operation_kv_args,
|
151
|
-
ops_file = OpsFile.new(self, ops_file_path)
|
175
|
+
ops_file_path, operation_kv_args, tmp_bundle_root_dir = get_entry_point_ops_file_and_args(package_operation_and_args)
|
152
176
|
|
153
|
-
|
154
|
-
if ops_file.package_file && ops_file.package_file.dirname.to_s !~ /#{Bundler::BUNDLE_DIR}/
|
155
|
-
ops_file = set_pwd_to_ops_file_package_directory(ops_file)
|
156
|
-
end
|
177
|
+
ops_file = load_entry_point_ops_file(ops_file_path, tmp_bundle_root_dir)
|
157
178
|
|
158
179
|
if @verbose
|
159
180
|
puts "Running: #{ops_file.ops_file_path}"
|
@@ -177,19 +198,52 @@ module OpsWalrus
|
|
177
198
|
|
178
199
|
exit_status
|
179
200
|
ensure
|
180
|
-
FileUtils.remove_entry(
|
201
|
+
FileUtils.remove_entry(tmp_bundle_root_dir) if tmp_bundle_root_dir
|
202
|
+
end
|
203
|
+
|
204
|
+
def load_entry_point_ops_file(ops_file_path, tmp_bundle_root_dir)
|
205
|
+
ops_file = OpsFile.new(self, ops_file_path)
|
206
|
+
|
207
|
+
# we are running the ops file from within a temporary bundle root directory created by unzipping a zip bundle workspace
|
208
|
+
if tmp_bundle_root_dir
|
209
|
+
return set_pwd_and_rebase_ops_file(tmp_bundle_root_dir, ops_file)
|
210
|
+
end
|
211
|
+
|
212
|
+
# if the ops file is contained within a bundle directory, then that means we're probably running this command invocation
|
213
|
+
# on a remote host, e.g. /home/linuxbrew/.linuxbrew/bin/gem exec -g opswalrus ops run --script /tmp/d20230822-18829-2j5ij2 opswalrus_bundle docker install install
|
214
|
+
# and the corresponding entry point, e.g. /tmp/d20230822-18829-2j5ij2/opswalrus_bundle/docker/install/install.ops
|
215
|
+
# is actually being run from a temporary zip bundle root directory
|
216
|
+
# so we want to set the app's pwd to be the parent directory of the Bundler::BUNDLE_DIR directory, e.g. /tmp/d20230822-18829-2j5ij2
|
217
|
+
if ops_file.ops_file_path.to_s =~ /#{Bundler::BUNDLE_DIR}/
|
218
|
+
return set_pwd_to_parent_of_bundle_dir(ops_file)
|
219
|
+
end
|
220
|
+
|
221
|
+
# if the ops file is part of a package, then set the package directory as the app's pwd
|
222
|
+
if ops_file.package_file && ops_file.package_file.dirname.to_s !~ /#{Bundler::BUNDLE_DIR}/
|
223
|
+
return set_pwd_to_ops_file_package_directory(ops_file)
|
224
|
+
end
|
225
|
+
|
226
|
+
ops_file
|
227
|
+
end
|
228
|
+
|
229
|
+
def set_pwd_to_parent_of_bundle_dir(ops_file)
|
230
|
+
match = /^(.*)#{Bundler::BUNDLE_DIR}.*$/.match(ops_file.ops_file_path.to_s)
|
231
|
+
parent_directory_path = match.captures.first.to_pathname.cleanpath
|
232
|
+
set_pwd_and_rebase_ops_file(parent_directory_path, ops_file)
|
181
233
|
end
|
182
234
|
|
183
235
|
# sets the App's pwd to the ops file's package directory and
|
184
|
-
# returns a new OpsFile that points at the revised pathname
|
236
|
+
# returns a new OpsFile that points at the revised pathname when considered as relative to the package file's directory
|
185
237
|
def set_pwd_to_ops_file_package_directory(ops_file)
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
238
|
+
set_pwd_and_rebase_ops_file(ops_file.package_file.dirname, ops_file)
|
239
|
+
end
|
240
|
+
|
241
|
+
# returns a new OpsFile that points at the revised pathname when considered as relative to the new working directory
|
242
|
+
def set_pwd_and_rebase_ops_file(new_working_directory, ops_file)
|
243
|
+
set_pwd(new_working_directory)
|
244
|
+
rebased_ops_file_relative_path = ops_file.ops_file_path.relative_path_from(new_working_directory)
|
245
|
+
# note: rebased_ops_file_relative_path is a relative path that is relative to new_working_directory
|
246
|
+
absolute_ops_file_path = new_working_directory.join(rebased_ops_file_relative_path)
|
193
247
|
OpsFile.new(self, absolute_ops_file_path)
|
194
248
|
end
|
195
249
|
|
@@ -203,18 +257,18 @@ module OpsWalrus
|
|
203
257
|
# - ["../../my-package/operation1", "arg1:val1", "arg2:val2", "arg3:val3"]
|
204
258
|
# - ["../../my-package/operation1"]
|
205
259
|
#
|
206
|
-
# returns 3-tuple of the form: [ ops_file_path, operation_kv_args,
|
207
|
-
# such that the third item -
|
260
|
+
# returns 3-tuple of the form: [ ops_file_path, operation_kv_args, optional_tmp_bundle_root_dir ]
|
261
|
+
# such that the third item - optional_tmp_bundle_root_dir - if present, should be deleted after the script has completed running
|
208
262
|
def get_entry_point_ops_file_and_args(package_operation_and_args)
|
209
263
|
package_operation_and_args = package_operation_and_args.dup
|
210
264
|
package_or_ops_file_reference = package_operation_and_args.slice!(0, 1).first
|
211
|
-
|
265
|
+
tmp_bundle_root_dir = nil
|
212
266
|
|
213
267
|
case
|
214
268
|
when Dir.exist?(package_or_ops_file_reference)
|
215
269
|
dir = package_or_ops_file_reference
|
216
270
|
ops_file_path, operation_kv_args = find_entry_point_ops_file_in_dir(dir, package_operation_and_args)
|
217
|
-
[ops_file_path, operation_kv_args,
|
271
|
+
[ops_file_path, operation_kv_args, tmp_bundle_root_dir]
|
218
272
|
when File.exist?(package_or_ops_file_reference)
|
219
273
|
first_filepath = package_or_ops_file_reference.to_pathname.realpath
|
220
274
|
|
@@ -222,18 +276,18 @@ module OpsWalrus
|
|
222
276
|
when ".ops"
|
223
277
|
[first_filepath, package_operation_and_args]
|
224
278
|
when ".zip"
|
225
|
-
|
279
|
+
tmp_bundle_root_dir = Dir.mktmpdir.to_pathname # this is the temporary bundle root dir
|
226
280
|
|
227
281
|
# unzip the bundle into the temp directory
|
228
|
-
DirZipper.unzip(first_filepath,
|
282
|
+
DirZipper.unzip(first_filepath, tmp_bundle_root_dir)
|
229
283
|
|
230
|
-
find_entry_point_ops_file_in_dir(
|
284
|
+
find_entry_point_ops_file_in_dir(tmp_bundle_root_dir, package_operation_and_args)
|
231
285
|
else
|
232
286
|
raise Error, "Unknown file type for entrypoint: #{first_filepath}"
|
233
287
|
end
|
234
288
|
|
235
289
|
# operation_kv_args = package_operation_and_args
|
236
|
-
[ops_file_path, operation_kv_args,
|
290
|
+
[ops_file_path, operation_kv_args, tmp_bundle_root_dir]
|
237
291
|
when repo_url = Git.repo?(package_or_ops_file_reference)
|
238
292
|
destination_package_path = bundler.download_git_package(repo_url)
|
239
293
|
|
@@ -241,7 +295,7 @@ module OpsWalrus
|
|
241
295
|
|
242
296
|
# for an original package_operation_and_args of ["github.com/davidkellis/my-package", "operation1", "arg1:val1", "arg2:val2", "arg3:val3"]
|
243
297
|
# we return: [ "#{pwd}/#{Bundler::BUNDLE_DIR}/github-com-davidkellis-my-package/operation1.ops", ["arg1:val1", "arg2:val2", "arg3:val3"] ]
|
244
|
-
[ops_file_path, operation_kv_args,
|
298
|
+
[ops_file_path, operation_kv_args, tmp_bundle_root_dir]
|
245
299
|
else
|
246
300
|
raise Error, "Unknown operation reference: #{package_or_ops_file_reference.inspect}"
|
247
301
|
end
|
data/lib/opswalrus/host.rb
CHANGED
@@ -173,6 +173,10 @@ module OpsWalrus
|
|
173
173
|
|
174
174
|
# the subclasses of this class will define methods that handle method dispatch via HostProxyOpsFileInvocationBuilder objects
|
175
175
|
|
176
|
+
def to_s
|
177
|
+
@_host.to_s
|
178
|
+
end
|
179
|
+
|
176
180
|
def method_missing(name, ...)
|
177
181
|
@_host.send(name, ...)
|
178
182
|
end
|
data/lib/opswalrus/invocation.rb
CHANGED
@@ -1,15 +1,3 @@
|
|
1
|
-
# require 'json'
|
2
|
-
# require 'set'
|
3
|
-
# require 'shellwords'
|
4
|
-
# require 'socket'
|
5
|
-
# require 'stringio'
|
6
|
-
|
7
|
-
# require 'sshkit'
|
8
|
-
# require 'sshkit/dsl'
|
9
|
-
|
10
|
-
# require_relative 'host'
|
11
|
-
# require_relative 'sshkit_ext'
|
12
|
-
# require_relative 'walrus_lang'
|
13
1
|
|
14
2
|
module OpsWalrus
|
15
3
|
|
@@ -127,7 +115,7 @@ module OpsWalrus
|
|
127
115
|
resolved_symbol = @namespace_or_ops_file.resolve_symbol(method_name)
|
128
116
|
if resolved_symbol.is_a? OpsFile
|
129
117
|
params_hash = resolved_symbol.build_params_hash(*args, **kwargs)
|
130
|
-
resolved_symbol.invoke(runtime_env, params_hash)
|
118
|
+
resolved_symbol.invoke(@runtime_env, params_hash)
|
131
119
|
else
|
132
120
|
self
|
133
121
|
end
|
@@ -142,434 +130,5 @@ module OpsWalrus
|
|
142
130
|
_resolve_method_and_invoke(name, *args, **kwargs)
|
143
131
|
end
|
144
132
|
end
|
145
|
-
# class ArrayOrHashNavigationProxy
|
146
|
-
# def initialize(array_or_hash)
|
147
|
-
# @obj = array_or_hash
|
148
|
-
# end
|
149
|
-
# def [](index, *args, **kwargs, &block)
|
150
|
-
# @obj.method(:[]).call(index, *args, **kwargs, &block)
|
151
|
-
# end
|
152
|
-
# def respond_to_missing?(method, *)
|
153
|
-
# @obj.is_a?(Hash) && @obj.respond_to?(method)
|
154
|
-
# end
|
155
|
-
# def method_missing(name, *args, **kwargs, &block)
|
156
|
-
# case @obj
|
157
|
-
# when Array
|
158
|
-
# @obj.method(name).call(*args, **kwargs, &block)
|
159
|
-
# when Hash
|
160
|
-
# if @obj.respond_to?(name)
|
161
|
-
# @obj.method(name).call(*args, **kwargs, &block)
|
162
|
-
# else
|
163
|
-
# value = self[name.to_s]
|
164
|
-
# case value
|
165
|
-
# when Array, Hash
|
166
|
-
# ArrayOrHashNavigationProxy.new(value)
|
167
|
-
# else
|
168
|
-
# value
|
169
|
-
# end
|
170
|
-
# end
|
171
|
-
# end
|
172
|
-
# end
|
173
|
-
# end
|
174
|
-
|
175
|
-
# class InvocationParams
|
176
|
-
# # params : Hash
|
177
|
-
# def initialize(params)
|
178
|
-
# @params = params
|
179
|
-
# end
|
180
|
-
|
181
|
-
# def [](key)
|
182
|
-
# key = key.to_s if key.is_a? Symbol
|
183
|
-
# @params[key]
|
184
|
-
# end
|
185
|
-
|
186
|
-
# def dig(*keys)
|
187
|
-
# # keys = keys.map {|key| key.is_a?(Integer) ? key : key.to_s }
|
188
|
-
# @params.dig(*keys)
|
189
|
-
# end
|
190
|
-
|
191
|
-
# def method_missing(name, *args, **kwargs, &block)
|
192
|
-
# if @params.respond_to?(name)
|
193
|
-
# @params.method(name).call(*args, **kwargs, &block)
|
194
|
-
# else
|
195
|
-
# value = self[name]
|
196
|
-
# case value
|
197
|
-
# when Array, Hash
|
198
|
-
# ArrayOrHashNavigationProxy.new(value)
|
199
|
-
# else
|
200
|
-
# value
|
201
|
-
# end
|
202
|
-
# end
|
203
|
-
# end
|
204
|
-
# end
|
205
|
-
|
206
|
-
|
207
|
-
# # BootstrapLinuxHostShellScript = <<~SCRIPT
|
208
|
-
# # #!/usr/bin/env bash
|
209
|
-
# # ...
|
210
|
-
# # SCRIPT
|
211
|
-
|
212
|
-
# module InvocationDSL
|
213
|
-
# def ssh(*args, **kwargs, &block)
|
214
|
-
# host_proxy_class = @ops_file_script.host_proxy_class
|
215
|
-
# hosts = inventory(*args, **kwargs).map {|host| host_proxy_class.new(host) }
|
216
|
-
# sshkit_hosts = hosts.map(&:sshkit_host)
|
217
|
-
# sshkit_host_to_ops_host_map = sshkit_hosts.zip(hosts).to_h
|
218
|
-
# runtime_env = @runtime_env
|
219
|
-
# local_host = self
|
220
|
-
# # bootstrap_shell_script = BootstrapLinuxHostShellScript
|
221
|
-
# # on sshkit_hosts do |sshkit_host|
|
222
|
-
# SSHKit::Coordinator.new(sshkit_hosts).each(in: kwargs[:in] || :parallel) do |sshkit_host|
|
223
|
-
|
224
|
-
# # in this context, self is an instance of one of the subclasses of SSHKit::Backend::Abstract, e.g. SSHKit::Backend::Netssh
|
225
|
-
|
226
|
-
# host = sshkit_host_to_ops_host_map[sshkit_host]
|
227
|
-
# # puts "#{host.alias} / #{host}:"
|
228
|
-
|
229
|
-
# begin
|
230
|
-
# host.set_runtime_env(runtime_env)
|
231
|
-
# host.set_ssh_session_connection(self) # self is an instance of one of the subclasses of SSHKit::Backend::Abstract, e.g. SSHKit::Backend::Netssh
|
232
|
-
|
233
|
-
# # copy over bootstrap shell script
|
234
|
-
# # io = StringIO.new(bootstrap_shell_script)
|
235
|
-
# io = File.open(__FILE__.to_pathname.dirname.join("bootstrap.sh"))
|
236
|
-
# upload_success = host.upload(io, "tmpopsbootstrap.sh")
|
237
|
-
# io.close
|
238
|
-
# raise Error, "Unable to upload bootstrap shell script to remote host" unless upload_success
|
239
|
-
# host.execute(:chmod, "755", "tmpopsbootstrap.sh")
|
240
|
-
# host.execute(:sh, "tmpopsbootstrap.sh")
|
241
|
-
|
242
|
-
# # copy over ops bundle zip file
|
243
|
-
# zip_bundle_path = runtime_env.zip_bundle_path
|
244
|
-
# upload_success = host.upload(zip_bundle_path, "tmpops.zip")
|
245
|
-
# raise Error, "Unable to upload ops bundle to remote host" unless upload_success
|
246
|
-
|
247
|
-
# stdout, stderr, exit_status = host.run_ops(:bundle, "unzip tmpops.zip", in_bundle_root_dir: false)
|
248
|
-
# raise Error, "Unable to unzip ops bundle on remote host" unless exit_status == 0
|
249
|
-
# tmp_bundle_root_dir = stdout.strip
|
250
|
-
# host.set_ssh_session_tmp_bundle_root_dir(tmp_bundle_root_dir)
|
251
|
-
|
252
|
-
# # we run the block in the context of the host, s.t. `self` within the block evaluates to `host`
|
253
|
-
# retval = host.instance_exec(local_host, &block) # host is passed as the argument to the block
|
254
|
-
|
255
|
-
# # puts retval.inspect
|
256
|
-
|
257
|
-
# # cleanup
|
258
|
-
# if tmp_bundle_root_dir =~ /tmp/ # sanity check the temp path before we blow away something we don't intend
|
259
|
-
# host.execute(:rm, "-rf", "tmpopsbootstrap.sh", "tmpops.zip", tmp_bundle_root_dir)
|
260
|
-
# else
|
261
|
-
# host.execute(:rm, "-rf", "tmpopsbootstrap.sh", "tmpops.zip")
|
262
|
-
# end
|
263
|
-
|
264
|
-
# retval
|
265
|
-
# rescue SSHKit::Command::Failed => e
|
266
|
-
# puts "[!] Command failed:"
|
267
|
-
# puts e.message
|
268
|
-
# rescue Net::SSH::ConnectionTimeout
|
269
|
-
# puts "[!] The host '#{host}' not alive!"
|
270
|
-
# rescue Net::SSH::Timeout
|
271
|
-
# puts "[!] The host '#{host}' disconnected/timeouted unexpectedly!"
|
272
|
-
# rescue Errno::ECONNREFUSED
|
273
|
-
# puts "[!] Incorrect port #{port} for #{host}"
|
274
|
-
# rescue Net::SSH::HostKeyMismatch => e
|
275
|
-
# puts "[!] The host fingerprint does not match the last observed fingerprint for #{host}"
|
276
|
-
# puts e.message
|
277
|
-
# puts "You might try `ssh-keygen -f ~/.ssh/known_hosts -R \"#{host}\"`"
|
278
|
-
# rescue Net::SSH::AuthenticationFailed
|
279
|
-
# puts "Wrong Password: #{host} | #{user}:#{password}"
|
280
|
-
# rescue Net::SSH::Authentication::DisallowedMethod
|
281
|
-
# puts "[!] The host '#{host}' doesn't accept password authentication method."
|
282
|
-
# rescue Errno::EHOSTUNREACH => e
|
283
|
-
# puts "[!] The host '#{host}' is unreachable"
|
284
|
-
# rescue => e
|
285
|
-
# puts e.class
|
286
|
-
# puts e.message
|
287
|
-
# # puts e.backtrace.join("\n")
|
288
|
-
# ensure
|
289
|
-
# host.clear_ssh_session
|
290
|
-
# end
|
291
|
-
# end
|
292
|
-
# end
|
293
|
-
|
294
|
-
# def inventory(*args, **kwargs)
|
295
|
-
# tags = args.map(&:to_s)
|
296
|
-
|
297
|
-
# kwargs = kwargs.transform_keys(&:to_s)
|
298
|
-
# tags.concat(kwargs["tags"]) if kwargs["tags"]
|
299
|
-
|
300
|
-
# @runtime_env.app.inventory(tags)
|
301
|
-
# end
|
302
|
-
|
303
|
-
# def exit(exit_status, message = nil)
|
304
|
-
# if message
|
305
|
-
# puts message
|
306
|
-
# end
|
307
|
-
# result = if exit_status == 0
|
308
|
-
# Invocation::Success.new(nil)
|
309
|
-
# else
|
310
|
-
# Invocation::Error.new(nil, exit_status)
|
311
|
-
# end
|
312
|
-
# throw :exit_now, result
|
313
|
-
# end
|
314
|
-
|
315
|
-
# def env(*keys)
|
316
|
-
# keys = keys.map(&:to_s)
|
317
|
-
# if keys.empty?
|
318
|
-
# @env
|
319
|
-
# else
|
320
|
-
# @env.dig(*keys)
|
321
|
-
# end
|
322
|
-
# end
|
323
|
-
|
324
|
-
# # currently, import may only be used to import a package that is referenced in the script's package file
|
325
|
-
# # I may decide to extend this to work with dynamic package references
|
326
|
-
# #
|
327
|
-
# # local_package_name is the local package name defined for the package dependency that is attempting to be referenced
|
328
|
-
# def import(local_package_name)
|
329
|
-
# local_package_name = local_package_name.to_s
|
330
|
-
# package_reference = @ops_file_script.ops_file.package_file&.dependency(local_package_name)
|
331
|
-
# raise Error, "Unknown package reference: #{local_package_name}" unless package_reference
|
332
|
-
# import_reference = PackageDependencyReference.new(local_package_name, package_reference)
|
333
|
-
# # puts "import: #{import_reference.inspect}"
|
334
|
-
# @runtime_env.resolve_import_reference(@ops_file_script.ops_file, import_reference)
|
335
|
-
# end
|
336
|
-
|
337
|
-
# def params(*keys, default: nil)
|
338
|
-
# keys = keys.map(&:to_s)
|
339
|
-
# if keys.empty?
|
340
|
-
# @params
|
341
|
-
# else
|
342
|
-
# @params.dig(*keys) || default
|
343
|
-
# end
|
344
|
-
# end
|
345
|
-
|
346
|
-
# # returns the stdout from the command
|
347
|
-
# def sh(desc_or_cmd = nil, cmd = nil, input: nil, &block)
|
348
|
-
# out, err, status = *shell!(desc_or_cmd, cmd, block, input: input)
|
349
|
-
# out
|
350
|
-
# end
|
351
|
-
|
352
|
-
# # returns the tuple: [stdout, stderr, exit_status]
|
353
|
-
# def shell(desc_or_cmd = nil, cmd = nil, input: nil, &block)
|
354
|
-
# shell!(desc_or_cmd, cmd, block, input: input)
|
355
|
-
# end
|
356
|
-
|
357
|
-
# # returns the tuple: [stdout, stderr, exit_status]
|
358
|
-
# def shell!(desc_or_cmd = nil, cmd = nil, block = nil, input: nil)
|
359
|
-
# # description = nil
|
360
|
-
|
361
|
-
# 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
|
362
|
-
|
363
|
-
# description = desc_or_cmd if cmd || block
|
364
|
-
# cmd = block.call if block
|
365
|
-
# cmd ||= desc_or_cmd
|
366
|
-
|
367
|
-
# cmd = WalrusLang.render(cmd, block.binding) if block && cmd =~ /{{.*}}/
|
368
|
-
|
369
|
-
# #cmd = Shellwords.escape(cmd)
|
370
|
-
|
371
|
-
# # puts "shell! self: #{self.inspect}"
|
372
|
-
|
373
|
-
# if App.instance.report_mode?
|
374
|
-
# print "[#{@runtime_env.local_hostname}] "
|
375
|
-
# print "#{description}: " if description
|
376
|
-
# puts cmd
|
377
|
-
# end
|
378
|
-
|
379
|
-
# return unless cmd && !cmd.strip.empty?
|
380
|
-
|
381
|
-
# sshkit_cmd = @runtime_env.handle_input(input) do |interaction_handler|
|
382
|
-
# # self is a Module instance that is serving as the evaluation context in an instance of a subclass of an Invocation; see Invocation#evaluate
|
383
|
-
# backend.execute_cmd(cmd, interaction_handler: interaction_handler, verbosity: :info)
|
384
|
-
# end
|
385
|
-
|
386
|
-
# [sshkit_cmd.full_stdout, sshkit_cmd.full_stderr, sshkit_cmd.exit_status]
|
387
|
-
# end
|
388
|
-
|
389
|
-
# # def init_brew
|
390
|
-
# # execute('eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"')
|
391
|
-
# # end
|
392
|
-
|
393
|
-
# end
|
394
|
-
|
395
|
-
# # An Invocation object represents a stack frame, and the params_hash represents the
|
396
|
-
# # arguments that the caller has supplied for that stack frame to reference
|
397
|
-
# class Invocation
|
398
|
-
# class Result
|
399
|
-
# attr_accessor :value
|
400
|
-
# attr_accessor :exit_status
|
401
|
-
# def initialize(value, exit_status = 0)
|
402
|
-
# @value = value
|
403
|
-
# @exit_status = exit_status
|
404
|
-
# end
|
405
|
-
# def success?
|
406
|
-
# !failure?
|
407
|
-
# end
|
408
|
-
# def failure?
|
409
|
-
# !success?
|
410
|
-
# end
|
411
|
-
# end
|
412
|
-
# class Success < Result
|
413
|
-
# def initialize(value)
|
414
|
-
# super(value, 0)
|
415
|
-
# end
|
416
|
-
# def success?
|
417
|
-
# true
|
418
|
-
# end
|
419
|
-
# end
|
420
|
-
# class Error < Result
|
421
|
-
# def initialize(value, exit_status = 1)
|
422
|
-
# super(value, exit_status == 0 ? 1 : exit_status)
|
423
|
-
# end
|
424
|
-
# def failure?
|
425
|
-
# true
|
426
|
-
# end
|
427
|
-
# end
|
428
|
-
|
429
|
-
|
430
|
-
# def self.define_invocation_class(ops_file)
|
431
|
-
# klass = Class.new(Invocation)
|
432
|
-
|
433
|
-
# methods_defined = Set.new
|
434
|
-
|
435
|
-
# # define methods for every import in the script
|
436
|
-
# ops_file.local_symbol_table.each do |symbol_name, import_reference|
|
437
|
-
# unless methods_defined.include? symbol_name
|
438
|
-
# klass.define_method(symbol_name) do |*args, **kwargs, &block|
|
439
|
-
# # puts "0" * 80
|
440
|
-
# # puts "@runtime_env.resolve_import_reference(@ops_file_script.ops_file, #{import_reference.inspect})"
|
441
|
-
# # puts @ops_file_script.ops_file.ops_file_path
|
442
|
-
# # puts symbol_name
|
443
|
-
# namespace_or_ops_file = @runtime_env.resolve_import_reference(@ops_file_script.ops_file, import_reference)
|
444
|
-
# # puts namespace_or_ops_file.inspect
|
445
|
-
# # puts "0" * 80
|
446
|
-
# case namespace_or_ops_file
|
447
|
-
# when Namespace
|
448
|
-
# namespace_or_ops_file
|
449
|
-
# when OpsFile
|
450
|
-
# params_hash = namespace_or_ops_file.build_params_hash(*args, **kwargs)
|
451
|
-
# namespace_or_ops_file.invoke(@runtime_env, params_hash)
|
452
|
-
# end
|
453
|
-
# end
|
454
|
-
# methods_defined << symbol_name
|
455
|
-
# end
|
456
|
-
# end
|
457
|
-
|
458
|
-
# # define methods for every Namespace or OpsFile within the namespace that the OpsFile resides within
|
459
|
-
# sibling_symbol_table = Set.new
|
460
|
-
# sibling_symbol_table |= ops_file.dirname.glob("*.ops").map {|ops_file_path| ops_file_path.basename(".ops").to_s } # OpsFiles
|
461
|
-
# sibling_symbol_table |= ops_file.dirname.glob("*").select(&:directory?).map {|dir_path| dir_path.basename.to_s } # Namespaces
|
462
|
-
# sibling_symbol_table.each do |symbol_name|
|
463
|
-
# unless methods_defined.include? symbol_name
|
464
|
-
# klass.define_method(symbol_name) do |*args, **kwargs, &block|
|
465
|
-
# # puts "0" * 80
|
466
|
-
# # puts "@runtime_env.resolve_symbol(@ops_file_script.ops_file, #{symbol_name})"
|
467
|
-
# # puts @ops_file_script.ops_file.ops_file_path
|
468
|
-
# # puts symbol_name
|
469
|
-
# namespace_or_ops_file = @runtime_env.resolve_sibling_symbol(@ops_file_script.ops_file, symbol_name)
|
470
|
-
# # puts namespace_or_ops_file.inspect
|
471
|
-
# # puts "0" * 80
|
472
|
-
# case namespace_or_ops_file
|
473
|
-
# when Namespace
|
474
|
-
# namespace_or_ops_file
|
475
|
-
# when OpsFile
|
476
|
-
# params_hash = namespace_or_ops_file.build_params_hash(*args, **kwargs)
|
477
|
-
# namespace_or_ops_file.invoke(@runtime_env, params_hash)
|
478
|
-
# end
|
479
|
-
# end
|
480
|
-
# methods_defined << symbol_name
|
481
|
-
# end
|
482
|
-
# end
|
483
|
-
|
484
|
-
# klass
|
485
|
-
# end
|
486
|
-
|
487
|
-
# include InvocationDSL
|
488
|
-
|
489
|
-
# def initialize(ops_file_script, runtime_env, params_hash)
|
490
|
-
# @ops_file_script = ops_file_script
|
491
|
-
# @runtime_env = runtime_env
|
492
|
-
# @params = InvocationParams.new(params_hash)
|
493
|
-
# end
|
494
|
-
|
495
|
-
# def backend
|
496
|
-
# @runtime_env.pty
|
497
|
-
# end
|
498
|
-
|
499
|
-
# def debug?
|
500
|
-
# @runtime_env.debug?
|
501
|
-
# end
|
502
|
-
|
503
|
-
# def verbose?
|
504
|
-
# @runtime_env.verbose?
|
505
|
-
# end
|
506
|
-
|
507
|
-
# # def evaluate
|
508
|
-
# # # the evaluation context needs to be a module with all of the following:
|
509
|
-
# # # - InvocationDSL methods
|
510
|
-
# # # - @ops_file_script
|
511
|
-
# # # - @runtime_env
|
512
|
-
# # # - @params
|
513
|
-
# # # - #backend
|
514
|
-
# # # - #debug?
|
515
|
-
# # # - #verbose?
|
516
|
-
# # # - all the dynamically defined methods in the subclass of Invocation
|
517
|
-
# # end
|
518
|
-
|
519
|
-
# def evaluate
|
520
|
-
# eval(@ops_file_script.script, nil, @ops_file_script.ops_file.ops_file_path.to_s, @ops_file_script.ops_file.script_line_offset)
|
521
|
-
# end
|
522
|
-
|
523
|
-
# # def evaluate
|
524
|
-
# # ops_file_script = @ops_file_script
|
525
|
-
# # runtime_env = @runtime_env
|
526
|
-
# # invocation_params = @params
|
527
|
-
|
528
|
-
# # # we use a Module as the evaluation context instead of this #evaluate method so that classes and other structures may be defined in an .ops file
|
529
|
-
# # evaluation_context = Module.new
|
530
|
-
# # evaluation_context.module_exec {
|
531
|
-
# # include InvocationDSL
|
532
|
-
|
533
|
-
# # @ops_file_script = ops_file_script
|
534
|
-
# # @runtime_env = runtime_env
|
535
|
-
# # @params = invocation_params
|
536
|
-
|
537
|
-
# # def backend
|
538
|
-
# # @runtime_env.pty
|
539
|
-
# # end
|
540
|
-
|
541
|
-
# # def debug?
|
542
|
-
# # @runtime_env.debug?
|
543
|
-
# # end
|
544
|
-
|
545
|
-
# # def verbose?
|
546
|
-
# # @runtime_env.verbose?
|
547
|
-
# # end
|
548
|
-
# # }
|
549
|
-
|
550
|
-
# # evaluation_context.module_eval(@ops_file_script.script, @ops_file_script.ops_file.ops_file_path.to_s, @ops_file_script.ops_file.script_line_offset)
|
551
|
-
# # end
|
552
|
-
|
553
|
-
# # def method_missing(name, *args, **kwargs, &block)
|
554
|
-
# # puts "1" * 80
|
555
|
-
# # import_reference = @ops_file_script.ops_file.resolve_import(name)
|
556
|
-
# # if import_reference
|
557
|
-
# # puts "2" * 80
|
558
|
-
# # resolved_value = @runtime_env.resolve_import_reference(@ops_file_script.ops_file, import_reference)
|
559
|
-
# # return resolved_value if resolved_value
|
560
|
-
# # end
|
561
133
|
|
562
|
-
# # puts "3" * 80
|
563
|
-
# # case namespace_or_ops_file = @runtime_env.resolve_symbol(@ops_file_script.ops_file, name.to_s)
|
564
|
-
# # when Namespace
|
565
|
-
# # puts "4" * 80
|
566
|
-
# # namespace_or_ops_file
|
567
|
-
# # when OpsFile
|
568
|
-
# # puts "5" * 80
|
569
|
-
# # namespace_or_ops_file.invoke(@runtime_env, *args, **kwargs, &block)
|
570
|
-
# # else
|
571
|
-
# # raise NoMethodError, "No method named '#{name}'"
|
572
|
-
# # end
|
573
|
-
# # end
|
574
|
-
# end
|
575
134
|
end
|
@@ -58,25 +58,25 @@ module OpsWalrus
|
|
58
58
|
Invocation::Success.new(ruby_script_return)
|
59
59
|
end
|
60
60
|
rescue SSHKit::Command::Failed => e
|
61
|
-
|
61
|
+
App.instance.error "[!] Command failed: #{e.message}"
|
62
62
|
rescue Error => e
|
63
|
-
|
64
|
-
|
65
|
-
|
63
|
+
App.instance.error "Error: Ops script crashed."
|
64
|
+
App.instance.error e.message
|
65
|
+
App.instance.error e.backtrace.take(5).join("\n")
|
66
66
|
Invocation::Error.new(e)
|
67
67
|
rescue => e
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
68
|
+
App.instance.error "Unhandled Error: Ops script crashed."
|
69
|
+
App.instance.error e.class
|
70
|
+
App.instance.error e.message
|
71
|
+
App.instance.error e.backtrace.take(10).join("\n")
|
72
72
|
Invocation::Error.new(e)
|
73
73
|
end
|
74
74
|
|
75
75
|
if app.debug? && result.failure?
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
76
|
+
App.instance.debug "Ops script error details:"
|
77
|
+
App.instance.debug "Error: #{result.value}"
|
78
|
+
App.instance.debug "Status code: #{result.exit_status}"
|
79
|
+
App.instance.debug @entry_point_ops_file.script
|
80
80
|
end
|
81
81
|
|
82
82
|
result
|
@@ -14,22 +14,15 @@ module OpsWalrus
|
|
14
14
|
# define methods for the OpsFile's local_symbol_table: local imports and private lib directory
|
15
15
|
ops_file.local_symbol_table.each do |symbol_name, import_reference|
|
16
16
|
unless methods_defined.include? symbol_name
|
17
|
-
|
17
|
+
App.instance.debug "defining method for local symbol table entry: #{symbol_name}"
|
18
18
|
klass.define_method(symbol_name) do |*args, **kwargs, &block|
|
19
|
-
|
19
|
+
App.instance.debug "resolving local symbol table entry: #{symbol_name}"
|
20
20
|
namespace_or_ops_file = @runtime_env.resolve_import_reference(ops_file, import_reference)
|
21
|
-
|
21
|
+
App.instance.debug "namespace_or_ops_file=#{namespace_or_ops_file.to_s}"
|
22
22
|
|
23
23
|
invocation_context = LocalImportInvocationContext.new(@runtime_env, namespace_or_ops_file)
|
24
24
|
invocation_context._invoke(*args, **kwargs)
|
25
25
|
|
26
|
-
# case namespace_or_ops_file
|
27
|
-
# when Namespace
|
28
|
-
# namespace_or_ops_file._invoke_if_namespace_has_ops_file_of_same_name(*args, **kwargs)
|
29
|
-
# when OpsFile
|
30
|
-
# params_hash = namespace_or_ops_file.build_params_hash(*args, **kwargs)
|
31
|
-
# namespace_or_ops_file.invoke(@runtime_env, params_hash)
|
32
|
-
# end
|
33
26
|
end
|
34
27
|
methods_defined << symbol_name
|
35
28
|
end
|
@@ -40,26 +33,18 @@ module OpsWalrus
|
|
40
33
|
sibling_symbol_table_names |= ops_file.dirname.glob("*.ops").map {|ops_file_path| ops_file_path.basename(".ops").to_s } # OpsFiles
|
41
34
|
sibling_symbol_table_names |= ops_file.dirname.glob("*").select(&:directory?).map {|dir_path| dir_path.basename.to_s } # Namespaces
|
42
35
|
# puts "sibling_symbol_table_names=#{sibling_symbol_table_names}"
|
43
|
-
|
36
|
+
App.instance.debug "methods_defined=#{methods_defined}"
|
44
37
|
sibling_symbol_table_names.each do |symbol_name|
|
45
38
|
unless methods_defined.include? symbol_name
|
46
|
-
|
39
|
+
App.instance.debug "defining method for implicit imports: #{symbol_name}"
|
47
40
|
klass.define_method(symbol_name) do |*args, **kwargs, &block|
|
48
|
-
|
41
|
+
App.instance.debug "resolving implicit import: #{symbol_name}"
|
49
42
|
namespace_or_ops_file = @runtime_env.resolve_sibling_symbol(ops_file, symbol_name)
|
50
|
-
|
43
|
+
App.instance.debug "namespace_or_ops_file=#{namespace_or_ops_file.to_s}"
|
51
44
|
|
52
45
|
invocation_context = LocalImportInvocationContext.new(@runtime_env, namespace_or_ops_file)
|
53
46
|
invocation_context._invoke(*args, **kwargs)
|
54
47
|
|
55
|
-
|
56
|
-
# case namespace_or_ops_file
|
57
|
-
# when Namespace
|
58
|
-
# namespace_or_ops_file._invoke_if_namespace_has_ops_file_of_same_name(*args, **kwargs)
|
59
|
-
# when OpsFile
|
60
|
-
# params_hash = namespace_or_ops_file.build_params_hash(*args, **kwargs)
|
61
|
-
# namespace_or_ops_file.invoke(@runtime_env, params_hash)
|
62
|
-
# end
|
63
48
|
end
|
64
49
|
methods_defined << symbol_name
|
65
50
|
end
|
@@ -37,6 +37,17 @@ module OpsWalrus
|
|
37
37
|
local_name
|
38
38
|
end
|
39
39
|
|
40
|
+
def to_s
|
41
|
+
"PackageReference(local_name=#{@local_name}, package_uri=#{@package_uri}, version=#{@version})"
|
42
|
+
end
|
43
|
+
|
44
|
+
def summary
|
45
|
+
if version
|
46
|
+
"#{package_uri}:#{version}"
|
47
|
+
else
|
48
|
+
package_uri
|
49
|
+
end
|
50
|
+
end
|
40
51
|
end
|
41
52
|
|
42
53
|
# these are dynamic package references defined at runtime when an OpsFile's imports are being evaluated.
|
@@ -23,6 +23,12 @@ module OpsWalrus
|
|
23
23
|
super(local_name)
|
24
24
|
@package_reference = package_reference
|
25
25
|
end
|
26
|
+
def to_s
|
27
|
+
"PackageDependencyReference(local_name=#{@local_name}, package_reference=#{@package_reference})"
|
28
|
+
end
|
29
|
+
def summary
|
30
|
+
"#{local_name}: #{package_reference.summary}"
|
31
|
+
end
|
26
32
|
end
|
27
33
|
|
28
34
|
class DirectoryReference < ImportReference
|
@@ -31,6 +37,12 @@ module OpsWalrus
|
|
31
37
|
super(local_name)
|
32
38
|
@dirname = dirname
|
33
39
|
end
|
40
|
+
def to_s
|
41
|
+
"DirectoryReference(local_name=#{@local_name}, dirname=#{@dirname})"
|
42
|
+
end
|
43
|
+
def summary
|
44
|
+
"#{local_name}: #{dirname}"
|
45
|
+
end
|
34
46
|
end
|
35
47
|
|
36
48
|
class DynamicPackageImportReference < ImportReference
|
@@ -39,6 +51,12 @@ module OpsWalrus
|
|
39
51
|
super(local_name)
|
40
52
|
@package_reference = package_reference
|
41
53
|
end
|
54
|
+
def to_s
|
55
|
+
"DynamicPackageImportReference(local_name=#{@local_name}, package_reference=#{@package_reference})"
|
56
|
+
end
|
57
|
+
def summary
|
58
|
+
"#{local_name}: #{package_reference.summary}"
|
59
|
+
end
|
42
60
|
end
|
43
61
|
|
44
62
|
class OpsFileReference < ImportReference
|
@@ -47,6 +65,12 @@ module OpsWalrus
|
|
47
65
|
super(local_name)
|
48
66
|
@ops_file_path = ops_file_path
|
49
67
|
end
|
68
|
+
def to_s
|
69
|
+
"DirectoryReference(local_name=#{@local_name}, ops_file_path=#{@ops_file_path})"
|
70
|
+
end
|
71
|
+
def summary
|
72
|
+
"#{local_name}: #{ops_file_path}"
|
73
|
+
end
|
50
74
|
end
|
51
75
|
|
52
76
|
# Namespace is really just a Map of symbol_name -> (Namespace | OpsFile) pairs
|
@@ -178,6 +202,10 @@ module OpsWalrus
|
|
178
202
|
path_map
|
179
203
|
end
|
180
204
|
|
205
|
+
def includes_path?(path)
|
206
|
+
!!@path_map[path]
|
207
|
+
end
|
208
|
+
|
181
209
|
def dynamically_add_new_package_dir(new_package_dir)
|
182
210
|
# patch the symbol resolution (namespace) tree
|
183
211
|
dir_basename = new_package_dir.basename
|
@@ -198,19 +226,22 @@ module OpsWalrus
|
|
198
226
|
|
199
227
|
# returns a Namespace or OpsFile
|
200
228
|
def resolve_symbol(origin_ops_file, symbol_name)
|
201
|
-
lookup_namespace(origin_ops_file)&.resolve_symbol(symbol_name)
|
229
|
+
resolved_namespace_or_ops_file = lookup_namespace(origin_ops_file)&.resolve_symbol(symbol_name)
|
230
|
+
App.instance.debug("LoadPath#resolve_symbol(#{origin_ops_file}, #{symbol_name}) -> #{resolved_namespace_or_ops_file}")
|
231
|
+
resolved_namespace_or_ops_file
|
202
232
|
end
|
203
233
|
|
204
234
|
# returns a Namespace | OpsFile
|
205
235
|
def resolve_import_reference(origin_ops_file, import_reference)
|
206
|
-
case import_reference
|
236
|
+
resolved_namespace_or_ops_file = case import_reference
|
207
237
|
when PackageDependencyReference
|
208
238
|
# puts "root namespace: #{@root_namespace.symbol_table}"
|
209
|
-
@root_namespace.resolve_symbol(import_reference.package_reference.import_resolution_dirname) # returns the Namespace associated with the bundled package
|
239
|
+
@root_namespace.resolve_symbol(import_reference.package_reference.import_resolution_dirname) # returns the Namespace associated with the bundled package import_resolution_dirname (i.e. the local name)
|
210
240
|
when DynamicPackageImportReference
|
211
241
|
dynamic_package_reference = import_reference.package_reference
|
212
242
|
@dynamic_package_additions_memo[dynamic_package_reference] ||= begin
|
213
243
|
# puts "Downloading dynamic package: #{dynamic_package_reference.inspect}"
|
244
|
+
App.instance.debug("Downloading dynamic package: #{dynamic_package_reference}")
|
214
245
|
dynamically_added_package_dir = @runtime_env.app.bundler.download_git_package(dynamic_package_reference.package_uri, dynamic_package_reference.version)
|
215
246
|
dynamically_add_new_package_dir(dynamically_added_package_dir)
|
216
247
|
dynamically_added_package_dir
|
@@ -221,6 +252,8 @@ module OpsWalrus
|
|
221
252
|
when OpsFileReference
|
222
253
|
@path_map[import_reference.ops_file_path]
|
223
254
|
end
|
255
|
+
App.instance.debug("LoadPath#resolve_import_reference(#{origin_ops_file}, #{import_reference}) -> #{resolved_namespace_or_ops_file}")
|
256
|
+
resolved_namespace_or_ops_file
|
224
257
|
end
|
225
258
|
end
|
226
259
|
|
@@ -315,23 +348,41 @@ module OpsWalrus
|
|
315
348
|
ops_file.invoke(self, params_hash)
|
316
349
|
end
|
317
350
|
|
351
|
+
def find_load_path_that_includes_path(path)
|
352
|
+
load_path = [@bundle_load_path, @app_load_path].find {|load_path| load_path.includes_path?(path) }
|
353
|
+
raise SymbolResolutionError, "No load path includes the path: #{path}" unless load_path
|
354
|
+
load_path
|
355
|
+
end
|
356
|
+
|
318
357
|
# returns a Namespace | OpsFile
|
319
358
|
def resolve_sibling_symbol(origin_ops_file, symbol_name)
|
320
|
-
|
359
|
+
# if the origin_ops_file's file path is contained within a Bundler::BUNDLE_DIR directory, then we want to consult the @bundle_load_path
|
360
|
+
# otherwise, we want to consult the @app_load_path
|
361
|
+
load_path = find_load_path_that_includes_path(origin_ops_file.ops_file_path)
|
362
|
+
namespace_or_ops_file = load_path.resolve_symbol(origin_ops_file, symbol_name)
|
363
|
+
raise SymbolResolutionError, "Symbol '#{symbol_name}' not in load path for #{origin_ops_file.ops_file_path}" unless namespace_or_ops_file
|
364
|
+
namespace_or_ops_file
|
321
365
|
end
|
322
366
|
|
323
367
|
# returns a Namespace | OpsFile
|
324
368
|
def resolve_import_reference(origin_ops_file, import_reference)
|
325
|
-
case import_reference
|
369
|
+
load_path = case import_reference
|
326
370
|
|
327
|
-
#
|
371
|
+
# 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.
|
372
|
+
# Package references are guaranteed to live in the bundle dir
|
328
373
|
when PackageDependencyReference, DynamicPackageImportReference
|
329
|
-
@bundle_load_path
|
330
|
-
|
331
|
-
|
332
|
-
when
|
333
|
-
|
374
|
+
@bundle_load_path
|
375
|
+
when DirectoryReference
|
376
|
+
find_load_path_that_includes_path(import_reference.dirname)
|
377
|
+
when OpsFileReference
|
378
|
+
find_load_path_that_includes_path(import_reference.ops_file_path)
|
334
379
|
end
|
380
|
+
|
381
|
+
namespace_or_ops_file = load_path.resolve_import_reference(origin_ops_file, import_reference)
|
382
|
+
|
383
|
+
raise SymbolResolutionError, "Import reference '#{import_reference.summary}' not in load path for #{origin_ops_file.ops_file_path}" unless namespace_or_ops_file
|
384
|
+
|
385
|
+
namespace_or_ops_file
|
335
386
|
end
|
336
387
|
|
337
388
|
end
|
data/lib/opswalrus/version.rb
CHANGED
data/opswalrus.gemspec
CHANGED
@@ -32,9 +32,11 @@ Gem::Specification.new do |spec|
|
|
32
32
|
spec.require_paths = ["lib"]
|
33
33
|
|
34
34
|
# gem dependencies
|
35
|
+
spec.add_dependency "amazing_print", "~> 1.5"
|
35
36
|
spec.add_dependency "citrus", "~> 3.0"
|
36
37
|
spec.add_dependency "gli", "~> 2.21"
|
37
38
|
spec.add_dependency "git", "~> 1.18"
|
39
|
+
spec.add_dependency "ougai", "~> 2.0"
|
38
40
|
spec.add_dependency "rubyzip", "~> 2.3"
|
39
41
|
|
40
42
|
spec.add_dependency "bcrypt_pbkdf", "~> 1.1"
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
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.15
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- David Ellis
|
@@ -10,6 +10,20 @@ bindir: exe
|
|
10
10
|
cert_chain: []
|
11
11
|
date: 2023-08-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: amazing_print
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.5'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.5'
|
13
27
|
- !ruby/object:Gem::Dependency
|
14
28
|
name: citrus
|
15
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -52,6 +66,20 @@ dependencies:
|
|
52
66
|
- - "~>"
|
53
67
|
- !ruby/object:Gem::Version
|
54
68
|
version: '1.18'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: ougai
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '2.0'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '2.0'
|
55
83
|
- !ruby/object:Gem::Dependency
|
56
84
|
name: rubyzip
|
57
85
|
requirement: !ruby/object:Gem::Requirement
|
@@ -132,6 +160,7 @@ files:
|
|
132
160
|
- lib/opswalrus/bootstrap.sh
|
133
161
|
- lib/opswalrus/bundler.rb
|
134
162
|
- lib/opswalrus/cli.rb
|
163
|
+
- lib/opswalrus/errors.rb
|
135
164
|
- lib/opswalrus/git.rb
|
136
165
|
- lib/opswalrus/host.rb
|
137
166
|
- lib/opswalrus/hosts_file.rb
|