runsible 0.1.2.1 → 0.1.3.1
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 +4 -4
- data/VERSION +1 -1
- data/bin/runsible +1 -1
- data/lib/runsible.rb +92 -68
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f31a8f06a0427b8f580a9240bc5698bc87e1b439
|
|
4
|
+
data.tar.gz: bf3daedd7b2e648992ced1ea3ec8140f83e9f30c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3c43fd82babf3dd5630fa2d1e69b82ff141a33ac23781b670a51d50e893e5313f8ad45305de6f9cd779176e720cf047e9c25866b8b66e59a5eebd6e79665b230
|
|
7
|
+
data.tar.gz: 539a72b37cab94eba245b16fdd5d67bbd31ade5cecadd84cd10f34a5457a29b3a3eca3b4085c1ec5c8b3f865fef52cd515ace50f45e5ee92a27cd6e455617823
|
data/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.1.
|
|
1
|
+
0.1.3.1
|
data/bin/runsible
CHANGED
data/lib/runsible.rb
CHANGED
|
@@ -18,8 +18,8 @@ end
|
|
|
18
18
|
# STDOUT and STDERR
|
|
19
19
|
#
|
|
20
20
|
module Runsible
|
|
21
|
-
SSH_CNX_TIMEOUT = 10
|
|
22
21
|
class Error < RuntimeError; end
|
|
22
|
+
SSH_CNX_TIMEOUT = 10
|
|
23
23
|
|
|
24
24
|
SETTINGS = {
|
|
25
25
|
user: ENV['USER'],
|
|
@@ -29,6 +29,10 @@ module Runsible
|
|
|
29
29
|
vars: [],
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
#
|
|
33
|
+
# Utility stuff
|
|
34
|
+
#
|
|
35
|
+
|
|
32
36
|
def self.default_settings
|
|
33
37
|
hsh = {}
|
|
34
38
|
SETTINGS.each { |sym, v|
|
|
@@ -48,32 +52,58 @@ module Runsible
|
|
|
48
52
|
exit 1
|
|
49
53
|
end
|
|
50
54
|
|
|
51
|
-
def self.
|
|
52
|
-
|
|
53
|
-
|
|
55
|
+
def self.alert(topic, message, settings)
|
|
56
|
+
backend = settings['alerts'] && settings['alerts']['backend']
|
|
57
|
+
case backend
|
|
58
|
+
when 'disabled', nil, false
|
|
59
|
+
return
|
|
60
|
+
when 'email'
|
|
61
|
+
Pony.mail(to: settings.fetch(:address),
|
|
62
|
+
from: 'runsible@spoon',
|
|
63
|
+
subject: topic,
|
|
64
|
+
body: message)
|
|
65
|
+
when 'kafka', 'slack'
|
|
66
|
+
# TODO
|
|
67
|
+
raise Error, "unsupported backend: #{backend.inspect}"
|
|
68
|
+
else
|
|
69
|
+
raise Error, "unknown backend: #{backend.inspect}"
|
|
70
|
+
end
|
|
54
71
|
end
|
|
55
72
|
|
|
56
|
-
def self.
|
|
57
|
-
|
|
58
|
-
|
|
73
|
+
def self.warn(msg)
|
|
74
|
+
$stdout.puts msg
|
|
75
|
+
$stderr.puts msg
|
|
76
|
+
end
|
|
59
77
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
78
|
+
def self.die!(msg, settings)
|
|
79
|
+
self.warn(msg)
|
|
80
|
+
self.alert("runsible:fatal:#{Process.pid}", msg, settings)
|
|
81
|
+
exit 1
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
#
|
|
86
|
+
# CLI or bin/runsible stuff
|
|
87
|
+
#
|
|
88
|
+
|
|
89
|
+
# bin/runsible entry point
|
|
90
|
+
def self.spoon(ssh_options = Hash.new)
|
|
91
|
+
opts = Runsible.slop_parse
|
|
92
|
+
yaml = self.extract_yaml(opts)
|
|
93
|
+
settings = Runsible.default_settings.merge(yaml['settings'] || Hash.new)
|
|
94
|
+
settings = self.merge(opts, settings)
|
|
95
|
+
self.ssh_runlist(settings, yaml['runlist'] || Array.new, ssh_options, yaml)
|
|
66
96
|
end
|
|
67
97
|
|
|
68
98
|
def self.slop_parse
|
|
69
|
-
d = SETTINGS
|
|
99
|
+
d = SETTINGS # display defaults
|
|
70
100
|
Slop.parse do |o|
|
|
71
101
|
o.banner = "usage: runsible [options] yaml_file"
|
|
72
102
|
o.on '-h', '--help' do
|
|
73
103
|
puts o
|
|
74
104
|
exit 0
|
|
75
105
|
end
|
|
76
|
-
o.on '-v', '--version', 'show
|
|
106
|
+
o.on '-v', '--version', 'show runsible version' do
|
|
77
107
|
puts Runsible.version
|
|
78
108
|
exit 0
|
|
79
109
|
end
|
|
@@ -89,43 +119,30 @@ module Runsible
|
|
|
89
119
|
end
|
|
90
120
|
end
|
|
91
121
|
|
|
92
|
-
def self.
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
}
|
|
96
|
-
settings['alerts'] = {} if opts.silent?
|
|
97
|
-
settings
|
|
98
|
-
end
|
|
122
|
+
def self.extract_yaml(opts)
|
|
123
|
+
yaml_filename = opts.arguments.shift
|
|
124
|
+
self.usage(opts, "yaml_file is required") if yaml_filename.nil?
|
|
99
125
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
return
|
|
105
|
-
when 'email'
|
|
106
|
-
Pony.mail(to: settings.fetch(:address),
|
|
107
|
-
from: 'runsible@spoon',
|
|
108
|
-
subject: topic,
|
|
109
|
-
body: message)
|
|
110
|
-
when 'kafka', 'slack'
|
|
111
|
-
# TODO
|
|
112
|
-
raise Error, "unsupported backend: #{backend.inspect}"
|
|
113
|
-
else
|
|
114
|
-
raise Error, "unknown backend: #{backend.inspect}"
|
|
126
|
+
begin
|
|
127
|
+
yaml = YAML.load_file(yaml_filename)
|
|
128
|
+
rescue RuntimeError => e
|
|
129
|
+
Runsible.usage(opts, "could not load yaml_file\n#{e}")
|
|
115
130
|
end
|
|
131
|
+
yaml
|
|
116
132
|
end
|
|
117
133
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
end
|
|
134
|
+
#
|
|
135
|
+
# Library stuff
|
|
136
|
+
#
|
|
122
137
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
self.ssh_runlist(
|
|
138
|
+
# run a YAML file without any consideration for command line options
|
|
139
|
+
def self.run_yaml(yaml_filename, ssh_options = Hash.new)
|
|
140
|
+
yaml = YAML.load_file(yaml_filename)
|
|
141
|
+
self.ssh_runlist(self.default_settings, yaml['runlist'] || Array.new,
|
|
142
|
+
ssh_options, yaml)
|
|
127
143
|
end
|
|
128
144
|
|
|
145
|
+
# initiate ssh connection, perform the runlist
|
|
129
146
|
def self.ssh_runlist(settings, runlist, ssh_options, yaml)
|
|
130
147
|
ssh_options[:forward_agent] ||= true
|
|
131
148
|
ssh_options[:port] ||= settings.fetch('port')
|
|
@@ -137,12 +154,6 @@ module Runsible
|
|
|
137
154
|
}
|
|
138
155
|
end
|
|
139
156
|
|
|
140
|
-
def self.die!(msg, settings)
|
|
141
|
-
self.warn(msg)
|
|
142
|
-
self.alert("runsible:fatal:#{Process.pid}", msg, settings)
|
|
143
|
-
exit 1
|
|
144
|
-
end
|
|
145
|
-
|
|
146
157
|
# execute runlist with failure handling, retries, alerting, etc.
|
|
147
158
|
def self.exec_runlist(ssh, runlist, settings, yaml = Hash.new)
|
|
148
159
|
runlist.each { |run|
|
|
@@ -177,6 +188,26 @@ module Runsible
|
|
|
177
188
|
}
|
|
178
189
|
end
|
|
179
190
|
|
|
191
|
+
# retry several times, rescuing CommandFailure
|
|
192
|
+
# raises on SSH channel exec failure and CommandFailure on final retry
|
|
193
|
+
def self.exec_retry(ssh, cmd, retries)
|
|
194
|
+
self.banner_wrap(cmd) {
|
|
195
|
+
success = false
|
|
196
|
+
retries.times { |i|
|
|
197
|
+
begin
|
|
198
|
+
success = self.exec(ssh, cmd)
|
|
199
|
+
break
|
|
200
|
+
rescue CommandFailure => e
|
|
201
|
+
$stdout.puts "#{e}; retrying shortly..."
|
|
202
|
+
$stderr.puts e
|
|
203
|
+
sleep 2
|
|
204
|
+
end
|
|
205
|
+
}
|
|
206
|
+
# the final retry, may blow up
|
|
207
|
+
success or self.exec(ssh, cmd)
|
|
208
|
+
}
|
|
209
|
+
end
|
|
210
|
+
|
|
180
211
|
# prints remote STDOUT to local STDOUT, likewise for STDERR
|
|
181
212
|
# raises on SSH channel exec failure or nonzero exit status
|
|
182
213
|
def self.exec(ssh, cmd)
|
|
@@ -199,24 +230,17 @@ module Runsible
|
|
|
199
230
|
exit_code == 0 or raise(CommandFailure, "[exit #{exit_code}] #{cmd}")
|
|
200
231
|
end
|
|
201
232
|
|
|
202
|
-
|
|
203
|
-
#
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
break
|
|
211
|
-
rescue CommandFailure => e
|
|
212
|
-
$stdout.puts "#{e}; retrying shortly..."
|
|
213
|
-
$stderr.puts e
|
|
214
|
-
sleep 2
|
|
215
|
-
end
|
|
216
|
-
}
|
|
217
|
-
# the final retry, may blow up
|
|
218
|
-
success or self.exec(ssh, cmd)
|
|
233
|
+
|
|
234
|
+
#
|
|
235
|
+
# Necessities
|
|
236
|
+
#
|
|
237
|
+
|
|
238
|
+
def self.merge(opts, settings)
|
|
239
|
+
Runsible::SETTINGS.keys.each { |sym|
|
|
240
|
+
settings[sym.to_s] = opts[sym] if opts[sym]
|
|
219
241
|
}
|
|
242
|
+
settings['alerts'] = {} if opts.silent?
|
|
243
|
+
settings
|
|
220
244
|
end
|
|
221
245
|
|
|
222
246
|
def self.begin_banner(msg)
|