opswalrus 1.0.51 → 1.0.52

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: 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