opswalrus 1.0.51 → 1.0.52

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: be0988f270b372d5ad10f8ed1abb90db64ce6d07c34a1f17854d24e397c80d67
4
- data.tar.gz: bdf5f694430d8178f3a4799d53cad884fc3a8adaecc74c14bf76b2a1a451a756
3
+ metadata.gz: b1ce678751d6d62f4a9b5e40eb6127c22e2e664c6641023ce8df4135609fb73e
4
+ data.tar.gz: a2747abc2711e77a47371f7c16d1b5104d70b10419b09cd3e992e892cea2d6ca
5
5
  SHA512:
6
- metadata.gz: 54cf7193022eb5fec71c71a4417668567e5134a7fcb5f72a31ed10ecee7d52765c3fbe5ecb33ee70052d5f5c3e17f60076352beec4a16397944493ccd25cf317
7
- data.tar.gz: 293e8f8948d3c0d7ec7f64ea40551519fe5f14be4317185603f49e33e74f5ec188e1121933b51c1234a261fbd56e18a2bfa6ecc78d74e313d4c996763715edf4
6
+ metadata.gz: 5ec4e2b6a547e394b0290a47dafa7cc13960a9c21cef7e1a7edc4d20900cd304a1e0c828025df68f8e7e2e6ce22e3f48882fe44efcaaf1a25e3c74d357981071
7
+ data.tar.gz: 386afc9a49856131615ff5c5e0c54a1934bf8458d3a77f7f2c6e1da6479343c54a0be8436f8a08beec4e89d6f1b9019e463bc96a5c169f639a7061838f8f240f
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- opswalrus (1.0.51)
4
+ opswalrus (1.0.52)
5
5
  bcrypt_pbkdf (~> 1.1)
6
6
  binding_of_caller (~> 1.0)
7
7
  citrus (~> 3.0)
@@ -0,0 +1,64 @@
1
+ params:
2
+ delay: integer? # default: 1 - 1 second delay before reboot
3
+ sync: boolean? # default: true - wait for the remote host to become available again before returning success/failure
4
+ timeout: integer? # default: 300 - 300 seconds (5 minutes)
5
+ ...
6
+
7
+ delay = params.delay.integer!(default: 1)
8
+ sync = params.sync.boolean!(default: true)
9
+ timeout = params.timeout.integer!(default: 300)
10
+
11
+ delay = 1 if delay < 1
12
+
13
+ ssh_noprep in: :sequence do
14
+ # ssh_noprep do
15
+
16
+ # survey of command options:
17
+ # sudo reboot
18
+ # sudo systemctl reboot
19
+
20
+ # desc "Rebooting #{to_s} (alias=#{self.alias})"
21
+ # reboot_success = sh? 'sudo /bin/sh -c "(sleep {{ delay }} && reboot) &"'.mustache
22
+ # puts reboot_success
23
+
24
+ # reconnect_time = nil
25
+ # reconnect_success = if sync
26
+ # desc "Waiting for #{to_s} (alias=#{self.alias}) to finish rebooting"
27
+ # initial_reconnect_delay = delay + 10
28
+ # sleep initial_reconnect_delay
29
+
30
+ # reconnected = false
31
+ # give_up = false
32
+ # t1 = Time.now
33
+ # until reconnected || give_up
34
+ # begin
35
+ # reconnected = sh?('true')
36
+ # # while trying to reconnect, we expect the following exceptions:
37
+ # # 1. Net::SSH::Disconnect < Net::SSH::Exception with message: "connection closed by remote host"
38
+ # # 2. Errno::ECONNRESET < SystemCallError with message: "Connection reset by peer"
39
+ # rescue Net::SSH::Disconnect, Errno::ECONNRESET => e
40
+ # # noop; we expect these while we're trying to reconnect
41
+ # rescue => e
42
+ # puts "#{e.class} < #{e.class.superclass}"
43
+ # puts e.message
44
+ # puts e.backtrace.take(5).join("\n")
45
+ # end
46
+
47
+ # wait_time_elapsed_in_seconds = Time.now - t1
48
+ # give_up = wait_time_elapsed_in_seconds > timeout
49
+ # sleep 5
50
+ # end
51
+ # reconnect_time = initial_reconnect_delay + (Time.now - t1)
52
+ # reconnected
53
+ # else
54
+ # false
55
+ # end
56
+
57
+ # {
58
+ # success: reboot_success && (sync == reconnect_success),
59
+ # rebooted: reboot_success,
60
+ # reconnected: reconnect_success,
61
+ # reboot_duration: reconnect_time
62
+ # }
63
+ reboot(delay: delay, sync: sync, timeout: timeout)
64
+ end
data/lib/opswalrus/app.rb CHANGED
@@ -243,6 +243,26 @@ module OpsWalrus
243
243
  1
244
244
  end
245
245
 
