opswalrus 1.0.15 → 1.0.17

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
- log("Unable to find interaction handler mapping for #{stream_name}: #{data.inspect} so no response was sent")
66
+ trace(Style.red("No interaction handler mapping for #{stream_name}: #{data} so no response was sent"))
69
67
  else
70
- log("Sending #{response_data.inspect}")
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 log(message)
84
- # puts message
85
- SSHKit.config.output.send(@log_level, message) unless @log_level.nil?
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
- puts 'space, do nothing'
133
+ nil
132
134
  else
133
135
  raise "Unexpected prompt: #{data} on stream #{stream_name} and channel #{channel.inspect}"
134
- end
136
+ end
135
137
  end
136
138
  end
137
139
 
138
-
139
140
  end
@@ -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
- _invoke_if_namespace_has_ops_file_of_same_name(*args, **kwargs)
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
- end
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
- self
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 _invoke(*args, **kwargs)
97
- case @namespace_or_ops_file
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 _invoke_local(*args, **kwargs)
106
- params_hash = @namespace_or_ops_file.build_params_hash(*args, **kwargs)
107
- @namespace_or_ops_file.invoke(@runtime_env, params_hash)
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, &block)
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 _resolve_method_and_invoke(name, *args, **kwargs)
125
- @namespace_or_ops_file = @namespace_or_ops_file.resolve_symbol(name)
126
- _invoke(*args, **kwargs)
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 method_missing(name, *args, **kwargs, &block)
130
- _resolve_method_and_invoke(name, *args, **kwargs)
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
@@ -112,7 +112,6 @@ module SSHKit
112
112
  buffer || ""
113
113
  end
114
114
 
115
-
116
115
  def handle_data_for_stderr(output, cmd, buffer, stdin, is_blocked)
117
116
  # we're blocked on reading, so let's process the buffer
118
117
  lines, buffer = split_buffer(buffer)
@@ -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", "arg2:val2", ...]
32
- # params_json_hash is a Hash representation of a JSON string
33
- def run(runtime_kv_args, params_json_hash: nil)
34
- params_hash = runtime_kv_args.reduce(params_json_hash || {}) do |memo, kv_pair_string|
35
- str_key, str_value = kv_pair_string.split(":", 2)
36
- if pre_existing_value = memo[str_key]
37
- array = pre_existing_value.is_a?(Array) ? pre_existing_value : [pre_existing_value]
38
- array << str_value
39
- memo[str_key] = array
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[str_key] = str_value
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
- puts "Script:"
48
- puts @entry_point_ops_file.script
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.message
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.message
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
@@ -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, params_hash)
177
- script._invoke(runtime_env, params_hash)
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.debug "defining method for local symbol table entry: #{symbol_name}"
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.debug "resolving local symbol table entry: #{symbol_name}"
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.debug "namespace_or_ops_file=#{namespace_or_ops_file.to_s}"
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.debug "methods_defined=#{methods_defined}"
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.debug "defining method for implicit imports: #{symbol_name}"
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.debug "resolving implicit import: #{symbol_name}"
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.debug "namespace_or_ops_file=#{namespace_or_ops_file.to_s}"
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, params_hash)
134
+ def _invoke(runtime_env, hashlike_params)
64
135
  @runtime_env = runtime_env
65
- @params = InvocationParams.new(params_hash)
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, params_hash)
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
- print "[#{@runtime_env.local_hostname}] "
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
- sshkit_cmd = @runtime_env.handle_input(input) do |interaction_handler|
285
- # self is a Module instance that is serving as the evaluation context in an instance of a subclass of an Invocation; see Invocation#evaluate
286
- backend.execute_cmd(cmd, interaction_handler: interaction_handler, verbosity: :info)
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
- sanitized_package_uri
57
+ DynamicPackageReference.import_resolution_dirname(@package_uri, @version)
58
58
  end
59
59
  end
60
60