opswalrus 1.0.15 → 1.0.17
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 -18
- 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 +21 -114
- 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 -69
- 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
@@ -5,6 +5,7 @@ module OpsWalrus
|
|
5
5
|
class ScopedMappingInteractionHandler
|
6
6
|
attr_accessor :input_mappings # Hash[ String | Regex => String ]
|
7
7
|
|
8
|
+
# log_level is one of: :fatal, :error, :warn, :info, :debug, :trace
|
8
9
|
def initialize(mapping, log_level = nil)
|
9
10
|
@log_level = log_level
|
10
11
|
@input_mappings = mapping
|
@@ -21,11 +22,10 @@ module OpsWalrus
|
|
21
22
|
# end
|
22
23
|
|
23
24
|
# sudo_password : String
|
24
|
-
def mapping_for_sudo_password(sudo_password)
|
25
|
+
def self.mapping_for_sudo_password(sudo_password)
|
25
26
|
{
|
26
27
|
/\[sudo\] password for .*?:\s*/ => "#{sudo_password}\n",
|
27
28
|
App::LOCAL_SUDO_PASSWORD_PROMPT => "#{sudo_password}\n",
|
28
|
-
# /\s+/ => nil, # unnecessary
|
29
29
|
}
|
30
30
|
end
|
31
31
|
|
@@ -39,7 +39,7 @@ module OpsWalrus
|
|
39
39
|
raise ArgumentError.new("mapping must be a Hash") unless mapping.is_a?(Hash)
|
40
40
|
|
41
41
|
if sudo_password
|
42
|
-
mapping.merge!(mapping_for_sudo_password(sudo_password))
|
42
|
+
mapping.merge!(ScopedMappingInteractionHandler.mapping_for_sudo_password(sudo_password))
|
43
43
|
end
|
44
44
|
|
45
45
|
if mapping.empty?
|
@@ -57,17 +57,16 @@ module OpsWalrus
|
|
57
57
|
end
|
58
58
|
|
59
59
|
def on_data(_command, stream_name, data, channel)
|
60
|
-
log("Looking up response for #{stream_name} message #{data.inspect}")
|
61
|
-
|
62
60
|
response_data = begin
|
63
61
|
first_matching_key_value_pair = @input_mappings.find {|k, _v| k === data }
|
64
62
|
first_matching_key_value_pair&.last
|
65
63
|
end
|
66
64
|
|
67
65
|
if response_data.nil?
|
68
|
-
|
66
|
+
trace(Style.red("No interaction handler mapping for #{stream_name}: #{data} so no response was sent"))
|
69
67
|
else
|
70
|
-
|
68
|
+
debug(Style.cyan("Handling #{stream_name} message #{data}"))
|
69
|
+
debug(Style.cyan("Sending response #{response_data}"))
|
71
70
|
if channel.respond_to?(:send_data) # Net SSH Channel
|
72
71
|
channel.send_data(response_data)
|
73
72
|
elsif channel.respond_to?(:write) # Local IO
|
@@ -80,16 +79,21 @@ module OpsWalrus
|
|
80
79
|
|
81
80
|
private
|
82
81
|
|
83
|
-
def
|
84
|
-
|
85
|
-
|
82
|
+
def trace(message)
|
83
|
+
App.instance.trace(message)
|
84
|
+
end
|
85
|
+
|
86
|
+
def debug(message)
|
87
|
+
App.instance.debug(message)
|
88
|
+
if [:fatal, :error, :warn, :info, :debug, :trace].include? @log_level
|
89
|
+
SSHKit.config.output.send(@log_level, message)
|
90
|
+
end
|
86
91
|
end
|
87
92
|
|
88
93
|
end
|
89
94
|
|
90
95
|
class PasswdInteractionHandler
|
91
96
|
def on_data(command, stream_name, data, channel)
|
92
|
-
# puts data
|
93
97
|
case data
|
94
98
|
when '(current) UNIX password: '
|
95
99
|
channel.send_data("old_pw\n")
|
@@ -118,8 +122,6 @@ module OpsWalrus
|
|
118
122
|
|
119
123
|
class SudoPromptInteractionHandler
|
120
124
|
def on_data(command, stream_name, data, channel)
|
121
|
-
# puts "0" * 80
|
122
|
-
# puts data.inspect
|
123
125
|
case data
|
124
126
|
when /\[sudo\] password for/
|
125
127
|
if channel.respond_to?(:send_data) # Net::SSH channel
|
@@ -128,12 +130,11 @@ module OpsWalrus
|
|
128
130
|
channel.write("conquer\n")
|
129
131
|
end
|
130
132
|
when /\s+/
|
131
|
-
|
133
|
+
nil
|
132
134
|
else
|
133
135
|
raise "Unexpected prompt: #{data} on stream #{stream_name} and channel #{channel.inspect}"
|
134
|
-
|
136
|
+
end
|
135
137
|
end
|
136
138
|
end
|
137
139
|
|
138
|
-
|
139
140
|
end
|
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
|
@@ -264,6 +203,7 @@ module OpsWalrus
|
|
264
203
|
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
|
265
204
|
|
266
205
|
description = desc_or_cmd if cmd || block
|
206
|
+
description = WalrusLang.render(description, block.binding) if description && block
|
267
207
|
cmd = block.call if block
|
268
208
|
cmd ||= desc_or_cmd
|
269
209
|
|
@@ -274,19 +214,23 @@ module OpsWalrus
|
|
274
214
|
# puts "shell! self: #{self.inspect}"
|
275
215
|
|
276
216
|
if App.instance.report_mode?
|
277
|
-
|
217
|
+
puts Style.green("*" * 80)
|
218
|
+
print "[#{Style.blue(@runtime_env.local_hostname)}] "
|
278
219
|
print "#{description}: " if description
|
279
|
-
puts cmd
|
220
|
+
puts Style.yellow(cmd)
|
280
221
|
end
|
281
222
|
|
282
223
|
return unless cmd && !cmd.strip.empty?
|
283
224
|
|
284
|
-
|
285
|
-
|
286
|
-
|
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]
|
287
233
|
end
|
288
|
-
|
289
|
-
[sshkit_cmd.full_stdout, sshkit_cmd.full_stderr, sshkit_cmd.exit_status]
|
290
234
|
end
|
291
235
|
|
292
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
|
|