246
+ def reboot()
247
+ set_pwd(__FILE__.to_pathname.dirname)
248
+ shell_ops_file = OpsFile.new(self, __FILE__.to_pathname.dirname.join("_reboot.ops"))
249
+ op = OperationRunner.new(self, shell_ops_file)
250
+ result = op.run([], params_json_hash: @params)
251
+ puts "result class=#{result.class}"
252
+ exit_status = result.exit_status
253
+ stdout = JSON.pretty_generate(result.value)
254
+ output = if exit_status == 0
255
+ Style.green(stdout)
256
+ else
257
+ Style.red(stdout)
258
+ end
259
+ puts output
260
+ exit_status
261
+ rescue Error => e
262
+ puts "Error: #{e.message}"
263
+ 1
264
+ end
265
+
246
266
  # args is of the form ["github.com/davidkellis/my-package/sub-package1", "operation1", "arg1:val1", "arg2:val2", "arg3:val3"]
247
267
  # if the first argument is the path to a .ops file, then treat it as a local path, and add the containing package
248
268
  # to the load path
data/lib/opswalrus/cli.rb CHANGED
@@ -189,6 +189,37 @@ module OpsWalrus
189
189
  end
190
190
  end
191
191
 
192
+ desc "Reboot one or more remote hosts"
193
+ long_desc 'Reboot one or more remote hosts'
194
+ command :reboot do |c|
195
+ # dry run
196
+ c.switch :noop, desc: "Perform a dry run"
197
+ c.switch :dryrun, desc: "Perform a dry run"
198
+ c.switch :dry_run, desc: "Perform a dry run"
199
+
200
+ c.action do |global_options, options, args|
201
+ $app.set_log_level(global_options[:trace] && :trace || global_options[:debug] && :debug || global_options[:verbose] && :info || :warn)
202
+
203
+ hosts = global_options[:hosts]
204
+ $app.set_inventory_hosts(hosts)
205
+
206
+ tags = global_options[:tags]
207
+ $app.set_inventory_tags(tags)
208
+
209
+ id_files = global_options[:id]
210
+ id_files = OpsWalrus.env_specified_age_ids if id_files.empty?
211
+
212
+ $app.set_identity_files(id_files)
213
+
214
+ dry_run = [:noop, :dryrun, :dry_run].any? {|sym| global_options[sym] || options[sym] }
215
+ $app.dry_run! if dry_run
216
+
217
+ exit_status = $app.reboot()
218
+
219
+ exit_now!("error", exit_status) unless exit_status == 0
220
+ end
221
+ end
222
+
192
223
  desc 'Run an operation from a package'
193
224
  long_desc 'Run the specified operation found within the specified package'
194
225
  arg 'args', :multiple
@@ -143,6 +143,57 @@ module OpsWalrus
143
143
 
144
144
 
145
145
  module HostDSL
146
+ # delay: integer? # default: 1 - 1 second delay before reboot
147
+ # sync: boolean? # default: true - wait for the remote host to become available again before returning success/failure
148
+ # timeout: integer? # default: 300 - 300 seconds (5 minutes)
149
+ def reboot(delay: 1, sync: true, timeout: 300)
150
+ delay = 1 if delay < 1
151
+
152
+ desc "Rebooting #{to_s} (alias=#{self.alias})"
153
+ reboot_success = sh? 'sudo /bin/sh -c "(sleep {{ delay }} && reboot) &"'.mustache
154
+ puts reboot_success
155
+
156
+ reconnect_time = nil
157
+ reconnect_success = if sync
158
+ desc "Waiting for #{to_s} (alias=#{self.alias}) to finish rebooting"
159
+ initial_reconnect_delay = delay + 10
160
+ sleep initial_reconnect_delay
161
+
162
+ reconnected = false
163
+ give_up = false
164
+ t1 = Time.now
165
+ until reconnected || give_up
166
+ begin
167
+ reconnected = sh?('true')
168
+ # while trying to reconnect, we expect the following exceptions:
169
+ # 1. Net::SSH::Disconnect < Net::SSH::Exception with message: "connection closed by remote host"
170
+ # 2. Errno::ECONNRESET < SystemCallError with message: "Connection reset by peer"
171
+ rescue Net::SSH::Disconnect, Errno::ECONNRESET => e
172
+ # noop; we expect these while we're trying to reconnect
173
+ rescue => e
174
+ puts "#{e.class} < #{e.class.superclass}"
175
+ puts e.message
176
+ puts e.backtrace.take(5).join("\n")
177
+ end
178
+
179
+ wait_time_elapsed_in_seconds = Time.now - t1
180
+ give_up = wait_time_elapsed_in_seconds > timeout
181
+ sleep 5
182
+ end
183
+ reconnect_time = initial_reconnect_delay + (Time.now - t1)
184
+ reconnected
185
+ else
186
+ false
187
+ end
188
+
189
+ {
190
+ success: reboot_success && (sync == reconnect_success),
191
+ rebooted: reboot_success,
192
+ reconnected: reconnect_success,
193
+ reboot_duration: reconnect_time
194
+ }
195
+ end
196
+
146
197
  # runs the given command
