runbook 0.12.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 +7 -0
- data/.gitignore +12 -0
- data/.rspec +2 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +5 -0
- data/CHANGELOG.md +46 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +21 -0
- data/README.md +999 -0
- data/Rakefile +6 -0
- data/TODO.md +38 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/exe/runbook +5 -0
- data/images/runbook_anatomy_diagram.png +0 -0
- data/images/runbook_example.gif +0 -0
- data/images/runbook_execution_modes.png +0 -0
- data/lib/hacks/ssh_kit.rb +58 -0
- data/lib/runbook/cli.rb +90 -0
- data/lib/runbook/configuration.rb +110 -0
- data/lib/runbook/dsl.rb +21 -0
- data/lib/runbook/entities/book.rb +17 -0
- data/lib/runbook/entities/section.rb +7 -0
- data/lib/runbook/entities/step.rb +7 -0
- data/lib/runbook/entity.rb +127 -0
- data/lib/runbook/errors.rb +7 -0
- data/lib/runbook/extensions/add.rb +13 -0
- data/lib/runbook/extensions/description.rb +14 -0
- data/lib/runbook/extensions/sections.rb +15 -0
- data/lib/runbook/extensions/shared_variables.rb +51 -0
- data/lib/runbook/extensions/ssh_config.rb +76 -0
- data/lib/runbook/extensions/statements.rb +26 -0
- data/lib/runbook/extensions/steps.rb +14 -0
- data/lib/runbook/extensions/tmux.rb +13 -0
- data/lib/runbook/helpers/format_helper.rb +11 -0
- data/lib/runbook/helpers/ssh_kit_helper.rb +143 -0
- data/lib/runbook/helpers/tmux_helper.rb +174 -0
- data/lib/runbook/hooks.rb +88 -0
- data/lib/runbook/node.rb +23 -0
- data/lib/runbook/run.rb +283 -0
- data/lib/runbook/runner.rb +64 -0
- data/lib/runbook/runs/ssh_kit.rb +186 -0
- data/lib/runbook/statement.rb +22 -0
- data/lib/runbook/statements/ask.rb +11 -0
- data/lib/runbook/statements/assert.rb +25 -0
- data/lib/runbook/statements/capture.rb +14 -0
- data/lib/runbook/statements/capture_all.rb +14 -0
- data/lib/runbook/statements/command.rb +11 -0
- data/lib/runbook/statements/confirm.rb +10 -0
- data/lib/runbook/statements/description.rb +9 -0
- data/lib/runbook/statements/download.rb +12 -0
- data/lib/runbook/statements/layout.rb +10 -0
- data/lib/runbook/statements/note.rb +10 -0
- data/lib/runbook/statements/notice.rb +10 -0
- data/lib/runbook/statements/ruby_command.rb +9 -0
- data/lib/runbook/statements/tmux_command.rb +11 -0
- data/lib/runbook/statements/upload.rb +12 -0
- data/lib/runbook/statements/wait.rb +10 -0
- data/lib/runbook/toolbox.rb +43 -0
- data/lib/runbook/util/repo.rb +56 -0
- data/lib/runbook/util/runbook.rb +25 -0
- data/lib/runbook/util/sticky_hash.rb +26 -0
- data/lib/runbook/util/stored_pose.rb +54 -0
- data/lib/runbook/version.rb +3 -0
- data/lib/runbook/view.rb +24 -0
- data/lib/runbook/viewer.rb +24 -0
- data/lib/runbook/views/markdown.rb +109 -0
- data/lib/runbook.rb +110 -0
- data/runbook.gemspec +48 -0
- data/samples/hooks_runbook.rb +72 -0
- data/samples/layout_runbook.rb +26 -0
- data/samples/restart_nginx.rb +26 -0
- data/samples/simple_runbook.rb +41 -0
- metadata +324 -0
data/lib/runbook/run.rb
ADDED
@@ -0,0 +1,283 @@
|
|
1
|
+
module Runbook
|
2
|
+
module Run
|
3
|
+
def self.included(base)
|
4
|
+
base.extend(ClassMethods)
|
5
|
+
_register_kill_all_panes_hook(base)
|
6
|
+
_register_additional_step_whitespace_hook(base)
|
7
|
+
::Runbook::Util::Repo.register_save_repo_hook(base)
|
8
|
+
::Runbook::Util::Repo.register_delete_stored_repo_hook(base)
|
9
|
+
::Runbook::Util::StoredPose.register_save_pose_hook(base)
|
10
|
+
::Runbook::Util::StoredPose.register_delete_stored_pose_hook(base)
|
11
|
+
end
|
12
|
+
|
13
|
+
module ClassMethods
|
14
|
+
include Runbook::Hooks
|
15
|
+
include Runbook::Helpers::FormatHelper
|
16
|
+
include Runbook::Helpers::TmuxHelper
|
17
|
+
|
18
|
+
def execute(object, metadata)
|
19
|
+
return if should_skip?(metadata)
|
20
|
+
|
21
|
+
method = _method_name(object)
|
22
|
+
if respond_to?(method)
|
23
|
+
send(method, object, metadata)
|
24
|
+
else
|
25
|
+
msg = "ERROR! No execution rule for #{object.class} (#{_method_name(object)}) in #{to_s}"
|
26
|
+
metadata[:toolbox].error(msg)
|
27
|
+
return
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def runbook__entities__book(object, metadata)
|
32
|
+
metadata[:toolbox].output("Executing #{object.title}...\n\n")
|
33
|
+
end
|
34
|
+
|
35
|
+
def runbook__entities__section(object, metadata)
|
36
|
+
metadata[:toolbox].output("Section #{metadata[:position]}: #{object.title}\n\n")
|
37
|
+
end
|
38
|
+
|
39
|
+
def runbook__entities__step(object, metadata)
|
40
|
+
toolbox = metadata[:toolbox]
|
41
|
+
title = " #{object.title}".rstrip
|
42
|
+
toolbox.output("Step #{metadata[:position]}:#{title}\n\n")
|
43
|
+
return if metadata[:auto] || metadata[:noop] ||
|
44
|
+
!metadata[:paranoid] || object.title.nil?
|
45
|
+
continue_result = toolbox.expand("Continue?", _step_choices)
|
46
|
+
_handle_continue_result(continue_result, object, metadata)
|
47
|
+
end
|
48
|
+
|
49
|
+
def runbook__statements__ask(object, metadata)
|
50
|
+
target = object.parent.dsl
|
51
|
+
existing_val = target.instance_variable_get(:"@#{object.into}")
|
52
|
+
default = existing_val || object.default
|
53
|
+
|
54
|
+
if metadata[:auto]
|
55
|
+
if default
|
56
|
+
target.singleton_class.class_eval { attr_accessor object.into }
|
57
|
+
target.send("#{object.into}=".to_sym, default)
|
58
|
+
return
|
59
|
+
end
|
60
|
+
|
61
|
+
error_msg = "ERROR! Can't execute ask statement without default in automatic mode!"
|
62
|
+
metadata[:toolbox].error(error_msg)
|
63
|
+
raise Runbook::Runner::ExecutionError, error_msg
|
64
|
+
end
|
65
|
+
|
66
|
+
if metadata[:noop]
|
67
|
+
default_msg = default ? " (default: #{default})" : ""
|
68
|
+
metadata[:toolbox].output("[NOOP] Ask: #{object.prompt} (store in: #{object.into})#{default_msg}")
|
69
|
+
return
|
70
|
+
end
|
71
|
+
|
72
|
+
result = metadata[:toolbox].ask(object.prompt, default: default)
|
73
|
+
|
74
|
+
target = object.parent.dsl
|
75
|
+
target.singleton_class.class_eval { attr_accessor object.into }
|
76
|
+
target.send("#{object.into}=".to_sym, result)
|
77
|
+
end
|
78
|
+
|
79
|
+
def runbook__statements__confirm(object, metadata)
|
80
|
+
if metadata[:auto]
|
81
|
+
metadata[:toolbox].output("Skipping confirmation (auto): #{object.prompt}")
|
82
|
+
else
|
83
|
+
if metadata[:noop]
|
84
|
+
metadata[:toolbox].output("[NOOP] Prompt: #{object.prompt}")
|
85
|
+
return
|
86
|
+
end
|
87
|
+
|
88
|
+
result = metadata[:toolbox].yes?(object.prompt)
|
89
|
+
metadata[:toolbox].exit(1) unless result
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def runbook__statements__description(object, metadata)
|
94
|
+
metadata[:toolbox].output("Description:")
|
95
|
+
metadata[:toolbox].output("#{object.msg}\n")
|
96
|
+
end
|
97
|
+
|
98
|
+
def runbook__statements__layout(object, metadata)
|
99
|
+
if metadata[:noop]
|
100
|
+
metadata[:toolbox].output(
|
101
|
+
"[NOOP] Layout: #{object.structure.inspect}"
|
102
|
+
)
|
103
|
+
unless ENV["TMUX"]
|
104
|
+
msg = "Warning: layout statement called outside a tmux pane."
|
105
|
+
metadata[:toolbox].warn(msg)
|
106
|
+
end
|
107
|
+
return
|
108
|
+
end
|
109
|
+
|
110
|
+
unless ENV["TMUX"]
|
111
|
+
error_msg = "Error: layout statement called outside a tmux pane. Exiting..."
|
112
|
+
metadata[:toolbox].error(error_msg)
|
113
|
+
metadata[:toolbox].exit(1)
|
114
|
+
end
|
115
|
+
|
116
|
+
structure = object.structure
|
117
|
+
title = object.parent.title
|
118
|
+
layout_panes = setup_layout(structure, runbook_title: title)
|
119
|
+
metadata[:layout_panes].merge!(layout_panes)
|
120
|
+
end
|
121
|
+
|
122
|
+
def runbook__statements__note(object, metadata)
|
123
|
+
metadata[:toolbox].output("Note: #{object.msg}")
|
124
|
+
end
|
125
|
+
|
126
|
+
def runbook__statements__notice(object, metadata)
|
127
|
+
metadata[:toolbox].warn("Notice: #{object.msg}")
|
128
|
+
end
|
129
|
+
|
130
|
+
def runbook__statements__tmux_command(object, metadata)
|
131
|
+
if metadata[:noop]
|
132
|
+
metadata[:toolbox].output("[NOOP] Run: `#{object.cmd}` in pane #{object.pane}")
|
133
|
+
return
|
134
|
+
end
|
135
|
+
|
136
|
+
send_keys(object.cmd, metadata[:layout_panes][object.pane])
|
137
|
+
end
|
138
|
+
|
139
|
+
def runbook__statements__ruby_command(object, metadata)
|
140
|
+
if metadata[:noop]
|
141
|
+
metadata[:toolbox].output("[NOOP] Run the following Ruby block:\n")
|
142
|
+
begin
|
143
|
+
source = deindent(object.block.source)
|
144
|
+
metadata[:toolbox].output("```ruby\n#{source}\n```\n")
|
145
|
+
rescue ::MethodSource::SourceNotFoundError => e
|
146
|
+
metadata[:toolbox].output("Unable to retrieve source code")
|
147
|
+
end
|
148
|
+
return
|
149
|
+
end
|
150
|
+
|
151
|
+
next_index = metadata[:index] + 1
|
152
|
+
parent_items = object.parent.items
|
153
|
+
remaining_items = parent_items.slice!(next_index..-1)
|
154
|
+
object.parent.dsl.instance_exec(object, metadata, &object.block)
|
155
|
+
parent_items[next_index..-1].each { |item| item.dynamic! }
|
156
|
+
parent_items.push(*remaining_items)
|
157
|
+
end
|
158
|
+
|
159
|
+
def runbook__statements__wait(object, metadata)
|
160
|
+
if metadata[:noop]
|
161
|
+
metadata[:toolbox].output("[NOOP] Sleep #{object.time} seconds")
|
162
|
+
return
|
163
|
+
end
|
164
|
+
|
165
|
+
time = object.time
|
166
|
+
message = "Sleeping #{time} seconds [:bar] :current/:total"
|
167
|
+
pastel = Pastel.new
|
168
|
+
yellow = pastel.on_yellow(" ")
|
169
|
+
green = pastel.on_green(" ")
|
170
|
+
progress_bar = TTY::ProgressBar.new(
|
171
|
+
message,
|
172
|
+
total: time,
|
173
|
+
width: 60,
|
174
|
+
head: ">",
|
175
|
+
incomplete: yellow,
|
176
|
+
complete: green,
|
177
|
+
)
|
178
|
+
progress_bar.start
|
179
|
+
time.times do
|
180
|
+
sleep(1)
|
181
|
+
progress_bar.advance(1)
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
def should_skip?(metadata)
|
186
|
+
if metadata[:reversed] && metadata[:position].empty?
|
187
|
+
current_pose = "0"
|
188
|
+
else
|
189
|
+
current_pose = metadata[:position]
|
190
|
+
end
|
191
|
+
return false if current_pose.empty?
|
192
|
+
position = Gem::Version.new(current_pose)
|
193
|
+
start_at = Gem::Version.new(metadata[:start_at])
|
194
|
+
return position < start_at
|
195
|
+
end
|
196
|
+
|
197
|
+
def start_at_is_substep?(object, metadata)
|
198
|
+
return false unless object.is_a?(Entity)
|
199
|
+
return true if metadata[:position].empty?
|
200
|
+
metadata[:start_at].start_with?(metadata[:position])
|
201
|
+
end
|
202
|
+
|
203
|
+
def past_position?(current_position, position)
|
204
|
+
current_pose = Gem::Version.new(current_position)
|
205
|
+
pose = Gem::Version.new(position)
|
206
|
+
return pose <= current_pose
|
207
|
+
end
|
208
|
+
|
209
|
+
def _method_name(object)
|
210
|
+
object.class.to_s.underscore.gsub("/", "__")
|
211
|
+
end
|
212
|
+
|
213
|
+
def _step_choices
|
214
|
+
[
|
215
|
+
{key: "c", name: "Continue to execute this step", value: :continue},
|
216
|
+
{key: "s", name: "Skip this step", value: :skip},
|
217
|
+
{key: "j", name: "Jump to the specified position", value: :jump},
|
218
|
+
{key: "P", name: "Disable paranoid mode", value: :no_paranoid},
|
219
|
+
{key: "e", name: "Exit the runbook", value: :exit},
|
220
|
+
]
|
221
|
+
end
|
222
|
+
|
223
|
+
def _handle_continue_result(result, object, metadata)
|
224
|
+
toolbox = metadata[:toolbox]
|
225
|
+
case result
|
226
|
+
when :continue
|
227
|
+
return
|
228
|
+
when :skip
|
229
|
+
position = metadata[:position]
|
230
|
+
current_step = position.split(".")[-1].to_i
|
231
|
+
new_step = current_step + 1
|
232
|
+
start_at = position.gsub(/\.#{current_step}$/, ".#{new_step}")
|
233
|
+
metadata[:start_at] = start_at
|
234
|
+
when :jump
|
235
|
+
result = toolbox.ask("What position would you like to jump to?")
|
236
|
+
if past_position?(metadata[:position], result)
|
237
|
+
metadata[:reverse] = true
|
238
|
+
metadata[:reversed] = true
|
239
|
+
end
|
240
|
+
metadata[:start_at] = result
|
241
|
+
when :no_paranoid
|
242
|
+
metadata[:paranoid] = false
|
243
|
+
when :exit
|
244
|
+
toolbox.exit(0)
|
245
|
+
end
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
def self._register_kill_all_panes_hook(base)
|
250
|
+
base.register_hook(
|
251
|
+
:kill_all_panes_after_book,
|
252
|
+
:after,
|
253
|
+
Runbook::Entities::Book,
|
254
|
+
) do |object, metadata|
|
255
|
+
next if metadata[:noop] || metadata[:layout_panes].none?
|
256
|
+
if metadata[:auto]
|
257
|
+
metadata[:toolbox].output("Killing all opened tmux panes...")
|
258
|
+
kill_all_panes(metadata[:layout_panes])
|
259
|
+
else
|
260
|
+
prompt = "Kill all opened panes?"
|
261
|
+
result = metadata[:toolbox].yes?(prompt)
|
262
|
+
if result
|
263
|
+
kill_all_panes(metadata[:layout_panes])
|
264
|
+
end
|
265
|
+
end
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
def self._register_additional_step_whitespace_hook(base)
|
270
|
+
base.register_hook(
|
271
|
+
:add_additional_step_whitespace_hook,
|
272
|
+
:after,
|
273
|
+
Runbook::Statement,
|
274
|
+
) do |object, metadata|
|
275
|
+
if object.parent.is_a?(Runbook::Entities::Step)
|
276
|
+
if object.parent.items.last == object
|
277
|
+
metadata[:toolbox].output("\n")
|
278
|
+
end
|
279
|
+
end
|
280
|
+
end
|
281
|
+
end
|
282
|
+
end
|
283
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module Runbook
|
2
|
+
class Runner
|
3
|
+
attr_reader :book
|
4
|
+
|
5
|
+
def initialize(book)
|
6
|
+
@book = book
|
7
|
+
end
|
8
|
+
|
9
|
+
def run(
|
10
|
+
run: :ssh_kit,
|
11
|
+
noop: false,
|
12
|
+
auto: false,
|
13
|
+
paranoid: true,
|
14
|
+
start_at: "0"
|
15
|
+
)
|
16
|
+
run = "Runbook::Runs::#{run.to_s.camelize}".constantize
|
17
|
+
toolbox = Runbook::Toolbox.new
|
18
|
+
metadata = Util::StickyHash.new.merge({
|
19
|
+
noop: noop,
|
20
|
+
auto: auto,
|
21
|
+
paranoid: Util::Glue.new(paranoid),
|
22
|
+
start_at: Util::Glue.new(start_at || "0"),
|
23
|
+
toolbox: Util::Glue.new(toolbox),
|
24
|
+
book_title: book.title,
|
25
|
+
}).
|
26
|
+
merge(Runbook::Entities::Book.initial_run_metadata).
|
27
|
+
merge(additional_metadata)
|
28
|
+
|
29
|
+
stored_pose = _stored_position(metadata)
|
30
|
+
if metadata[:start_at] == "0" && stored_pose
|
31
|
+
if _resume_previous_pose?(metadata, stored_pose)
|
32
|
+
metadata[:start_at] = stored_pose
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
if metadata[:start_at] != "0"
|
37
|
+
Util::Repo.load(metadata)
|
38
|
+
end
|
39
|
+
|
40
|
+
book.run(run, metadata)
|
41
|
+
end
|
42
|
+
|
43
|
+
def additional_metadata
|
44
|
+
{
|
45
|
+
layout_panes: {},
|
46
|
+
repo: {},
|
47
|
+
reverse: Util::Glue.new(false),
|
48
|
+
reversed: Util::Glue.new(false),
|
49
|
+
}
|
50
|
+
end
|
51
|
+
|
52
|
+
def _stored_position(metadata)
|
53
|
+
Runbook::Util::StoredPose.load(metadata)
|
54
|
+
end
|
55
|
+
|
56
|
+
def _resume_previous_pose?(metadata, pose)
|
57
|
+
return false if metadata[:auto] || metadata[:noop]
|
58
|
+
pose_msg = "Previous position detected: #{pose}"
|
59
|
+
metadata[:toolbox].output(pose_msg)
|
60
|
+
resume_msg = "Do you want to resume at this position?"
|
61
|
+
metadata[:toolbox].yes?(resume_msg)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,186 @@
|
|
1
|
+
module Runbook::Runs
|
2
|
+
module SSHKit
|
3
|
+
include Runbook::Run
|
4
|
+
extend Runbook::Helpers::SSHKitHelper
|
5
|
+
|
6
|
+
def self.included(base)
|
7
|
+
base.extend(ClassMethods)
|
8
|
+
end
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
def runbook__statements__assert(object, metadata)
|
12
|
+
cmd_ssh_config = find_ssh_config(object, :cmd_ssh_config)
|
13
|
+
|
14
|
+
if metadata[:noop]
|
15
|
+
ssh_config_output = render_ssh_config_output(cmd_ssh_config)
|
16
|
+
metadata[:toolbox].output(ssh_config_output) unless ssh_config_output.empty?
|
17
|
+
interval_msg = "(running every #{object.interval} second(s))"
|
18
|
+
metadata[:toolbox].output("[NOOP] Assert: `#{object.cmd}` returns 0 #{interval_msg}")
|
19
|
+
if object.timeout > 0 || object.attempts > 0
|
20
|
+
timeout_msg = object.timeout > 0 ? "#{object.timeout} second(s)" : nil
|
21
|
+
attempts_msg = object.attempts > 0 ? "#{object.attempts} attempts" : nil
|
22
|
+
giveup_msg = "after #{[timeout_msg, attempts_msg].compact.join(" or ")}, give up..."
|
23
|
+
metadata[:toolbox].output(giveup_msg)
|
24
|
+
if object.timeout_statement
|
25
|
+
object.timeout_statement.parent = object.parent
|
26
|
+
object.timeout_statement.run(self, metadata.dup)
|
27
|
+
end
|
28
|
+
metadata[:toolbox].output("and exit")
|
29
|
+
end
|
30
|
+
return
|
31
|
+
end
|
32
|
+
|
33
|
+
gave_up = false
|
34
|
+
test_args = ssh_kit_command(object.cmd, raw: object.cmd_raw)
|
35
|
+
test_options = ssh_kit_command_options(cmd_ssh_config)
|
36
|
+
|
37
|
+
with_ssh_config(cmd_ssh_config) do
|
38
|
+
time = Time.now
|
39
|
+
count = object.attempts
|
40
|
+
while !(test(*test_args, test_options))
|
41
|
+
if ((count -= 1) == 0)
|
42
|
+
gave_up = true
|
43
|
+
break
|
44
|
+
end
|
45
|
+
|
46
|
+
if (object.timeout > 0 && Time.now - time > object.timeout)
|
47
|
+
gave_up = true
|
48
|
+
break
|
49
|
+
end
|
50
|
+
|
51
|
+
sleep(object.interval)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
if gave_up
|
56
|
+
error_msg = "Error! Assertion `#{object.cmd}` failed"
|
57
|
+
metadata[:toolbox].error(error_msg)
|
58
|
+
if object.timeout_statement
|
59
|
+
object.timeout_statement.parent = object.parent
|
60
|
+
object.timeout_statement.run(self, metadata.dup)
|
61
|
+
end
|
62
|
+
raise Runbook::Runner::ExecutionError, error_msg
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def runbook__statements__capture(object, metadata)
|
67
|
+
_handle_capture(object, metadata) do |ssh_config, capture_args, capture_options|
|
68
|
+
if (ssh_config[:servers].size > 1)
|
69
|
+
warn_msg = "Warning: `capture` does not support multiple servers. Use `capture_all` instead.\n"
|
70
|
+
metadata[:toolbox].warn(warn_msg)
|
71
|
+
end
|
72
|
+
|
73
|
+
result = ""
|
74
|
+
with_ssh_config(ssh_config) do
|
75
|
+
result = capture(*capture_args, capture_options)
|
76
|
+
end
|
77
|
+
result
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def runbook__statements__capture_all(object, metadata)
|
82
|
+
_handle_capture(object, metadata) do |ssh_config, capture_args, capture_options|
|
83
|
+
result = {}
|
84
|
+
mutex = Mutex.new
|
85
|
+
with_ssh_config(ssh_config) do
|
86
|
+
hostname = self.host.hostname
|
87
|
+
capture_result = capture(*capture_args, capture_options)
|
88
|
+
mutex.synchronize { result[hostname] = capture_result }
|
89
|
+
end
|
90
|
+
result
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def _handle_capture(object, metadata, &block)
|
95
|
+
ssh_config = find_ssh_config(object)
|
96
|
+
|
97
|
+
if metadata[:noop]
|
98
|
+
ssh_config_output = render_ssh_config_output(ssh_config)
|
99
|
+
metadata[:toolbox].output(ssh_config_output) unless ssh_config_output.empty?
|
100
|
+
metadata[:toolbox].output("[NOOP] Capture: `#{object.cmd}` into #{object.into}")
|
101
|
+
return
|
102
|
+
end
|
103
|
+
|
104
|
+
metadata[:toolbox].output("\n") # for formatting
|
105
|
+
|
106
|
+
capture_args = ssh_kit_command(object.cmd, raw: object.raw)
|
107
|
+
capture_options = ssh_kit_command_options(ssh_config)
|
108
|
+
capture_options[:strip] = object.strip
|
109
|
+
capture_options[:verbosity] = Logger::INFO
|
110
|
+
|
111
|
+
capture_msg = "Capturing output of `#{object.cmd}`\n\n"
|
112
|
+
metadata[:toolbox].output(capture_msg)
|
113
|
+
|
114
|
+
result = block.call(ssh_config, capture_args, capture_options)
|
115
|
+
|
116
|
+
target = object.parent.dsl
|
117
|
+
target.singleton_class.class_eval { attr_accessor object.into }
|
118
|
+
target.send("#{object.into}=".to_sym, result)
|
119
|
+
end
|
120
|
+
|
121
|
+
def runbook__statements__command(object, metadata)
|
122
|
+
ssh_config = find_ssh_config(object)
|
123
|
+
|
124
|
+
if metadata[:noop]
|
125
|
+
ssh_config_output = render_ssh_config_output(ssh_config)
|
126
|
+
metadata[:toolbox].output(ssh_config_output) unless ssh_config_output.empty?
|
127
|
+
metadata[:toolbox].output("[NOOP] Run: `#{object.cmd}`")
|
128
|
+
return
|
129
|
+
end
|
130
|
+
|
131
|
+
metadata[:toolbox].output("\n") # for formatting
|
132
|
+
|
133
|
+
execute_args = ssh_kit_command(object.cmd, raw: object.raw)
|
134
|
+
exec_options = ssh_kit_command_options(ssh_config)
|
135
|
+
|
136
|
+
with_ssh_config(ssh_config) do
|
137
|
+
execute(*execute_args, exec_options)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def runbook__statements__download(object, metadata)
|
142
|
+
ssh_config = find_ssh_config(object)
|
143
|
+
|
144
|
+
if metadata[:noop]
|
145
|
+
ssh_config_output = render_ssh_config_output(ssh_config)
|
146
|
+
metadata[:toolbox].output(ssh_config_output) unless ssh_config_output.empty?
|
147
|
+
options = object.options
|
148
|
+
to = " to #{object.to}" if object.to
|
149
|
+
opts = " with options #{options}" unless options == {}
|
150
|
+
noop_msg = "[NOOP] Download: #{object.from}#{to}#{opts}"
|
151
|
+
metadata[:toolbox].output(noop_msg)
|
152
|
+
return
|
153
|
+
end
|
154
|
+
|
155
|
+
metadata[:toolbox].output("\n") # for formatting
|
156
|
+
|
157
|
+
with_ssh_config(ssh_config) do
|
158
|
+
download!(object.from, object.to, object.options)
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
def runbook__statements__upload(object, metadata)
|
163
|
+
ssh_config = find_ssh_config(object)
|
164
|
+
|
165
|
+
if metadata[:noop]
|
166
|
+
ssh_config_output = render_ssh_config_output(ssh_config)
|
167
|
+
metadata[:toolbox].output(ssh_config_output) unless ssh_config_output.empty?
|
168
|
+
options = object.options
|
169
|
+
to = " to #{object.to}" if object.to
|
170
|
+
opts = " with options #{options}" unless options == {}
|
171
|
+
noop_msg = "[NOOP] Upload: #{object.from}#{to}#{opts}"
|
172
|
+
metadata[:toolbox].output(noop_msg)
|
173
|
+
return
|
174
|
+
end
|
175
|
+
|
176
|
+
metadata[:toolbox].output("\n") # for formatting
|
177
|
+
|
178
|
+
with_ssh_config(ssh_config) do
|
179
|
+
upload!(object.from, object.to, object.options)
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
extend ClassMethods
|
185
|
+
end
|
186
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Runbook
|
2
|
+
class Statement < Node
|
3
|
+
include Runbook::Hooks::Invoker
|
4
|
+
|
5
|
+
attr_accessor :parent
|
6
|
+
|
7
|
+
def render(view, output, metadata)
|
8
|
+
invoke_with_hooks(view, self, output, metadata) do
|
9
|
+
view.render(self, output, metadata)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def run(run, metadata)
|
14
|
+
return if dynamic? && visited?
|
15
|
+
|
16
|
+
invoke_with_hooks(run, self, metadata) do
|
17
|
+
run.execute(self, metadata)
|
18
|
+
end
|
19
|
+
self.visited!
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Runbook::Statements
|
2
|
+
class Assert < Runbook::Statement
|
3
|
+
attr_reader :cmd, :cmd_ssh_config, :cmd_raw
|
4
|
+
attr_reader :interval, :timeout, :attempts
|
5
|
+
attr_reader :timeout_statement
|
6
|
+
|
7
|
+
def initialize(
|
8
|
+
cmd,
|
9
|
+
cmd_ssh_config: nil,
|
10
|
+
cmd_raw: false,
|
11
|
+
interval: 1,
|
12
|
+
timeout: 0,
|
13
|
+
attempts: 0,
|
14
|
+
timeout_statement: nil
|
15
|
+
)
|
16
|
+
@cmd = cmd
|
17
|
+
@cmd_ssh_config = cmd_ssh_config
|
18
|
+
@cmd_raw = cmd_raw
|
19
|
+
@interval = interval
|
20
|
+
@timeout = timeout
|
21
|
+
@attempts = attempts
|
22
|
+
@timeout_statement = timeout_statement
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Runbook::Statements
|
2
|
+
class Capture < Runbook::Statement
|
3
|
+
attr_reader :cmd, :into, :ssh_config, :raw, :strip
|
4
|
+
|
5
|
+
def initialize(cmd, into:, ssh_config: nil, raw: false, strip: true)
|
6
|
+
@cmd = cmd
|
7
|
+
@into = into
|
8
|
+
@ssh_config = ssh_config
|
9
|
+
@raw = raw
|
10
|
+
@strip = strip
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Runbook::Statements
|
2
|
+
class CaptureAll < Runbook::Statement
|
3
|
+
attr_reader :cmd, :into, :ssh_config, :raw, :strip
|
4
|
+
|
5
|
+
def initialize(cmd, into:, ssh_config: nil, raw: false, strip: true)
|
6
|
+
@cmd = cmd
|
7
|
+
@into = into
|
8
|
+
@ssh_config = ssh_config
|
9
|
+
@raw = raw
|
10
|
+
@strip = strip
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module Runbook::Statements
|
2
|
+
class Download < Runbook::Statement
|
3
|
+
attr_reader :from, :to, :options, :ssh_config
|
4
|
+
|
5
|
+
def initialize(from, to: nil, ssh_config: nil, options: {})
|
6
|
+
@from = from
|
7
|
+
@to = to
|
8
|
+
@ssh_config = ssh_config
|
9
|
+
@options = options
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|