opswalrus 1.0.97 → 1.0.98

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: ccf8e333c2a08306ce5afc14a557c69fe01d8681ffeb5e3957b80b09b68bcafa
4
- data.tar.gz: b8d586b564c368d7e055746c515c7ea5883765b4a5ca3a9d389f89cd04e06e2c
3
+ metadata.gz: b22cc033fad90a7f21523cd1ff33ffcb8f5903605a826ac0abc0b14b17e5425b
4
+ data.tar.gz: 73861ce2fb52cebc7d39931076e29bf1a388d23d5724c0f896d18efbf8d34e3f
5
5
  SHA512:
6
- metadata.gz: 2d7a74e6e3872014452eaa8a19203e1da2b81c9af00c9a6e5f0499caff2a3309f18af2122e00097e7276d80bcd89fb937ab3de7e3b01373187611387e0e23b38
7
- data.tar.gz: 5b24d54d03bbb5f9bdd03f2d5ddd1644f94819e54803b6d51f5812f5a8758c31f24a58b273c1d42600f00788c0b4e5777e8e3204a8c3a061e10e532c5a150ac9
6
+ metadata.gz: c557b19ce11df00720f9513e23b3bd49116641a3b2f2c7afc671258229bb7d4208d23ee40c15e614b2d17c214c0144300c5dafa4a93507cb77c7131aeea565b1
7
+ data.tar.gz: 0c4ea42f15bba85132c5dbc3f0a44c346ceb0c366b3db539c8949e9d527a67c06fb4303a413bcf882b1164b505e1f6cbdc5ad4db5bb508ff90319fc349937735
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- opswalrus (1.0.97)
4
+ opswalrus (1.0.98)
5
5
  activesupport (~> 7.0)
6
6
  bcrypt_pbkdf (~> 1.1)
7
7
  binding_of_caller (~> 1.0)
@@ -15,11 +15,13 @@ PATH
15
15
  semantic_logger (~> 4.13)
16
16
  sshkit (~> 1.21)
17
17
  tty-editor (~> 0.7)
18
+ tty-exit (~> 0.1)
19
+ tty-option (~> 0.3)
18
20
 
19
21
  GEM
20
22
  remote: https://rubygems.org/
21
23
  specs:
22
- activesupport (7.1.2)
24
+ activesupport (7.1.3)
23
25
  base64
24
26
  bigdecimal
25
27
  concurrent-ruby (~> 1.0, >= 1.0.2)
@@ -29,33 +31,33 @@ GEM
29
31
  minitest (>= 5.1)
30
32
  mutex_m
31
33
  tzinfo (~> 2.0)
32
- addressable (2.8.5)
34
+ addressable (2.8.6)
33
35
  public_suffix (>= 2.0.2, < 6.0)
34
36
  ast (2.4.2)
35
37
  backport (1.2.0)
36
38
  base64 (0.2.0)
37
39
  bcrypt_pbkdf (1.1.0)
38
40
  benchmark (0.3.0)
39
- bigdecimal (3.1.4)
41
+ bigdecimal (3.1.6)
40
42
  binding_of_caller (1.0.0)
41
43
  debug_inspector (>= 0.0.1)
42
44
  citrus (3.0.2)
43
- concurrent-ruby (1.2.2)
45
+ concurrent-ruby (1.2.3)
44
46
  connection_pool (2.4.1)
45
- debug_inspector (1.1.0)
47
+ debug_inspector (1.2.0)
46
48
  diff-lcs (1.5.0)
47
49
  drb (2.2.0)
48
50
  ruby2_keywords
49
51
  e2mmap (0.1.0)
50
52
  ed25519 (1.3.0)
51
- git (1.18.0)
53
+ git (1.19.1)
52
54
  addressable (~> 2.8)
53
55
  rchardet (~> 1.8)
54
56
  gli (2.21.1)
55
57
  i18n (1.14.1)
56
58
  concurrent-ruby (~> 1.0)
57
59
  jaro_winkler (1.5.6)
58
- json (2.6.3)
60
+ json (2.7.1)
59
61
  kleene (0.10.0)
