opswalrus 1.0.8 → 1.0.10

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5cc78e2afa6b27ff1d9c88d43b8ddf5e805768c020b22a31d8c51b592fedbb9c
4
- data.tar.gz: 3f66378e5cfeb379aaed583ec180c14ecd5845bce81cf3e210fcac9bbe90b665
3
+ metadata.gz: 472ae5aafb306653a32149c08a481288237c6c524a3f72761e09ac9887a4c0f4
4
+ data.tar.gz: 449e723590784e10faf30bbbebb122d0ae00c6652e02559decc0cbbac0ba670f
5
5
  SHA512:
6
- metadata.gz: a0f2aed13949ccafd89f6ae5ab274db40e0c9904c7f1eee1a15e19c22eefc5f24a5d47a62df04bd58628eca6c4a003a0063de57d796f48ee28e14156be794a70
7
- data.tar.gz: ca70e69f2bba12706f983ec6ff78a1ab5a32e65c495bcb8e07b3f4cdccfac50ec3073b60085f7c42c885ec71e5d8bc8f369b0d51f6e86242c3efd4c9b68d1309
6
+ metadata.gz: 319c28870d5b1e91680e9aed29834df12c25dae3bbc6abeb0d54566f7d6a7662064dc8d54f7851d9d9ce7dc82d48c73a019abe58b384281e8b7c98ec5d252d99
7
+ data.tar.gz: 3e4a3d90ef1f30f29f4acb0acc8a34422e4f6f687f42fef3cba890e052b1d07848f38276b88d5c95f91be3d3e966c5b4a1e3f5c8e1faa8d31b1342dd8364c64e
data/CNAME ADDED
@@ -0,0 +1 @@
1
+ opswalrus.com
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- opswalrus (1.0.8)
4
+ opswalrus (1.0.10)
5
5
  bcrypt_pbkdf (~> 1.1)
6
6
  citrus (~> 3.0)
7
7
  ed25519 (~> 1.3)
data/README.md CHANGED
@@ -10,12 +10,18 @@ You have two options:
10
10
 
11
11
  ## Rubygems install
12
12
 
13
+ ```shell
14
+ gem install opswalrus
15
+
16
+ ops version
17
+ ```
18
+
13
19
  ## Docker install
14
20
 
15
21
  ```shell
16
22
  alias ops='docker run --rm -it -v $HOME/.ssh:/root/.ssh -v /var/run/docker.sock:/var/run/docker.sock -v ${PWD}/:/workdir ghcr.io/opswalrus/ops'
17
23
 
18
- ops --version
24
+ ops version
19
25
  ```
20
26
 
21
27
  # Examples
@@ -206,7 +212,7 @@ davidinfra
206
212
  │   ├── install
207
213
  │   │   └── debian.ops
208
214
  │   └── install.ops
209
- ├── hosts.yml
215
+ ├── hosts.yaml
210
216
  ├── main.ops
211
217
  ├── prepare_host
212
218
  │   ├── all.ops
@@ -225,9 +231,10 @@ davidinfra
225
231
  │   └── davidinfra
226
232
  │ ├── caddy
227
233
  │ │   ├── install
228
- │ │   │   └── debian.ops
229
- │ │   └── install.ops
230
- ├── hosts.yml
234
+ │ │   │   ├── debian.ops
235
+ │ │   │   └── install.ops
236
+ │   └── restart.ops
237
+ │ ├── hosts.yaml
231
238
  │ ├── main.ops
232
239
  │ ├── prepare_host
233
240
  │ │   ├── all.ops
@@ -238,8 +245,9 @@ davidinfra
238
245
  ├── caddy
239
246
  │   ├── install
240
247
  │   │   └── debian.ops
241
- │   └── install.ops
242
- ├── hosts.yml
248
+ │   │   └── install.ops
249
+ │   └── restart.ops
250
+ ├── hosts.yaml
243
251
  ├── main.ops
