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
data/lib/opswalrus/invocation.rb
CHANGED
@@ -13,10 +13,18 @@ module OpsWalrus
|
|
13
13
|
def method_missing(name, *args, **kwargs, &block)
|
14
14
|
raise "Not implemented in base class"
|
15
15
|
end
|
16
|
+
|
17
|
+
def _bang_method?(name)
|
18
|
+
name.to_s.end_with?("!")
|
19
|
+
end
|
20
|
+
|
21
|
+
def _non_bang_method(name)
|
22
|
+
name.to_s.sub(/!$/, '')
|
23
|
+
end
|
16
24
|
end
|
17
25
|
|
18
26
|
class RemoteImportInvocationContext < ImportInvocationContext
|
19
|
-
def initialize(runtime_env, host_proxy, namespace_or_ops_file, is_invocation_a_call_to_package_in_bundle_dir = false)
|
27
|
+
def initialize(runtime_env, host_proxy, namespace_or_ops_file, is_invocation_a_call_to_package_in_bundle_dir = false, prompt_for_sudo_password: nil)
|
20
28
|
@runtime_env = runtime_env
|
21
29
|
@host_proxy = host_proxy
|
22
30
|
@initial_namespace_or_ops_file = @namespace_or_ops_file = namespace_or_ops_file
|
@@ -24,12 +32,46 @@ module OpsWalrus
|
|
24
32
|
|
25
33
|
initial_method_name = @namespace_or_ops_file.dirname.basename
|
26
34
|
@method_chain = [initial_method_name]
|
35
|
+
@prompt_for_sudo_password = prompt_for_sudo_password
|
36
|
+
end
|
37
|
+
|
38
|
+
def method_missing(name, *args, **kwargs, &block)
|
39
|
+
_resolve_method_and_invoke(name, *args, **kwargs)
|
40
|
+
end
|
41
|
+
|
42
|
+
def _resolve_method_and_invoke(name, *args, **kwargs)
|
43
|
+
if _bang_method?(name) # foo! is an attempt to invoke the module's default entrypoint
|
44
|
+
method_name = _non_bang_method(name)
|
45
|
+
|
46
|
+
@method_chain << method_name
|
47
|
+
|
48
|
+
@namespace_or_ops_file = @namespace_or_ops_file.resolve_symbol(method_name)
|
49
|
+
_invoke_if_namespace_has_ops_file_of_same_name(*args, **kwargs)
|
50
|
+
else
|
51
|
+
@method_chain << name.to_s
|
52
|
+
|
53
|
+
@namespace_or_ops_file = @namespace_or_ops_file.resolve_symbol(name)
|
54
|
+
_invoke(*args, **kwargs)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# if this namespace contains an OpsFile of the same name as the namespace, e.g. pkg/install/install.ops, then this
|
59
|
+
# method invokes the OpsFile of that same name and returns the result;
|
60
|
+
# otherwise we return this namespace object
|
61
|
+
def _invoke_if_namespace_has_ops_file_of_same_name(*args, **kwargs, &block)
|
62
|
+
method_name = @namespace_or_ops_file.dirname.basename
|
63
|
+
resolved_symbol = @namespace_or_ops_file.resolve_symbol(method_name)
|
64
|
+
if resolved_symbol.is_a? OpsFile
|
65
|
+
_resolve_method_and_invoke(method_name)
|
66
|
+
else
|
67
|
+
self
|
68
|
+
end
|
27
69
|
end
|
28
70
|
|
29
71
|
def _invoke(*args, **kwargs)
|
30
72
|
case @namespace_or_ops_file
|
31
73
|
when Namespace
|
32
|
-
|
74
|
+
self
|
33
75
|
when OpsFile
|
34
76
|
_invoke_remote(*args, **kwargs)
|
35
77
|
end
|
@@ -59,32 +101,13 @@ module OpsWalrus
|
|
59
101
|
end.join(" ")
|
60
102
|
end
|
61
103
|
|
62
|
-
@host_proxy.run_ops(:run, "--script", remote_run_command_args)
|
63
|
-
|
64
|
-
|
65
|
-
# if this namespace contains an OpsFile of the same name as the namespace, e.g. pkg/install/install.ops, then this
|
66
|
-
# method invokes the OpsFile of that same name and returns the result;
|
67
|
-
# otherwise we return this namespace object
|
68
|
-
def _invoke_if_namespace_has_ops_file_of_same_name(*args, **kwargs, &block)
|
69
|
-
method_name = @namespace_or_ops_file.dirname.basename
|
70
|
-
resolved_symbol = @namespace_or_ops_file.resolve_symbol(method_name)
|
71
|
-
if resolved_symbol.is_a? OpsFile
|
72
|
-
_resolve_method_and_invoke(method_name)
|
104
|
+
# @host_proxy.run_ops(:run, "--script", remote_run_command_args)
|
105
|
+
if @prompt_for_sudo_password
|
106
|
+
@host_proxy.run_ops(:run, "--pass", remote_run_command_args)
|
73
107
|
else
|
74
|
-
|
108
|
+
@host_proxy.run_ops(:run, remote_run_command_args)
|
75
109
|
end
|
76
110
|
end
|
77
|
-
|
78
|
-
def _resolve_method_and_invoke(name, *args, **kwargs)
|
79
|
-
@method_chain << name.to_s
|
80
|
-
|
81
|
-
@namespace_or_ops_file = @namespace_or_ops_file.resolve_symbol(name)
|
82
|
-
_invoke(*args, **kwargs)
|
83
|
-
end
|
84
|
-
|
85
|
-
def method_missing(name, *args, **kwargs, &block)
|
86
|
-
_resolve_method_and_invoke(name, *args, **kwargs)
|
87
|
-
end
|
88
111
|
end
|
89
112
|
|
90
113
|
class LocalImportInvocationContext < ImportInvocationContext
|
@@ -93,24 +116,25 @@ module OpsWalrus
|
|
93
116
|
@initial_namespace_or_ops_file = @namespace_or_ops_file = namespace_or_ops_file
|
94
117
|
end
|
95
118
|
|
96
|
-
def
|
97
|
-
|
98
|
-
when Namespace
|
99
|
-
_invoke_if_namespace_has_ops_file_of_same_name(*args, **kwargs)
|
100
|
-
when OpsFile
|
101
|
-
_invoke_local(*args, **kwargs)
|
102
|
-
end
|
119
|
+
def method_missing(name, *args, **kwargs, &block)
|
120
|
+
_resolve_method_and_invoke(name, *args, **kwargs)
|
103
121
|
end
|
104
122
|
|
105
|
-
def
|
106
|
-
|
107
|
-
|
123
|
+
def _resolve_method_and_invoke(name, *args, **kwargs)
|
124
|
+
if _bang_method?(name) # foo! is an attempt to invoke the module's default entrypoint
|
125
|
+
method_name = _non_bang_method(name)
|
126
|
+
@namespace_or_ops_file = @namespace_or_ops_file.resolve_symbol(method_name)
|
127
|
+
_invoke_if_namespace_has_ops_file_of_same_name(*args, **kwargs)
|
128
|
+
else
|
129
|
+
@namespace_or_ops_file = @namespace_or_ops_file.resolve_symbol(name)
|
130
|
+
_invoke(*args, **kwargs)
|
131
|
+
end
|
108
132
|
end
|
109
133
|
|
110
134
|
# if this namespace contains an OpsFile of the same name as the namespace, e.g. pkg/install/install.ops, then this
|
111
135
|
# method invokes the OpsFile of that same name and returns the result;
|
112
136
|
# otherwise we return this namespace object
|
113
|
-
def _invoke_if_namespace_has_ops_file_of_same_name(*args, **kwargs
|
137
|
+
def _invoke_if_namespace_has_ops_file_of_same_name(*args, **kwargs)
|
114
138
|
method_name = @namespace_or_ops_file.dirname.basename
|
115
139
|
resolved_symbol = @namespace_or_ops_file.resolve_symbol(method_name)
|
116
140
|
if resolved_symbol.is_a? OpsFile
|
@@ -121,14 +145,20 @@ module OpsWalrus
|
|
121
145
|
end
|
122
146
|
end
|
123
147
|
|
124
|
-
def
|
125
|
-
|
126
|
-
|
148
|
+
def _invoke(*args, **kwargs)
|
149
|
+
case @namespace_or_ops_file
|
150
|
+
when Namespace
|
151
|
+
self
|
152
|
+
when OpsFile
|
153
|
+
_invoke_local(*args, **kwargs)
|
154
|
+
end
|
127
155
|
end
|
128
156
|
|
129
|
-
def
|
130
|
-
|
157
|
+
def _invoke_local(*args, **kwargs)
|
158
|
+
params_hash = @namespace_or_ops_file.build_params_hash(*args, **kwargs)
|
159
|
+
@namespace_or_ops_file.invoke(@runtime_env, params_hash)
|
131
160
|
end
|
161
|
+
|
132
162
|
end
|
133
163
|
|
134
164
|
end
|
@@ -28,24 +28,39 @@ module OpsWalrus
|
|
28
28
|
@app.sudo_password
|
29
29
|
end
|
30
30
|
|
31
|
-
# runtime_kv_args is an Array(String) of the form: ["arg1:val1", "
|
32
|
-
#
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
memo[
|
31
|
+
# runtime_kv_args is an Array(String) of the form: ["arg1:val1", "arg1:val2", ...]
|
32
|
+
# irb(main):057:0> build_params_hash(["names:foo", "names:bar", "names:baz", "age:5", "profile:name:corge", "profile:language:en", "height:5ft8in"])
|
33
|
+
# => {"names"=>["foo", "bar", "baz"], "age"=>"5", "profile"=>{"name"=>"corge", "language"=>"en"}, "height"=>"5ft8in"}
|
34
|
+
def build_params_hash(runtime_kv_args, params_json_hash: nil)
|
35
|
+
runtime_kv_args.reduce(params_json_hash || {}) do |memo, kv_pair_string|
|
36
|
+
param_name, str_value = kv_pair_string.split(":", 2)
|
37
|
+
key, value = str_value.split(":", 2)
|
38
|
+
if pre_existing_value = memo[param_name]
|
39
|
+
memo[param_name] = if value # we're dealing with a Hash parameter value
|
40
|
+
pre_existing_value.merge(key => value)
|
41
|
+
else # we're dealing with an Array parameter value or a scalar parameter value
|
42
|
+
array = pre_existing_value.is_a?(Array) ? pre_existing_value : [pre_existing_value]
|
43
|
+
array << str_value
|
44
|
+
end
|
40
45
|
else
|
41
|
-
memo[
|
46
|
+
memo[param_name] = if value # we're dealing with a Hash parameter value
|
47
|
+
{key => value}
|
48
|
+
else # we're dealing with an Array parameter value or a scalar parameter value
|
49
|
+
str_value
|
50
|
+
end
|
42
51
|
end
|
43
52
|
memo
|
44
53
|
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# runtime_kv_args is an Array(String) of the form: ["arg1:val1", "arg1:val2", ...]
|
57
|
+
# params_json_hash is a Hash representation of a JSON string
|
58
|
+
def run(runtime_kv_args, params_json_hash: nil)
|
59
|
+
params_hash = build_params_hash(runtime_kv_args, params_json_hash: params_json_hash)
|
45
60
|
|
46
61
|
if app.debug?
|
47
|
-
|
48
|
-
|
62
|
+
App.instance.trace "Script:"
|
63
|
+
App.instance.trace @entry_point_ops_file.script
|
49
64
|
end
|
50
65
|
|
51
66
|
result = begin
|
@@ -61,14 +76,14 @@ module OpsWalrus
|
|
61
76
|
App.instance.error "[!] Command failed: #{e.message}"
|
62
77
|
rescue Error => e
|
63
78
|
App.instance.error "Error: Ops script crashed."
|
64
|
-
App.instance.error e
|
65
|
-
App.instance.error e.backtrace.take(5).join("\n")
|
79
|
+
App.instance.error e
|
80
|
+
# App.instance.error e.backtrace.take(5).join("\n")
|
66
81
|
Invocation::Error.new(e)
|
67
82
|
rescue => e
|
68
83
|
App.instance.error "Unhandled Error: Ops script crashed."
|
69
84
|
App.instance.error e.class
|
70
|
-
App.instance.error e
|
71
|
-
App.instance.error e.backtrace.take(10).join("\n")
|
85
|
+
App.instance.error e
|
86
|
+
# App.instance.error e.backtrace.take(10).join("\n")
|
72
87
|
Invocation::Error.new(e)
|
73
88
|
end
|
74
89
|
|
@@ -76,7 +91,7 @@ module OpsWalrus
|
|
76
91
|
App.instance.debug "Ops script error details:"
|
77
92
|
App.instance.debug "Error: #{result.value}"
|
78
93
|
App.instance.debug "Status code: #{result.exit_status}"
|
79
|
-
App.instance.debug @entry_point_ops_file.script
|
94
|
+
App.instance.debug @entry_point_ops_file.script.to_s
|
80
95
|
end
|
81
96
|
|
82
97
|
result
|
data/lib/opswalrus/ops_file.rb
CHANGED
@@ -173,8 +173,9 @@ module OpsWalrus
|
|
173
173
|
raise Error, "Unknown import reference: #{local_name}: #{import_str.inspect}"
|
174
174
|
end
|
175
175
|
|
176
|
-
def invoke(runtime_env,
|
177
|
-
|
176
|
+
def invoke(runtime_env, hashlike_params)
|
177
|
+
# this invokes the dynamically generated _invoke method that is defined at runtime within OpsFileScript.define_for(...)
|
178
|
+
script._invoke(runtime_env, hashlike_params)
|
178
179
|
end
|
179
180
|
|
180
181
|
def build_params_hash(*args, **kwargs)
|
@@ -1,9 +1,80 @@
|
|
1
|
+
require 'forwardable'
|
1
2
|
require 'set'
|
2
3
|
require_relative 'invocation'
|
3
4
|
require_relative 'ops_file_script_dsl'
|
4
5
|
|
5
6
|
module OpsWalrus
|
6
7
|
|
8
|
+
class ArrayOrHashNavigationProxy
|
9
|
+
extend Forwardable
|
10
|
+
|
11
|
+
def initialize(array_or_hash)
|
12
|
+
@obj = array_or_hash
|
13
|
+
end
|
14
|
+
|
15
|
+
def_delegators :@obj, :to_s, :inspect, :hash, :===, :eql?, :kind_of?, :is_a?, :instance_of?, :respond_to?, :<=>
|
16
|
+
|
17
|
+
def [](index, *args, **kwargs, &block)
|
18
|
+
@obj.method(:[]).call(index, *args, **kwargs, &block)
|
19
|
+
end
|
20
|
+
def respond_to_missing?(method, *)
|
21
|
+
@obj.is_a?(Hash) && @obj.respond_to?(method)
|
22
|
+
end
|
23
|
+
def method_missing(name, *args, **kwargs, &block)
|
24
|
+
case @obj
|
25
|
+
when Array
|
26
|
+
@obj.method(name).call(*args, **kwargs, &block)
|
27
|
+
when Hash
|
28
|
+
if @obj.respond_to?(name)
|
29
|
+
@obj.method(name).call(*args, **kwargs, &block)
|
30
|
+
else
|
31
|
+
value = self[name.to_s]
|
32
|
+
case value
|
33
|
+
when Array, Hash
|
34
|
+
ArrayOrHashNavigationProxy.new(value)
|
35
|
+
else
|
36
|
+
value
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
class InvocationParams
|
44
|
+
# @params : Hash
|
45
|
+
|
46
|
+
# params : Hash | ArrayOrHashNavigationProxy
|
47
|
+
def initialize(hashlike_params)
|
48
|
+
# this doesn't seem to make any difference
|
49
|
+
@params = hashlike_params.to_h
|
50
|
+
# @params = hashlike_params
|
51
|
+
end
|
52
|
+
|
53
|
+
def [](key)
|
54
|
+
key = key.to_s if key.is_a? Symbol
|
55
|
+
@params[key]
|
56
|
+
end
|
57
|
+
|
58
|
+
def dig(*keys)
|
59
|
+
# keys = keys.map {|key| key.is_a?(Integer) ? key : key.to_s }
|
60
|
+
@params.dig(*keys)
|
61
|
+
end
|
62
|
+
|
63
|
+
def method_missing(name, *args, **kwargs, &block)
|
64
|
+
if @params.respond_to?(name)
|
65
|
+
@params.method(name).call(*args, **kwargs, &block)
|
66
|
+
else
|
67
|
+
value = self[name]
|
68
|
+
case value
|
69
|
+
when Array, Hash
|
70
|
+
ArrayOrHashNavigationProxy.new(value)
|
71
|
+
else
|
72
|
+
value
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
7
78
|
class OpsFileScript
|
8
79
|
|
9
80
|
def self.define_for(ops_file, ruby_script)
|
@@ -14,11 +85,11 @@ module OpsWalrus
|
|
14
85
|
# define methods for the OpsFile's local_symbol_table: local imports and private lib directory
|
15
86
|
ops_file.local_symbol_table.each do |symbol_name, import_reference|
|
16
87
|
unless methods_defined.include? symbol_name
|
17
|
-
App.instance.
|
88
|
+
App.instance.trace "defining method for local symbol table entry: #{symbol_name}"
|
18
89
|
klass.define_method(symbol_name) do |*args, **kwargs, &block|
|
19
|
-
App.instance.
|
90
|
+
App.instance.trace "resolving local symbol table entry: #{symbol_name}"
|
20
91
|
namespace_or_ops_file = @runtime_env.resolve_import_reference(ops_file, import_reference)
|
21
|
-
App.instance.
|
92
|
+
App.instance.trace "namespace_or_ops_file=#{namespace_or_ops_file.to_s}"
|
22
93
|
|
23
94
|
invocation_context = LocalImportInvocationContext.new(@runtime_env, namespace_or_ops_file)
|
24
95
|
invocation_context._invoke(*args, **kwargs)
|
@@ -33,14 +104,14 @@ module OpsWalrus
|
|
33
104
|
sibling_symbol_table_names |= ops_file.dirname.glob("*.ops").map {|ops_file_path| ops_file_path.basename(".ops").to_s } # OpsFiles
|
34
105
|
sibling_symbol_table_names |= ops_file.dirname.glob("*").select(&:directory?).map {|dir_path| dir_path.basename.to_s } # Namespaces
|
35
106
|
# puts "sibling_symbol_table_names=#{sibling_symbol_table_names}"
|
36
|
-
App.instance.
|
107
|
+
App.instance.trace "methods_defined=#{methods_defined}"
|
37
108
|
sibling_symbol_table_names.each do |symbol_name|
|
38
109
|
unless methods_defined.include? symbol_name
|
39
|
-
App.instance.
|
110
|
+
App.instance.trace "defining method for implicit imports: #{symbol_name}"
|
40
111
|
klass.define_method(symbol_name) do |*args, **kwargs, &block|
|
41
|
-
App.instance.
|
112
|
+
App.instance.trace "resolving implicit import: #{symbol_name}"
|
42
113
|
namespace_or_ops_file = @runtime_env.resolve_sibling_symbol(ops_file, symbol_name)
|
43
|
-
App.instance.
|
114
|
+
App.instance.trace "namespace_or_ops_file=#{namespace_or_ops_file.to_s}"
|
44
115
|
|
45
116
|
invocation_context = LocalImportInvocationContext.new(@runtime_env, namespace_or_ops_file)
|
46
117
|
invocation_context._invoke(*args, **kwargs)
|
@@ -60,9 +131,9 @@ module OpsWalrus
|
|
60
131
|
# - #verbose?
|
61
132
|
# - all the dynamically defined methods in the subclass of Invocation
|
62
133
|
invoke_method_definition = <<~INVOKE_METHOD
|
63
|
-
def _invoke(runtime_env,
|
134
|
+
def _invoke(runtime_env, hashlike_params)
|
64
135
|
@runtime_env = runtime_env
|
65
|
-
@params = InvocationParams.new(
|
136
|
+
@params = InvocationParams.new(hashlike_params)
|
66
137
|
#{ruby_script}
|
67
138
|
end
|
68
139
|
INVOKE_METHOD
|
@@ -101,7 +172,7 @@ module OpsWalrus
|
|
101
172
|
end
|
102
173
|
|
103
174
|
# The _invoke method is dynamically defined as part of OpsFileScript.define_for
|
104
|
-
def _invoke(runtime_env,
|
175
|
+
def _invoke(runtime_env, hashlike_params)
|
105
176
|
raise "Not implemented in base class."
|
106
177
|
end
|
107
178
|
|
@@ -12,67 +12,6 @@ require_relative 'walrus_lang'
|
|
12
12
|
|
13
13
|
module OpsWalrus
|
14
14
|
|
15
|
-
class ArrayOrHashNavigationProxy
|
16
|
-
def initialize(array_or_hash)
|
17
|
-
@obj = array_or_hash
|
18
|
-
end
|
19
|
-
def [](index, *args, **kwargs, &block)
|
20
|
-
@obj.method(:[]).call(index, *args, **kwargs, &block)
|
21
|
-
end
|
22
|
-
def respond_to_missing?(method, *)
|
23
|
-
@obj.is_a?(Hash) && @obj.respond_to?(method)
|
24
|
-
end
|
25
|
-
def method_missing(name, *args, **kwargs, &block)
|
26
|
-
case @obj
|
27
|
-
when Array
|
28
|
-
@obj.method(name).call(*args, **kwargs, &block)
|
29
|
-
when Hash
|
30
|
-
if @obj.respond_to?(name)
|
31
|
-
@obj.method(name).call(*args, **kwargs, &block)
|
32
|
-
else
|
33
|
-
value = self[name.to_s]
|
34
|
-
case value
|
35
|
-
when Array, Hash
|
36
|
-
ArrayOrHashNavigationProxy.new(value)
|
37
|
-
else
|
38
|
-
value
|
39
|
-
end
|
40
|
-
end
|
41
|
-
end
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
|
-
class InvocationParams
|
46
|
-
# params : Hash
|
47
|
-
def initialize(params)
|
48
|
-
@params = params
|
49
|
-
end
|
50
|
-
|
51
|
-
def [](key)
|
52
|
-
key = key.to_s if key.is_a? Symbol
|
53
|
-
@params[key]
|
54
|
-
end
|
55
|
-
|
56
|
-
def dig(*keys)
|
57
|
-
# keys = keys.map {|key| key.is_a?(Integer) ? key : key.to_s }
|
58
|
-
@params.dig(*keys)
|
59
|
-
end
|
60
|
-
|
61
|
-
def method_missing(name, *args, **kwargs, &block)
|
62
|
-
if @params.respond_to?(name)
|
63
|
-
@params.method(name).call(*args, **kwargs, &block)
|
64
|
-
else
|
65
|
-
value = self[name]
|
66
|
-
case value
|
67
|
-
when Array, Hash
|
68
|
-
ArrayOrHashNavigationProxy.new(value)
|
69
|
-
else
|
70
|
-
value
|
71
|
-
end
|
72
|
-
end
|
73
|
-
end
|
74
|
-
end
|
75
|
-
|
76
15
|
module Invocation
|
77
16
|
class Result
|
78
17
|
attr_accessor :value
|
@@ -157,7 +96,7 @@ module OpsWalrus
|
|
157
96
|
|
158
97
|
# puts retval.inspect
|
159
98
|
|
160
|
-
# cleanup
|
99
|
+
# todo: cleanup
|
161
100
|
# if tmp_bundle_root_dir =~ /tmp/ # sanity check the temp path before we blow away something we don't intend
|
162
101
|
# host.execute(:rm, "-rf", "tmpopsbootstrap.sh", "tmpops.zip", tmp_bundle_root_dir)
|
163
102
|
# else
|
@@ -187,7 +126,7 @@ module OpsWalrus
|
|
187
126
|
rescue => e
|
188
127
|
puts e.class
|
189
128
|
puts e.message
|
190
|
-
|
129
|
+
puts e.backtrace.join("\n")
|
191
130
|
ensure
|
192
131
|
host.clear_ssh_session
|
193
132
|
end
|
@@ -275,19 +214,23 @@ module OpsWalrus
|
|
275
214
|
# puts "shell! self: #{self.inspect}"
|
276
215
|
|
277
216
|
if App.instance.report_mode?
|
278
|
-
|
217
|
+
puts Style.green("*" * 80)
|
218
|
+
print "[#{Style.blue(@runtime_env.local_hostname)}] "
|
279
219
|
print "#{description}: " if description
|
280
|
-
puts cmd
|
220
|
+
puts Style.yellow(cmd)
|
281
221
|
end
|
282
222
|
|
283
223
|
return unless cmd && !cmd.strip.empty?
|
284
224
|
|
285
|
-
|
286
|
-
|
287
|
-
|
225
|
+
if App.instance.dry_run?
|
226
|
+
["", "", 0]
|
227
|
+
else
|
228
|
+
sshkit_cmd = @runtime_env.handle_input(input) do |interaction_handler|
|
229
|
+
# self is a Module instance that is serving as the evaluation context in an instance of a subclass of an Invocation; see Invocation#evaluate
|
230
|
+
backend.execute_cmd(cmd, interaction_handler: interaction_handler, verbosity: :info)
|
231
|
+
end
|
232
|
+
[sshkit_cmd.full_stdout, sshkit_cmd.full_stderr, sshkit_cmd.exit_status]
|
288
233
|
end
|
289
|
-
|
290
|
-
[sshkit_cmd.full_stdout, sshkit_cmd.full_stderr, sshkit_cmd.exit_status]
|
291
234
|
end
|
292
235
|
|
293
236
|
# def init_brew
|
@@ -15,15 +15,6 @@ module OpsWalrus
|
|
15
15
|
@local_name, @package_uri, @version = local_name, package_uri, version
|
16
16
|
end
|
17
17
|
|
18
|
-
def sanitized_package_uri
|
19
|
-
sanitize_path(@package_uri)
|
20
|
-
end
|
21
|
-
|
22
|
-
def sanitize_path(path)
|
23
|
-
# found this at https://apidock.com/rails/v5.2.3/ActiveStorage/Filename/sanitized
|
24
|
-
path.encode(Encoding::UTF_8, invalid: :replace, undef: :replace, replace: "�").strip.tr("\u{202E}%$|:;/\t\r\n\\", "-")
|
25
|
-
end
|
26
|
-
|
27
18
|
# important: the import_resolution_dirname implemented as the local_name is critical because Bundler#download_package downloads
|
28
19
|
# package dependencies to the name that this method returns, which must match the package reference's local name
|
29
20
|
# so that later, when the package is being looked up on the load path (in LoadPath#resolve_import_reference),
|
@@ -34,7 +25,7 @@ module OpsWalrus
|
|
34
25
|
# change in order for the three things to reconcile with respect to one another, since all three bits of logic are
|
35
26
|
# what make bundling package dependencies and loading them function properly.
|
36
27
|
def import_resolution_dirname
|
37
|
-
local_name
|
28
|
+
"pkg_#{local_name}_version_#{version}"
|
38
29
|
end
|
39
30
|
|
40
31
|
def to_s
|
@@ -53,8 +44,17 @@ module OpsWalrus
|
|
53
44
|
# these are dynamic package references defined at runtime when an OpsFile's imports are being evaluated.
|
54
45
|
# this will usually be the case when an ops file does not belong to a package
|
55
46
|
class DynamicPackageReference < PackageReference
|
47
|
+
def self.import_resolution_dirname(package_uri, version)
|
48
|
+
sanitized_package_uri = sanitize_path(package_uri || raise(Error, "Unspecified package reference"))
|
49
|
+
sanitized_version = sanitize_path(version || "")
|
50
|
+
"pkg_#{sanitized_package_uri}_version_#{sanitized_version}"
|
51
|
+
end
|
52
|
+
def self.sanitize_path(path)
|
53
|
+
# found this at https://apidock.com/rails/v5.2.3/ActiveStorage/Filename/sanitized
|
54
|
+
path.encode(Encoding::UTF_8, invalid: :replace, undef: :replace, replace: "�").strip.tr("\u{202E}%$|:;/\t\r\n\\", "-")
|
55
|
+
end
|
56
56
|
def import_resolution_dirname
|
57
|
-
|
57
|
+
DynamicPackageReference.import_resolution_dirname(@package_uri, @version)
|
58
58
|
end
|
59
59
|
end
|
60
60
|
|
@@ -90,7 +90,7 @@ module OpsWalrus
|
|
90
90
|
str = "Namespace: #{@dirname.to_s}\n"
|
91
91
|
@symbol_table.each do |k, v|
|
92
92
|
if v.is_a? Namespace
|
93
|
-
str << "#{' ' * (indent)}|- #{k} : #{v.to_s(indent + 1)}
|
93
|
+
str << "#{' ' * (indent)}|- #{k} : #{v.to_s(indent + 1)}"
|
94
94
|
else
|
95
95
|
str << "#{' ' * (indent)}|- #{k} : #{v.to_s}\n"
|
96
96
|
end
|
@@ -106,31 +106,6 @@ module OpsWalrus
|
|
106
106
|
@symbol_table[symbol_name.to_s]
|
107
107
|
end
|
108
108
|
|
109
|
-
# # if this namespace contains an OpsFile of the same name as the namespace, e.g. pkg/install/install.ops, then this
|
110
|
-
# # method invokes the OpsFile of that same name and returns the result;
|
111
|
-
# # otherwise we return this namespace object
|
112
|
-
# def _invoke_if_namespace_has_ops_file_of_same_name(*args, **kwargs, &block)
|
113
|
-
# resolved_symbol = resolve_symbol(@dirname.basename)
|
114
|
-
# if resolved_symbol.is_a? OpsFile
|
115
|
-
# params_hash = resolved_symbol.build_params_hash(*args, **kwargs)
|
116
|
-
# resolved_symbol.invoke(runtime_env, params_hash)
|
117
|
-
# else
|
118
|
-
# self
|
119
|
-
# end
|
120
|
-
# end
|
121
|
-
|
122
|
-
# def method_missing(name, *args, **kwargs, &block)
|
123
|
-
# # puts "method_missing: #{name}"
|
124
|
-
# # puts caller
|
125
|
-
# resolved_symbol = resolve_symbol(name)
|
126
|
-
# case resolved_symbol
|
127
|
-
# when Namespace
|
128
|
-
# resolved_symbol._invoke_if_namespace_has_ops_file_of_same_name(*args, **kwargs)
|
129
|
-
# when OpsFile
|
130
|
-
# params_hash = resolved_symbol.build_params_hash(*args, **kwargs)
|
131
|
-
# resolved_symbol.invoke(runtime_env, params_hash)
|
132
|
-
# end
|
133
|
-
# end
|
134
109
|
end
|
135
110
|
|
136
111
|
# the assumption is that we have a bundle directory with all the packages in it
|
@@ -147,16 +122,13 @@ module OpsWalrus
|
|
147
122
|
@root_namespace = build_symbol_resolution_tree(@dir)
|
148
123
|
@path_map = build_path_map(@root_namespace)
|
149
124
|
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
# @path_map.each do |k,v|
|
158
|
-
# puts "#{k.to_s}: #{v.to_s}"
|
159
|
-
# end
|
125
|
+
App.instance.trace "LoadPath for #{@dir} ************************************************************"
|
126
|
+
App.instance.trace 'root namespace ******************************************************************'
|
127
|
+
App.instance.trace @root_namespace.to_s
|
128
|
+
App.instance.trace 'path map ************************************************************************'
|
129
|
+
@path_map.each do |k,v|
|
130
|
+
App.instance.trace "#{k.to_s}: #{v.to_s}"
|
131
|
+
end
|
160
132
|
|
161
133
|
@dynamic_package_additions_memo = {}
|
162
134
|
end
|
@@ -268,9 +240,16 @@ module OpsWalrus
|
|
268
240
|
@bundle_load_path = LoadPath.new(self, @app.bundle_dir)
|
269
241
|
@app_load_path = LoadPath.new(self, @app.pwd)
|
270
242
|
|
271
|
-
|
272
|
-
|
273
|
-
|
243
|
+
# we include this sudo password mapping by default because if a bundled script is being run on a remote host,
|
244
|
+
# then the remote command invocation will include the --pass flag when being run on the remote host, which will
|
245
|
+
# interactively prompt for a sudo password, and that will be the only opportunity for the command host
|
246
|
+
# that is running the bundled script on the remote host to interactively enter a sudo password for the remote context
|
247
|
+
# since the remote ops command will be running within a PTY, and the interactive prompts running on the remote
|
248
|
+
# host will be managed by the local ScopedMappingInteractionHandler running within the instance of the ops command
|
249
|
+
# process on the remote host, and the command host will not have any further opportunity to interactively enter
|
250
|
+
# any prompts on the remote host
|
251
|
+
interaction_handler_mapping_for_sudo_password = ScopedMappingInteractionHandler.mapping_for_sudo_password(sudo_password)
|
252
|
+
@interaction_handler = ScopedMappingInteractionHandler.new(interaction_handler_mapping_for_sudo_password)
|
274
253
|
|
275
254
|
configure_sshkit
|
276
255
|
end
|
@@ -291,6 +270,7 @@ module OpsWalrus
|
|
291
270
|
# SSHKit.config.use_format :simpletext
|
292
271
|
SSHKit.config.output_verbosity = :debug
|
293
272
|
elsif app.verbose?
|
273
|
+
SSHKit.config.use_format :pretty
|
294
274
|
# SSHKit.config.use_format :dot
|
295
275
|
SSHKit.config.output_verbosity = :info
|
296
276
|
end
|
@@ -344,8 +324,8 @@ module OpsWalrus
|
|
344
324
|
end.run
|
345
325
|
end
|
346
326
|
|
347
|
-
def invoke(ops_file,
|
348
|
-
ops_file.invoke(self,
|
327
|
+
def invoke(ops_file, hashlike_params)
|
328
|
+
ops_file.invoke(self, hashlike_params)
|
349
329
|
end
|
350
330
|
|
351
331
|
def find_load_path_that_includes_path(path)
|
data/lib/opswalrus/version.rb
CHANGED