60
62
  activesupport (~> 7.1)
61
63
  regexp_parser (~> 2.8)
@@ -64,26 +66,28 @@ GEM
64
66
  kramdown-parser-gfm (1.1.0)
65
67
  kramdown (~> 2.0)
66
68
  language_server-protocol (3.17.0.3)
67
- minitest (5.20.0)
69
+ minitest (5.21.2)
68
70
  mutex_m (0.2.0)
69
71
  net-scp (4.0.0)
70
72
  net-ssh (>= 2.6.5, < 8.0.0)
71
- net-ssh (7.2.0)
72
- nokogiri (1.15.4-x86_64-linux)
73
+ net-sftp (4.0.0)
74
+ net-ssh (>= 5.0.0, < 8.0.0)
75
+ net-ssh (7.2.1)
76
+ nokogiri (1.16.0-x86_64-linux)
73
77
  racc (~> 1.4)
74
- parallel (1.23.0)
75
- parser (3.2.2.4)
78
+ parallel (1.24.0)
79
+ parser (3.3.0.4)
76
80
  ast (~> 2.4.1)
77
81
  racc
78
82
  pastel (0.8.0)
79
83
  tty-color (~> 0.5)
80
- public_suffix (5.0.3)
84
+ public_suffix (5.0.4)
81
85
  racc (1.7.3)
82
86
  rainbow (3.1.1)
83
87
  rake (13.1.0)
84
88
  rbs (2.8.4)
85
89
  rchardet (1.8.0)
86
- regexp_parser (2.8.2)
90
+ regexp_parser (2.9.0)
87
91
  reverse_markdown (2.1.1)
88
92
  nokogiri
89
93
  rexml (3.2.6)
@@ -100,15 +104,15 @@ GEM
100
104
  diff-lcs (>= 1.2.0, < 2.0)
101
105
  rspec-support (~> 3.12.0)
102
106
  rspec-support (3.12.1)
103
- rubocop (1.57.2)
107
+ rubocop (1.60.1)
104
108
  json (~> 2.3)
105
109
  language_server-protocol (>= 3.17.0)
106
110
  parallel (~> 1.10)
107
- parser (>= 3.2.2.4)
111
+ parser (>= 3.3.0.2)
108
112
  rainbow (>= 2.2.2, < 4.0)
109
113
  regexp_parser (>= 1.8, < 3.0)
110
114
  rexml (>= 3.2.5, < 4.0)
111
- rubocop-ast (>= 1.28.1, < 2.0)
115
+ rubocop-ast (>= 1.30.0, < 2.0)
112
116
  ruby-progressbar (~> 1.7)
113
117
  unicode-display_width (>= 2.4.0, < 3.0)
114
118
  rubocop-ast (1.30.0)
@@ -118,7 +122,7 @@ GEM
118
122
  rubyzip (2.3.2)
119
123
  semantic_logger (4.15.0)
120
124
  concurrent-ruby (~> 1.0)
121
- solargraph (0.49.0)
125
+ solargraph (0.50.0)
122
126
  backport (~> 1.2)
123
127
  benchmark
124
128
  bundler (~> 2.0)
@@ -134,8 +138,10 @@ GEM
134
138
  thor (~> 1.0)
135
139
  tilt (~> 2.0)
136
140
  yard (~> 0.9, >= 0.9.24)
137
- sshkit (1.21.5)
141
+ sshkit (1.22.0)
142
+ mutex_m
138
143
  net-scp (>= 1.1.2)
144
+ net-sftp (>= 2.1.2)
139
145
  net-ssh (>= 2.8.0)
140
146
  thor (1.3.0)
141
147
  tilt (2.3.0)
@@ -143,6 +149,8 @@ GEM
143
149
  tty-cursor (0.7.1)
144
150
  tty-editor (0.7.0)
145
151
  tty-prompt (~> 0.22)
152
+ tty-exit (0.1.0)
153
+ tty-option (0.3.0)
146
154
  tty-prompt (0.23.1)
147
155
  pastel (~> 0.8)
148
156
  tty-reader (~> 0.8)
@@ -150,7 +158,7 @@ GEM
150
158
  tty-cursor (~> 0.7)
