runbook 0.13.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.dockerignore +17 -0
- data/.gitignore +4 -0
- data/.ruby-version +1 -1
- data/.travis.yml +25 -2
- data/Appraisals +8 -0
- data/CHANGELOG.md +65 -0
- data/README.md +330 -163
- data/Rakefile +7 -1
- data/TODO.md +316 -46
- data/bin/console +2 -0
- data/dockerfiles/Dockerfile-runbook +18 -0
- data/dockerfiles/Dockerfile-sshd +4 -0
- data/{samples → examples}/hooks_runbook.rb +0 -0
- data/{samples → examples}/layout_runbook.rb +0 -0
- data/{samples → examples}/restart_nginx.rb +0 -0
- data/{samples → examples}/simple_runbook.rb +0 -0
- data/examples/suppress_capture_output.rb +47 -0
- data/gemfiles/.bundle/config +2 -0
- data/gemfiles/activesupport_5.gemfile +7 -0
- data/gemfiles/activesupport_6.gemfile +7 -0
- data/lib/runbook.rb +26 -6
- data/lib/runbook/airbrussh_context.rb +25 -0
- data/lib/runbook/cli.rb +32 -6
- data/lib/runbook/configuration.rb +11 -1
- data/lib/runbook/entities/book.rb +2 -2
- data/lib/runbook/entities/section.rb +2 -2
- data/lib/runbook/entities/setup.rb +7 -0
- data/lib/runbook/entities/step.rb +2 -2
- data/lib/runbook/entity.rb +6 -4
- data/lib/runbook/extensions/add.rb +1 -0
- data/lib/runbook/extensions/sections.rb +6 -2
- data/lib/runbook/extensions/setup.rb +17 -0
- data/lib/runbook/extensions/ssh_config.rb +2 -0
- data/lib/runbook/extensions/statements.rb +5 -0
- data/lib/runbook/extensions/steps.rb +12 -2
- data/lib/runbook/generators/project/project.rb +5 -1
- data/lib/runbook/helpers/tmux_helper.rb +6 -4
- data/lib/runbook/{installer.rb → initializer.rb} +3 -3
- data/lib/runbook/node.rb +10 -0
- data/lib/runbook/run.rb +16 -9
- data/lib/runbook/runs/ssh_kit.rb +20 -13
- data/lib/runbook/statement.rb +0 -2
- data/lib/runbook/statements/ask.rb +3 -2
- data/lib/runbook/statements/assert.rb +11 -2
- data/lib/runbook/toolbox.rb +3 -9
- data/lib/runbook/util/repo.rb +4 -3
- data/lib/runbook/util/runbook.rb +14 -0
- data/lib/runbook/util/stored_pose.rb +4 -3
- data/lib/runbook/version.rb +1 -1
- data/lib/runbook/views/markdown.rb +15 -7
- data/runbook.gemspec +14 -11
- metadata +101 -36
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1c6c617aa11aeff1ab914c2f6d4d7b4db2cc5f46e6c70acc67a97eda7ba5b3a6
|
4
|
+
data.tar.gz: 3d68b0e53afbddc94ac0fa7c56eab9a8ac78ad104830c551f79c477314a81c4f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cd186e2e3d0bde065b1ed0cccecce8c5943ec6ca626dac0cfa38725f4dd4050f5208df8343e770c602f2ec6cf7b38be9b01fee69ac0f304a16a14fe6c610378a
|
7
|
+
data.tar.gz: 4ce8c856c8cbd26cbffdfc1f7c2fe1b1fad5f6d5cdfc511d3be731d8db43db5ddeff1abf1d1ab2738374ac5eac27dbe33151e99f97abd604f3ddebb10394bf8c
|
data/.dockerignore
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
**/.git
|
2
|
+
/.bundle/
|
3
|
+
/.yardoc
|
4
|
+
/Gemfile.lock
|
5
|
+
/_yardoc/
|
6
|
+
/coverage/
|
7
|
+
/doc/
|
8
|
+
/pkg/
|
9
|
+
/spec/reports/
|
10
|
+
/tmp/
|
11
|
+
|
12
|
+
# rspec failure tracking
|
13
|
+
.rspec_status
|
14
|
+
|
15
|
+
# recommended:
|
16
|
+
# https://github.com/thoughtbot/appraisal#version-control
|
17
|
+
gemfiles/*.gemfile.lock
|
data/.gitignore
CHANGED
data/.ruby-version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
ruby-2.
|
1
|
+
ruby-2.5.5
|
data/.travis.yml
CHANGED
@@ -1,5 +1,28 @@
|
|
1
|
-
|
1
|
+
dist: xenial
|
2
2
|
language: ruby
|
3
|
+
services:
|
4
|
+
- docker
|
3
5
|
rvm:
|
4
|
-
- 2.2.
|
6
|
+
- 2.2.10
|
7
|
+
- 2.3.8
|
8
|
+
- 2.4.6
|
9
|
+
- 2.5.5
|
10
|
+
- 2.6.3
|
11
|
+
gemfile:
|
12
|
+
- gemfiles/activesupport_5.gemfile
|
13
|
+
- gemfiles/activesupport_6.gemfile
|
14
|
+
matrix:
|
15
|
+
exclude:
|
16
|
+
- rvm: 2.2.3
|
17
|
+
gemfile: gemfiles/activesupport_6.gemfile
|
18
|
+
- rvm: 2.2.10
|
19
|
+
gemfile: gemfiles/activesupport_6.gemfile
|
20
|
+
- rvm: 2.3.8
|
21
|
+
gemfile: gemfiles/activesupport_6.gemfile
|
22
|
+
- rvm: 2.4.6
|
23
|
+
gemfile: gemfiles/activesupport_6.gemfile
|
24
|
+
|
5
25
|
before_install: gem install bundler -v 1.15.4
|
26
|
+
|
27
|
+
script:
|
28
|
+
- bundle exec rake spec_all
|
data/Appraisals
ADDED
data/CHANGELOG.md
CHANGED
@@ -4,6 +4,71 @@ This log maintains a list of all substantive changes to Runbook. The log include
|
|
4
4
|
|
5
5
|
## master
|
6
6
|
|
7
|
+
## `v1.0.0` (2020-07-24)
|
8
|
+
|
9
|
+
### Breaking Changes:
|
10
|
+
|
11
|
+
* Commands and tmux commands that previously escaped single quotes will now require un-escaped single quotes
|
12
|
+
|
13
|
+
### Features:
|
14
|
+
|
15
|
+
* Add Node#parent_entity to find the containinng entity for a node
|
16
|
+
|
17
|
+
### Fixes:
|
18
|
+
|
19
|
+
* Fix bugs requiring escaping single quotes in commands and tmux_commands (BACKWARDS INCOMPATIBLE CHANGE)
|
20
|
+
* Fix `File.exists?` deprecation warning (Thanks onk!)
|
21
|
+
|
22
|
+
### Documentation:
|
23
|
+
|
24
|
+
* Add suppress_capture_output.rb runbook example
|
25
|
+
|
26
|
+
## `v0.16.1` (2019-11-25)
|
27
|
+
|
28
|
+
### Fixes:
|
29
|
+
|
30
|
+
* Fix bug preventing skipping of steps not nested in sections (Thanks celeen!)
|
31
|
+
|
32
|
+
## `v0.16.0` (2019-11-22)
|
33
|
+
|
34
|
+
### Fixes:
|
35
|
+
|
36
|
+
* Add better error messages for runtime values accessed at compile time
|
37
|
+
|
38
|
+
### New Features
|
39
|
+
|
40
|
+
* Add entity tags and labels
|
41
|
+
* Add `setup` entity for initial runbook setup code
|
42
|
+
* Add `Runbook.views` method for accessing an array of all defined views
|
43
|
+
* Add airbrussh context for better ssh_kit output
|
44
|
+
* Backtick "into" targets in markdown view output (Thanks fwolfst!)
|
45
|
+
|
46
|
+
## `v0.15.0` (2019-09-29)
|
47
|
+
|
48
|
+
### Fixes:
|
49
|
+
|
50
|
+
* Halt the project generator if gem generation fails
|
51
|
+
* Replace timeout_statement with abort_statement for assert statements
|
52
|
+
* Make runbook state files only readable by the current user
|
53
|
+
* Allow / characters in the title of a runbook (Thanks brafales!)
|
54
|
+
|
55
|
+
### New Features
|
56
|
+
|
57
|
+
* Allow books to have steps as children
|
58
|
+
* Allow "echo: false" for ask statements
|
59
|
+
* Expose "run" as an argument to ruby_commands
|
60
|
+
|
61
|
+
## `v0.14.0` (2019-08-15)
|
62
|
+
|
63
|
+
### Fixes:
|
64
|
+
|
65
|
+
* Relax gem dependencies to be compatible with gems up to the next major version
|
66
|
+
|
67
|
+
### New Features
|
68
|
+
|
69
|
+
* Alias `install` cli command to `init`
|
70
|
+
* Add `Runbook.config` alias for `Runbook.configuration`
|
71
|
+
|
7
72
|
## `v0.13.0` (2019-07-10)
|
8
73
|
|
9
74
|
### Potentially Breaking Changes:
|
data/README.md
CHANGED
@@ -1,6 +1,10 @@
|
|
1
1
|
# Runbook
|
2
2
|
|
3
3
|
[![Gem Version](https://badge.fury.io/rb/runbook.svg)](https://badge.fury.io/rb/runbook)
|
4
|
+
[![Build Status](https://travis-ci.org/braintree/runbook.svg?branch=master)](https://travis-ci.org/braintree/runbook)
|
5
|
+
[![Gitter](https://badges.gitter.im/braintree/runbook.svg)](https://gitter.im/braintree/runbook?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
|
6
|
+
|
7
|
+
_See our [blog post](https://medium.com/braintree-product-technology/https-medium-com-braintree-product-technology-runbook-be6f072cfc0d) for the philosophy behind Runbook and an overview of its features._
|
4
8
|
|
5
9
|
Runbook provides a DSL for specifying a series of steps to execute an operation. Once your runbook is specified, you can use it to generate a formatted representation of the book or to execute the runbook interactively. For example, you can export your runbook to markdown or use the same runbook to execute commands on remote servers.
|
6
10
|
|
@@ -26,7 +30,7 @@ Lastly, Runbook provides an extendable interface for augmenting the DSL and defi
|
|
26
30
|
* **Dynamic Control Flow** - Runbooks can start execution at any step and can skip steps based on user input.
|
27
31
|
* **Resumable** - Runbooks save their state at each step. If your runbook encounters an error, you can resume your runbook at the previous step after addressing the error.
|
28
32
|
* **Noop and Auto Modes** - Runbooks can be executed in noop mode. This allows you to see what a runbook will do before it executes. Runbooks can be run in auto mode to eliminate the need for human interaction.
|
29
|
-
* **Execution Lifecycle Hooks** - Runbook provides before, after, around hooks to augment its execution behavior.
|
33
|
+
* **Execution Lifecycle Hooks** - Runbook provides before, after, and around hooks to augment its execution behavior.
|
30
34
|
* **Tmux Integration** - Runbook integrates with [tmux](https://github.com/tmux/tmux). You can define terminal pane layouts and send commands to terminal panes.
|
31
35
|
* **Generators** - Runbook provides commands to generate runbooks, extensions, and runbook projects. You can define your own generators for easy, customized runbook creation.
|
32
36
|
* **Extendable DSL** - Runbook's DSL is designed to be extendable. You can extend its DSL to add your own behavior.
|
@@ -107,18 +111,20 @@ Install the Runbook gem:
|
|
107
111
|
|
108
112
|
$ bundle install
|
109
113
|
|
110
|
-
|
114
|
+
Initialize Runbook in your project:
|
111
115
|
|
112
|
-
$ bundle exec runbook
|
116
|
+
$ bundle exec runbook init
|
113
117
|
|
114
118
|
## Contents
|
115
119
|
|
116
120
|
* [1. Runbook Anatomy](#runbook-anatomy)
|
117
121
|
* [1.1 Entities, Statements, and Setters](#entities-statements-and-setters)
|
118
|
-
* [1.1.1 Books, Sections, and
|
122
|
+
* [1.1.1 Books, Sections, Steps, and Setup](#books-sections-steps-and-setup)
|
119
123
|
* [1.1.1.1 Books](#books)
|
120
124
|
* [1.1.1.2 Sections](#sections)
|
121
125
|
* [1.1.1.3 Steps](#steps)
|
126
|
+
* [1.1.1.4 Setup](#setup)
|
127
|
+
* [1.1.1.5 Tags and Labels](#tags-and-labels)
|
122
128
|
* [1.1.2 Statements](#statements)
|
123
129
|
* [1.1.2.1 Ask](#ask)
|
124
130
|
* [1.1.2.2 Assert](#assert)
|
@@ -137,38 +143,41 @@ Install Runbook into your project:
|
|
137
143
|
* [1.1.2.15 Wait](#wait)
|
138
144
|
* [1.1.2.16 Tmux Layouts](#tmux-layouts)
|
139
145
|
* [1.1.3 Setters](#setters)
|
140
|
-
* [2.
|
141
|
-
* [2.1
|
142
|
-
* [
|
143
|
-
* [3
|
144
|
-
|
145
|
-
* [3.
|
146
|
+
* [2. Working With Runbooks](#working-with-runbooks)
|
147
|
+
* [2.1 Via The Command Line](#via-the-command-line)
|
148
|
+
* [2.2 From Within Your Project](#from-within-your-project)
|
149
|
+
* [2.3 Self-executable](#self-executable)
|
150
|
+
* [3. Configuration](#configuration)
|
151
|
+
* [3.1 Configuration Files](#configuration-files)
|
146
152
|
* [4. Best Practices](#best-practices)
|
147
153
|
* [4.1 Iterative Automation](#iterative-automation)
|
148
154
|
* [4.2 Parameterizing Runbooks](#parameterizing-runbooks)
|
149
|
-
* [4.3
|
150
|
-
* [4.4
|
151
|
-
* [4.5
|
152
|
-
* [4.6
|
153
|
-
* [4.7
|
155
|
+
* [4.3 Passing State](#passing-state)
|
156
|
+
* [4.4 Execution Best Practices](#execution-best-practices)
|
157
|
+
* [4.5 Remote Command Execution](#remote-command-execution)
|
158
|
+
* [4.6 Composing Runbooks](#composing-runbooks)
|
159
|
+
* [4.7 Deep Nesting](#deep-nesting)
|
160
|
+
* [4.8 Load Vs. Eval](#load-vs-eval)
|
154
161
|
* [5. Generators](#generators)
|
155
162
|
* [5.1 Predefined Generators](#predefined-generators)
|
156
163
|
* [5.2 Custom Generators](#custom-generators)
|
157
164
|
* [6. Extending Runbook](#extending-runbook)
|
158
|
-
* [6.1 Adding
|
159
|
-
* [6.2
|
160
|
-
* [6.3
|
161
|
-
* [6.4 Adding
|
165
|
+
* [6.1 Adding New Statements](#adding-new-statements)
|
166
|
+
* [6.2 Adding Run and View Functionality](#adding-run-and-view-functionality)
|
167
|
+
* [6.3 DSL Extensions](#dsl-extensions)
|
168
|
+
* [6.4 Adding Runs and Views](#adding-runs-and-views)
|
162
169
|
* [6.5 Augmenting Functionality With Hooks](#augmenting-functionality-with-hooks)
|
163
170
|
* [6.6 Adding New Run Behaviors](#adding-new-run-behaviors)
|
164
171
|
* [6.7 Adding to Runbook's Run Metadata](#adding-to-runbooks-run-metadata)
|
165
172
|
* [6.8 Adding to Runbook's Configuration](#adding-to-runbooks-configuration)
|
166
|
-
* [7.
|
167
|
-
* [8.
|
168
|
-
* [9.
|
169
|
-
* [10.
|
170
|
-
* [11.
|
171
|
-
* [12.
|
173
|
+
* [7. Testing](#testing)
|
174
|
+
* [8. Known Issues](#known-issues)
|
175
|
+
* [9. FAQ](#faq)
|
176
|
+
* [10. Development](#development)
|
177
|
+
* [11. Contributing](#contributing)
|
178
|
+
* [12. Feature Requests](#feature-requests)
|
179
|
+
* [13. License](#license)
|
180
|
+
* [14. Code of Conduct](#code-of-conduct)
|
172
181
|
|
173
182
|
## Runbook Anatomy
|
174
183
|
|
@@ -215,7 +224,7 @@ Hierarchically, a runbook looks like this:
|
|
215
224
|
|
216
225
|
A runbook is composed of entities, statements, and setters. Runbook entities contain either other entities or statements. Examples of entities include Books, Sections, and Steps. They define the structure of the runbook and can be considered the "nodes" of the tree structure. As entities are the nodes of the tree structure, statements are the "leaves" of the structure and comprise the various behaviors or commands of the runbook. Setters, typically referenced from within steps, associate state with the node, which can be accessed by its children.
|
217
226
|
|
218
|
-
#### Books, Sections, and
|
227
|
+
#### Books, Sections, Steps, and Setup
|
219
228
|
|
220
229
|
Entities are composed of a title and a list of items which are their children. Each entity can be rendered with a specific view or executed with a specific run.
|
221
230
|
|
@@ -228,7 +237,7 @@ Runbook.book "Unbalance node" do
|
|
228
237
|
end
|
229
238
|
```
|
230
239
|
|
231
|
-
Every book requires a title. Books can have description, layout, and
|
240
|
+
Every book requires a title. Books can have description, layout, section, and step children. Descriptions describe the book and are declared with the `description` keyword.
|
232
241
|
|
233
242
|
##### Sections
|
234
243
|
|
@@ -238,18 +247,78 @@ A book is broken up into sections. Every section requires a title. Sections can
|
|
238
247
|
|
239
248
|
Steps hold state and group together a set of statements. Steps do not require titles or children. This allows runbooks to be very flexible. You can fill out steps as needed, or be terse when the behavior of the step is self-evident. Steps without titles will not prompt to continue when running in paranoid mode.
|
240
249
|
|
250
|
+
##### Setup
|
251
|
+
|
252
|
+
Setup is a special entity that is always executed. It is not skipped when starting or resuming execution in the middle of a runbook. A prompt is never presented to determine if you should or should not execute the setup section. The setup section is similar to the step entity in that it shares the same DSL. In other words, any keywords available within steps are also available within the setup section.
|
253
|
+
|
254
|
+
The setup section provides two important use cases. First, it allows you ensure any dependent values are defined when executing your runbook. If skipping the initial steps of your runbook and starting in the middle, you can be sure that any initialization steps have been executed. For example, presume you have a runbook that prompts for a list of servers, stops the servers, and then starts them. It would be advantageous to define the prompting server logic in a setup section, so you can start the runbook at the start server step and know that the list of servers is defined.
|
255
|
+
|
256
|
+
Second, if you dynamically define the sections and steps in your runbook based on user input, then doing this in the setup section allows you start the runbook in the middle of the dynamically defined steps.
|
257
|
+
|
258
|
+
Because the setup section is always executed, it's execution should be idempotent. In other words, the setup section should be able to be executed multiple times in a row and produce the same result.
|
259
|
+
|
260
|
+
It may be ideal to ensure user input is only asked for once when executing a setup section.
|
261
|
+
|
262
|
+
```ruby
|
263
|
+
Runbook.book "Make Pizza" do
|
264
|
+
setup do
|
265
|
+
ruby_command do
|
266
|
+
@toppings ||= ENV["TOPPINGS"]
|
267
|
+
ask "What toppings would you like?", into: :toppings, default: "cheese" unless @toppings
|
268
|
+
end
|
269
|
+
end
|
270
|
+
end
|
271
|
+
```
|
272
|
+
|
273
|
+
The above example will set `@toppings` from a passed-in environment variable if present, otherwise it will ask the user to set `@toppings`. If toppings have already has been defined from a previous execution, it will not prompt the user for the value again. Because this logic references a value that is defined at runtime (`@toppings`), it must be wrapped in a `ruby_command`.
|
274
|
+
|
275
|
+
##### Tags and Labels
|
276
|
+
|
277
|
+
Any entity can be associated with arbitrary tags or labels. Once tags or labels are assigned, entity behavior can be modified using [hooks](#augmenting-functionality-with-hooks).
|
278
|
+
|
279
|
+
```ruby
|
280
|
+
Runbook.book "Bounce Nodes", :untested do
|
281
|
+
step "Disable monitoring", :skip do
|
282
|
+
confirm "Have you disabled health monitoring?"
|
283
|
+
end
|
284
|
+
|
285
|
+
step "Restart nodes", :aws_only, :mutator, labels: {rails_env: :production} do
|
286
|
+
confirm "Have you restarted the nodes?"
|
287
|
+
end
|
288
|
+
end
|
289
|
+
```
|
290
|
+
|
291
|
+
```ruby
|
292
|
+
Runbook::Runs::SSHKit.register_hook(:warn_for_untested_runbook, :before, Runbook::Entities::Book) do |object, metadata|
|
293
|
+
warning = "This runbook has not yet been tested. Beware of bugs!"
|
294
|
+
metadata[:toolbox].warn(warning) if object.tags.include?(:untested)
|
295
|
+
end
|
296
|
+
|
297
|
+
Runbook::Runs::SSHKit.register_hook(:skip_skippable_entities, :around, Runbook::Entity) do |object, metadata, block|
|
298
|
+
next if object.tags.include?(:skip)
|
299
|
+
next if object.labels[:rails_env] && object.labels[:rails_env] != ENV["RAILS_ENV"]
|
300
|
+
block.call
|
301
|
+
end
|
302
|
+
```
|
303
|
+
|
241
304
|
#### Statements
|
242
305
|
|
243
306
|
Statements are the workhorses of runbooks. They comprise all the behavior runbooks execute. Runbook comes with the following statements:
|
244
307
|
|
245
308
|
##### Ask
|
246
309
|
|
247
|
-
Prompts the user for a string and stores its value on the containing step entity. Once this statement is executed, its value is accessed as an instance variable under the `into` parameter. This value can be referenced in later statements such as the `ruby_command` statement.
|
310
|
+
Prompts the user for a string and stores its value on the containing step entity. Once this statement is executed, its value is accessed as an instance variable under the `into` parameter. This value can be referenced in later statements such as the `ruby_command` statement. An optional `default` value can be specified. An optional `echo` parameter can be specified to indicate whether typed input should be echoed to the screen.
|
248
311
|
|
249
312
|
```ruby
|
250
|
-
ask "What percentage of requests are failing?", into: :failing_request_percentage, default: "100"
|
313
|
+
ask "What percentage of requests are failing?", into: :failing_request_percentage, default: "100", echo: true
|
314
|
+
|
315
|
+
ruby_command do
|
316
|
+
note "Failing request percentage: #{@failing_request_percentage}"
|
317
|
+
end
|
251
318
|
```
|
252
319
|
|
320
|
+
In the above example, the `note` statement must be wrapped in a `ruby_command` statement. Without wrapping `note` in a `ruby_command`, it would be evaluated at compile time but the user will only be asked for input when the runbook is executed (so `@failing_request_percentage` would not have a value). If you find yourself wrapping many or all runbook statements in ruby commands it may make sense to set these values at compile time using environment variables.
|
321
|
+
|
253
322
|
##### Assert
|
254
323
|
|
255
324
|
Runs the provided `cmd` repeatedly until it returns true. A timeout and maximum number of attempts can be set. You can specify a command to be run if a timeout occurs or the maximum number of attempts is hit. Commands can optionally be specified as `raw`. This tells SSHKit to not perform auto-wrapping of the commands, but execute the exact string on the remote server. See SSHKit's documentation for more details.
|
@@ -262,7 +331,7 @@ assert(
|
|
262
331
|
interval: 3, # seconds
|
263
332
|
timeout: 300, # seconds
|
264
333
|
attempts: 3,
|
265
|
-
|
334
|
+
abort_statement: Runbook::Statements::Command.new(
|
266
335
|
"echo 'help' | mail -s 'need help' page-me@page-me.com",
|
267
336
|
ssh_config: {servers: [:local], parallelization: {strategy: :parallel}},
|
268
337
|
raw: false
|
@@ -272,7 +341,7 @@ assert(
|
|
272
341
|
|
273
342
|
##### Capture
|
274
343
|
|
275
|
-
Runs the provided `cmd` and captures its output into `into`. An optional `ssh_config` can be specified to configure how the capture command gets run. Capture commands take an optional `strip` parameter that indicates if the returned output should have leading and trailing whitespace removed. Capture commands also take an optional `raw` parameter that tells SSHKit whether the command should be executed as is, or to include the auto-wrapping of the ssh_config.
|
344
|
+
Runs the provided `cmd` and captures its output into `into`. Once captured, this value can be referenced in later statements such as the `ruby_command` statement. An optional `ssh_config` can be specified to configure how the capture command gets run. Capture commands take an optional `strip` parameter that indicates if the returned output should have leading and trailing whitespace removed. Capture commands also take an optional `raw` parameter that tells SSHKit whether the command should be executed as is, or to include the auto-wrapping of the ssh_config.
|
276
345
|
|
277
346
|
```ruby
|
278
347
|
capture %Q{wc -l file.txt | cut -d " " -f 1}, into: :num_lines, strip: true, ssh_config: {user: "root"}
|
@@ -318,7 +387,7 @@ DESC
|
|
318
387
|
Downloads the specified file to `to`. An optional `ssh_config` can be specified to configure how the download command gets run, for example specifying the remote host and remote directory to download from. Optional `options` can be specified that get passed down to the underlying sshkit implementation
|
319
388
|
|
320
389
|
```ruby
|
321
|
-
download /home/pblesi/rad_file.txt, to: my_rad_file.txt, ssh_config: {servers: ["host1.prod"]}, options: {log_percent: 10}
|
390
|
+
download '/home/pblesi/rad_file.txt', to: my_rad_file.txt, ssh_config: {servers: ["host1.prod"]}, options: {log_percent: 10}
|
322
391
|
```
|
323
392
|
|
324
393
|
##### Layout
|
@@ -350,10 +419,10 @@ notice "There be dragons!"
|
|
350
419
|
|
351
420
|
##### Ruby Command
|
352
421
|
|
353
|
-
Executes its block in the context of the parent step. The block is passed the ruby_command statement
|
422
|
+
Executes its block in the context of the parent step. The block is passed the ruby_command statement, the execution metadata, and the run as arguments.
|
354
423
|
|
355
424
|
```ruby
|
356
|
-
ruby_command do |rb_cmd, metadata|
|
425
|
+
ruby_command do |rb_cmd, metadata, run|
|
357
426
|
if (failure_rate = rb_cmd.parent.failing_request_percentage) > 25
|
358
427
|
`echo 'Help! failure rate at #{failure_rate}' | mail -s 'High failure rate!' page-me@page-me.com`
|
359
428
|
else
|
@@ -539,51 +608,10 @@ step do
|
|
539
608
|
end
|
540
609
|
```
|
541
610
|
|
542
|
-
## Configuration
|
543
|
-
|
544
|
-
Runbook is configured using its configuration object. Below is an example of how to configure Runbook.
|
545
|
-
|
546
|
-
```ruby
|
547
|
-
Runbook.configure do |config|
|
548
|
-
config.ssh_kit.umask = "077"
|
549
|
-
config.ssh_kit.default_runner_config = {in: :groups, limit: 5}
|
550
|
-
config.ssh_kit.default_env = {rails_env: :staging}
|
551
|
-
|
552
|
-
config.enable_sudo_prompt = true
|
553
|
-
config.use_same_sudo_password = true
|
554
|
-
end
|
555
|
-
```
|
556
|
-
|
557
|
-
If the `ssh_kit` configuration looks familiar, that's because it's an SSHKit Configuration object. Any configuration options set on `SSHKit.config` can be set on `config.ssh_kit`.
|
558
|
-
|
559
|
-
### Configuration Files
|
560
|
-
|
561
|
-
Runbook loads configuration from a number of predefined files. Runbook will attempt to load configuration from the following locations on startup: `/etc/runbook.conf`, a `Runbookfile` in a parent directory from the current directory, a `.runbook.conf` file in the current user's home directory, a file specified with `--config` on the command line, any configuration specified in a runbook. Runbook will also load configuration from these files in this order of preference, respectively. That is, configuration values specified at the project level (`Runbookfile`) will override configuration values set at the global level (`/etc/runbook.conf`), etc.
|
562
|
-
|
563
611
|
## Working With Runbooks
|
564
612
|
|
565
613
|
You can integrate with Runbook in several different ways. You can create your own project or incorporate Runbook into your existing projects. You can use Runbook via the command line. And you can even create self-executing runbooks.
|
566
614
|
|
567
|
-
### From Within Your Project
|
568
|
-
|
569
|
-
Runbooks can be executed using the `Runbook::Viewer` and `Runbook::Runner` classes.
|
570
|
-
|
571
|
-
#### Executing a runbook using `Runbook::Viewer`
|
572
|
-
|
573
|
-
```ruby
|
574
|
-
Runbook::Viewer.new(book).generate(view: :markdown)
|
575
|
-
```
|
576
|
-
|
577
|
-
In this case book is a `Runbook::Entities::Book` and `:markdown` refers to the specific view type (`Runbook::Views::Markdown`).
|
578
|
-
|
579
|
-
#### Executing a runbook using `Runbook::Runner`
|
580
|
-
|
581
|
-
```ruby
|
582
|
-
Runbook::Runner.new(book).run(run: :ssh_kit, noop: false, auto: false, paranoid: true, start_at: "0")
|
583
|
-
```
|
584
|
-
|
585
|
-
This will execute `book` using the `Runbook::Runs::SSHKit` run type. It will not run the book in `noop` mode. It will not run the book in `auto` mode. It will run the book in `paranoid` mode. And it will start at the beginning of the book. Noop mode runs the book without side-effects outside of printing what it will execute. Auto mode will skip any prompts in the runbook. If there are any required prompts in the runbook (such as the `ask` statement), then the run will fail. Paranoid mode will prompt the user for whether they should continue at every step. Finally `start_at` can be used to skip parts of the runbook or to restart at a certain point in the event of failures, stopping and starting the runbook, etc.
|
586
|
-
|
587
615
|
### Via The Command Line
|
588
616
|
|
589
617
|
Runbook can be used to write stand-alone runbook files that can be executed via the command line. Below is a list of examples of how to use Runbook via the command line.
|
@@ -637,6 +665,26 @@ the runbook at runtime.
|
|
637
665
|
$ HOSTS="appbox{01..30}.prod" ENV="production" runbook exec --start-at 1.2.1 my_runbook.rb
|
638
666
|
```
|
639
667
|
|
668
|
+
### From Within Your Project
|
669
|
+
|
670
|
+
Runbooks can be executed using the `Runbook::Viewer` and `Runbook::Runner` classes. Using these classes, you can invoke runbooks from within your existing codebase. This could be ideal for several reasons. It allows you to maintain a consistent interface with other system tasks such as rake tasks or cap tasks. It allows you to perform setup or sanity check functionality before executing your runbooks. And it allows you to load an environment to be accessed within your runbooks, such as providing access to a canonical list of servers or shared business logic.
|
671
|
+
|
672
|
+
#### Executing a runbook using `Runbook::Viewer`
|
673
|
+
|
674
|
+
```ruby
|
675
|
+
Runbook::Viewer.new(book).generate(view: :markdown)
|
676
|
+
```
|
677
|
+
|
678
|
+
In this case book is a `Runbook::Entities::Book` and `:markdown` refers to the specific view type (`Runbook::Views::Markdown`).
|
679
|
+
|
680
|
+
#### Executing a runbook using `Runbook::Runner`
|
681
|
+
|
682
|
+
```ruby
|
683
|
+
Runbook::Runner.new(book).run(run: :ssh_kit, noop: false, auto: false, paranoid: true, start_at: "0")
|
684
|
+
```
|
685
|
+
|
686
|
+
This will execute `book` using the `Runbook::Runs::SSHKit` run type. It will not run the book in `noop` mode. It will not run the book in `auto` mode. It will run the book in `paranoid` mode. And it will start at the beginning of the book. Noop mode runs the book without side-effects outside of printing what it will execute. Auto mode will skip any prompts in the runbook. If there are any required prompts in the runbook (such as the `ask` statement), then the run will fail. Paranoid mode will prompt the user for whether they should continue at every step. Finally `start_at` can be used to skip parts of the runbook or to restart at a certain point in the event of failures, stopping and starting the runbook, etc.
|
687
|
+
|
640
688
|
### Self-executable
|
641
689
|
|
642
690
|
Runbooks can be written to be self-executable
|
@@ -673,6 +721,27 @@ runbook = Runbook.books.last # Runbooks register themselves to Runbook.books whe
|
|
673
721
|
Runbook::Runner.new(runbook).run(auto: true)
|
674
722
|
```
|
675
723
|
|
724
|
+
## Configuration
|
725
|
+
|
726
|
+
Runbook is configured using its configuration object. Below is an example of how to configure Runbook.
|
727
|
+
|
728
|
+
```ruby
|
729
|
+
Runbook.configure do |config|
|
730
|
+
config.ssh_kit.umask = "077"
|
731
|
+
config.ssh_kit.default_runner_config = {in: :groups, limit: 5}
|
732
|
+
config.ssh_kit.default_env = {rails_env: :staging}
|
733
|
+
|
734
|
+
config.enable_sudo_prompt = true
|
735
|
+
config.use_same_sudo_password = true
|
736
|
+
end
|
737
|
+
```
|
738
|
+
|
739
|
+
If the `ssh_kit` configuration looks familiar, that's because it's an SSHKit Configuration object. Any configuration options set on `SSHKit.config` can be set on `config.ssh_kit`.
|
740
|
+
|
741
|
+
### Configuration Files
|
742
|
+
|
743
|
+
Runbook loads configuration from a number of predefined files. Runbook will attempt to load configuration from the following locations on startup: `/etc/runbook.conf`, a `Runbookfile` in a parent directory from the current directory, a `.runbook.conf` file in the current user's home directory, a file specified with `--config` on the command line, any configuration specified in a runbook. Runbook will also load configuration from these files in this order of preference, respectively. That is, configuration values specified at the project level (`Runbookfile`) will override configuration values set at the global level (`/etc/runbook.conf`), etc.
|
744
|
+
|
676
745
|
## Best Practices
|
677
746
|
|
678
747
|
The following are best practices when developing your own runbooks.
|
@@ -695,12 +764,60 @@ rails_env = `facter rails_env`
|
|
695
764
|
customer_list = File.read("/tmp/customer_list.txt")
|
696
765
|
```
|
697
766
|
|
767
|
+
### Passing State
|
768
|
+
|
769
|
+
Runbook provides a number of different mechanisms for passing state throughout a runbook. For any data that is known at compile time, local variables can be used because Runbooks are lexically scoped.
|
770
|
+
|
771
|
+
```ruby
|
772
|
+
home_planet = "Krypton"
|
773
|
+
Runbook.book "Book Using Local Variables" do
|
774
|
+
hometown = "Smallville"
|
775
|
+
|
776
|
+
section "My Biography" do
|
777
|
+
step do
|
778
|
+
note "Home Planet: #{home_planet}"
|
779
|
+
note "Home Town: #{hometown}"
|
780
|
+
end
|
781
|
+
end
|
782
|
+
end
|
783
|
+
```
|
784
|
+
|
785
|
+
When looking to pass data generated at runtime, for example data from `ruby_command`, `ask`, or `capture` statements, Runbook persists and synchronizes instance variables for these commands.
|
786
|
+
|
787
|
+
```ruby
|
788
|
+
Runbook.book "Book Using Instance Variables" do
|
789
|
+
section "The Transported Man" do
|
790
|
+
step do
|
791
|
+
ask "Who's the greatest magician?", into: :greatest, default: "Alfred Borden"
|
792
|
+
ruby_command { @magician = "Robert Angier" }
|
793
|
+
end
|
794
|
+
|
795
|
+
step do
|
796
|
+
ruby_command {
|
797
|
+
note "Magician: #{@magician}"
|
798
|
+
note "Greatest Magician: #{@greatest}"
|
799
|
+
}
|
800
|
+
end
|
801
|
+
end
|
802
|
+
end
|
803
|
+
```
|
804
|
+
|
805
|
+
Instance variables are only passed between statements such as `ruby_command`. They should not be set on entities such as steps, sections, or books. Instance variables are persisted using `metadata[:repo]`. They are copied to the repo after each statement finishes executing and copied from the repo before each statement starts executing. Because instance variables utilize the repo, they are persisted if the runbook is stopped and restarted at the same step.
|
806
|
+
|
807
|
+
Be careful with your naming of instance variables as it is possible to clobber the step's DSL methods because they share the same namespace.
|
808
|
+
|
698
809
|
### Execution Best Practices
|
699
810
|
|
700
811
|
As a best practice, Runbooks should always be nooped before they are run. This will allow you to catch runtime errors such as using the ask statement when running in auto mode, typos in your runbooks, and to visually confirm what will be executed.
|
701
812
|
|
702
813
|
Additionally, it can be nice to have a generated view of the runbook you are executing to have a good high-level overview of the steps in the runbook.
|
703
814
|
|
815
|
+
### Remote Command Execution
|
816
|
+
|
817
|
+
Runbook uses [SSHKit](https://github.com/capistrano/sshkit) for remote command execution. When specifying `servers`, you are specifying the target host to execute the command. If you want to use a non-standard port or login using a different user than your current user, then you can specify the `server` as `lucy@host1.prod:2345`. Alternatively, you can use an ssh config file such as `~/.ssh/config` to specify the user and port used to ssh to a given host. See [Capistrano's SSH setup instructions](https://capistranorb.com/documentation/getting-started/installation/#ssh) for further support on setting up SSH to execute commands on remote hosts.
|
818
|
+
|
819
|
+
The `user` setter designates the user you will sudo as once sshed to the remote host. Runbook supports password-protected sudo execution. That is, if your server requires a password to execute commands as another user, Runbook will allow you to enter your password when prompted. The `enable_sudo_prompt` configuration value controls this behavior. Enabling the sudo password prompt requires that your commands execute using a tty, which can lead to unexpected behavior when executing certain commands. Enabling `use_same_sudo_password` will use the same password accross different hosts and users instead of re-prompting for each unique user/host combo.
|
820
|
+
|
704
821
|
### Composing Runbooks
|
705
822
|
|
706
823
|
Runbooks can be composed using the `add` keyword. Below is an example of composing a runbook from smaller, reusable components.
|
@@ -720,6 +837,8 @@ Runbook.book "Update configuration" do
|
|
720
837
|
end
|
721
838
|
```
|
722
839
|
|
840
|
+
If you want to parameterize these runbook snippets, you can place them in a ruby function that takes arguments and generates the desired entity or statement. If these snippets set information that is used by the runbook, such as with `capture` statements, it is a good practice to parameterize where the result is stored. This lets the snippet fit different contexts and makes clear what data is being returned from the snippet.
|
841
|
+
|
723
842
|
### Deep Nesting
|
724
843
|
|
725
844
|
Because the Runbook DSL is declarative, it is generally discouraged to develop elaborate nested decision trees. For example, it is discouraged to use the `ask` statement to gather user feedback, branch on this information in a `ruby_command`, and follow completely separate sets of steps. This is because deep nesting eliminates the benefits of the declarative DSL. You can no longer noop the deeply nested structure for example.
|
@@ -734,7 +853,7 @@ step "Inspect plate" do
|
|
734
853
|
when "carrots"
|
735
854
|
add carrots_book
|
736
855
|
when "peas"
|
737
|
-
system("runbook exec
|
856
|
+
system("runbook exec examples/print_peas.rb")
|
738
857
|
else
|
739
858
|
metadata[:toolbox].warn("Found #{veggie}!")
|
740
859
|
end
|
@@ -759,48 +878,6 @@ runbook = eval(File.read("my_runbook.rb"))
|
|
759
878
|
|
760
879
|
Loading your runbook file is more ideal, but adds slight complexity. This method is prefered because the Ruby mechanism for retrieving source code does not work for code that has been `eval`ed. This means that you will not see `ruby_command` code blocks in view and noop output when using the `eval` method. You will see an "Unable to retrieve source code" message instead.
|
761
880
|
|
762
|
-
### Passing State
|
763
|
-
|
764
|
-
Runbook provides a number of different mechanisms for passing state throughout a runbook. For any data that is known at compile time, local variables can be used because Runbooks are lexically scoped.
|
765
|
-
|
766
|
-
```ruby
|
767
|
-
home_planet = "Krypton"
|
768
|
-
Runbook.book "Book Using Local Variables" do
|
769
|
-
hometown = "Smallville"
|
770
|
-
|
771
|
-
section "My Biography" do
|
772
|
-
step do
|
773
|
-
note "Home Planet: #{home_planet}"
|
774
|
-
note "Home Town: #{hometown}"
|
775
|
-
end
|
776
|
-
end
|
777
|
-
end
|
778
|
-
```
|
779
|
-
|
780
|
-
When looking to pass data generated at runtime, for example data from `ruby_command`, `ask`, or `capture` statements, Runbook persists and synchronizes instance variables for these commands.
|
781
|
-
|
782
|
-
```ruby
|
783
|
-
Runbook.book "Book Using Instance Variables" do
|
784
|
-
section "The Transported Man" do
|
785
|
-
step do
|
786
|
-
ask "Who's the greatest magician?", into: :greatest, default: "Alfred Borden"
|
787
|
-
ruby_command { @magician = "Robert Angier" }
|
788
|
-
end
|
789
|
-
|
790
|
-
step do
|
791
|
-
ruby_command {
|
792
|
-
note "Magician: #{@magician}"
|
793
|
-
note "Greatest Magician: #{@greatest}"
|
794
|
-
}
|
795
|
-
end
|
796
|
-
end
|
797
|
-
end
|
798
|
-
```
|
799
|
-
|
800
|
-
Instance variables are only passed between statements such as `ruby_command`. They should not be set on entities such as steps, sections, or books. Instance variables are persisted using `metadata[:repo]`. They are copied to the repo after each statement finishes executing and copied from the repo before each statement starts executing. Because instance variables utilize the repo, they are persisted if the runbook is stopped and restarted at the same step.
|
801
|
-
|
802
|
-
Be careful with your naming of instance variables as it is possible to clobber the step's DSL methods because they share the same namespace.
|
803
|
-
|
804
881
|
## Generators
|
805
882
|
|
806
883
|
Runbook provides a number of generators accessible via the command line that can be used to generate code for new runbooks, Runbook projects, and Runbook extensions. Additionally, Runbook provides a generator generator so you can define your own custom generators.
|
@@ -849,28 +926,47 @@ Generate your own generator using the `generate generator` command
|
|
849
926
|
|
850
927
|
Runbook can be extended to add custom functionality.
|
851
928
|
|
852
|
-
### Adding
|
929
|
+
### Adding New Statements
|
853
930
|
|
854
|
-
|
931
|
+
In order to add a new statement to your DSL, create a class under `Runbook::Statements` that inherits from `Runbook::Statement`. This statement will be initialized with all arguments passed to the corresponding keyword in the DSL. Remember to also add a corresponding method to runs and views so your new statement can be interpreted in each context.
|
855
932
|
|
856
933
|
```ruby
|
857
|
-
module Runbook::
|
858
|
-
|
859
|
-
|
934
|
+
module Runbook::Statements
|
935
|
+
class Diagram < Runbook::Statement
|
936
|
+
attr_reader :alt_text, :url
|
860
937
|
|
861
|
-
|
862
|
-
|
863
|
-
|
864
|
-
output << "---\n"
|
865
|
-
output << "book:\n"
|
866
|
-
output << " title: #{object.title}\n"
|
938
|
+
def initialize(alt_text, url)
|
939
|
+
@alt_text = alt_text
|
940
|
+
@url = url
|
867
941
|
end
|
942
|
+
end
|
943
|
+
end
|
944
|
+
```
|
868
945
|
|
869
|
-
|
946
|
+
In the above example a keyword `diagram` will be added to the step dsl and its arguments will be used to initialize the Diagram object.
|
947
|
+
|
948
|
+
New statements can be generated using the statement generator.
|
949
|
+
|
950
|
+
$ runbook generate statement diagram --root lib/runbook/extensions
|
951
|
+
|
952
|
+
### Adding Run and View Functionality
|
953
|
+
|
954
|
+
You can add handlers for new statements and entities to your runs and views by prepending the modules with the new desired functionality.
|
955
|
+
|
956
|
+
```ruby
|
957
|
+
module MyRunbook::Extensions
|
958
|
+
module Diagram
|
959
|
+
def self.runbook__entities__diagram(object, output, metadata)
|
960
|
+
output << "![#{object.alt_text}](#{object.url})"
|
961
|
+
end
|
870
962
|
end
|
963
|
+
|
964
|
+
Runbook::Views::Markdown.prepend(Diagram)
|
871
965
|
end
|
872
966
|
```
|
873
967
|
|
968
|
+
If you are not modifying existing methods, you can simply re-open the module to add new methods.
|
969
|
+
|
874
970
|
### DSL Extensions
|
875
971
|
|
876
972
|
You can add arbitrary keywords to your entity DSLs. For example, you could add an alias to Runbook's Book DSL as follows:
|
@@ -889,43 +985,32 @@ module MyRunbook::Extensions
|
|
889
985
|
end
|
890
986
|
```
|
891
987
|
|
892
|
-
|
893
|
-
|
894
|
-
In order to add a new statement to your DSL, create a class under `Runbook::Statements` that inherits from `Runbook::Statement`. This statement will be initialized with all arguments passed to the corresponding keyword in the DSL. Remember to also add a corresponding method to runs and views so your new statement can be interpretted in each context.
|
895
|
-
|
896
|
-
```ruby
|
897
|
-
module Runbook::Statements
|
898
|
-
class Diagram < Runbook::Statement
|
899
|
-
attr_reader :alt_text, :url
|
900
|
-
|
901
|
-
def initialize(alt_text, url)
|
902
|
-
@alt_text = alt_text
|
903
|
-
@url = url
|
904
|
-
end
|
905
|
-
end
|
906
|
-
end
|
907
|
-
```
|
988
|
+
DSL extensions can be generated using the dsl_extension generator.
|
908
989
|
|
909
|
-
|
990
|
+
$ runbook generate dsl_extension aliases --root lib/runbook/extensions
|
910
991
|
|
911
|
-
### Adding
|
992
|
+
### Adding Runs and Views
|
912
993
|
|
913
|
-
You can add
|
994
|
+
You can add new run and view types by defining modules under `Runbook:::Runs` and `Runbook::Views` respectively. They will automatically be accessible from the command line or via the `Runner` and `Viewer` classes. See `lib/runbook/runs/ssh_kit.rb` or `lib/runbook/views/markdown.rb` for examples of how to implement runs and views.
|
914
995
|
|
915
996
|
```ruby
|
916
|
-
module
|
917
|
-
module
|
918
|
-
|
919
|
-
|
997
|
+
module Runbook::Views
|
998
|
+
module Yaml
|
999
|
+
include Runbook::View
|
1000
|
+
|
1001
|
+
# handler names correspond to the entity or statement class name
|
1002
|
+
# Everything is underscored and "::" is replaced by "__"
|
1003
|
+
def self.runbook__entities__book(object, output, metadata)
|
1004
|
+
output << "---\n"
|
1005
|
+
output << "book:\n"
|
1006
|
+
output << " title: #{object.title}\n"
|
920
1007
|
end
|
921
|
-
end
|
922
1008
|
|
923
|
-
|
1009
|
+
# Add other handlers here
|
1010
|
+
end
|
924
1011
|
end
|
925
1012
|
```
|
926
1013
|
|
927
|
-
If you are not modifying existing methods, you can simply re-open the module to add new methods.
|
928
|
-
|
929
1014
|
### Augmenting Functionality With Hooks
|
930
1015
|
|
931
1016
|
You can add `before`, `after`, or `around` hooks to any statement or entity by defining a hook on a `Run` or `View`.
|
@@ -948,6 +1033,22 @@ end
|
|
948
1033
|
|
949
1034
|
When registering a hook, you specify the name of the hook, the type, and the statement or entity to add the hook to. `before` and `after` hooks execute the block before and after executing the entity or statement, respectively. `around` hooks take a block which executes the specified entity or statement. When specifying the class that the hook applies to, you can have the hook apply to all entities by specifying `Runbook::Entity`, all statements by specifying `Runbook::Statement`, or all items by specifying `Object`. Additionally, you can specify any specific entity or statement you would like the hook to apply to.
|
950
1035
|
|
1036
|
+
Hooks are defined on the run or view objects themselves. For example, you would register a hook with `Runbook::Runs::SSHKit` to have the hook be applied to the `SSHKit` run. You would register a hook with the `Runbook::Views::Markdown` view to have hooks apply to this view. If you want to apply a hook to all runs or views, you can use the `Runbook.runs` method or `Runbook.views` method to iterate through the runs or views respectively.
|
1037
|
+
|
1038
|
+
```ruby
|
1039
|
+
Runbook.runs.each do |run|
|
1040
|
+
run.register_hook(
|
1041
|
+
:give_words_of_encouragement,
|
1042
|
+
:before,
|
1043
|
+
Runbook::Entities::Book
|
1044
|
+
) do |object, metadata|
|
1045
|
+
metadata[:toolbox].output("You've got this!")
|
1046
|
+
end
|
1047
|
+
end
|
1048
|
+
```
|
1049
|
+
|
1050
|
+
Hooks can be defined anywhere prior to runbook execution. If defining a hook for only a single runbook, it makes sense to define the hook immediately prior to the runbook definition. If you want a hook to apply to all runbooks in your project, it can be defined in a config file such as the `Runbookfile`. If you want to selectively apply the hook to certain runbooks, it may make sense to define it in a file that can be required by runbooks when it is needed.
|
1051
|
+
|
951
1052
|
When starting at a certain position in the runbook, hooks for any preceding sections and steps will be skipped. After hooks will be run for a parent when starting at a child entity of a parent.
|
952
1053
|
|
953
1054
|
### Adding New Run Behaviors
|
@@ -1024,11 +1125,24 @@ module MyRunbook::Extensions
|
|
1024
1125
|
end
|
1025
1126
|
```
|
1026
1127
|
|
1027
|
-
This will add a `log_level` attribute to Runbook's configuration with a default value of `:info`.
|
1128
|
+
This will add a `log_level` attribute to Runbook's configuration with a default value of `:info`. This configuration value can be accessed via `Runbook.config.log_level`.
|
1129
|
+
|
1130
|
+
## Testing
|
1131
|
+
|
1132
|
+
Runbooks are inherently difficult to test because they are primarily composed of side-effects. That being said, there are a number of strategies you can employ to test your runbooks.
|
1133
|
+
|
1134
|
+
1. Push complex logic to stand-alone Ruby objects that can be tested in isolation
|
1135
|
+
2. Use `TEST` or `DEBUG` environment variables to conditionally disable side-effects during execution
|
1136
|
+
3. Execute your runbooks in staging environments
|
1137
|
+
4. Noop your runbooks to understand what they will be executing before executing them
|
1138
|
+
|
1139
|
+
See Runbook's test suite for more ideas on how to test your runbooks. For example, Runbook uses [aruba](https://github.com/cucumber/aruba) to test Runbook at the CLI level.
|
1140
|
+
|
1141
|
+
Additionally, runbooks should contain their own assertions, sanity checks, monitoring, and alerting to mitigate errors and alert you if intervention is required.
|
1028
1142
|
|
1029
1143
|
## Known Issues
|
1030
1144
|
|
1031
|
-
### Command Quoting
|
1145
|
+
### Command Quoting (Prior to v1.0)
|
1032
1146
|
|
1033
1147
|
Because ssh_config declarations such as `user`, `group`, `path`, `env`, and `umask` are implemented as wrappers around your provided commands, you must be aware that issues can arise if your commands contain characters such as single quotes that are not properly escaped.
|
1034
1148
|
|
@@ -1046,6 +1160,8 @@ command "echo '\\''I love cheese'\\''"
|
|
1046
1160
|
|
1047
1161
|
Alternatively, if you wish to avoid issues with SSHKit command wrapping, you can specify that your commands be executed in raw form, passed directly as written to the specified host.
|
1048
1162
|
|
1163
|
+
`tmux_command` wraps the input passed to it in single quotes. Therefore any single quotes passed to the `tmux_command` should be escaped using `'\\''`. This issue can manifest itself as part of the command not being echoed to the tmux pane.
|
1164
|
+
|
1049
1165
|
### Specifying env values
|
1050
1166
|
|
1051
1167
|
When specifying the `env` for running commands, if you place curly braces `{}` around the env values, it is required to enclose the arguments in parenthesis `()`, otherwise the following syntax error will result:
|
@@ -1072,21 +1188,72 @@ not as
|
|
1072
1188
|
env {rails_env: :production}
|
1073
1189
|
```
|
1074
1190
|
|
1191
|
+
## FAQ
|
1192
|
+
|
1193
|
+
### Are runbooks compiled?
|
1194
|
+
|
1195
|
+
Yes they are. When you define a runbook, a tree data structure is constructed much like an [abstract syntax tree](https://en.wikipedia.org/wiki/Abstract_syntax_tree). This is important because you do not have to worry about any side-effects such as executing server commands when this data structure is compiled. Once compiled, choosing either the run or view to execute the runbook object determines what behavior is executed at each node.
|
1196
|
+
|
1197
|
+
### Why are runbooks compiled?
|
1198
|
+
|
1199
|
+
Runbook is designed to minimize and mitigate issues that arise when running operations in production enviroments. One way this is accomplished is by compiling all statements in the runbook before execution is started. Validations and assertions can be made to reduce the likelihood that a runbook will encounter an error in the middle of an operation. In other words, Runbook provides some guarantees about proper formatting of a runbook before any commands execute that could affect live systems.
|
1200
|
+
|
1201
|
+
### Why is my variable/method not set?
|
1202
|
+
|
1203
|
+
Because runbooks are compiled, statements that set values such as `ask`, `capture`, and `capture_all` statements (using the `:into` keyword) only expose their values at runtime. This means any references to these methods or variables (specified with `:into`) can only happen within `ruby_command` blocks which are evaluated at runtime. If an argument to a statement references the values set by these statements, then the statement must be wrapped in a `ruby_command` block. See [Passing State](#passing-state) for specific examples.
|
1204
|
+
|
1205
|
+
### How do I define and call methods within a runbook?
|
1206
|
+
|
1207
|
+
When defining and referencing your own functions in a runbook, functions should be wrapped in a module so they can be referenced globally. For example:
|
1208
|
+
|
1209
|
+
```ruby
|
1210
|
+
module Adder
|
1211
|
+
def add(x, y)
|
1212
|
+
x + y
|
1213
|
+
end
|
1214
|
+
end
|
1215
|
+
|
1216
|
+
Runbook.book "Add Two Numbers" do
|
1217
|
+
step "Add numbers" do
|
1218
|
+
ask "X?", into: :x
|
1219
|
+
ask "Y?", into: :y
|
1220
|
+
|
1221
|
+
ruby_command do |rb_cmd, metadata, run|
|
1222
|
+
metadata[:toolbox].output("Result: #{Adder.add(x, y)}")
|
1223
|
+
end
|
1224
|
+
end
|
1225
|
+
end
|
1226
|
+
```
|
1227
|
+
|
1228
|
+
### Why does my command work on the command line but not in runbook?
|
1229
|
+
|
1230
|
+
There are a number of reasons why a command may work directly on your command line, but not when executed using the `command` statement. Some possible things to try include:
|
1231
|
+
|
1232
|
+
* Print the command with any variables substituted
|
1233
|
+
* Ensure the command works outside of runbook
|
1234
|
+
* Use full paths. The `PATH` environment variable may not be set.
|
1235
|
+
* Check for aliases. Aliases are usually not set for non-interactive shells.
|
1236
|
+
* Check for environment variables. Differences between your shell environment variables and those set for the executing shell may modify command behavior.
|
1237
|
+
* Check for differing behavior between the bourne shell (`sh`) and your shell (usually bash).
|
1238
|
+
* Check that quotes are properly being escaped.
|
1239
|
+
* Simplify the command you are executing and then slowly build it back up
|
1240
|
+
* Check for permissions issues that might cause different execution behavior.
|
1241
|
+
|
1075
1242
|
## Development
|
1076
1243
|
|
1077
1244
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
1078
1245
|
|
1079
1246
|
To install this gem onto your local machine, run `bundle exec rake install`.
|
1080
1247
|
|
1081
|
-
To execute runbook using this repo, run `bundle exec exe/runbook exec
|
1248
|
+
To execute runbook using this repo, run `bundle exec exe/runbook exec examples/layout_runbook.rb`.
|
1082
1249
|
|
1083
1250
|
To release a new version:
|
1084
1251
|
|
1085
1252
|
1. Update the version number in `version.rb`.
|
1086
1253
|
2. Update the changelog in `CHANGELOG.rb`.
|
1087
1254
|
3. Commit changes with commit messsage: "Bump runbook version to X.Y.Z"
|
1088
|
-
4. Run `
|
1089
|
-
5.
|
1255
|
+
4. Run `gem signin` to ensure you can push the new version to rubygems.org
|
1256
|
+
5. Run `bundle exec rake release`, which will create a git tag for the version and push git commits and tags.
|
1090
1257
|
|
1091
1258
|
## Contributing
|
1092
1259
|
|
@@ -1094,7 +1261,7 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/braint
|
|
1094
1261
|
|
1095
1262
|
## Feature Requests
|
1096
1263
|
|
1097
|
-
Any feature requests are always welcome and will be considered in accordance with time and need. Additionally, existing feature requests are tracked in TODO.md. If you choose to contribute, your contributions will be greatly appreciated.
|
1264
|
+
Any feature requests are always welcome and will be considered in accordance with time and need. Additionally, existing feature requests are tracked in TODO.md. If you choose to contribute, your contributions will be greatly appreciated. Please reach out before creating any substantial pull requests. A bit of discussion can save a lot of time and increase the chances that your pull request will be accepted.
|
1098
1265
|
|
1099
1266
|
## License
|
1100
1267
|
|