opswalrus 1.0.8 → 1.0.9
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CNAME +1 -0
- data/Gemfile.lock +1 -1
- data/README.md +22 -15
- data/lib/opswalrus/app.rb +16 -7
- data/lib/opswalrus/bootstrap.sh +5 -0
- data/lib/opswalrus/cli.rb +8 -14
- 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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 97d8c56ff040eeca8da82c47442a507247e952faec4077ea87293be581234c49
|
4
|
+
data.tar.gz: d9fb2905bcb900acaea9c85ee74af89b01b2200594d05d043da3b1decbb08ff8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3268ce43c666de5dd7923286d0c43dce2eac82b0632665b2edd2e77675beab4efb5444b49afccde0e8940f140480750776fe1618cc78c436ff89aa3c864e1820
|
7
|
+
data.tar.gz: bf341b62f93981af7f74c4f9195ac36b30eeaf53b525a64b96ebc9921c0484fb53c9f21304587b7adb3da3e9cb057c6cf23c9f265e89e31d3fe043575c77f103
|
data/CNAME
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
opswalrus.com
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -206,7 +206,7 @@ davidinfra
|
|
206
206
|
│ ├── install
|
207
207
|
│ │ └── debian.ops
|
208
208
|
│ └── install.ops
|
209
|
-
├── hosts.
|
209
|
+
├── hosts.yaml
|
210
210
|
├── main.ops
|
211
211
|
├── prepare_host
|
212
212
|
│ ├── all.ops
|
@@ -225,9 +225,10 @@ davidinfra
|
|
225
225
|
│ └── davidinfra
|
226
226
|
│ ├── caddy
|
227
227
|
│ │ ├── install
|
228
|
-
│ │ │
|
229
|
-
│ │ └── install.ops
|
230
|
-
│
|
228
|
+
│ │ │ ├── debian.ops
|
229
|
+
│ │ │ └── install.ops
|
230
|
+
│ │ └── restart.ops
|
231
|
+
│ ├── hosts.yaml
|
231
232
|
│ ├── main.ops
|
232
233
|
│ ├── prepare_host
|
233
234
|
│ │ ├── all.ops
|
@@ -238,8 +239,9 @@ davidinfra
|
|
238
239
|
├── caddy
|
239
240
|
│ ├── install
|
240
241
|
│ │ └── debian.ops
|
241
|
-
│ └── install.ops
|
242
|
-
|
242
|
+
│ │ └── install.ops
|
243
|
+
│ └── restart.ops
|
244
|
+
├── hosts.yaml
|
243
245
|
├── main.ops
|
244
246
|
├── prepare_host
|
245
247
|
│ ├── all.ops
|
@@ -255,17 +257,22 @@ The import and symbol resolution rules are as follows:
|
|
255
257
|
Within the lexical scope of an ops file's ruby script, any ops files or subdirectories that are implicitly imported
|
256
258
|
may be referenced by their name.
|
257
259
|
For example:
|
258
|
-
- main.ops may invoke caddy/install.ops with the expression `caddy.
|
260
|
+
- main.ops may invoke caddy/install.ops with the expression `caddy.restart(...)`
|
259
261
|
- all.ops may invoke hostname.ops with the expression `hostname(...)`
|
260
|
-
2. If there is an ops file and a directory that share the same name (with the exception of the .ops file extension),
|
261
|
-
then only the
|
262
|
-
|
263
|
-
|
264
|
-
|
262
|
+
2. If there is an ops file and a directory that share the same name (with the exception of the .ops file extension), and
|
263
|
+
are both contained by the same parent directory, then only the .ops file may be referenced and invoked by other ops files.
|
264
|
+
The directory of the same name will be treated as a library directory and if there is a Ruby source file in the library
|
265
|
+
directory with the same name, then that ruby file will automatically be loaded. Other ruby files within the library directory
|
266
|
+
will be required/loaded as instructed by the entrypoint .rb file.
|
267
|
+
3. If there is an ops file and a directory that share the same name (with the exception of the .ops file extension), and
|
268
|
+
the ops file is contained by the directory of the same name, then the ops file is considered to be the primary API
|
269
|
+
interface for a sub-module that is implemented by the ops files and ruby scripts contained within the directory.
|
270
|
+
Consequently, the directory containing the ops file of the same name (with the exception of the .ops file extension)
|
271
|
+
may be invoked as if it were the primary API interface ops file.
|
265
272
|
For example:
|
266
|
-
- main.ops may invoke `caddy.install(...)
|
267
|
-
- install.ops may invoke `
|
268
|
-
|
273
|
+
- main.ops may invoke `caddy.install(...)` as a shorthand syntax for `caddy.install.install(...)`
|
274
|
+
- install.ops may invoke `debian(...)`, and reference other files or subpackages within the caddy/install directory
|
275
|
+
4. Ops files may import packages or relative paths:
|
269
276
|
1. a package reference that matches one of the local package names in the dependencies captured in packages.yaml
|
270
277
|
2. a package reference that resolves to a relative path pointing at a package directory
|
271
278
|
3. a relative path that resolves to a directory containing ops files
|
data/lib/opswalrus/app.rb
CHANGED
@@ -40,6 +40,7 @@ module OpsWalrus
|
|
40
40
|
@pwd = pwd.to_pathname
|
41
41
|
@bundler = Bundler.new(@pwd)
|
42
42
|
@local_hostname = "localhost"
|
43
|
+
@mode = :report # :report | :script
|
43
44
|
end
|
44
45
|
|
45
46
|
def to_s
|
@@ -50,12 +51,16 @@ module OpsWalrus
|
|
50
51
|
"" # return empty string because we won't want anyone accidentally printing or inspecting @sudo_password
|
51
52
|
end
|
52
53
|
|
53
|
-
def
|
54
|
-
@
|
54
|
+
def script_mode!
|
55
|
+
@mode = :script
|
55
56
|
end
|
56
57
|
|
57
|
-
def
|
58
|
-
@
|
58
|
+
def report_mode?
|
59
|
+
@mode == :report
|
60
|
+
end
|
61
|
+
|
62
|
+
def script_mode?
|
63
|
+
@mode == :script
|
59
64
|
end
|
60
65
|
|
61
66
|
def set_local_hostname(hostname)
|
@@ -149,8 +154,12 @@ module OpsWalrus
|
|
149
154
|
ops_file = set_pwd_to_ops_file_package_directory(ops_file)
|
150
155
|
end
|
151
156
|
|
157
|
+
if @verbose
|
158
|
+
puts "Running: #{ops_file.ops_file_path}"
|
159
|
+
end
|
160
|
+
|
152
161
|
op = OperationRunner.new(self, ops_file)
|
153
|
-
result = op.run(operation_kv_args, params_json_hash: @params
|
162
|
+
result = op.run(operation_kv_args, params_json_hash: @params)
|
154
163
|
exit_status = result.exit_status
|
155
164
|
|
156
165
|
if @verbose
|
@@ -161,7 +170,7 @@ module OpsWalrus
|
|
161
170
|
puts JSON.pretty_generate(result.value)
|
162
171
|
end
|
163
172
|
|
164
|
-
if
|
173
|
+
if script_mode?
|
165
174
|
puts JSON.pretty_generate(result.value)
|
166
175
|
end
|
167
176
|
|
@@ -302,7 +311,7 @@ module OpsWalrus
|
|
302
311
|
tags = @inventory_tag_selections + (tag_selection || [])
|
303
312
|
tags.uniq!
|
304
313
|
|
305
|
-
host_references = ["hosts.
|
314
|
+
host_references = ["hosts.yaml"] if (host_references.nil? || host_references.empty?) && File.exist?("hosts.yaml")
|
306
315
|
|
307
316
|
hosts_files, host_strings = host_references.partition {|ref| File.exist?(ref) }
|
308
317
|
hosts_files = hosts_files.map {|file_path| HostsFile.new(file_path) }
|
data/lib/opswalrus/bootstrap.sh
CHANGED
@@ -7,10 +7,15 @@ if [ -x "$(command -v /home/linuxbrew/.linuxbrew/bin/brew)" ]; then
|
|
7
7
|
# exit early if ruby already exists
|
8
8
|
if [ -x "$(command -v ruby)" ]; then
|
9
9
|
echo 'Ruby is already installed.' >&2
|
10
|
+
|
11
|
+
# make sure the latest opswalrus gem is installed
|
12
|
+
gem install opswalrus
|
13
|
+
|
10
14
|
exit 0
|
11
15
|
fi
|
12
16
|
fi
|
13
17
|
|
18
|
+
# https://github.com/chef/os_release documents the contents of /etc/os-release from a bunch of distros
|
14
19
|
OS=$(cat /etc/os-release | grep "^ID=")
|
15
20
|
if echo $OS | grep -q 'ubuntu'; then
|
16
21
|
# update package list
|
data/lib/opswalrus/cli.rb
CHANGED
@@ -15,10 +15,11 @@ module OpsWalrus
|
|
15
15
|
|
16
16
|
# this is invoked on an unhandled exception or a call to exit_now!
|
17
17
|
on_error do |exception|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
18
|
+
next(false) if exception.is_a? GLI::CustomExit
|
19
|
+
|
20
|
+
puts "catchall exception handler:"
|
21
|
+
puts exception.message
|
22
|
+
puts exception.backtrace.join("\n")
|
22
23
|
false # disable built-in exception handling
|
23
24
|
end
|
24
25
|
|
@@ -53,7 +54,7 @@ module OpsWalrus
|
|
53
54
|
c.flag [:u, :user], desc: "Specify the user that the operation will run as"
|
54
55
|
c.switch :pass, desc: "Prompt for a sudo password"
|
55
56
|
c.flag [:p, :params], desc: "JSON string that represents the input parameters for the operation. The JSON string must conform to the params schema for the operation."
|
56
|
-
c.switch :
|
57
|
+
c.switch :script, desc: "Script mode"
|
57
58
|
|
58
59
|
c.action do |global_options, options, args|
|
59
60
|
hosts = global_options[:hosts] || []
|
@@ -81,17 +82,10 @@ module OpsWalrus
|
|
81
82
|
$app.prompt_sudo_password
|
82
83
|
end
|
83
84
|
|
84
|
-
if options[:
|
85
|
-
$app.
|
85
|
+
if options[:script]
|
86
|
+
$app.script_mode!
|
86
87
|
end
|
87
88
|
|
88
|
-
# puts "verbose"
|
89
|
-
# puts verbose.inspect
|
90
|
-
# puts "user"
|
91
|
-
# puts user.inspect
|
92
|
-
# puts "args"
|
93
|
-
# puts args.inspect
|
94
|
-
|
95
89
|
exit_status = $app.run(args)
|
96
90
|
|
97
91
|
exit_now!("error", exit_status) unless exit_status == 0
|
data/lib/opswalrus/host.rb
CHANGED
@@ -5,71 +5,6 @@ require_relative "interaction_handlers"
|
|
5
5
|
|
6
6
|
module OpsWalrus
|
7
7
|
|
8
|
-
module HostDSL
|
9
|
-
# returns the stdout from the command
|
10
|
-
def sh(desc_or_cmd = nil, cmd = nil, input: nil, &block)
|
11
|
-
out, err, status = *shell!(desc_or_cmd, cmd, block, input: input)
|
12
|
-
out
|
13
|
-
end
|
14
|
-
|
15
|
-
# returns the tuple: [stdout, stderr, exit_status]
|
16
|
-
def shell(desc_or_cmd = nil, cmd = nil, input: nil, &block)
|
17
|
-
shell!(desc_or_cmd, cmd, block, input: input)
|
18
|
-
end
|
19
|
-
|
20
|
-
# returns the tuple: [stdout, stderr, exit_status]
|
21
|
-
def shell!(desc_or_cmd = nil, cmd = nil, block = nil, input: nil)
|
22
|
-
# description = nil
|
23
|
-
|
24
|
-
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
|
25
|
-
|
26
|
-
description = desc_or_cmd if cmd || block
|
27
|
-
cmd = block.call if block
|
28
|
-
cmd ||= desc_or_cmd
|
29
|
-
|
30
|
-
cmd = WalrusLang.render(cmd, block.binding) if block && cmd =~ /{{.*}}/
|
31
|
-
|
32
|
-
#cmd = Shellwords.escape(cmd)
|
33
|
-
|
34
|
-
if self.alias
|
35
|
-
print "[#{self.alias} | #{host}] "
|
36
|
-
else
|
37
|
-
print "[#{host}] "
|
38
|
-
end
|
39
|
-
print "#{description}: " if description
|
40
|
-
puts cmd
|
41
|
-
|
42
|
-
return unless cmd && !cmd.strip.empty?
|
43
|
-
|
44
|
-
# puts "shell: #{cmd}"
|
45
|
-
# puts "shell: #{cmd.inspect}"
|
46
|
-
# puts "sudo_password: #{sudo_password}"
|
47
|
-
|
48
|
-
sshkit_cmd = execute_cmd(cmd, input: input)
|
49
|
-
|
50
|
-
[sshkit_cmd.full_stdout, sshkit_cmd.full_stderr, sshkit_cmd.exit_status]
|
51
|
-
end
|
52
|
-
|
53
|
-
# def init_brew
|
54
|
-
# execute('eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"')
|
55
|
-
# end
|
56
|
-
|
57
|
-
# runs the specified ops command with the specified command arguments
|
58
|
-
def run_ops(command, command_arguments, in_bundle_root_dir: true, verbose: false)
|
59
|
-
# e.g. /home/linuxbrew/.linuxbrew/bin/gem exec -g opswalrus ops bundle unzip tmpops.zip
|
60
|
-
# e.g. /home/linuxbrew/.linuxbrew/bin/gem exec -g opswalrus ops run echo.ops args:foo args:bar
|
61
|
-
|
62
|
-
cmd = "/home/linuxbrew/.linuxbrew/bin/gem exec -g opswalrus ops"
|
63
|
-
cmd << " -v" if verbose
|
64
|
-
cmd << " #{command.to_s}"
|
65
|
-
cmd << " #{@tmp_bundle_root_dir}" if in_bundle_root_dir
|
66
|
-
cmd << " #{command_arguments}" unless command_arguments.empty?
|
67
|
-
|
68
|
-
shell!(cmd)
|
69
|
-
end
|
70
|
-
|
71
|
-
end
|
72
|
-
|
73
8
|
class HostProxyOpsFileInvocationBuilder
|
74
9
|
def initialize(host_proxy, is_invocation_a_call_to_package_in_bundle_dir = false)
|
75
10
|
@host_proxy = host_proxy
|
@@ -116,6 +51,53 @@ module OpsWalrus
|
|
116
51
|
|
117
52
|
# the subclasses of HostProxy will define methods that handle method dispatch via HostProxyOpsFileInvocationBuilder objects
|
118
53
|
class HostProxy
|
54
|
+
def self.define_host_proxy_class(ops_file)
|
55
|
+
klass = Class.new(HostProxy)
|
56
|
+
|
57
|
+
methods_defined = Set.new
|
58
|
+
|
59
|
+
# define methods for every import in the script
|
60
|
+
ops_file.local_symbol_table.each do |symbol_name, import_reference|
|
61
|
+
unless methods_defined.include? symbol_name
|
62
|
+
# puts "1. defining: #{symbol_name}(...)"
|
63
|
+
klass.define_method(symbol_name) do |*args, **kwargs, &block|
|
64
|
+
invocation_builder = case import_reference
|
65
|
+
# we know we're dealing with a package dependency reference, so we want to run an ops file contained within the bundle directory,
|
66
|
+
# therefore, we want to reference the specified ops file with respect to the bundle dir
|
67
|
+
when PackageDependencyReference
|
68
|
+
HostProxyOpsFileInvocationBuilder.new(self, true)
|
69
|
+
|
70
|
+
# we know we're dealing with a directory reference or OpsFile reference outside of the bundle dir, so we want to reference
|
71
|
+
# the specified ops file with respect to the root directory, and not with respect to the bundle dir
|
72
|
+
when DirectoryReference, OpsFileReference
|
73
|
+
HostProxyOpsFileInvocationBuilder.new(self, false)
|
74
|
+
end
|
75
|
+
|
76
|
+
invocation_builder.send(symbol_name, *args, **kwargs, &block)
|
77
|
+
end
|
78
|
+
methods_defined << symbol_name
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# define methods for every Namespace or OpsFile within the namespace that the OpsFile resides within
|
83
|
+
sibling_symbol_table = Set.new
|
84
|
+
sibling_symbol_table |= ops_file.dirname.glob("*.ops").map {|ops_file_path| ops_file_path.basename(".ops").to_s } # OpsFiles
|
85
|
+
sibling_symbol_table |= ops_file.dirname.glob("*").select(&:directory?).map {|dir_path| dir_path.basename.to_s } # Namespaces
|
86
|
+
sibling_symbol_table.each do |symbol_name|
|
87
|
+
unless methods_defined.include? symbol_name
|
88
|
+
# puts "2. defining: #{symbol_name}(...)"
|
89
|
+
klass.define_method(symbol_name) do |*args, **kwargs, &block|
|
90
|
+
invocation_builder = HostProxyOpsFileInvocationBuilder.new(self, false)
|
91
|
+
invocation_builder.invoke(symbol_name, *args, **kwargs, &block)
|
92
|
+
end
|
93
|
+
methods_defined << symbol_name
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
klass
|
98
|
+
end
|
99
|
+
|
100
|
+
|
119
101
|
attr_accessor :_host
|
120
102
|
|
121
103
|
def initialize(host)
|
@@ -129,6 +111,74 @@ module OpsWalrus
|
|
129
111
|
end
|
130
112
|
end
|
131
113
|
|
114
|
+
|
115
|
+
module HostDSL
|
116
|
+
# returns the stdout from the command
|
117
|
+
def sh(desc_or_cmd = nil, cmd = nil, input: nil, &block)
|
118
|
+
out, err, status = *shell!(desc_or_cmd, cmd, block, input: input)
|
119
|
+
out
|
120
|
+
end
|
121
|
+
|
122
|
+
# returns the tuple: [stdout, stderr, exit_status]
|
123
|
+
def shell(desc_or_cmd = nil, cmd = nil, input: nil, &block)
|
124
|
+
shell!(desc_or_cmd, cmd, block, input: input)
|
125
|
+
end
|
126
|
+
|
127
|
+
# returns the tuple: [stdout, stderr, exit_status]
|
128
|
+
def shell!(desc_or_cmd = nil, cmd = nil, block = nil, input: nil)
|
129
|
+
# description = nil
|
130
|
+
|
131
|
+
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
|
132
|
+
|
133
|
+
description = desc_or_cmd if cmd || block
|
134
|
+
cmd = block.call if block
|
135
|
+
cmd ||= desc_or_cmd
|
136
|
+
|
137
|
+
cmd = WalrusLang.render(cmd, block.binding) if block && cmd =~ /{{.*}}/
|
138
|
+
|
139
|
+
#cmd = Shellwords.escape(cmd)
|
140
|
+
|
141
|
+
if App.instance.report_mode?
|
142
|
+
if self.alias
|
143
|
+
print "[#{self.alias} | #{host}] "
|
144
|
+
else
|
145
|
+
print "[#{host}] "
|
146
|
+
end
|
147
|
+
print "#{description}: " if description
|
148
|
+
puts cmd
|
149
|
+
end
|
150
|
+
|
151
|
+
return unless cmd && !cmd.strip.empty?
|
152
|
+
|
153
|
+
# puts "shell: #{cmd}"
|
154
|
+
# puts "shell: #{cmd.inspect}"
|
155
|
+
# puts "sudo_password: #{sudo_password}"
|
156
|
+
|
157
|
+
sshkit_cmd = execute_cmd(cmd, input: input)
|
158
|
+
|
159
|
+
[sshkit_cmd.full_stdout, sshkit_cmd.full_stderr, sshkit_cmd.exit_status]
|
160
|
+
end
|
161
|
+
|
162
|
+
# def init_brew
|
163
|
+
# execute('eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"')
|
164
|
+
# end
|
165
|
+
|
166
|
+
# runs the specified ops command with the specified command arguments
|
167
|
+
def run_ops(command, command_arguments, in_bundle_root_dir: true, verbose: false)
|
168
|
+
# e.g. /home/linuxbrew/.linuxbrew/bin/gem exec -g opswalrus ops bundle unzip tmpops.zip
|
169
|
+
# e.g. /home/linuxbrew/.linuxbrew/bin/gem exec -g opswalrus ops run echo.ops args:foo args:bar
|
170
|
+
|
171
|
+
cmd = "/home/linuxbrew/.linuxbrew/bin/gem exec -g opswalrus ops"
|
172
|
+
cmd << " -v" if verbose
|
173
|
+
cmd << " #{command.to_s}"
|
174
|
+
cmd << " #{@tmp_bundle_root_dir}" if in_bundle_root_dir
|
175
|
+
cmd << " #{command_arguments}" unless command_arguments.empty?
|
176
|
+
|
177
|
+
shell!(cmd)
|
178
|
+
end
|
179
|
+
|
180
|
+
end
|
181
|
+
|
132
182
|
class Host
|
133
183
|
include HostDSL
|
134
184
|
|
@@ -222,18 +272,12 @@ module OpsWalrus
|
|
222
272
|
end
|
223
273
|
|
224
274
|
def execute(*args, input: nil)
|
225
|
-
# puts "interaction handler responds with: #{ssh_password}"
|
226
|
-
# @sshkit_backend.capture(*args, interaction_handler: SudoPasswordMapper.new(ssh_password).interaction_handler, verbosity: :info)
|
227
|
-
# @sshkit_backend.capture(*args, interaction_handler: SudoPromptInteractionHandler.new, verbosity: :info)
|
228
|
-
|
229
275
|
@runtime_env.handle_input(input, ssh_password) do |interaction_handler|
|
230
276
|
@sshkit_backend.capture(*args, interaction_handler: interaction_handler, verbosity: :info)
|
231
277
|
end
|
232
|
-
|
233
278
|
end
|
234
279
|
|
235
280
|
def execute_cmd(*args, input: nil)
|
236
|
-
# @sshkit_backend.execute_cmd(*args, interaction_handler: SudoPasswordMapper.new(ssh_password).interaction_handler, verbosity: :info)
|
237
281
|
@runtime_env.handle_input(input, ssh_password) do |interaction_handler|
|
238
282
|
@sshkit_backend.execute_cmd(*args, interaction_handler: interaction_handler, verbosity: :info)
|
239
283
|
end
|