151
159
  tty-screen (~> 0.8)
152
160
  wisper (~> 2.0)
153
- tty-screen (0.8.1)
161
+ tty-screen (0.8.2)
154
162
  tzinfo (2.0.6)
155
163
  concurrent-ruby (~> 1.0)
156
164
  unicode-display_width (2.5.0)
data/exe/ops2 ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'opswalrus'
4
+
5
+ OpsWalrus::Cli2.run(ARGV)
@@ -11,55 +11,6 @@ timeout = params.timeout.integer!(default: 300)
11
11
  delay = 1 if delay < 1
12
12
 
13
13
  host_to_value_map = 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
14
  reboot(delay: delay, sync: sync, timeout: timeout)
64
15
  end
65
16
 
@@ -1,21 +1,19 @@
1
1
  #!/usr/bin/env bash
2
2
 
3
- export PATH="$HOME/.local/share/rtx/bin:$PATH" # this is key for activating rtx without running `eval "$($RTX activate bash)"`
4
- # eval "$(rtx activate bash)"
5
- RTX="$HOME/.local/share/rtx/bin/rtx"
6
- rtx_init() { eval "$($RTX activate bash)"; }
7
- # RTX_RUBY="$HOME/.local/share/rtx/bin/rtx x ruby -- ruby"
8
- # RTX_GEM="$HOME/.local/share/rtx/bin/rtx x ruby -- gem"
9
- RTX_RUBY="$HOME/.local/share/rtx/shims/ruby"
10
- RTX_GEM="$HOME/.local/share/rtx/shims/gem"
11
- RUBY_CMD=$RTX_RUBY
12
- GEM_CMD=$RTX_GEM
3
+ export PATH="$HOME/.local/bin:$PATH" # this is key for activating mise without running `eval "$($MISE activate bash)"`
4
+ MISE="$HOME/.local/bin/mise"
5
+ # mise_init() { eval "$($MISE activate bash)"; }
6
+ # MISE_RUBY="$HOME/.local/bin/mise x ruby -- ruby"
7
+ # MISE_GEM="$HOME/.local/bin/mise x ruby -- gem"
8
+ MISE_RUBY="$HOME/.local/share/mise/shims/ruby"
9
+ MISE_GEM="$HOME/.local/share/mise/shims/gem"
10
+ RUBY_CMD=$MISE_RUBY
11
+ GEM_CMD=$MISE_GEM
13
12
  # RUBY_CMD="ruby"
14
13
  # GEM_CMD="gem"
15
14
 
16
- if [ -x $RTX ]; then
17
- # rtx_init;
18
- # eval "$(rtx activate bash)"
15
+ if [ -x $MISE ]; then
16
+ # mise_init;
19
17
  # if brew is already installed, initialize this shell environment with brew
20
18
  # if [ -x "$(command -v /home/linuxbrew/.linuxbrew/bin/brew)" ]; then
21
19
  if $RUBY_CMD -e "major, minor, patch = RUBY_VERSION.split('.'); exit 1 unless major.to_i >= 3"; then
@@ -29,7 +27,7 @@ if [ -x $RTX ]; then
29
27
  # todo: figure out how to install this differently, so that test versions will work
30
28
  # gem install opswalrus
31
29
  $GEM_CMD install opswalrus
32
- $RTX reshim
30
+ $MISE reshim
33
31
 
34
32
  exit 0
35
33
  # fi
@@ -81,24 +79,8 @@ fi
81
79
  # brew install age # https://github.com/FiloSottile/age
82
80
 
83
81
 
84
- ### install ruby
82
+ ### install ruby via mise
85
83
 
86
- # 1. via homebrew
87
- # brew install ruby
88
- # this doesn't install some gems nicely
89
-
90
- # 2. via ruby-install
91
- # brew install bash grep wget curl md5sha1sum sha2 gnu-tar bzip2 xz patchutils gcc
92
- # brew install ruby-install
93
- # ruby-install --update
94
- # ruby-install ruby 3.2.2
95
-
96
- # 3. rvm
97
- # gpg2 --keyserver keyserver.ubuntu.com --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3 7D2BAF1CF37B13E2069D6956105BD0E739499BDB
98
- # \curl -sSL https://get.rvm.io | bash -s stable --autolibs=homebrew
99
- # rvm install 3.2.2
100
-
101
- # 4. rtx (asdf clone)
102
84
  if echo $OS | grep -q 'ubuntu'; then