147
198
  # returns the stdout from the command
148
199
  def sh(desc_or_cmd = nil, cmd = nil, input: nil, &block)
@@ -179,7 +230,7 @@ module OpsWalrus
179
230
  else
180
231
  offset = 3 # 3, because 1 references the stack frame corresponding to the caller of WalrusLang.eval,
181
232
  # 2 references the stack frame corresponding to the caller of shell!,
182
- # and 3 references the stack frame corresponding to teh caller of either sh/sh?/shell
233
+ # and 3 references the stack frame corresponding to the caller of either sh/sh?/shell
183
234
  WalrusLang.eval(cmd, offset)
184
235
  end
185
236
  else
@@ -55,6 +55,9 @@ module OpsWalrus
55
55
  sshkit_hosts = hosts.map(&:sshkit_host)
56
56
  sshkit_host_to_ops_host_map = sshkit_hosts.zip(hosts).to_h
57
57
  ops_file_script = local_host = self
58
+
59
+ results_lock = Thread::Mutex.new
60
+ results = {}
58
61
  # on sshkit_hosts do |sshkit_host|
59
62
  SSHKit::Coordinator.new(sshkit_hosts).each(in: kwargs[:in] || :parallel) do |sshkit_host|
60
63
  # in this context, self is an instance of one of the subclasses of SSHKit::Backend::Abstract, e.g. SSHKit::Backend::Netssh
@@ -73,6 +76,10 @@ module OpsWalrus
73
76
  # we run the block in the context of the host proxy object, s.t. `self` within the block evaluates to the host proxy object
74
77
  retval = host.instance_exec(local_host, &block) # local_host is passed as the argument to the block
75
78
 
79
+ results_lock.synchronize do
80
+ results[host] = retval
81
+ end
82
+
76
83
  retval
77
84
  rescue SSHKit::Command::Failed => e
78
85
  App.instance.error "[!] Command failed:"
@@ -102,6 +109,7 @@ module OpsWalrus
102
109
  end
103
110
  end # runtime_env.handle_input
104
111
  end # SSHKit::Coordinator
112
+ results
105
113
  end # def ssh
106
114
 
107
115
  def ssh(*args, **kwargs, &block)
@@ -111,6 +119,9 @@ module OpsWalrus
111
119
  sshkit_hosts = hosts.map(&:sshkit_host)
112
120
  sshkit_host_to_ops_host_map = sshkit_hosts.zip(hosts).to_h
113
121
  ops_file_script = local_host = self
122
+
123
+ results_lock = Thread::Mutex.new
124
+ results = {}
114
125
  # bootstrap_shell_script = BootstrapLinuxHostShellScript
115
126
  # on sshkit_hosts do |sshkit_host|
116
127
  SSHKit::Coordinator.new(sshkit_hosts).each(in: kwargs[:in] || :parallel) do |sshkit_host|
@@ -134,6 +145,10 @@ module OpsWalrus
134
145
  puts "Failed to bootstrap #{host}. Unable to run operation."
135
146
  end
136
147
 
148
+ results_lock.synchronize do
149
+ results[host] = retval
150
+ end
151
+
137
152
  retval
138
153
  rescue SSHKit::Command::Failed => e
139
154
  App.instance.error "[!] Command failed:"
@@ -163,6 +178,7 @@ module OpsWalrus
163
178
  end
164
179
  end # runtime_env.handle_input
165
180
  end # SSHKit::Coordinator
181
+ results
166
182
  end # def ssh
167
183
 
168
184
  def current_dir
@@ -261,7 +277,7 @@ module OpsWalrus
261
277
  else
262
278
  offset = 3 # 3, because 1 references the stack frame corresponding to the caller of WalrusLang.eval,
263
279
  # 2 references the stack frame corresponding to the caller of shell!,
264
- # and 3 references the stack frame corresponding to teh caller of either sh/sh?/shell
280
+ # and 3 references the stack frame corresponding to the caller of either sh/sh?/shell
265
281
  WalrusLang.eval(cmd, offset)
266
282
  end
267
283
  else
@@ -1,3 +1,3 @@
1
1
  module OpsWalrus
2
- VERSION = "1.0.51"
2
+ VERSION = "1.0.52"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: opswalrus
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.51
4
+ version: 1.0.52
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Ellis
@@ -185,6 +185,7 @@ files:
185
185
  - exe/ops
186
186
  - lib/opswalrus.rb
187
187
  - lib/opswalrus/_bootstrap.ops
188
+ - lib/opswalrus/_reboot.ops
188
189
  - lib/opswalrus/_shell.ops
189
190
  - lib/opswalrus/app.rb
190
191
  - lib/opswalrus/bootstrap.sh