opswalrus 1.0.0
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 +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
|