103
85
  # update package list
104
86
  sudo apt update -qy
@@ -129,13 +111,12 @@ else
129
111
  echo "unsupported OS"
130
112
  exit 1
131
113
  fi
132
- curl https://rtx.pub/install.sh | sh
133
- # eval "$($HOME/.local/share/rtx/bin/rtx activate bash)"
134
- $RTX use -g ruby@3.2
135
- $RTX reshim
114
+ curl https://mise.jdx.dev/install.sh | sh
115
+ $MISE use -g ruby@latest
116
+ $MISE reshim
136
117
 
137
118
 
138
- # 5. age
119
+ # 5. age encryption
139
120
  if echo $OS | grep -q 'ubuntu'; then
140
121
  # update package list
141
122
  sudo apt update -qy
@@ -161,4 +142,4 @@ fi
161
142
 
162
143
  # install opswalrus gem
163
144
  $GEM_CMD install opswalrus
164
- $RTX reshim
145
+ $MISE reshim
@@ -0,0 +1,115 @@
1
+ require 'tty-exit'
2
+ require 'tty-option'
3
+
4
+ require_relative "app"
5
+
6
+ module OpsWalrus
7
+ def self.env_specified_age_ids()
8
+ # ENV['AGE_ID'] || (ENV['OPSWALRUS_AGE_IDS'] && Dir.glob(ENV['OPSWALRUS_AGE_IDS']))
9
+ ENV['OPSWALRUS_AGE_IDS'] && Dir.glob(ENV['OPSWALRUS_AGE_IDS'])
10
+ end
11
+
12
+
13
+ class Cli2
14
+ def self.run(argv = ARGV)
15
+ app = App.instance(Dir.pwd)
16
+ app.set_local_hostname(ENV["OPSWALRUS_LOCAL_HOSTNAME"]) if ENV["OPSWALRUS_LOCAL_HOSTNAME"]
17
+
18
+ command = CliCommand.new()
19
+ command.parse(argv)
20
+ exit_status = command.run(app)
21
+
22
+ TTY::Exit.exit_with(exit_status)
23
+ end
24
+
25
+ def initialize(app)
26
+ @app = app
27
+ end
28
+ end
29
+
30
+ class OpsCmd
31
+ include TTY::Option
32
+
33
+ usage do
34
+ program "ops"
35
+
36
+ command "run"
37
+
38
+ desc "Run a command in a new container"
39
+
40
+ example "Set working directory (-w)",
41
+ " $ dock run -w /path/to/dir/ ubuntu pwd"
42
+
43
+ example <<~EOS
44
+ Mount volume
45
+ $ dock run -v `pwd`:`pwd` -w `pwd` ubuntu pwd
46
+ EOS
47
+ end
48
+
49
+ # argument :image do
50
+ # required
51
+ # desc "The name of the image to use"
52
+ # end
53
+
54
+ # argument :command do
55
+ # optional
56
+ # desc "The command to run inside the image"
57
+ # end
58
+
59
+ # keyword :restart do
60
+ # default "no"
61
+ # permit %w[no on-failure always unless-stopped]
62
+ # desc "Restart policy to apply when a container exits"
63
+ # end
64
+
65
+ option :verbose do
66
+ arity "*"
67
+ short "-v"
68
+ desc "Verbose mode"
69
+ end
70
+
71
+ # flag :detach do
72
+ # short "-d"
73
+ # long "--detach"
74
+ # desc "Run container in background and print container ID"
75
+ # end
76
+
77
+ # flag :help do
78
+ # short "-h"
79
+ # long "--help"
80
+ # desc "Print usage"
81
+ # end
82
+
83
+ # option :name do
84
+ # required
85
+ # long "--name string"
86
+ # desc "Assign a name to the container"
87
+ # end
88
+
89
+ # option :port do
90
+ # arity one_or_more
91
+ # short "-p"
92
+ # long "--publish list"
93
+ # convert :list
94
+ # desc "Publish a container's port(s) to the host"
95
+ # end
96
+
97
+ def run(app)
98
+ if params[:help]
99
+ print help
100
+ elsif params.errors.any?
101
+ puts params.errors.summary
102
+ else
103
+ pp params.to_h
104
+ end
105
+ 11
106
+ rescue => e
107
+ app.fatal "catchall exception handler:"
108
+ app.fatal exception.class
109
+ app.fatal exception.message
110
+ app.fatal exception.backtrace.join("\n")
111
+ 1
112
+ end
113
+ end
114
+
115
+ end
@@ -1,4 +1,6 @@
1
1
  module OpsWalrus
