opswalrus 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.rspec +3 -0
- data/Gemfile +9 -0
- data/Gemfile.lock +59 -0
- data/LICENSE +674 -0
- data/README.md +256 -0
- data/Rakefile +8 -0
- data/exe/ops +6 -0
- data/exe/run_ops_bundle +22 -0
- data/lib/opswalrus/app.rb +328 -0
- data/lib/opswalrus/bootstrap.sh +57 -0
- data/lib/opswalrus/bootstrap_linux_host1.sh +12 -0
- data/lib/opswalrus/bootstrap_linux_host2.sh +37 -0
- data/lib/opswalrus/bootstrap_linux_host3.sh +21 -0
- data/lib/opswalrus/bundler.rb +175 -0
- data/lib/opswalrus/cli.rb +143 -0
- data/lib/opswalrus/host.rb +177 -0
- data/lib/opswalrus/hosts_file.rb +55 -0
- data/lib/opswalrus/interaction_handlers.rb +53 -0
- data/lib/opswalrus/local_non_blocking_backend.rb +132 -0
- data/lib/opswalrus/local_pty_backend.rb +89 -0
- data/lib/opswalrus/operation_runner.rb +85 -0
- data/lib/opswalrus/ops_file.rb +235 -0
- data/lib/opswalrus/ops_file_script.rb +472 -0
- data/lib/opswalrus/package_file.rb +102 -0
- data/lib/opswalrus/runtime_environment.rb +258 -0
- data/lib/opswalrus/sshkit_ext.rb +51 -0
- data/lib/opswalrus/traversable.rb +15 -0
- data/lib/opswalrus/version.rb +3 -0
- data/lib/opswalrus/walrus_lang.rb +83 -0
- data/lib/opswalrus/zip.rb +57 -0
- data/lib/opswalrus.rb +10 -0
- data/opswalrus.gemspec +45 -0
- data/sig/opswalrus.rbs +4 -0
- metadata +178 -0
@@ -0,0 +1,132 @@
|
|
1
|
+
require 'open3'
|
2
|
+
require 'fileutils'
|
3
|
+
|
4
|
+
module SSHKit
|
5
|
+
|
6
|
+
module Backend
|
7
|
+
|
8
|
+
# this backend is compatible with sudo if you use the -S flag, e.g.: sudo -S ...
|
9
|
+
class LocalNonBlocking < Local
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def execute_command(cmd)
|
14
|
+
output.log_command_start(cmd.with_redaction)
|
15
|
+
cmd.started = Time.now
|
16
|
+
Open3.popen3(cmd.to_command) do |stdin, stdout, stderr, wait_thr|
|
17
|
+
stdout_thread = Thread.new do
|
18
|
+
buffer = ""
|
19
|
+
partial_buffer = ""
|
20
|
+
while !stdout.closed?
|
21
|
+
# puts "9" * 80
|
22
|
+
begin
|
23
|
+
stdout.read_nonblock(4096, partial_buffer)
|
24
|
+
buffer << partial_buffer
|
25
|
+
# puts "nonblocking1. buffer=#{buffer} partial_buffer=#{partial_buffer}"
|
26
|
+
buffer = handle_data_for_stdout(output, cmd, buffer, stdin, false)
|
27
|
+
# puts "nonblocking2. buffer=#{buffer} partial_buffer=#{partial_buffer}"
|
28
|
+
rescue IO::WaitReadable, Errno::EAGAIN, Errno::EWOULDBLOCK
|
29
|
+
# puts "blocking. buffer=#{buffer} partial_buffer=#{partial_buffer}"
|
30
|
+
buffer = handle_data_for_stdout(output, cmd, buffer, stdin, true)
|
31
|
+
IO.select([stdout])
|
32
|
+
retry
|
33
|
+
|
34
|
+
# per https://stackoverflow.com/questions/1154846/continuously-read-from-stdout-of-external-process-in-ruby
|
35
|
+
# and https://stackoverflow.com/questions/10238298/ruby-on-linux-pty-goes-away-without-eof-raises-errnoeio
|
36
|
+
# the PTY can raise an Errno::EIO because the child process unexpectedly goes away
|
37
|
+
rescue EOFError, Errno::EIO
|
38
|
+
# puts "eof!"
|
39
|
+
handle_data_for_stdout(output, cmd, buffer, stdin, true)
|
40
|
+
stdout.close
|
41
|
+
rescue => e
|
42
|
+
puts "closing PTY due to unexpected error: #{e.message}"
|
43
|
+
handle_data_for_stdout(output, cmd, buffer, stdin, true)
|
44
|
+
stdout.close
|
45
|
+
# puts e.message
|
46
|
+
# puts e.backtrace.join("\n")
|
47
|
+
end
|
48
|
+
end
|
49
|
+
# puts "end!"
|
50
|
+
end
|
51
|
+
stderr_thread = Thread.new do
|
52
|
+
buffer = ""
|
53
|
+
partial_buffer = ""
|
54
|
+
while !stderr.closed?
|
55
|
+
# puts "9" * 80
|
56
|
+
begin
|
57
|
+
stderr.read_nonblock(4096, partial_buffer)
|
58
|
+
buffer << partial_buffer
|
59
|
+
# puts "nonblocking1. buffer=#{buffer} partial_buffer=#{partial_buffer}"
|
60
|
+
buffer = handle_data_for_stderr(output, cmd, buffer, stdin, false)
|
61
|
+
# puts "nonblocking2. buffer=#{buffer} partial_buffer=#{partial_buffer}"
|
62
|
+
rescue IO::WaitReadable, Errno::EAGAIN, Errno::EWOULDBLOCK
|
63
|
+
# puts "blocking. buffer=#{buffer} partial_buffer=#{partial_buffer}"
|
64
|
+
buffer = handle_data_for_stderr(output, cmd, buffer, stdin, true)
|
65
|
+
IO.select([stderr])
|
66
|
+
retry
|
67
|
+
|
68
|
+
# per https://stackoverflow.com/questions/1154846/continuously-read-from-stdout-of-external-process-in-ruby
|
69
|
+
# and https://stackoverflow.com/questions/10238298/ruby-on-linux-pty-goes-away-without-eof-raises-errnoeio
|
70
|
+
# the PTY can raise an Errno::EIO because the child process unexpectedly goes away
|
71
|
+
rescue EOFError, Errno::EIO
|
72
|
+
# puts "eof!"
|
73
|
+
handle_data_for_stderr(output, cmd, buffer, stdin, true)
|
74
|
+
stderr.close
|
75
|
+
rescue => e
|
76
|
+
puts "closing PTY due to unexpected error: #{e.message}"
|
77
|
+
handle_data_for_stderr(output, cmd, buffer, stdin, true)
|
78
|
+
stderr.close
|
79
|
+
# puts e.message
|
80
|
+
# puts e.backtrace.join("\n")
|
81
|
+
end
|
82
|
+
end
|
83
|
+
# puts "end!"
|
84
|
+
end
|
85
|
+
stdout_thread.join
|
86
|
+
stderr_thread.join
|
87
|
+
cmd.exit_status = wait_thr.value.to_i
|
88
|
+
output.log_command_exit(cmd)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
|
93
|
+
# returns [complete lines, new buffer]
|
94
|
+
def split_buffer(buffer)
|
95
|
+
lines = buffer.split(/(\r\n)\r|\n/)
|
96
|
+
buffer = lines.pop
|
97
|
+
[lines, buffer]
|
98
|
+
end
|
99
|
+
|
100
|
+
def handle_data_for_stdout(output, cmd, buffer, stdin, is_blocked)
|
101
|
+
# we're blocked on reading, so let's process the buffer
|
102
|
+
lines, buffer = split_buffer(buffer)
|
103
|
+
lines.each do |line|
|
104
|
+
cmd.on_stdout(stdin, line)
|
105
|
+
output.log_command_data(cmd, :stdout, line)
|
106
|
+
end
|
107
|
+
if is_blocked && buffer
|
108
|
+
cmd.on_stdout(stdin, buffer)
|
109
|
+
output.log_command_data(cmd, :stdout, buffer)
|
110
|
+
buffer = ""
|
111
|
+
end
|
112
|
+
buffer || ""
|
113
|
+
end
|
114
|
+
|
115
|
+
|
116
|
+
def handle_data_for_stderr(output, cmd, buffer, stdin, is_blocked)
|
117
|
+
# we're blocked on reading, so let's process the buffer
|
118
|
+
lines, buffer = split_buffer(buffer)
|
119
|
+
lines.each do |line|
|
120
|
+
cmd.on_stderr(stdin, line)
|
121
|
+
output.log_command_data(cmd, :stderr, line)
|
122
|
+
end
|
123
|
+
if is_blocked && buffer
|
124
|
+
cmd.on_stderr(stdin, buffer)
|
125
|
+
output.log_command_data(cmd, :stderr, buffer)
|
126
|
+
buffer = ""
|
127
|
+
end
|
128
|
+
buffer || ""
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
require 'open3'
|
2
|
+
require 'pty'
|
3
|
+
require 'fileutils'
|
4
|
+
|
5
|
+
module SSHKit
|
6
|
+
|
7
|
+
module Backend
|
8
|
+
|
9
|
+
# this backend is compatible with sudo, even without the -S flag, e.g.: sudo ...
|
10
|
+
class LocalPty < Local
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def execute_command(cmd)
|
15
|
+
output.log_command_start(cmd.with_redaction)
|
16
|
+
cmd.started = Time.now
|
17
|
+
PTY.spawn(cmd.to_command) do |stdout, stdin, pid|
|
18
|
+
stdout_thread = Thread.new do
|
19
|
+
buffer = ""
|
20
|
+
partial_buffer = ""
|
21
|
+
while !stdout.closed?
|
22
|
+
# puts "9" * 80
|
23
|
+
begin
|
24
|
+
stdout.read_nonblock(4096, partial_buffer)
|
25
|
+
buffer << partial_buffer
|
26
|
+
# puts "nonblocking1. buffer=#{buffer} partial_buffer=#{partial_buffer}"
|
27
|
+
buffer = handle_data_for_stdout(output, cmd, buffer, stdin, false)
|
28
|
+
# puts "nonblocking2. buffer=#{buffer} partial_buffer=#{partial_buffer}"
|
29
|
+
rescue IO::WaitReadable, Errno::EAGAIN, Errno::EWOULDBLOCK
|
30
|
+
# puts "blocking. buffer=#{buffer} partial_buffer=#{partial_buffer}"
|
31
|
+
buffer = handle_data_for_stdout(output, cmd, buffer, stdin, true)
|
32
|
+
IO.select([stdout])
|
33
|
+
retry
|
34
|
+
|
35
|
+
# per https://stackoverflow.com/questions/1154846/continuously-read-from-stdout-of-external-process-in-ruby
|
36
|
+
# and https://stackoverflow.com/questions/10238298/ruby-on-linux-pty-goes-away-without-eof-raises-errnoeio
|
37
|
+
# the PTY can raise an Errno::EIO because the child process unexpectedly goes away
|
38
|
+
rescue EOFError, Errno::EIO
|
39
|
+
# puts "eof!"
|
40
|
+
handle_data_for_stdout(output, cmd, buffer, stdin, true)
|
41
|
+
stdout.close
|
42
|
+
rescue => e
|
43
|
+
puts "closing PTY due to unexpected error: #{e.message}"
|
44
|
+
handle_data_for_stdout(output, cmd, buffer, stdin, true)
|
45
|
+
stdout.close
|
46
|
+
# puts e.message
|
47
|
+
# puts e.backtrace.join("\n")
|
48
|
+
end
|
49
|
+
end
|
50
|
+
# puts "end!"
|
51
|
+
|
52
|
+
end
|
53
|
+
stdout_thread.join
|
54
|
+
_pid, status = Process.wait2(pid)
|
55
|
+
cmd.exit_status = status.exitstatus
|
56
|
+
output.log_command_exit(cmd)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# returns [complete lines, new buffer]
|
61
|
+
def split_buffer(buffer)
|
62
|
+
lines = buffer.split(/(\r\n)\r|\n/)
|
63
|
+
buffer = lines.pop
|
64
|
+
[lines, buffer]
|
65
|
+
end
|
66
|
+
|
67
|
+
def handle_data_for_stdout(output, cmd, buffer, stdin, is_blocked)
|
68
|
+
# we're blocked on reading, so let's process the buffer
|
69
|
+
lines, buffer = split_buffer(buffer)
|
70
|
+
lines.each do |line|
|
71
|
+
# puts "1" * 80
|
72
|
+
# puts line
|
73
|
+
cmd.on_stdout(stdin, line)
|
74
|
+
output.log_command_data(cmd, :stdout, line)
|
75
|
+
end
|
76
|
+
if is_blocked && buffer
|
77
|
+
# puts "2" * 80
|
78
|
+
# puts buffer
|
79
|
+
cmd.on_stdout(stdin, buffer)
|
80
|
+
output.log_command_data(cmd, :stdout, buffer)
|
81
|
+
buffer = ""
|
82
|
+
end
|
83
|
+
buffer || ""
|
84
|
+
end
|
85
|
+
|
86
|
+
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
require "random/formatter"
|
2
|
+
require "socket"
|
3
|
+
|
4
|
+
require_relative "runtime_environment"
|
5
|
+
require_relative "ops_file"
|
6
|
+
|
7
|
+
module OpsWalrus
|
8
|
+
class OperationRunner
|
9
|
+
attr_accessor :app
|
10
|
+
attr_accessor :entry_point_ops_file
|
11
|
+
|
12
|
+
def initialize(app, entry_point_ops_file)
|
13
|
+
@app = app
|
14
|
+
@entry_point_ops_file = entry_point_ops_file
|
15
|
+
# @entry_point_ops_file_in_bundle_dir = bundle!(@entry_point_ops_file)
|
16
|
+
end
|
17
|
+
|
18
|
+
# def bundle!(entry_point_ops_file)
|
19
|
+
# path_to_entry_point_ops_file_in_bundle_dir = @app.bundler.build_bundle_for_ops_file(entry_point_ops_file)
|
20
|
+
# OpsFile.new(app, path_to_entry_point_ops_file_in_bundle_dir)
|
21
|
+
# end
|
22
|
+
|
23
|
+
def sudo_user
|
24
|
+
@app.sudo_user
|
25
|
+
end
|
26
|
+
|
27
|
+
def sudo_password
|
28
|
+
@app.sudo_password
|
29
|
+
end
|
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, verbose: false)
|
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
|
40
|
+
else
|
41
|
+
memo[str_key] = str_value
|
42
|
+
end
|
43
|
+
memo
|
44
|
+
end
|
45
|
+
|
46
|
+
if verbose == 2
|
47
|
+
puts "Script:"
|
48
|
+
puts @entry_point_ops_file.script
|
49
|
+
end
|
50
|
+
|
51
|
+
result = begin
|
52
|
+
# update the bundle for the package
|
53
|
+
# @entry_point_ops_file.package_file&.bundle! # we don't do this here because when the script is run
|
54
|
+
# on a remote host, the package references may be invalid
|
55
|
+
# so we will be unable to bundle at runtime on the remote host
|
56
|
+
catch(:exit_now) do
|
57
|
+
ruby_script_return = RuntimeEnvironment.new(app).run(@entry_point_ops_file, params_hash)
|
58
|
+
Invocation::Success.new(ruby_script_return)
|
59
|
+
end
|
60
|
+
rescue SSHKit::Command::Failed => e
|
61
|
+
puts "[!] Command failed: #{e.message}"
|
62
|
+
rescue Error => e
|
63
|
+
$stderr.puts "Error: Ops script crashed."
|
64
|
+
$stderr.puts e.message
|
65
|
+
$stderr.puts e.backtrace.join("\n")
|
66
|
+
Invocation::Error.new(e)
|
67
|
+
rescue => e
|
68
|
+
$stderr.puts "Unhandled Error: Ops script crashed."
|
69
|
+
$stderr.puts e.class
|
70
|
+
$stderr.puts e.message
|
71
|
+
$stderr.puts e.backtrace.join("\n")
|
72
|
+
Invocation::Error.new(e)
|
73
|
+
end
|
74
|
+
|
75
|
+
if verbose && result.failure?
|
76
|
+
puts "Ops script error details:"
|
77
|
+
puts "Error: #{result.value}"
|
78
|
+
puts "Status code: #{result.exit_status}"
|
79
|
+
puts @entry_point_ops_file.script
|
80
|
+
end
|
81
|
+
|
82
|
+
result
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,235 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
require 'yaml'
|
3
|
+
require_relative 'ops_file_script'
|
4
|
+
|
5
|
+
module OpsWalrus
|
6
|
+
|
7
|
+
class OpsFile
|
8
|
+
attr_accessor :app
|
9
|
+
attr_accessor :ops_file_path
|
10
|
+
attr_accessor :yaml
|
11
|
+
attr_accessor :script
|
12
|
+
|
13
|
+
def initialize(app, ops_file_path)
|
14
|
+
@app = app
|
15
|
+
@ops_file_path = ops_file_path.to_pathname.expand_path
|
16
|
+
end
|
17
|
+
|
18
|
+
def hash
|
19
|
+
@ops_file_path.hash
|
20
|
+
end
|
21
|
+
|
22
|
+
def eql?(other)
|
23
|
+
self.class == other.class && self.hash == other.hash
|
24
|
+
end
|
25
|
+
|
26
|
+
def yaml
|
27
|
+
@yaml || (load_file && @yaml)
|
28
|
+
end
|
29
|
+
|
30
|
+
def script
|
31
|
+
@script || (load_file && @script)
|
32
|
+
end
|
33
|
+
|
34
|
+
def load_file
|
35
|
+
yaml, ruby_script = if @ops_file_path.exist?
|
36
|
+
parse(File.read(@ops_file_path))
|
37
|
+
end || ["", ""]
|
38
|
+
@yaml = YAML.load(yaml) || {} # post_invariant: @yaml is a Hash
|
39
|
+
@script = OpsFileScript.new(self, ruby_script)
|
40
|
+
end
|
41
|
+
|
42
|
+
def parse(script_string)
|
43
|
+
file_halves = script_string.split(/^\.\.\.$/, 2)
|
44
|
+
case file_halves.count
|
45
|
+
when 1
|
46
|
+
yaml, ruby_script = "", file_halves
|
47
|
+
when 2
|
48
|
+
yaml, ruby_script = *file_halves
|
49
|
+
else
|
50
|
+
raise Error, "Unexpected number of file sections: #{file_halves.inspect}"
|
51
|
+
end
|
52
|
+
[yaml, ruby_script]
|
53
|
+
end
|
54
|
+
|
55
|
+
def params
|
56
|
+
yaml["params"]
|
57
|
+
end
|
58
|
+
|
59
|
+
def output
|
60
|
+
yaml["output"]
|
61
|
+
end
|
62
|
+
|
63
|
+
def package_file
|
64
|
+
return @package_file if @package_file_evaluated
|
65
|
+
@package_file ||= begin
|
66
|
+
ops_file_path = @ops_file_path.realpath
|
67
|
+
ops_file_path.ascend.each do |path|
|
68
|
+
candidate_package_file_path = path.join("package.yaml")
|
69
|
+
return PackageFile.new(candidate_package_file_path) if candidate_package_file_path.exist?
|
70
|
+
end
|
71
|
+
nil
|
72
|
+
end
|
73
|
+
@package_file_evaluated = true
|
74
|
+
@package_file
|
75
|
+
end
|
76
|
+
|
77
|
+
# returns a map of the form: {"local_symbol" => import_reference, ... }
|
78
|
+
# import_reference is one of:
|
79
|
+
# 1. a package reference that matches one of the local package names in the dependencies captured in packages.yaml
|
80
|
+
# 2. a package reference that resolves to a relative path pointing at a package directory
|
81
|
+
# 3. a path that resolves to a directory containing ops files
|
82
|
+
# 4. a path that resolves to an ops file
|
83
|
+
def imports
|
84
|
+
@imports ||= begin
|
85
|
+
imports_hash = yaml["imports"] || {}
|
86
|
+
imports_hash.map do |local_name, yaml_import_reference|
|
87
|
+
local_name = local_name.to_s
|
88
|
+
import_reference = case yaml_import_reference
|
89
|
+
in String => import_str
|
90
|
+
case
|
91
|
+
when package_reference = package_file&.dependency(import_str) # package dependency reference
|
92
|
+
# in this context, import_str is the local package name documented in the package's dependencies
|
93
|
+
PackageDependencyReference.new(local_name, package_reference)
|
94
|
+
when import_str.to_pathname.exist? # path reference
|
95
|
+
path = import_str.to_pathname
|
96
|
+
case
|
97
|
+
when path.directory?
|
98
|
+
DirectoryReference.new(local_name, path.realpath)
|
99
|
+
when path.file? && path.extname.downcase == ".ops"
|
100
|
+
OpsFileReference.new(local_name, path.realpath)
|
101
|
+
else
|
102
|
+
raise Error, "Unknown import reference: #{local_name} -> #{import_str.inspect}"
|
103
|
+
end
|
104
|
+
end
|
105
|
+
# in Hash
|
106
|
+
# url = package_defn["url"]
|
107
|
+
# version = package_defn["version"]
|
108
|
+
# PackageReference.new(local_name, url, version&.to_s)
|
109
|
+
else
|
110
|
+
raise Error, "Unknown package reference: #{package_defn.inspect}"
|
111
|
+
end
|
112
|
+
[local_name, import_reference]
|
113
|
+
end.to_h
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def invoke(runtime_env, params_hash)
|
118
|
+
puts "invoking: #{ops_file_path}"
|
119
|
+
script.invoke(runtime_env, params_hash)
|
120
|
+
end
|
121
|
+
|
122
|
+
def build_params_hash(*args, **kwargs)
|
123
|
+
params_hash = {}
|
124
|
+
|
125
|
+
# if there is only one Hash object in args, treat that as the params hash
|
126
|
+
if args.size == 1 && args.first.is_a?(Hash)
|
127
|
+
tmp_params_hash = args.first.transform_keys(&:to_s)
|
128
|
+
params_hash.merge!(tmp_params_hash)
|
129
|
+
end
|
130
|
+
|
131
|
+
# if there are the same number of args as there are params, then treat each one as the corresponding param
|
132
|
+
if args.size == params.keys.size
|
133
|
+
tmp_params_hash = params.keys.zip(args).to_h.transform_keys(&:to_s)
|
134
|
+
params_hash.merge!(tmp_params_hash)
|
135
|
+
end
|
136
|
+
|
137
|
+
# merge in the kwargs as part of the params hash
|
138
|
+
params_hash.merge!(kwargs.transform_keys(&:to_s))
|
139
|
+
|
140
|
+
params_hash
|
141
|
+
end
|
142
|
+
|
143
|
+
# symbol table derived from explicit imports and the import for the private lib directory if it exists
|
144
|
+
# map of: "symbol_name" => ImportReference
|
145
|
+
def local_symbol_table
|
146
|
+
@local_symbol_table ||= begin
|
147
|
+
local_symbol_table = {}
|
148
|
+
|
149
|
+
local_symbol_table.merge(imports)
|
150
|
+
|
151
|
+
# this is the import for the private lib directory if it exists
|
152
|
+
if private_lib_dir.exist?
|
153
|
+
local_symbol_table[basename.to_s] = DirectoryReference.new(basename.to_s, private_lib_dir)
|
154
|
+
end
|
155
|
+
|
156
|
+
local_symbol_table
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
def resolve_import(symbol_name)
|
161
|
+
local_symbol_table[symbol_name]
|
162
|
+
end
|
163
|
+
|
164
|
+
# def namespace
|
165
|
+
# @namespace ||= begin
|
166
|
+
# ns = Namespace.new
|
167
|
+
# sibling_ops_files.each do |ops_file|
|
168
|
+
# ns.add(ops_file.basename, ops_file)
|
169
|
+
# end
|
170
|
+
# sibling_directories.each do |dir_path|
|
171
|
+
# dir_basename = dir_path.basename
|
172
|
+
# ns.add(dir_basename, ) unless resolve_symbol.resolve_symbol(dir_basename)
|
173
|
+
# end
|
174
|
+
# ns
|
175
|
+
# end
|
176
|
+
# end
|
177
|
+
|
178
|
+
# "/home/david/sync/projects/ops/ops/core/host/info.ops" => "/home/david/sync/projects/ops/ops/core/host"
|
179
|
+
def dirname
|
180
|
+
@ops_file_path.dirname
|
181
|
+
end
|
182
|
+
|
183
|
+
# "/home/david/sync/projects/ops/ops/core/host/info.ops" => "info"
|
184
|
+
def basename
|
185
|
+
@ops_file_path.basename(".ops")
|
186
|
+
end
|
187
|
+
|
188
|
+
# "/home/david/sync/projects/ops/ops/core/host/info.ops" => "/home/david/sync/projects/ops/ops/core/host/info"
|
189
|
+
def private_lib_dir
|
190
|
+
dirname.join(basename)
|
191
|
+
end
|
192
|
+
|
193
|
+
def sibling_ops_files
|
194
|
+
dirname.glob("*.ops").map {|path| OpsFile.new(app, path) }
|
195
|
+
end
|
196
|
+
|
197
|
+
# irb(main):073:0> OpsFile.new("/home/david/sync/projects/ops/example/davidinfra/test.ops").sibling_directories
|
198
|
+
# => [#<Pathname:/home/david/sync/projects/ops/example/davidinfra/caddy>, #<Pathname:/home/david/sync/projects/ops/example/davidinfra/prepare_host>, #<Pathname:/home/david/sync/projects/ops/example/davidinfra/roles>]
|
199
|
+
def sibling_directories
|
200
|
+
dirname.glob("*").select(&:directory?)
|
201
|
+
end
|
202
|
+
|
203
|
+
|
204
|
+
|
205
|
+
|
206
|
+
|
207
|
+
|
208
|
+
|
209
|
+
|
210
|
+
def supporting_library_include_dir_and_require_lib
|
211
|
+
if Dir.exist?(ops_file_helper_library_directory)
|
212
|
+
[ops_file_helper_library_directory, ops_file_helper_library_basename]
|
213
|
+
elsif File.exist?(ops_file_sibling_helper_library_file)
|
214
|
+
[dirname, ops_file_helper_library_basename]
|
215
|
+
else
|
216
|
+
[nil, nil]
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
def ops_file_helper_library_basename
|
221
|
+
basename.sub_ext(".rb")
|
222
|
+
end
|
223
|
+
|
224
|
+
# "/home/david/sync/projects/ops/ops/core/host/info.ops" => "/home/david/sync/projects/ops/ops/core/host/info"
|
225
|
+
def ops_file_helper_library_directory
|
226
|
+
File.join(dirname, basename)
|
227
|
+
end
|
228
|
+
|
229
|
+
# "/home/david/sync/projects/ops/ops/core/host/info.ops" => "/home/david/sync/projects/ops/ops/core/host/info.rb"
|
230
|
+
def ops_file_sibling_helper_library_file
|
231
|
+
"#{ops_file_helper_library_directory}.rb"
|
232
|
+
end
|
233
|
+
|
234
|
+
end
|
235
|
+
end
|