careacademy-runbook 1.2.0

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