2
+ ExitCodeHostTemporarilyUnavailable = 11
3
+
2
4
  class Error < StandardError
3
5
  end
4
6
 
@@ -9,6 +11,9 @@ module OpsWalrus
9
11
  end
10
12
  end
11
13
 
14
+ class RetriableRemoteInvocationError < RemoteInvocationError
15
+ end
16
+
12
17
  class SymbolResolutionError < Error
13
18
  end
14
19
  end
@@ -162,39 +162,8 @@ module OpsWalrus
162
162
  reboot_success = sh? 'sudo /bin/sh -c "(sleep {{ delay }} && reboot) &"'.mustache
163
163
  puts reboot_success
164
164
 
165
- reconnect_time = nil
166
- reconnect_success = if sync
167
- desc "Waiting for #{to_s} (alias=#{self.alias}) to finish rebooting"
168
- initial_reconnect_delay = delay + 10
169
- sleep initial_reconnect_delay
170
-
171
- reconnected = false
172
- give_up = false
173
- t1 = Time.now
174
- until reconnected || give_up
175
- begin
176
- reconnected = sh?('true')
177
- # while trying to reconnect, we expect the following exceptions:
178
- # 1. Net::SSH::Disconnect < Net::SSH::Exception with message: "connection closed by remote host"
179
- # 2. Errno::ECONNRESET < SystemCallError with message: "Connection reset by peer"
180
- # 3. Errno::ECONNREFUSED < SystemCallError with message: "Connection refused - connect(2) for 192.168.56.10:22"
181
- rescue Net::SSH::Disconnect, Net::SSH::ConnectionTimeout, Errno::ECONNRESET, Errno::ECONNREFUSED => e
182
- # noop; we expect these while we're trying to reconnect
183
- rescue => e
184
- puts "#{e.class} < #{e.class.superclass}"
185
- puts e.message
186
- puts e.backtrace.take(5).join("\n")
187
- end
188
-
189
- wait_time_elapsed_in_seconds = Time.now - t1
190
- give_up = wait_time_elapsed_in_seconds > timeout
191
- sleep 5
192
- end
193
- reconnect_time = initial_reconnect_delay + (Time.now - t1)
194
- reconnected
195
- else
196
- false
197
- end
165
+ reconnect_time = reconnect(delay, timeout) if sync
166
+ reconnect_success = !!reconnect_time
198
167
 
199
168
  {
200
169
  success: reboot_success && (sync == reconnect_success),
@@ -204,6 +173,50 @@ module OpsWalrus
204
173
  }
205
174
  end
206
175
 
