opswalrus 1.0.8 → 1.0.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CNAME +1 -0
- data/Gemfile.lock +1 -1
- data/README.md +29 -16
- data/lib/opswalrus/app.rb +21 -7
- data/lib/opswalrus/bootstrap.sh +5 -0
- data/lib/opswalrus/cli.rb +16 -15
- data/lib/opswalrus/host.rb +115 -71
- data/lib/opswalrus/invocation.rb +446 -0
- data/lib/opswalrus/operation_runner.rb +3 -3
- data/lib/opswalrus/ops_file.rb +71 -32
- data/lib/opswalrus/ops_file_script.rb +55 -473
- data/lib/opswalrus/ops_file_script_dsl.rb +297 -0
- data/lib/opswalrus/patches.rb +1 -0
- data/lib/opswalrus/runtime_environment.rb +40 -9
- data/lib/opswalrus/sshkit_ext.rb +6 -3
- data/lib/opswalrus/version.rb +1 -1
- data/opswalrus.gemspec +1 -1
- metadata +5 -2
@@ -0,0 +1,446 @@
|
|
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
|
+
|
14
|
+
# module OpsWalrus
|
15
|
+
|
16
|
+
# class ArrayOrHashNavigationProxy
|
17
|
+
# def initialize(array_or_hash)
|
18
|
+
# @obj = array_or_hash
|
19
|
+
# end
|
20
|
+
# def [](index, *args, **kwargs, &block)
|
21
|
+
# @obj.method(:[]).call(index, *args, **kwargs, &block)
|
22
|
+
# end
|
23
|
+
# def respond_to_missing?(method, *)
|
24
|
+
# @obj.is_a?(Hash) && @obj.respond_to?(method)
|
25
|
+
# end
|
26
|
+
# def method_missing(name, *args, **kwargs, &block)
|
27
|
+
# case @obj
|
28
|
+
# when Array
|
29
|
+
# @obj.method(name).call(*args, **kwargs, &block)
|
30
|
+
# when Hash
|
31
|
+
# if @obj.respond_to?(name)
|
32
|
+
# @obj.method(name).call(*args, **kwargs, &block)
|
33
|
+
# else
|
34
|
+
# value = self[name.to_s]
|
35
|
+
# case value
|
36
|
+
# when Array, Hash
|
37
|
+
# ArrayOrHashNavigationProxy.new(value)
|
38
|
+
# else
|
39
|
+
# value
|
40
|
+
# end
|
41
|
+
# end
|
42
|
+
# end
|
43
|
+
# end
|
44
|
+
# end
|
45
|
+
|
46
|
+
# class InvocationParams
|
47
|
+
# # params : Hash
|
48
|
+
# def initialize(params)
|
49
|
+
# @params = params
|
50
|
+
# end
|
51
|
+
|
52
|
+
# def [](key)
|
53
|
+
# key = key.to_s if key.is_a? Symbol
|
54
|
+
# @params[key]
|
55
|
+
# end
|
56
|
+
|
57
|
+
# def dig(*keys)
|
58
|
+
# # keys = keys.map {|key| key.is_a?(Integer) ? key : key.to_s }
|
59
|
+
# @params.dig(*keys)
|
60
|
+
# end
|
61
|
+
|
62
|
+
# def method_missing(name, *args, **kwargs, &block)
|
63
|
+
# if @params.respond_to?(name)
|
64
|
+
# @params.method(name).call(*args, **kwargs, &block)
|
65
|
+
# else
|
66
|
+
# value = self[name]
|
67
|
+
# case value
|
68
|
+
# when Array, Hash
|
69
|
+
# ArrayOrHashNavigationProxy.new(value)
|
70
|
+
# else
|
71
|
+
# value
|
72
|
+
# end
|
73
|
+
# end
|
74
|
+
# end
|
75
|
+
# end
|
76
|
+
|
77
|
+
|
78
|
+
# # BootstrapLinuxHostShellScript = <<~SCRIPT
|
79
|
+
# # #!/usr/bin/env bash
|
80
|
+
# # ...
|
81
|
+
# # SCRIPT
|
82
|
+
|
83
|
+
# module InvocationDSL
|
84
|
+
# def ssh(*args, **kwargs, &block)
|
85
|
+
# host_proxy_class = @ops_file_script.host_proxy_class
|
86
|
+
# hosts = inventory(*args, **kwargs).map {|host| host_proxy_class.new(host) }
|
87
|
+
# sshkit_hosts = hosts.map(&:sshkit_host)
|
88
|
+
# sshkit_host_to_ops_host_map = sshkit_hosts.zip(hosts).to_h
|
89
|
+
# runtime_env = @runtime_env
|
90
|
+
# local_host = self
|
91
|
+
# # bootstrap_shell_script = BootstrapLinuxHostShellScript
|
92
|
+
# # on sshkit_hosts do |sshkit_host|
|
93
|
+
# SSHKit::Coordinator.new(sshkit_hosts).each(in: kwargs[:in] || :parallel) do |sshkit_host|
|
94
|
+
|
95
|
+
# # in this context, self is an instance of one of the subclasses of SSHKit::Backend::Abstract, e.g. SSHKit::Backend::Netssh
|
96
|
+
|
97
|
+
# host = sshkit_host_to_ops_host_map[sshkit_host]
|
98
|
+
# # puts "#{host.alias} / #{host}:"
|
99
|
+
|
100
|
+
# begin
|
101
|
+
# host.set_runtime_env(runtime_env)
|
102
|
+
# host.set_ssh_session_connection(self) # self is an instance of one of the subclasses of SSHKit::Backend::Abstract, e.g. SSHKit::Backend::Netssh
|
103
|
+
|
104
|
+
# # copy over bootstrap shell script
|
105
|
+
# # io = StringIO.new(bootstrap_shell_script)
|
106
|
+
# io = File.open(__FILE__.to_pathname.dirname.join("bootstrap.sh"))
|
107
|
+
# upload_success = host.upload(io, "tmpopsbootstrap.sh")
|
108
|
+
# io.close
|
109
|
+
# raise Error, "Unable to upload bootstrap shell script to remote host" unless upload_success
|
110
|
+
# host.execute(:chmod, "755", "tmpopsbootstrap.sh")
|
111
|
+
# host.execute(:sh, "tmpopsbootstrap.sh")
|
112
|
+
|
113
|
+
# # copy over ops bundle zip file
|
114
|
+
# zip_bundle_path = runtime_env.zip_bundle_path
|
115
|
+
# upload_success = host.upload(zip_bundle_path, "tmpops.zip")
|
116
|
+
# raise Error, "Unable to upload ops bundle to remote host" unless upload_success
|
117
|
+
|
118
|
+
# stdout, stderr, exit_status = host.run_ops(:bundle, "unzip tmpops.zip", in_bundle_root_dir: false)
|
119
|
+
# raise Error, "Unable to unzip ops bundle on remote host" unless exit_status == 0
|
120
|
+
# tmp_bundle_root_dir = stdout.strip
|
121
|
+
# host.set_ssh_session_tmp_bundle_root_dir(tmp_bundle_root_dir)
|
122
|
+
|
123
|
+
# # we run the block in the context of the host, s.t. `self` within the block evaluates to `host`
|
124
|
+
# retval = host.instance_exec(local_host, &block) # host is passed as the argument to the block
|
125
|
+
|
126
|
+
# # puts retval.inspect
|
127
|
+
|
128
|
+
# # cleanup
|
129
|
+
# if tmp_bundle_root_dir =~ /tmp/ # sanity check the temp path before we blow away something we don't intend
|
130
|
+
# host.execute(:rm, "-rf", "tmpopsbootstrap.sh", "tmpops.zip", tmp_bundle_root_dir)
|
131
|
+
# else
|
132
|
+
# host.execute(:rm, "-rf", "tmpopsbootstrap.sh", "tmpops.zip")
|
133
|
+
# end
|
134
|
+
|
135
|
+
# retval
|
136
|
+
# rescue SSHKit::Command::Failed => e
|
137
|
+
# puts "[!] Command failed:"
|
138
|
+
# puts e.message
|
139
|
+
# rescue Net::SSH::ConnectionTimeout
|
140
|
+
# puts "[!] The host '#{host}' not alive!"
|
141
|
+
# rescue Net::SSH::Timeout
|
142
|
+
# puts "[!] The host '#{host}' disconnected/timeouted unexpectedly!"
|
143
|
+
# rescue Errno::ECONNREFUSED
|
144
|
+
# puts "[!] Incorrect port #{port} for #{host}"
|
145
|
+
# rescue Net::SSH::HostKeyMismatch => e
|
146
|
+
# puts "[!] The host fingerprint does not match the last observed fingerprint for #{host}"
|
147
|
+
# puts e.message
|
148
|
+
# puts "You might try `ssh-keygen -f ~/.ssh/known_hosts -R \"#{host}\"`"
|
149
|
+
# rescue Net::SSH::AuthenticationFailed
|
150
|
+
# puts "Wrong Password: #{host} | #{user}:#{password}"
|
151
|
+
# rescue Net::SSH::Authentication::DisallowedMethod
|
152
|
+
# puts "[!] The host '#{host}' doesn't accept password authentication method."
|
153
|
+
# rescue Errno::EHOSTUNREACH => e
|
154
|
+
# puts "[!] The host '#{host}' is unreachable"
|
155
|
+
# rescue => e
|
156
|
+
# puts e.class
|
157
|
+
# puts e.message
|
158
|
+
# # puts e.backtrace.join("\n")
|
159
|
+
# ensure
|
160
|
+
# host.clear_ssh_session
|
161
|
+
# end
|
162
|
+
# end
|
163
|
+
# end
|
164
|
+
|
165
|
+
# def inventory(*args, **kwargs)
|
166
|
+
# tags = args.map(&:to_s)
|
167
|
+
|
168
|
+
# kwargs = kwargs.transform_keys(&:to_s)
|
169
|
+
# tags.concat(kwargs["tags"]) if kwargs["tags"]
|
170
|
+
|
171
|
+
# @runtime_env.app.inventory(tags)
|
172
|
+
# end
|
173
|
+
|
174
|
+
# def exit(exit_status, message = nil)
|
175
|
+
# if message
|
176
|
+
# puts message
|
177
|
+
# end
|
178
|
+
# result = if exit_status == 0
|
179
|
+
# Invocation::Success.new(nil)
|
180
|
+
# else
|
181
|
+
# Invocation::Error.new(nil, exit_status)
|
182
|
+
# end
|
183
|
+
# throw :exit_now, result
|
184
|
+
# end
|
185
|
+
|
186
|
+
# def env(*keys)
|
187
|
+
# keys = keys.map(&:to_s)
|
188
|
+
# if keys.empty?
|
189
|
+
# @env
|
190
|
+
# else
|
191
|
+
# @env.dig(*keys)
|
192
|
+
# end
|
193
|
+
# end
|
194
|
+
|
195
|
+
# # currently, import may only be used to import a package that is referenced in the script's package file
|
196
|
+
# # I may decide to extend this to work with dynamic package references
|
197
|
+
# #
|
198
|
+
# # local_package_name is the local package name defined for the package dependency that is attempting to be referenced
|
199
|
+
# def import(local_package_name)
|
200
|
+
# local_package_name = local_package_name.to_s
|
201
|
+
# package_reference = @ops_file_script.ops_file.package_file&.dependency(local_package_name)
|
202
|
+
# raise Error, "Unknown package reference: #{local_package_name}" unless package_reference
|
203
|
+
# import_reference = PackageDependencyReference.new(local_package_name, package_reference)
|
204
|
+
# # puts "import: #{import_reference.inspect}"
|
205
|
+
# @runtime_env.resolve_import_reference(@ops_file_script.ops_file, import_reference)
|
206
|
+
# end
|
207
|
+
|
208
|
+
# def params(*keys, default: nil)
|
209
|
+
# keys = keys.map(&:to_s)
|
210
|
+
# if keys.empty?
|
211
|
+
# @params
|
212
|
+
# else
|
213
|
+
# @params.dig(*keys) || default
|
214
|
+
# end
|
215
|
+
# end
|
216
|
+
|
217
|
+
# # returns the stdout from the command
|
218
|
+
# def sh(desc_or_cmd = nil, cmd = nil, input: nil, &block)
|
219
|
+
# out, err, status = *shell!(desc_or_cmd, cmd, block, input: input)
|
220
|
+
# out
|
221
|
+
# end
|
222
|
+
|
223
|
+
# # returns the tuple: [stdout, stderr, exit_status]
|
224
|
+
# def shell(desc_or_cmd = nil, cmd = nil, input: nil, &block)
|
225
|
+
# shell!(desc_or_cmd, cmd, block, input: input)
|
226
|
+
# end
|
227
|
+
|
228
|
+
# # returns the tuple: [stdout, stderr, exit_status]
|
229
|
+
# def shell!(desc_or_cmd = nil, cmd = nil, block = nil, input: nil)
|
230
|
+
# # description = nil
|
231
|
+
|
232
|
+
# 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
|
233
|
+
|
234
|
+
# description = desc_or_cmd if cmd || block
|
235
|
+
# cmd = block.call if block
|
236
|
+
# cmd ||= desc_or_cmd
|
237
|
+
|
238
|
+
# cmd = WalrusLang.render(cmd, block.binding) if block && cmd =~ /{{.*}}/
|
239
|
+
|
240
|
+
# #cmd = Shellwords.escape(cmd)
|
241
|
+
|
242
|
+
# # puts "shell! self: #{self.inspect}"
|
243
|
+
|
244
|
+
# if App.instance.report_mode?
|
245
|
+
# print "[#{@runtime_env.local_hostname}] "
|
246
|
+
# print "#{description}: " if description
|
247
|
+
# puts cmd
|
248
|
+
# end
|
249
|
+
|
250
|
+
# return unless cmd && !cmd.strip.empty?
|
251
|
+
|
252
|
+
# sshkit_cmd = @runtime_env.handle_input(input) do |interaction_handler|
|
253
|
+
# # self is a Module instance that is serving as the evaluation context in an instance of a subclass of an Invocation; see Invocation#evaluate
|
254
|
+
# backend.execute_cmd(cmd, interaction_handler: interaction_handler, verbosity: :info)
|
255
|
+
# end
|
256
|
+
|
257
|
+
# [sshkit_cmd.full_stdout, sshkit_cmd.full_stderr, sshkit_cmd.exit_status]
|
258
|
+
# end
|
259
|
+
|
260
|
+
# # def init_brew
|
261
|
+
# # execute('eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"')
|
262
|
+
# # end
|
263
|
+
|
264
|
+
# end
|
265
|
+
|
266
|
+
# # An Invocation object represents a stack frame, and the params_hash represents the
|
267
|
+
# # arguments that the caller has supplied for that stack frame to reference
|
268
|
+
# class Invocation
|
269
|
+
# class Result
|
270
|
+
# attr_accessor :value
|
271
|
+
# attr_accessor :exit_status
|
272
|
+
# def initialize(value, exit_status = 0)
|
273
|
+
# @value = value
|
274
|
+
# @exit_status = exit_status
|
275
|
+
# end
|
276
|
+
# def success?
|
277
|
+
# !failure?
|
278
|
+
# end
|
279
|
+
# def failure?
|
280
|
+
# !success?
|
281
|
+
# end
|
282
|
+
# end
|
283
|
+
# class Success < Result
|
284
|
+
# def initialize(value)
|
285
|
+
# super(value, 0)
|
286
|
+
# end
|
287
|
+
# def success?
|
288
|
+
# true
|
289
|
+
# end
|
290
|
+
# end
|
291
|
+
# class Error < Result
|
292
|
+
# def initialize(value, exit_status = 1)
|
293
|
+
# super(value, exit_status == 0 ? 1 : exit_status)
|
294
|
+
# end
|
295
|
+
# def failure?
|
296
|
+
# true
|
297
|
+
# end
|
298
|
+
# end
|
299
|
+
|
300
|
+
|
301
|
+
# def self.define_invocation_class(ops_file)
|
302
|
+
# klass = Class.new(Invocation)
|
303
|
+
|
304
|
+
# methods_defined = Set.new
|
305
|
+
|
306
|
+
# # define methods for every import in the script
|
307
|
+
# ops_file.local_symbol_table.each do |symbol_name, import_reference|
|
308
|
+
# unless methods_defined.include? symbol_name
|
309
|
+
# klass.define_method(symbol_name) do |*args, **kwargs, &block|
|
310
|
+
# # puts "0" * 80
|
311
|
+
# # puts "@runtime_env.resolve_import_reference(@ops_file_script.ops_file, #{import_reference.inspect})"
|
312
|
+
# # puts @ops_file_script.ops_file.ops_file_path
|
313
|
+
# # puts symbol_name
|
314
|
+
# namespace_or_ops_file = @runtime_env.resolve_import_reference(@ops_file_script.ops_file, import_reference)
|
315
|
+
# # puts namespace_or_ops_file.inspect
|
316
|
+
# # puts "0" * 80
|
317
|
+
# case namespace_or_ops_file
|
318
|
+
# when Namespace
|
319
|
+
# namespace_or_ops_file
|
320
|
+
# when OpsFile
|
321
|
+
# params_hash = namespace_or_ops_file.build_params_hash(*args, **kwargs)
|
322
|
+
# namespace_or_ops_file.invoke(@runtime_env, params_hash)
|
323
|
+
# end
|
324
|
+
# end
|
325
|
+
# methods_defined << symbol_name
|
326
|
+
# end
|
327
|
+
# end
|
328
|
+
|
329
|
+
# # define methods for every Namespace or OpsFile within the namespace that the OpsFile resides within
|
330
|
+
# sibling_symbol_table = Set.new
|
331
|
+
# sibling_symbol_table |= ops_file.dirname.glob("*.ops").map {|ops_file_path| ops_file_path.basename(".ops").to_s } # OpsFiles
|
332
|
+
# sibling_symbol_table |= ops_file.dirname.glob("*").select(&:directory?).map {|dir_path| dir_path.basename.to_s } # Namespaces
|
333
|
+
# sibling_symbol_table.each do |symbol_name|
|
334
|
+
# unless methods_defined.include? symbol_name
|
335
|
+
# klass.define_method(symbol_name) do |*args, **kwargs, &block|
|
336
|
+
# # puts "0" * 80
|
337
|
+
# # puts "@runtime_env.resolve_symbol(@ops_file_script.ops_file, #{symbol_name})"
|
338
|
+
# # puts @ops_file_script.ops_file.ops_file_path
|
339
|
+
# # puts symbol_name
|
340
|
+
# namespace_or_ops_file = @runtime_env.resolve_sibling_symbol(@ops_file_script.ops_file, symbol_name)
|
341
|
+
# # puts namespace_or_ops_file.inspect
|
342
|
+
# # puts "0" * 80
|
343
|
+
# case namespace_or_ops_file
|
344
|
+
# when Namespace
|
345
|
+
# namespace_or_ops_file
|
346
|
+
# when OpsFile
|
347
|
+
# params_hash = namespace_or_ops_file.build_params_hash(*args, **kwargs)
|
348
|
+
# namespace_or_ops_file.invoke(@runtime_env, params_hash)
|
349
|
+
# end
|
350
|
+
# end
|
351
|
+
# methods_defined << symbol_name
|
352
|
+
# end
|
353
|
+
# end
|
354
|
+
|
355
|
+
# klass
|
356
|
+
# end
|
357
|
+
|
358
|
+
# include InvocationDSL
|
359
|
+
|
360
|
+
# def initialize(ops_file_script, runtime_env, params_hash)
|
361
|
+
# @ops_file_script = ops_file_script
|
362
|
+
# @runtime_env = runtime_env
|
363
|
+
# @params = InvocationParams.new(params_hash)
|
364
|
+
# end
|
365
|
+
|
366
|
+
# def backend
|
367
|
+
# @runtime_env.pty
|
368
|
+
# end
|
369
|
+
|
370
|
+
# def debug?
|
371
|
+
# @runtime_env.debug?
|
372
|
+
# end
|
373
|
+
|
374
|
+
# def verbose?
|
375
|
+
# @runtime_env.verbose?
|
376
|
+
# end
|
377
|
+
|
378
|
+
# # def evaluate
|
379
|
+
# # # the evaluation context needs to be a module with all of the following:
|
380
|
+
# # # - InvocationDSL methods
|
381
|
+
# # # - @ops_file_script
|
382
|
+
# # # - @runtime_env
|
383
|
+
# # # - @params
|
384
|
+
# # # - #backend
|
385
|
+
# # # - #debug?
|
386
|
+
# # # - #verbose?
|
387
|
+
# # # - all the dynamically defined methods in the subclass of Invocation
|
388
|
+
# # end
|
389
|
+
|
390
|
+
# def evaluate
|
391
|
+
# eval(@ops_file_script.script, nil, @ops_file_script.ops_file.ops_file_path.to_s, @ops_file_script.ops_file.script_line_offset)
|
392
|
+
# end
|
393
|
+
|
394
|
+
# # def evaluate
|
395
|
+
# # ops_file_script = @ops_file_script
|
396
|
+
# # runtime_env = @runtime_env
|
397
|
+
# # invocation_params = @params
|
398
|
+
|
399
|
+
# # # 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
|
400
|
+
# # evaluation_context = Module.new
|
401
|
+
# # evaluation_context.module_exec {
|
402
|
+
# # include InvocationDSL
|
403
|
+
|
404
|
+
# # @ops_file_script = ops_file_script
|
405
|
+
# # @runtime_env = runtime_env
|
406
|
+
# # @params = invocation_params
|
407
|
+
|
408
|
+
# # def backend
|
409
|
+
# # @runtime_env.pty
|
410
|
+
# # end
|
411
|
+
|
412
|
+
# # def debug?
|
413
|
+
# # @runtime_env.debug?
|
414
|
+
# # end
|
415
|
+
|
416
|
+
# # def verbose?
|
417
|
+
# # @runtime_env.verbose?
|
418
|
+
# # end
|
419
|
+
# # }
|
420
|
+
|
421
|
+
# # 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)
|
422
|
+
# # end
|
423
|
+
|
424
|
+
# # def method_missing(name, *args, **kwargs, &block)
|
425
|
+
# # puts "1" * 80
|
426
|
+
# # import_reference = @ops_file_script.ops_file.resolve_import(name)
|
427
|
+
# # if import_reference
|
428
|
+
# # puts "2" * 80
|
429
|
+
# # resolved_value = @runtime_env.resolve_import_reference(@ops_file_script.ops_file, import_reference)
|
430
|
+
# # return resolved_value if resolved_value
|
431
|
+
# # end
|
432
|
+
|
433
|
+
# # puts "3" * 80
|
434
|
+
# # case namespace_or_ops_file = @runtime_env.resolve_symbol(@ops_file_script.ops_file, name.to_s)
|
435
|
+
# # when Namespace
|
436
|
+
# # puts "4" * 80
|
437
|
+
# # namespace_or_ops_file
|
438
|
+
# # when OpsFile
|
439
|
+
# # puts "5" * 80
|
440
|
+
# # namespace_or_ops_file.invoke(@runtime_env, *args, **kwargs, &block)
|
441
|
+
# # else
|
442
|
+
# # raise NoMethodError, "No method named '#{name}'"
|
443
|
+
# # end
|
444
|
+
# # end
|
445
|
+
# end
|
446
|
+
# end
|
@@ -30,7 +30,7 @@ module OpsWalrus
|
|
30
30
|
|
31
31
|
# runtime_kv_args is an Array(String) of the form: ["arg1:val1", "arg2:val2", ...]
|
32
32
|
# params_json_hash is a Hash representation of a JSON string
|
33
|
-
def run(runtime_kv_args, params_json_hash: nil
|
33
|
+
def run(runtime_kv_args, params_json_hash: nil)
|
34
34
|
params_hash = runtime_kv_args.reduce(params_json_hash || {}) do |memo, kv_pair_string|
|
35
35
|
str_key, str_value = kv_pair_string.split(":", 2)
|
36
36
|
if pre_existing_value = memo[str_key]
|
@@ -43,7 +43,7 @@ module OpsWalrus
|
|
43
43
|
memo
|
44
44
|
end
|
45
45
|
|
46
|
-
if
|
46
|
+
if app.debug?
|
47
47
|
puts "Script:"
|
48
48
|
puts @entry_point_ops_file.script
|
49
49
|
end
|
@@ -72,7 +72,7 @@ module OpsWalrus
|
|
72
72
|
Invocation::Error.new(e)
|
73
73
|
end
|
74
74
|
|
75
|
-
if
|
75
|
+
if app.debug? && result.failure?
|
76
76
|
puts "Ops script error details:"
|
77
77
|
puts "Error: #{result.value}"
|
78
78
|
puts "Status code: #{result.exit_status}"
|
data/lib/opswalrus/ops_file.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'pathname'
|
2
2
|
require 'yaml'
|
3
3
|
require_relative 'git'
|
4
|
+
require_relative 'host'
|
4
5
|
require_relative 'ops_file_script'
|
5
6
|
|
6
7
|
module OpsWalrus
|
@@ -9,7 +10,6 @@ module OpsWalrus
|
|
9
10
|
attr_accessor :app
|
10
11
|
attr_accessor :ops_file_path
|
11
12
|
attr_accessor :yaml
|
12
|
-
attr_accessor :script
|
13
13
|
|
14
14
|
def initialize(app, ops_file_path)
|
15
15
|
@app = app
|
@@ -24,32 +24,46 @@ module OpsWalrus
|
|
24
24
|
self.class == other.class && self.hash == other.hash
|
25
25
|
end
|
26
26
|
|
27
|
+
def host_proxy_class
|
28
|
+
@host_proxy_class || (lazy_load_file && @host_proxy_class)
|
29
|
+
end
|
30
|
+
|
27
31
|
def yaml
|
28
|
-
@yaml || (
|
32
|
+
@yaml || (lazy_load_file && @yaml)
|
29
33
|
end
|
30
34
|
|
31
35
|
def script_line_offset
|
32
|
-
@script_line_offset || (
|
36
|
+
@script_line_offset || (lazy_load_file && @script_line_offset)
|
33
37
|
end
|
34
38
|
|
39
|
+
# returns a subclass of OpsFileScript
|
40
|
+
def script_class
|
41
|
+
@script_class || (lazy_load_file && @script_class)
|
42
|
+
end
|
43
|
+
|
44
|
+
# returns an instance of the class returned by #script_class
|
35
45
|
def script
|
36
|
-
@script || (
|
46
|
+
@script || (lazy_load_file && @script)
|
37
47
|
end
|
38
48
|
|
39
|
-
def
|
49
|
+
def lazy_load_file
|
50
|
+
# puts "OpsFile#lazy_load_file for #{ops_file_path}"
|
51
|
+
# puts caller
|
40
52
|
yaml, ruby_script = if @ops_file_path.exist?
|
41
53
|
parse(File.read(@ops_file_path))
|
42
54
|
end || ["", ""]
|
43
55
|
@script_line_offset = yaml.lines.size + 1 # +1 to account for the ... line
|
44
56
|
@yaml = YAML.load(yaml) || {} # post_invariant: @yaml is a Hash
|
45
|
-
@
|
57
|
+
@script_class = OpsFileScript.define_for(self, ruby_script)
|
58
|
+
@script = @script_class.new(self, ruby_script)
|
59
|
+
@host_proxy_class = HostProxy.define_host_proxy_class(self)
|
46
60
|
end
|
47
61
|
|
48
62
|
def parse(script_string)
|
49
63
|
file_halves = script_string.split(/^\.\.\.$/, 2)
|
50
64
|
case file_halves.count
|
51
65
|
when 1
|
52
|
-
yaml, ruby_script = "", file_halves
|
66
|
+
yaml, ruby_script = "", file_halves.first
|
53
67
|
when 2
|
54
68
|
yaml, ruby_script = *file_halves
|
55
69
|
else
|
@@ -59,7 +73,7 @@ module OpsWalrus
|
|
59
73
|
end
|
60
74
|
|
61
75
|
def params
|
62
|
-
yaml["params"]
|
76
|
+
yaml["params"] || {}
|
63
77
|
end
|
64
78
|
|
65
79
|
def output
|
@@ -97,28 +111,7 @@ module OpsWalrus
|
|
97
111
|
# imports:
|
98
112
|
# my_package: my_package
|
99
113
|
in String => import_str
|
100
|
-
|
101
|
-
when package_reference = package_file&.dependency(import_str) # package dependency reference
|
102
|
-
# in this context, import_str is the local package name documented in the package's dependencies
|
103
|
-
PackageDependencyReference.new(local_name, package_reference)
|
104
|
-
when import_str.to_pathname.exist? # path reference
|
105
|
-
path = import_str.to_pathname
|
106
|
-
case
|
107
|
-
when path.directory?
|
108
|
-
DirectoryReference.new(local_name, path.realpath)
|
109
|
-
when path.file? && path.extname.downcase == ".ops"
|
110
|
-
OpsFileReference.new(local_name, path.realpath)
|
111
|
-
else
|
112
|
-
raise Error, "Unknown import reference: #{local_name} -> #{import_str.inspect}"
|
113
|
-
end
|
114
|
-
when Git.repo?(import_str) # ops file has imported an ad-hoc git repo
|
115
|
-
package_uri = import_str
|
116
|
-
destination_package_path = app.bundler.dynamic_package_path_for_git_package(package_uri)
|
117
|
-
# puts "DynamicPackageImportReference: #{local_name} -> #{destination_package_path}"
|
118
|
-
DynamicPackageImportReference.new(local_name, DynamicPackageReference.new(local_name, package_uri, nil))
|
119
|
-
else
|
120
|
-
raise Error, "Unknown import reference: #{local_name}: #{yaml_import_reference.inspect}"
|
121
|
-
end
|
114
|
+
import_string_to_import_reference(local_name, import_str)
|
122
115
|
|
123
116
|
# when the imports line says:
|
124
117
|
# imports:
|
@@ -137,9 +130,51 @@ module OpsWalrus
|
|
137
130
|
end
|
138
131
|
end
|
139
132
|
|
133
|
+
def import_string_to_import_reference(local_name, import_str)
|
134
|
+
if package_reference = package_file&.dependency(import_str) # package dependency reference
|
135
|
+
# in this context, import_str is the local package name documented in the package's dependencies
|
136
|
+
return PackageDependencyReference.new(local_name, package_reference)
|
137
|
+
end
|
138
|
+
|
139
|
+
import_path = import_str.to_pathname
|
140
|
+
if import_path.absolute? && import_path.exist? # absolute path reference
|
141
|
+
return case
|
142
|
+
when import_path.directory?
|
143
|
+
DirectoryReference.new(local_name, import_path.realpath)
|
144
|
+
when import_path.file? && import_path.extname.downcase == ".ops"
|
145
|
+
OpsFileReference.new(local_name, import_path.realpath)
|
146
|
+
else
|
147
|
+
raise Error, "Unknown import reference for absolute path: #{local_name}: #{import_path}"
|
148
|
+
end
|
149
|
+
end
|
150
|
+
if import_path.relative? # relative path reference
|
151
|
+
rebased_path = dirname.join(import_path)
|
152
|
+
if rebased_path.exist?
|
153
|
+
return case
|
154
|
+
when rebased_path.directory?
|
155
|
+
DirectoryReference.new(local_name, rebased_path.realpath)
|
156
|
+
when rebased_path.file? && rebased_path.extname.downcase == ".ops"
|
157
|
+
OpsFileReference.new(local_name, rebased_path.realpath)
|
158
|
+
else
|
159
|
+
raise Error, "Unknown import reference for relative path: #{local_name}: #{import_path}"
|
160
|
+
end
|
161
|
+
elsif rebased_path.sub_ext(".ops").exist?
|
162
|
+
return OpsFileReference.new(local_name, rebased_path.sub_ext(".ops").realpath)
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
package_uri = import_str
|
167
|
+
if Git.repo?(package_uri) # ops file has imported an ad-hoc git repo
|
168
|
+
destination_package_path = app.bundler.dynamic_package_path_for_git_package(package_uri)
|
169
|
+
# puts "DynamicPackageImportReference: #{local_name} -> #{destination_package_path}"
|
170
|
+
return DynamicPackageImportReference.new(local_name, DynamicPackageReference.new(local_name, package_uri, nil))
|
171
|
+
end
|
172
|
+
|
173
|
+
raise Error, "Unknown import reference: #{local_name}: #{import_str.inspect}"
|
174
|
+
end
|
175
|
+
|
140
176
|
def invoke(runtime_env, params_hash)
|
141
|
-
|
142
|
-
script.invoke(runtime_env, params_hash)
|
177
|
+
script._invoke(runtime_env, params_hash)
|
143
178
|
end
|
144
179
|
|
145
180
|
def build_params_hash(*args, **kwargs)
|
@@ -223,5 +258,9 @@ module OpsWalrus
|
|
223
258
|
dirname.glob("*").select(&:directory?)
|
224
259
|
end
|
225
260
|
|
261
|
+
def to_s
|
262
|
+
"OpsFile: #{ops_file_path}"
|
263
|
+
end
|
264
|
+
|
226
265
|
end
|
227
266
|
end
|