244
252
  ├── prepare_host
245
253
  │   ├── all.ops
@@ -255,17 +263,22 @@ The import and symbol resolution rules are as follows:
255
263
  Within the lexical scope of an ops file's ruby script, any ops files or subdirectories that are implicitly imported
256
264
  may be referenced by their name.
257
265
  For example:
258
- - main.ops may invoke caddy/install.ops with the expression `caddy.install(...)`
266
+ - main.ops may invoke caddy/install.ops with the expression `caddy.restart(...)`
259
267
  - 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 install.ops file may be referenced and invoked by other ops files, while the directory of the same name,
262
- and its contents, may only be referenced from within the ops file of the matching name. This allows the details of
263
- an operation to be encapsulated away and a public API be exposed through the ops file, while the details of the
264
- implementation are hidden away in the ops files within the directory.
268
+ 2. If there is an ops file and a directory that share the same name (with the exception of the .ops file extension), and
269
+ are both contained by the same parent directory, then only the .ops file may be referenced and invoked by other ops files.
270
+ The directory of the same name will be treated as a library directory and if there is a Ruby source file in the library
271
+ directory with the same name, then that ruby file will automatically be loaded. Other ruby files within the library directory
272
+ will be required/loaded as instructed by the entrypoint .rb file.
273
+ 3. If there is an ops file and a directory that share the same name (with the exception of the .ops file extension), and
274
+ the ops file is contained by the directory of the same name, then the ops file is considered to be the primary API
275
+ interface for a sub-module that is implemented by the ops files and ruby scripts contained within the directory.
276
+ Consequently, the directory containing the ops file of the same name (with the exception of the .ops file extension)
277
+ may be invoked as if it were the primary API interface ops file.
265
278
  For example:
266
- - main.ops may invoke `caddy.install(...)`, but it may not invoke `caddy.install.debian(...)`
267
- - install.ops may invoke `install.debian(...)`, and reference other files or subpackages within the caddy/install directory
268
- 3. Ops files may import packages or relative paths:
279
+ - main.ops may invoke `caddy.install(...)` as a shorthand syntax for `caddy.install.install(...)`
280
+ - install.ops may invoke `debian(...)`, and reference other files or subpackages within the caddy/install directory
281
+ 4. Ops files may import packages or relative paths:
269
282
  1. a package reference that matches one of the local package names in the dependencies captured in packages.yaml
270
283
  2. a package reference that resolves to a relative path pointing at a package directory
271
284
  3. a relative path that resolves to a directory containing ops files
data/lib/opswalrus/app.rb CHANGED
@@ -14,6 +14,7 @@ require_relative "hosts_file"
14
14
  require_relative "operation_runner"
15
15
  require_relative "bundler"
16
16
  require_relative "package_file"
17
+ require_relative "version"
17
18
 
18
19
 
19
20
  module OpsWalrus
@@ -40,6 +41,7 @@ module OpsWalrus
40
41
  @pwd = pwd.to_pathname
41
42
  @bundler = Bundler.new(@pwd)
42
43
  @local_hostname = "localhost"
44
+ @mode = :report # :report | :script
43
45
  end
44
46
 
45
47
  def to_s
@@ -50,12 +52,16 @@ module OpsWalrus
50
52
  "" # return empty string because we won't want anyone accidentally printing or inspecting @sudo_password
51
53
  end
52
54
 
53
- def emit_json_output!
54
- @emit_json_output = true
55
+ def script_mode!
56
+ @mode = :script
55
57
  end
56
58
 
57
- def emit_json_output?
58
- @emit_json_output
59
+ def report_mode?
60
+ @mode == :report
61
+ end
62
+
63
+ def script_mode?
64
+ @mode == :script
59
65
  end
60
66
 
61
67
  def set_local_hostname(hostname)
@@ -149,8 +155,12 @@ module OpsWalrus
149
155
  ops_file = set_pwd_to_ops_file_package_directory(ops_file)
