opswalrus 1.0.7 → 1.0.9

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: b0c1c00f88ff00a4f7eb735a634ea35e1e8818b44fffdccea7a2347bac78e199
4
- data.tar.gz: fe54254076c753cfeaf26906fa0f7475bd11807c0ac70d4067da03653b0f774c
3
+ metadata.gz: 97d8c56ff040eeca8da82c47442a507247e952faec4077ea87293be581234c49
4
+ data.tar.gz: d9fb2905bcb900acaea9c85ee74af89b01b2200594d05d043da3b1decbb08ff8
5
5
  SHA512:
6
- metadata.gz: 28244a51a465941731663e9db3901049d36d1fa385a16b4c5ed9254c31fa62ca44012c2d776ef5b6d1d06906c012802b1352e166d72d28b3ec0aa6f9e27073f5
7
- data.tar.gz: 39ccb70ca9dc5b2ecf67b1ab29adb7ccc79cb43ad57d256be0deb68b49909bc4c5cd4ddc2871e68aeeb374ed3d0202c365094536e7933f61c3ecf700a2178076
6
+ metadata.gz: 3268ce43c666de5dd7923286d0c43dce2eac82b0632665b2edd2e77675beab4efb5444b49afccde0e8940f140480750776fe1618cc78c436ff89aa3c864e1820
7
+ data.tar.gz: bf341b62f93981af7f74c4f9195ac36b30eeaf53b525a64b96ebc9921c0484fb53c9f21304587b7adb3da3e9cb057c6cf23c9f265e89e31d3fe043575c77f103
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.7)
4
+ opswalrus (1.0.9)
5
5
  bcrypt_pbkdf (~> 1.1)
6
6
  citrus (~> 3.0)
7
7
  ed25519 (~> 1.3)
data/README.md CHANGED
@@ -206,7 +206,7 @@ davidinfra
206
206
  │   ├── install
207
207
  │   │   └── debian.ops
208
208
  │   └── install.ops
209
- ├── hosts.yml
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
- │ │   │   └── debian.ops
229
- │ │   └── install.ops
230
- ├── hosts.yml
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
- ├── hosts.yml
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.install(...)`
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 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.
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(...)`, 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:
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/build.ops CHANGED
@@ -3,8 +3,22 @@ params:
3
3
 
4
4
  imports:
5
5
  core: "https://github.com/opswalrus/core.git"
6
+
6
7
  ...
7
8
 
9
+ # when you run this script, it should do something like:
10
+ # ~/sync/projects/ops/opswalrus on  main via 💎 v3.2.2
11
+ # ❯ ../ops.sh run build.ops version:1.0.7
12
+ # Write version.rb for version 1.0.7
13
+ # [localhost] Build gem: gem build opswalrus.gemspec
14
+ # [localhost] Check whether Bitwarden is locked or not: bw status
15
+ # [localhost] Get Rubygems OTP: bw get totp Rubygems
16
+ # [localhost] Push gem: gem push opswalrus-1.0.7.gem
17
+ # [localhost] Build docker image: docker build -t opswalrus/ops:1.0.7 .
18
+
19
+ # ~/sync/projects/ops/opswalrus on  main via 💎 v3.2.2 took 44s
20
+
21
+
8
22
  version = params.version
9
23
 
10
24
  exit 1, "version parameter must be specified" unless version
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 emit_json_output!
54
- @emit_json_output = true
54
+ def script_mode!
55
+ @mode = :script
55
56
  end
56
57
 
57
- def emit_json_output?
58
- @emit_json_output
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, verbose: @verbose)
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 emit_json_output?
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.yml"] if (host_references.nil? || host_references.empty?) && File.exist?("hosts.yml")
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) }
@@ -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
 
@@ -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 :json, desc: "Emit JSON output"
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[:json]
85
- $app.emit_json_output!
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
@@ -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