176
+ def autoretry(&block)
177
+ attempts ||= 0
178
+ attempts += 1
179
+ block.call
180
+ rescue RetriableRemoteInvocationError => e
181
+ retry if attempts <= 3
182
+ end
183
+
184
+ # returns an integer number of seconds if reconnected; nil otherwise
185
+ def reconnect(delay: 1, timeout: 300)
186
+ delay = 1 if delay < 1
187
+
188
+ desc "Waiting for #{to_s} (alias=#{self.alias}) to become available. Reconnecting."
189
+ initial_reconnect_delay = delay + 10
190
+ sleep initial_reconnect_delay
191
+
192
+ reconnected = false
193
+ give_up = false
194
+ t1 = Time.now
195
+ until reconnected || give_up
196
+ begin
197
+ reconnected = sh?('true')
198
+ # while trying to reconnect, we expect the following exceptions:
199
+ # 1. Net::SSH::Disconnect < Net::SSH::Exception with message: "connection closed by remote host"
200
+ # 2. Errno::ECONNRESET < SystemCallError with message: "Connection reset by peer"
201
+ # 3. Errno::ECONNREFUSED < SystemCallError with message: "Connection refused - connect(2) for 192.168.56.10:22"
202
+ rescue Net::SSH::Disconnect, Net::SSH::ConnectionTimeout, Errno::ECONNRESET, Errno::ECONNREFUSED => e
203
+ # noop; we expect these while we're trying to reconnect
204
+ rescue => e
205
+ puts "#{e.class} < #{e.class.superclass}"
206
+ puts e.message
207
+ puts e.backtrace.take(5).join("\n")
208
+ end
209
+
210
+ wait_time_elapsed_in_seconds = Time.now - t1
211
+ give_up = wait_time_elapsed_in_seconds > timeout
212
+ sleep 5
213
+ end
214
+
215
+ if reconnected
216
+ initial_reconnect_delay + (Time.now - t1)
217
+ end
218
+ end
219
+
207
220
  # runs the given command
208
221
  # returns the stdout from the command
209
222
  def sh(desc_or_cmd = nil, cmd = nil, input: nil, &block)
@@ -215,7 +215,7 @@ module OpsWalrus
215
215
  retval = JSON.parse(json_string)
216
216
  case retval
217
217
  when Hash
218
- if retval["type"] == 'Invocation::Error'
218
+ if retval["type"] == Invocation::Error::Type
219
219
  # this structure comes from OpsWalrus::Invocation::Error being serialized in App#print_script_result being called from App#run
220
220
  # {
221
221
  # type: "Invocation::Error",
@@ -223,9 +223,15 @@ module OpsWalrus
223
223
  # error_class: value.class.name,
224
224
  # error: value,
225
225
  # backtrace: value.is_a?(Exception) ? value.backtrace.take(10).join("\n") : nil,
226
+ # exit_status: exit_status
226
227
  # }
227
228
  # raise RemoteInvocationError.new("Remote Invocation Error:\n #{retval["error_class"]}: #{retval["error"]}#{retval["backtrace"] && "\n Backtrace: #{retval['backtrace']}"}")