150
156
  end
151
157
 
158
+ if @verbose
159
+ puts "Running: #{ops_file.ops_file_path}"
160
+ end
161
+
152
162
  op = OperationRunner.new(self, ops_file)
153
- result = op.run(operation_kv_args, params_json_hash: @params, verbose: @verbose)
163
+ result = op.run(operation_kv_args, params_json_hash: @params)
154
164
  exit_status = result.exit_status
155
165
 
156
166
  if @verbose
@@ -161,7 +171,7 @@ module OpsWalrus
161
171
  puts JSON.pretty_generate(result.value)
162
172
  end
163
173
 
164
- if emit_json_output?
174
+ if script_mode?
165
175
  puts JSON.pretty_generate(result.value)
166
176
  end
167
177
 
@@ -302,7 +312,7 @@ module OpsWalrus
302
312
  tags = @inventory_tag_selections + (tag_selection || [])
303
313
  tags.uniq!
304
314
 
305
- host_references = ["hosts.yml"] if (host_references.nil? || host_references.empty?) && File.exist?("hosts.yml")
315
+ host_references = ["hosts.yaml"] if (host_references.nil? || host_references.empty?) && File.exist?("hosts.yaml")
306
316
 
307
317
  hosts_files, host_strings = host_references.partition {|ref| File.exist?(ref) }
308
318
  hosts_files = hosts_files.map {|file_path| HostsFile.new(file_path) }
@@ -327,6 +337,10 @@ module OpsWalrus
327
337
  selected_hosts.sort_by(&:to_s)
328
338
  end
329
339
 
340
+ def print_version
341
+ puts VERSION
342
+ end
343
+
330
344
  def unzip(zip_bundle_file = nil, output_dir = nil)
331
345
  bundler.unzip(zip_bundle_file, output_dir)
332
346
  end
@@ -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
- # puts "*" * 80
19
- # puts "catchall exception handler:"
20
- # puts exception.message
21
- # puts exception.backtrace.join("\n")
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
 
@@ -27,12 +28,19 @@ module OpsWalrus
27
28
  desc 'Be verbose'
28
29
  switch [:v, :verbose]
29
30
 
30
- desc 'Debug'
31
+ desc 'Turn on debug mode'
31
32
  switch [:d, :debug]
32
33
 
33
34
  flag [:h, :hosts], multiple: true, desc: "Specify the hosts.yaml file"
34
35
  flag [:t, :tags], multiple: true, desc: "Specify a set of tags to filter the hosts by"
35
36
 
37
+ desc 'Print version'
38
+ command :version do |c|
39
+ c.action do |global_options, options, args|
40
+ $app.print_version
41
+ end
42
+ end
43
+
36
44
  desc 'Report on the host inventory'
37
45
  long_desc 'Report on the host inventory'
38
46
  command :inventory do |c|
@@ -53,7 +61,7 @@ module OpsWalrus
53
61
  c.flag [:u, :user], desc: "Specify the user that the operation will run as"
54
62
  c.switch :pass, desc: "Prompt for a sudo password"
55
63
  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 :json, desc: "Emit JSON output"
64
+ c.switch :script, desc: "Script mode"
57
65
 
58
66
  c.action do |global_options, options, args|
59
67
  hosts = global_options[:hosts] || []
@@ -81,17 +89,10 @@ module OpsWalrus
81
89
  $app.prompt_sudo_password
82
90
  end
83
91
 
84
- if options[:json]
85
- $app.emit_json_output!
92
+ if options[:script]
93
+ $app.script_mode!
86
94
  end
87
95
 
88
- # puts "verbose"
89
- # puts verbose.inspect
90
- # puts "user"
91
- # puts user.inspect
92
- # puts "args"
93
- # puts args.inspect
94
-
95
96
  exit_status = $app.run(args)
96
97
 
97
98
  exit_now!("error", exit_status) unless exit_status == 0
@@ -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