228
- raise RemoteInvocationError.new("Remote Invocation Error:\n #{retval["error_class"]}: #{retval["error"]}#{retval["backtrace"] && "\n Backtrace: #{retval['backtrace']}"}", retval)
229
+
230
+ if retval["error_variant"] == Invocation::EarlyExitError.name && retval["exit_status"] == ExitCodeHostTemporarilyUnavailable
231
+ raise RetriableRemoteInvocationError.new("Retriable Remote Invocation Error (exit_status=#{retval["exit_status"]}):\n #{retval["error_class"]}: #{retval["error"]}#{retval["backtrace"] && "\n Backtrace: #{retval['backtrace']}"}", retval)
232
+ else
233
+ raise RemoteInvocationError.new("Remote Invocation Error (exit_status=#{retval["exit_status"]}):\n #{retval["error_class"]}: #{retval["error"]}#{retval["backtrace"] && "\n Backtrace: #{retval['backtrace']}"}", retval)
234
+ end
229
235
  else
230
236
  retval.with_indifferent_access.easynav
231
237
  end
@@ -70,6 +70,12 @@ module OpsWalrus
70
70
 
71
71
  # runtime_kv_args is an Array(String) of the form: ["arg1:val1", "arg1:val2", ...]
72
72
  # params_json_hash is a Hash representation of a JSON string
73
+ # returns:
74
+ # Invocation::Success on success
75
+ # or Invocation::EarlyExitError when the user's script intentionally exits early without running to completion
76
+ # or Invocation::SshError when a connection error condition is raised
77
+ # or Invocation::RuntimeError when some known error condition is raised
78
+ # or Invocation::UnhandledError when some unknown error condition is raised
73
79
  def run(runtime_kv_args, params_json_hash: nil)
74
80
  params_hash = build_params_hash(runtime_kv_args, params_json_hash: params_json_hash)
75
81
 
@@ -37,16 +37,18 @@ module OpsWalrus
37
37
  end
38
38
  end
39
39
  class Error < Result
40
+ Type = "Invocation::Error"
40
41
  def initialize(value, exit_status = 1)
41
42
  super(value, exit_status == 0 ? 1 : exit_status)
42
43
  end
43
44
  def serialize_error
44
45
  {
45
- type: "Invocation::Error",
46
+ type: Type,
46
47
  error_variant: self.class.name,
47
48
  error_class: value.class.name,
48
49
  error: value,
49
50
  backtrace: value.is_a?(Exception) ? value.backtrace.take(10).join("\n") : nil,
51
+ exit_status: exit_status
50
52
  }
51
53
  end
52
54
  def failure?
@@ -54,6 +56,8 @@ module OpsWalrus
54
56
  end
55
57
  end
56
58
  class EarlyExitError < Error
59
+ # irb(main):062> OpsWalrus::Invocation::EarlyExitError.new(nil, 11).serialize_error
60
+ # => {:type=>"Invocation::Error", :error_variant=>"OpsWalrus::Invocation::EarlyExitError", :error_class=>"NilClass", :error=>nil, :backtrace=>nil, :exit_status=>11}
57
61
  end
58
62
  class SshError < Error
59
63
  end
@@ -217,6 +221,22 @@ module OpsWalrus
217
221
  @runtime_env.app.inventory(tags).hosts
218
222
  end
219
223
 
224
+ # delay: integer? # default: 1 - 1 second delay before reboot
225
+ def reboot(delay: 1)
226
+ delay = 1 if delay < 1
227
+
228
+ # desc "Rebooting"
229
+ rebooting = sh? 'sudo /bin/sh -c "(sleep {{ delay }} && reboot) &"'.mustache
230
+ # puts reboot_success
231
+
232
+ rebooting
233
+ end
234
+
235
+ def reboot_and_exit(delay: 1)
236
+ # exit status 11 means Resource temporarily unavailable
237
+ exit(ExitCodeHostTemporarilyUnavailable, "Host temporarily unavailable. Rebooting.") if reboot(delay)
238
+ end
239
+
220
240
  def exit(exit_status, message = nil)
221
241
  if message
222
242
  puts message.mustache(1)
@@ -1,3 +1,3 @@
1
1
  module OpsWalrus
2
- VERSION = "1.0.97"
2
+ VERSION = "1.0.98"
3
3
  end
data/lib/opswalrus.rb CHANGED
@@ -4,6 +4,7 @@ require 'bundler/setup'
4
4
 
5
5
  require_relative 'opswalrus/app'
6
6
  require_relative 'opswalrus/cli'
7
+ require_relative 'opswalrus/cli2'
7
8
  require_relative 'opswalrus/version'
8
9
 
9
10
  module OpsWalrus
data/opswalrus.gemspec CHANGED
@@ -41,6 +41,8 @@ Gem::Specification.new do |spec|
41
41
  spec.add_dependency "rubyzip", "~> 2.3"
42
42
  spec.add_dependency "semantic_logger", "~> 4.13"
43
43
  spec.add_dependency "tty-editor", "~> 0.7"
44
+ spec.add_dependency "tty-exit", "~> 0.1"
45
+ spec.add_dependency "tty-option", "~> 0.3"
44
46
 
45
47
  spec.add_dependency "bcrypt_pbkdf", "~> 1.1"
46
48
  spec.add_dependency "ed25519", "~> 1.3"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: opswalrus
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.97
4
+ version: 1.0.98
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Ellis
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-12-04 00:00:00.000000000 Z
11
+ date: 2024-01-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -136,6 +136,34 @@ dependencies:
136
136
  - - "~>"
137
137
  - !ruby/object:Gem::Version
138
138
  version: '0.7'
139
+ - !ruby/object:Gem::Dependency
140
+ name: tty-exit
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - "~>"
144
+ - !ruby/object:Gem::Version
145
+ version: '0.1'
146
+ type: :runtime
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - "~>"
151
+ - !ruby/object:Gem::Version
152
+ version: '0.1'
153
+ - !ruby/object:Gem::Dependency
154
+ name: tty-option
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - "~>"
158
+ - !ruby/object:Gem::Version
159
+ version: '0.3'
160
+ type: :runtime
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - "~>"
165
+ - !ruby/object:Gem::Version
166
+ version: '0.3'
139
167
  - !ruby/object:Gem::Dependency
140
168
  name: bcrypt_pbkdf
141
169
  requirement: !ruby/object:Gem::Requirement
@@ -198,6 +226,7 @@ email:
198
226
  - david@conquerthelawn.com
199
227
  executables:
200
228
  - ops
229
+ - ops2
201
230
  extensions: []
202
231
  extra_rdoc_files: []
203
232
  files:
@@ -210,8 +239,8 @@ files:
210
239
  - README.md
211
240
  - Rakefile
212
241
  - build.ops
213
- - cli2.rb
214
242
  - exe/ops
243
+ - exe/ops2
215
244
  - lib/opswalrus.rb
216
245
  - lib/opswalrus/_bootstrap.ops
217
246
  - lib/opswalrus/_reboot.ops
@@ -221,6 +250,7 @@ files:
221
250
  - lib/opswalrus/bootstrap.sh
222
251
  - lib/opswalrus/bundler.rb
223
252
  - lib/opswalrus/cli.rb
253
+ - lib/opswalrus/cli2.rb
224
254
  - lib/opswalrus/errors.rb
225
255
  - lib/opswalrus/git.rb
226
256
  - lib/opswalrus/host.rb
@@ -273,7 +303,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
273
303
  - !ruby/object:Gem::Version
274
304
  version: '0'
275
305
  requirements: []
276
- rubygems_version: 3.4.10
306
+ rubygems_version: 3.5.3
277
307
  signing_key:
278
308
  specification_version: 4
279
309
  summary: opswalrus is a tool that runs scripts against a fleet of hosts
data/cli2.rb DELETED
@@ -1,88 +0,0 @@
1
- require 'tty-option'
2
-
3
- class Command
4
- include TTY::Option
5
-
6
- usage do
7
- program "dock"
8
-
9
- command "run"
10
-
11
- desc "Run a command in a new container"
12
-
13
- example "Set working directory (-w)",
14
- " $ dock run -w /path/to/dir/ ubuntu pwd"
15
-
16
- example <<~EOS
17
- Mount volume
18
- $ dock run -v `pwd`:`pwd` -w `pwd` ubuntu pwd
19
- EOS
20
- end
21
-
22
- argument :image do
23
- required
24
- desc "The name of the image to use"
25
- end
26
-
27
- argument :command do
28
- optional
29
- desc "The command to run inside the image"
30
- end
31
-
32
- keyword :restart do
33
- default "no"
34
- permit %w[no on-failure always unless-stopped]
35
- desc "Restart policy to apply when a container exits"
36
- end
37
-
38
- option :verbose do
39
- arity "+"
40
- short "-v"
41
- desc "Verbose mode"
42
- end
43
-
44
- flag :detach do
45
- short "-d"
46
- long "--detach"
47
- desc "Run container in background and print container ID"
48
- end
49
-
50
- flag :help do
51
- short "-h"
52
- long "--help"
53
- desc "Print usage"
54
- end
55
-
56
- option :name do
57
- required
58
- long "--name string"
59
- desc "Assign a name to the container"
60
- end
61
-
62
- option :port do
63
- arity one_or_more
64
- short "-p"
65
- long "--publish list"
66
- convert :list
67
- desc "Publish a container's port(s) to the host"
68
- end
69
-
70
- def run
71
- puts params[:verbose].inspect
72
- if params[:help]
73
- print help
74
- elsif params.errors.any?
75
- puts params.errors.summary
76
- else
77
- pp params.to_h
78
- end
79
- end
80
- end
81
-
82
- def main
83
- cmd = Command.new
84
- cmd.parse
85
- puts cmd.run
86
- end
87
-
88
- main