runbook 0.15.0 → 0.16.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9c05a6c35dcd51c2e91eabd838214a00c1bee394f372040c16f89d62d803a352
4
- data.tar.gz: 44122acdd87e97bf0baafb59eed9bbea5be8109e5cd31afa6560da358b454a21
3
+ metadata.gz: d8094ea43f8e96a2c1ccaf981248b84bef276b714460cd0b5c3fa1795f736e3a
4
+ data.tar.gz: 229c3d600c9bcd985451333027ac639a87e922547fa74e6688cef3db96373931
5
5
  SHA512:
6
- metadata.gz: 252644365a05249254a5b4917fced2303d7554c136710adb100fcb7403157da5cb34d6a46d9b2ee9434f8fe89800330a16753cab634e35bb867142156903a706
7
- data.tar.gz: 8d903da453e97dd258ff3f656f64540aa8674646b3c9a305e21cd9afd7018d41e8cf69aad8c9f43930f51ccce6b4cff5efeb6f3abca13c3216b96d7908108fbb
6
+ metadata.gz: be0ebd6c86a3ccaf34af851fe20d660a70752d7c4e32bbae9c14f6406e841ff230971e7c95f206da670594528af36ea3a97b4481e9981ca41753ede34b305fb8
7
+ data.tar.gz: f5b4f2b9d0c043e1eb10242f09892fa5011d1fd36e8325e83f46dccef50db3bd9691677379a82a70cc3697fa41c965c93f924ac8742f20868ce64b097d0aef55
@@ -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
@@ -1 +1 @@
1
- ruby-2.2.3
1
+ ruby-2.5.5
@@ -1,7 +1,8 @@
1
- sudo: false
1
+ dist: xenial
2
2
  language: ruby
3
+ services:
4
+ - docker
3
5
  rvm:
4
- - 2.2.3
5
6
  - 2.2.10
6
7
  - 2.3.8
7
8
  - 2.4.6
@@ -22,3 +23,6 @@ matrix:
22
23
  gemfile: gemfiles/activesupport_6.gemfile
23
24
 
24
25
  before_install: gem install bundler -v 1.15.4
26
+
27
+ script:
28
+ - bundle exec rake spec_all
@@ -4,6 +4,20 @@ This log maintains a list of all substantive changes to Runbook. The log include
4
4
 
5
5
  ## master
6
6
 
7
+ ## `v0.16.0` (2019-11-22)
8
+
9
+ ### Fixes:
10
+
11
+ * Add better error messages for runtime values accessed at compile time
12
+
13
+ ### New Features
14
+
15
+ * Add entity tags and labels
16
+ * Add `setup` entity for initial runbook setup code
17
+ * Add `Runbook.views` method for accessing an array of all defined views
18
+ * Add airbrussh context for better ssh_kit output
19
+ * Backtick "into" targets in markdown view output (Thanks fwolfst!)
20
+
7
21
  ## `v0.15.0` (2019-09-29)
8
22
 
9
23
  ### Fixes:
@@ -11,7 +25,7 @@ This log maintains a list of all substantive changes to Runbook. The log include
11
25
  * Halt the project generator if gem generation fails
12
26
  * Replace timeout_statement with abort_statement for assert statements
13
27
  * Make runbook state files only readable by the current user
14
- * Allow / characters in the title of a runbook (Thanks brafales)
28
+ * Allow / characters in the title of a runbook (Thanks brafales!)
15
29
 
16
30
  ### New Features
17
31
 
data/README.md CHANGED
@@ -118,10 +118,12 @@ Initialize Runbook in your project:
118
118
 
119
119
  * [1. Runbook Anatomy](#runbook-anatomy)
120
120
  * [1.1 Entities, Statements, and Setters](#entities-statements-and-setters)
121
- * [1.1.1 Books, Sections, and Steps](#books-sections-and-steps)
121
+ * [1.1.1 Books, Sections, Steps, and Setup](#books-sections-steps-and-setup)
122
122
  * [1.1.1.1 Books](#books)
123
123
  * [1.1.1.2 Sections](#sections)
124
124
  * [1.1.1.3 Steps](#steps)
125
+ * [1.1.1.4 Setup](#setup)
126
+ * [1.1.1.5 Tags and Labels](#tags-and-labels)
125
127
  * [1.1.2 Statements](#statements)
126
128
  * [1.1.2.1 Ask](#ask)
127
129
  * [1.1.2.2 Assert](#assert)
@@ -169,11 +171,12 @@ Initialize Runbook in your project:
169
171
  * [6.8 Adding to Runbook's Configuration](#adding-to-runbooks-configuration)
170
172
  * [7. Testing](#testing)
171
173
  * [8. Known Issues](#known-issues)
172
- * [9. Development](#development)
173
- * [10. Contributing](#contributing)
174
- * [11. Feature Requests](#feature-requests)
175
- * [12. License](#license)
176
- * [13. Code of Conduct](#code-of-conduct)
174
+ * [9. FAQ](#faq)
175
+ * [10. Development](#development)
176
+ * [11. Contributing](#contributing)
177
+ * [12. Feature Requests](#feature-requests)
178
+ * [13. License](#license)
179
+ * [14. Code of Conduct](#code-of-conduct)
177
180
 
178
181
  ## Runbook Anatomy
179
182
 
@@ -220,7 +223,7 @@ Hierarchically, a runbook looks like this:
220
223
 
221
224
  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.
222
225
 
223
- #### Books, Sections, and Steps
226
+ #### Books, Sections, Steps, and Setup
224
227
 
225
228
  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.
226
229
 
@@ -243,6 +246,60 @@ A book is broken up into sections. Every section requires a title. Sections can
243
246
 
244
247
  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.
245
248
 
249
+ ##### Setup
250
+
251
+ 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.
252
+
253
+ 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.
254
+
255
+ 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.
256
+
257
+ 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.
258
+
259
+ It may be ideal to ensure user input is only asked for once when executing a setup section.
260
+
261
+ ```ruby
262
+ Runbook.book "Make Pizza" do
263
+ setup do
264
+ ruby_command do
265
+ @toppings ||= ENV["TOPPINGS"]
266
+ ask "What toppings would you like?", into: :toppings, default: "cheese" unless @toppings
267
+ end
268
+ end
269
+ end
270
+ ```
271
+
272
+ 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`.
273
+
274
+ ##### Tags and Labels
275
+
276
+ 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).
277
+
278
+ ```ruby
279
+ Runbook.book "Bounce Nodes", :untested do
280
+ step "Disable monitoring", :skip do
281
+ confirm "Have you disabled health monitoring?"
282
+ end
283
+
284
+ step "Restart nodes", :aws_only, :mutator, labels: {rails_env: :production} do
285
+ confirm "Have you restarted the nodes?"
286
+ end
287
+ end
288
+ ```
289
+
290
+ ```ruby
291
+ Runbook::Runs::SSHKit.register_hook(:warn_for_untested_runbook, :before, Runbook::Entities::Book) do |object, metadata|
292
+ warning = "This runbook has not yet been tested. Beware of bugs!"
293
+ metadata[:toolbox].warn(warning) if object.tags.include?(:untested)
294
+ end
295
+
296
+ Runbook::Runs::SSHKit.register_hook(:skip_skippable_entities, :around, Runbook::Entity) do |object, metadata, block|
297
+ next if object.tags.include?(:skip)
298
+ next if object.labels[:rails_env] && object.labels[:rails_env] != ENV["RAILS_ENV"]
299
+ block.call
300
+ end
301
+ ```
302
+
246
303
  #### Statements
247
304
 
248
305
  Statements are the workhorses of runbooks. They comprise all the behavior runbooks execute. Runbook comes with the following statements:
@@ -253,8 +310,14 @@ Prompts the user for a string and stores its value on the containing step entity
253
310
 
254
311
  ```ruby
255
312
  ask "What percentage of requests are failing?", into: :failing_request_percentage, default: "100", echo: true
313
+
314
+ ruby_command do
315
+ note "Failing request percentage: #{@failing_request_percentage}"
316
+ end
256
317
  ```
257
318
 
319
+ 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.
320
+
258
321
  ##### Assert
259
322
 
260
323
  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.
@@ -277,7 +340,7 @@ assert(
277
340
 
278
341
  ##### Capture
279
342
 
280
- 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.
343
+ 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.
281
344
 
282
345
  ```ruby
283
346
  capture %Q{wc -l file.txt | cut -d " " -f 1}, into: :num_lines, strip: true, ssh_config: {user: "root"}
@@ -323,7 +386,7 @@ DESC
323
386
  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
324
387
 
325
388
  ```ruby
326
- download /home/pblesi/rad_file.txt, to: my_rad_file.txt, ssh_config: {servers: ["host1.prod"]}, options: {log_percent: 10}
389
+ download '/home/pblesi/rad_file.txt', to: my_rad_file.txt, ssh_config: {servers: ["host1.prod"]}, options: {log_percent: 10}
327
390
  ```
328
391
 
329
392
  ##### Layout
@@ -789,7 +852,7 @@ step "Inspect plate" do
789
852
  when "carrots"
790
853
  add carrots_book
791
854
  when "peas"
792
- system("runbook exec samples/print_peas.rb")
855
+ system("runbook exec examples/print_peas.rb")
793
856
  else
794
857
  metadata[:toolbox].warn("Found #{veggie}!")
795
858
  end
@@ -864,7 +927,7 @@ Runbook can be extended to add custom functionality.
864
927
 
865
928
  ### Adding New Statements
866
929
 
867
- 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.
930
+ 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.
868
931
 
869
932
  ```ruby
870
933
  module Runbook::Statements
@@ -969,6 +1032,22 @@ end
969
1032
 
970
1033
  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.
971
1034
 
1035
+ 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.
1036
+
1037
+ ```ruby
1038
+ Runbook.runs.each do |run|
1039
+ run.register_hook(
1040
+ :give_words_of_encouragement,
1041
+ :before,
1042
+ Runbook::Entities::Book
1043
+ ) do |object, metadata|
1044
+ metadata[:toolbox].output("You've got this!")
1045
+ end
1046
+ end
1047
+ ```
1048
+
1049
+ 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.
1050
+
972
1051
  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.
973
1052
 
974
1053
  ### Adding New Run Behaviors
@@ -1080,6 +1159,8 @@ command "echo '\\''I love cheese'\\''"
1080
1159
 
1081
1160
  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.
1082
1161
 
1162
+ `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.
1163
+
1083
1164
  ### Specifying env values
1084
1165
 
1085
1166
  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:
@@ -1106,13 +1187,64 @@ not as
1106
1187
  env {rails_env: :production}
1107
1188
  ```
1108
1189
 
1190
+ ## FAQ
1191
+
1192
+ ### Are runbooks compiled?
1193
+
1194
+ 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.
1195
+
1196
+ ### Why are runbooks compiled?
1197
+
1198
+ 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.
1199
+
1200
+ ### Why is my variable/method not set?
1201
+
1202
+ 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.
1203
+
1204
+ ### How do I define and call methods within a runbook?
1205
+
1206
+ 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:
1207
+
1208
+ ```ruby
1209
+ module Adder
1210
+ def add(x, y)
1211
+ x + y
1212
+ end
1213
+ end
1214
+
1215
+ Runbook.book "Add Two Numbers" do
1216
+ step "Add numbers" do
1217
+ ask "X?", into: :x
1218
+ ask "Y?", into: :y
1219
+
1220
+ ruby_command do |rb_cmd, metadata, run|
1221
+ metadata[:toolbox].output("Result: #{Adder.add(x, y)}")
1222
+ end
1223
+ end
1224
+ end
1225
+ ```
1226
+
1227
+ ### Why does my command work on the command line but not in runbook?
1228
+
1229
+ 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:
1230
+
1231
+ * Print the command with any variables substituted
1232
+ * Ensure the command works outside of runbook
1233
+ * Use full paths. The `PATH` environment variable may not be set.
1234
+ * Check for aliases. Aliases are usually not set for non-interactive shells.
1235
+ * Check for environment variables. Differences between your shell environment variables and those set for the executing shell may modify command behavior.
1236
+ * Check for differing behavior between the bourne shell (`sh`) and your shell (usually bash).
1237
+ * Check that quotes are properly being escaped.
1238
+ * Simplify the command you are executing and then slowly build it back up
1239
+ * Check for permissions issues that might cause different execution behavior.
1240
+
1109
1241
  ## Development
1110
1242
 
1111
1243
  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.
1112
1244
 
1113
1245
  To install this gem onto your local machine, run `bundle exec rake install`.
1114
1246
 
1115
- To execute runbook using this repo, run `bundle exec exe/runbook exec samples/layout_runbook.rb`.
1247
+ To execute runbook using this repo, run `bundle exec exe/runbook exec examples/layout_runbook.rb`.
1116
1248
 
1117
1249
  To release a new version:
1118
1250
 
data/Rakefile CHANGED
@@ -1,6 +1,12 @@
1
1
  require "bundler/gem_tasks"
2
2
  require "rspec/core/rake_task"
3
3
 
4
- RSpec::Core::RakeTask.new(:spec)
4
+ spec_all = RSpec::Core::RakeTask.new(:spec_all)
5
+
6
+ spec_task = RSpec::Core::RakeTask.new(:spec)
7
+ spec_task.exclude_pattern = "spec/fullstack/**{,/*/**}/*_spec.rb"
8
+
9
+ spec_fullstack = RSpec::Core::RakeTask.new(:spec_fullstack)
10
+ spec_fullstack.pattern = "spec/fullstack/**{,/*/**}/*_spec.rb"
5
11
 
6
12
  task :default => :spec
data/TODO.md CHANGED
@@ -31,6 +31,7 @@ Runbook is intended to be a light-weight, minimalistic library. This means every
31
31
  ## Features Outline
32
32
 
33
33
  * [Sudo Raw Command Support](#sudo-raw-command-support): Add support for sudo interaction handler for raw commands
34
+ * [Tmux Command Quote Escaping](#tmux-command-quote-escaping): Eliminate the need to escape single quotes in tmux commands
34
35
  * [Always-executed Setup Section](#always-executed-setup-section): Add support for a section that is never skipped
35
36
  * [Shortened Tmux Layout Keys](#shortened-tmux-layout-keys): Make tmux layout keys easier
36
37
  * [Add Host Aliases for SSH Config](#add-host-aliases-for-ssh-config): Use `host` and `hosts` instead of `server` and `servers`
@@ -39,6 +40,7 @@ Runbook is intended to be a light-weight, minimalistic library. This means every
39
40
  * [Runbook Logger](#runbook-logger): A logger for Runbook
40
41
  * [Mutation Command Skipping](#mutation-command-skipping): Allow skipping mutation commands
41
42
  * [Revert Section](#revert-section): A section that can be triggered which will revert the changes of the runbook
43
+ * [Better Error Formatting](#better-error-formatting): More cleanly format errors to aid in troubleshooting issues
42
44
  * [Runbook Versioning](#runbook-versioning): Specify a version in a runbook to allow for supporting backwards incompatible runbook DSL format changes
43
45
  * [Replace Thor CLI](#replace-thor-cli): Replace Thor for Runbook's CLI with something that can be more easily extended and customized
44
46
  * [Goto Statement](#goto-statement): A statement for jumping to a specific step
@@ -55,6 +57,7 @@ Runbook is intended to be a light-weight, minimalistic library. This means every
55
57
  * [Guard Runbook](#guard-runbook): Automatically update Runbook views when a runbook is saved
56
58
  * [Rake Task Interface](#rake-task-interface): Execute and view runbooks with rake tasks
57
59
  * [Multiple Commands In Groups](#multiple-commands-in-groups): Execute multiple sshkit commands for a group of servers before moving on to a new group of servers
60
+ * [Command Line Variables](#command-line-variables): Pass arbitrary variables via the command line
58
61
  * [Yaml Specification](#yaml-specification): Define your runbook using yaml instead of Ruby
59
62
  * [Runbook Web Server](#runbook-web-server): Automatically serve up Runbook views via the web
60
63
  * [Interactive Runbook Launcher](#interactive-runbook-launcher): A CLI launcher to review and execute runbooks
@@ -73,6 +76,28 @@ when raw is specified, number (!) above is not executed. Thus pty is not set to
73
76
 
74
77
  An ideal solution for this would allow the user to set pty true for the command to be executed via ssh_config. Additionally, it would be beneficial to set their own interaction handler. It seems safe to assume that if a user specifies that a command be executed with a pty, then we can set the sudo interaction handler by default if `enable_sudo_prompt` is set, but still allow for the interaction_handler to be overridden.
75
78
 
79
+ #### Tmux Command Quote Escaping
80
+
81
+ **Difficulty: 1**, **Desireability: 1**, **Conceptual Completeness: 1**
82
+
83
+ Using heredocs and command substitution, it is possible to eliminate the need to escape single quotes passed to tmux_commands. This would go a long way to make this command more intuitive and ensure that the string passed by the user gets executed in the desired pane. This can be accomplished by updating the `send_keys` method to the following:
84
+
85
+ ```ruby
86
+ def send_keys(command, target)
87
+ `
88
+ command=$(cat <<'MAGIC_WORD'
89
+ #{command}
90
+ MAGIC_WORD
91
+ )
92
+ tmux send-keys -t #{target} #{_pager_escape_sequence} "$command" C-m
93
+ `
94
+ end
95
+ ```
96
+
97
+ The two main concerns for this change are (1) is this implementation as widely supported as the current implementation (in terms of shells and their versions) and (2) This would technically be a backwards incompatible change where we would want to provide a deprecation path.
98
+
99
+ Additionally, is it worth pursuing a similar pattern for SSHKit?
100
+
76
101
  #### Always-executed Setup Section
77
102
 
78
103
  **Difficulty: 2**, **Desireability: 1**, **Conceptual Completeness: 2**
@@ -127,7 +152,7 @@ It would be nice to be able to skip commands that you know will have an effect o
127
152
 
128
153
  One way to accomplish this is through a tags implementation where entities and statements have a set of tags, and their behavior is controlled based on these tags. I have implemented a spike of adding tags to entities. One complication of skipping mutation commands is that it is not easy to determine if future commands rely on that command executing. This could reduce the overall benefit of flagging and skipping mutation commands. The skipping logic could be implemented using Runbook's hooks feature.
129
154
 
130
- Skipping mutation commands for the sake of testing may also be accomplished through the use of mocks. This could a testing library or perhapsa mocking solution built out for Runbook. Implementing a mocking pattern could resolve the downstream dependency issue of skipping mutation commands. A mocking solution could also be implemented using Runbook hooks.
155
+ Skipping mutation commands for the sake of testing may also be accomplished through the use of mocks. This could be a testing library or perhaps a mocking solution built out for Runbook. Implementing a mocking pattern could resolve the downstream dependency issue of skipping mutation commands. A mocking solution could also be implemented using Runbook hooks.
131
156
 
132
157
  The mutation command skipping behavior could be controlled via an environment variable. It may be controlled via config or other flag, but this feels too specific to be included in the more general config or run options.
133
158
 
@@ -141,6 +166,12 @@ This functionality could be achieved by adding a new revert entity which respond
141
166
 
142
167
  A workaround for this is to simply have a separate revert runbook to execute in the event a rollbock is needed.
143
168
 
169
+ #### Better Error Formatting
170
+
171
+ **Difficulty: 2**, **Desireability: 1**, **Conceptual Completeness: 3**
172
+
173
+ Right now interpretting errors and stacktraces is difficult for people new to Runbook or Ruby. Rspec does a very good job of presenting cleanly formatted errors and helpful error messages for common problems. See their [exception presenter](https://github.com/rspec/rspec-core/blob/6c5628fc0ea83ecf51f1a4d5e0eb2036819a0dab/lib/rspec/core/formatters/exception_presenter.rb) as an example. It would be helpful to examine Rspec's practices around error formatting and providing more helpful error messages to see what can be emulated for Runbook.
174
+
144
175
  #### Runbook Versioning
145
176
 
146
177
  **Difficulty: 1**, **Desireability: 3**, **Conceptual Completeness: 1**
@@ -195,7 +226,7 @@ It would be nice to capture output or status codes from an executed tmux command
195
226
 
196
227
  **Difficulty: 3**, **Desireability: 2**, **Conceptual Completeness: 2**
197
228
 
198
- In some instances a step may require that a previous step be executed before it can safely execute. It would be nice if these dependencies could be codified, so when a step is skipped, you can still ensure it is executed when executing a step that depends on it. Tracking which steps have been executed would probably want to exist for the life of the execution of the runbook, so surving restarts. In an extreme case, executing a runbook may boil down to a rake-like execution. Perhaps it would be possible to make steps independently executable, and then they could be weaved into rake.
229
+ In some instances a step may require that a previous step be executed before it can safely execute. It would be nice if these dependencies could be codified, so when a step is skipped, you can still ensure it is executed when executing a step that depends on it. Tracking which steps have been executed would probably want to exist for the life of the execution of the runbook, so surviving restarts. In an extreme case, executing a runbook may boil down to a rake-like execution. Perhaps it would be possible to make steps independently executable, and then they could be weaved into rake.
199
230
 
200
231
  #### Update Command Counts
201
232
 
@@ -256,6 +287,12 @@ When specifying to execute a command against multiple servers in groups using th
256
287
 
257
288
  I do not have an idea of how to cleanly accomplish this.
258
289
 
290
+ #### Command Line Variables
291
+
292
+ **Difficulty: 1**, **Desireability: 3**, **Conceptual Completeness: 2**
293
+
294
+ Add a `vars` command-line option for runtime variables passed through the command line. These arbitrary command-line vars would be stored in the metadata and accessible at runtime (for example in a `ruby_command` block). These can be useful for modifying the runbook's behavior within hooks in conjunction with tags and labels. This functionality already exists however with the use of environment variables. Environment variables are also available at compile time, where as these command line variables are not. In order to make the command line variables available at compile time, they would have to be set in a global context. It is not clear if these values would persist between runs. It may be best practice to save them and overwrite them if re-defined on the command line, but it is not clear if this is intuitive.
295
+
259
296
  #### Yaml Specification
260
297
 
261
298
  **Difficulty: 2**, **Desireability: 2**, **Conceptual Completeness: 1**
@@ -0,0 +1,18 @@
1
+ FROM ruby:2.5
2
+ RUN apt-get update -qq && apt-get install -y build-essential tmux
3
+
4
+ RUN mkdir /runbook
5
+ WORKDIR /runbook
6
+
7
+ RUN mkdir /runbook/bin
8
+ ADD bin/setup /runbook/bin/setup
9
+
10
+ ADD runbook.gemspec /runbook/runbook.gemspec
11
+ ADD Gemfile /runbook/Gemfile
12
+
13
+ RUN mkdir -p /runbook/lib/runbook
14
+ ADD lib/runbook/version.rb /runbook/lib/runbook/version.rb
15
+
16
+ RUN bin/setup
17
+
18
+ ADD . /runbook
File without changes
File without changes
File without changes
File without changes
@@ -12,6 +12,8 @@ require "tty-progressbar"
12
12
  require "tty-prompt"
13
13
  require "thor/group"
14
14
 
15
+ require "runbook/airbrussh_context"
16
+
15
17
  require "runbook/configuration"
16
18
 
17
19
  require "hacks/ssh_kit"
@@ -30,6 +32,7 @@ require "runbook/node"
30
32
  require "runbook/entity"
31
33
  require "runbook/entities/book"
32
34
  require "runbook/entities/section"
35
+ require "runbook/entities/setup"
33
36
  require "runbook/entities/step"
34
37
 
35
38
  require "runbook/statement"
@@ -74,6 +77,7 @@ require "runbook/extensions/add"
74
77
  require "runbook/extensions/description"
75
78
  require "runbook/extensions/shared_variables"
76
79
  require "runbook/extensions/sections"
80
+ require "runbook/extensions/setup"
77
81
  require "runbook/extensions/ssh_config"
78
82
  require "runbook/extensions/statements"
79
83
  require "runbook/extensions/steps"
@@ -86,24 +90,36 @@ ActiveSupport::Inflector.inflections(:en) do |inflect|
86
90
  end
87
91
 
88
92
  module Runbook
89
- def self.book(title, &block)
93
+ def self.book(title, *tags, labels: {}, &block)
90
94
  Configuration.load_config
91
- Entities::Book.new(title).tap do |book|
95
+ Entities::Book.new(title, tags: tags, labels: labels).tap do |book|
92
96
  book.dsl.instance_eval(&block)
93
97
  register(book)
94
98
  end
95
99
  end
96
100
 
97
- def self.section(title, &block)
101
+ def self.section(title, *tags, labels: {}, &block)
98
102
  Configuration.load_config
99
- Entities::Section.new(title).tap do |section|
103
+ Entities::Section.new(title, tags: tags, labels: labels).tap do |section|
100
104
  section.dsl.instance_eval(&block)
101
105
  end
102
106
  end
103
107
 
104
- def self.step(title=nil, &block)
108
+ def self.setup(*tags, labels: {}, &block)
105
109
  Configuration.load_config
106
- Entities::Step.new(title).tap do |step|
110
+ Entities::Setup.new(tags: tags, labels: labels).tap do |setup|
111
+ setup.dsl.instance_eval(&block)
112
+ end
113
+ end
114
+
115
+ def self.step(title=nil, *tags, labels: {}, &block)
116
+ if title.is_a?(Symbol)
117
+ tags.unshift(title)
118
+ title = nil
119
+ end
120
+
121
+ Configuration.load_config
122
+ Entities::Step.new(title, tags: tags, labels: labels).tap do |step|
107
123
  step.dsl.instance_eval(&block) if block
108
124
  end
109
125
  end
@@ -115,4 +131,8 @@ module Runbook
115
131
  def self.books
116
132
  @books ||= []
117
133
  end
134
+
135
+ def self.runtime_methods
136
+ @runtime_methods ||= []
137
+ end
118
138
  end
@@ -0,0 +1,25 @@
1
+ module Runbook
2
+ class AirbrusshContext
3
+ attr_reader :history, :current_task_name
4
+
5
+ def initialize(config=Airbrussh.configuration)
6
+ @history = []
7
+ end
8
+
9
+ def register_new_command(command)
10
+ hist_entry = command.to_s
11
+ first_execution = history.last != hist_entry
12
+ history << hist_entry if first_execution
13
+ first_execution
14
+ end
15
+
16
+ def position(command)
17
+ history.rindex(command.to_s)
18
+ end
19
+
20
+ def set_current_task_name(task_name)
21
+ @current_task_name = task_name
22
+ history.clear
23
+ end
24
+ end
25
+ end
@@ -104,8 +104,23 @@ module Runbook
104
104
  unless File.exist?(runbook)
105
105
  raise Thor::InvocationError, "#{cmd}: cannot access #{runbook}: No such file or directory"
106
106
  end
107
- load(runbook)
108
- Runbook.books.last || eval(File.read(runbook))
107
+
108
+ begin
109
+ load(runbook)
110
+ Runbook.books.last || eval(File.read(runbook))
111
+ rescue NameError => e
112
+ if Runbook.runtime_methods.include?(e.name)
113
+ message = (
114
+ "Runtime method `#{e.name}` cannot be referenced at " \
115
+ "compile time. Wrap statements referencing it in a " \
116
+ "`ruby_command` block in order to invoke the code at " \
117
+ "runtime."
118
+ )
119
+ raise e, message, e.backtrace
120
+ end
121
+
122
+ raise e
123
+ end
109
124
  end
110
125
  end
111
126
  end
@@ -19,6 +19,7 @@ module Runbook
19
19
  end
20
20
 
21
21
  class Configuration
22
+ attr_accessor :_airbrussh_context
22
23
  attr_accessor :ssh_kit
23
24
  attr_accessor :enable_sudo_prompt
24
25
  attr_reader :use_same_sudo_password
@@ -89,11 +90,16 @@ module Runbook
89
90
 
90
91
  def initialize
91
92
  self.ssh_kit = SSHKit.config
92
- ssh_kit.output = Airbrussh::Formatter.new(
93
+ formatter = Airbrussh::Formatter.new(
93
94
  $stdout,
94
95
  banner: nil,
95
96
  command_output: true,
97
+ context: AirbrusshContext,
96
98
  )
99
+ ssh_kit.output = formatter
100
+ self._airbrussh_context = formatter.formatters.find do |fmt|
101
+ fmt.is_a?(Airbrussh::ConsoleFormatter)
102
+ end.context
97
103
  self.enable_sudo_prompt = true
98
104
  self.use_same_sudo_password = true
99
105
  end
@@ -1,7 +1,7 @@
1
1
  module Runbook::Entities
2
2
  class Book < Runbook::Entity
3
- def initialize(title)
4
- super(title)
3
+ def initialize(title, tags: [], labels: {})
4
+ super(title, tags: tags, labels: labels)
5
5
  end
6
6
 
7
7
  # Seed data for 'render' tree traversal method
@@ -1,7 +1,7 @@
1
1
  module Runbook::Entities
2
2
  class Section < Runbook::Entity
3
- def initialize(title)
4
- super(title)
3
+ def initialize(title, tags: [], labels: {})
4
+ super(title, tags: tags, labels: labels)
5
5
  end
6
6
  end
7
7
  end
@@ -0,0 +1,7 @@
1
+ module Runbook::Entities
2
+ class Setup < Runbook::Entity
3
+ def initialize(tags: [], labels: {})
4
+ super("Setup", tags: tags, labels: labels)
5
+ end
6
+ end
7
+ end
@@ -1,7 +1,7 @@
1
1
  module Runbook::Entities
2
2
  class Step < Runbook::Entity
3
- def initialize(title=nil)
4
- super(title)
3
+ def initialize(title=nil, tags: [], labels: {})
4
+ super(title, tags: tags, labels: labels)
5
5
  end
6
6
  end
7
7
  end
@@ -8,10 +8,12 @@ module Runbook
8
8
  end
9
9
 
10
10
  attr_accessor :parent
11
- attr_reader :title, :dsl
11
+ attr_reader :title, :tags, :labels, :dsl
12
12
 
13
- def initialize(title, parent: nil)
13
+ def initialize(title, tags: [], labels: {}, parent: nil)
14
14
  @title = title
15
+ @tags = tags
16
+ @labels = labels
15
17
  @parent = parent
16
18
  @dsl = "#{self.class}::DSL".constantize.new(self)
17
19
  end
@@ -92,7 +94,8 @@ module Runbook
92
94
 
93
95
  def _run_metadata(items, item, metadata, index)
94
96
  pos_index = items.select do |item|
95
- item.is_a?(Entity)
97
+ item.is_a?(Entity) &&
98
+ !item.is_a?(Runbook::Entities::Setup)
96
99
  end.index(item)
97
100
 
98
101
  if pos_index
@@ -9,5 +9,6 @@ module Runbook::Extensions
9
9
 
10
10
  Runbook::Entities::Book::DSL.prepend(Add::DSL)
11
11
  Runbook::Entities::Section::DSL.prepend(Add::DSL)
12
+ Runbook::Entities::Setup::DSL.prepend(Add::DSL)
12
13
  Runbook::Entities::Step::DSL.prepend(Add::DSL)
13
14
  end
@@ -1,8 +1,12 @@
1
1
  module Runbook::Extensions
2
2
  module Sections
3
3
  module DSL
4
- def section(title, &block)
5
- Runbook::Entities::Section.new(title).tap do |section|
4
+ def section(title, *tags, labels: {}, &block)
5
+ Runbook::Entities::Section.new(
6
+ title,
7
+ tags: tags,
8
+ labels: labels,
9
+ ).tap do |section|
6
10
  parent.add(section)
7
11
  section.dsl.instance_eval(&block)
8
12
  end
@@ -0,0 +1,17 @@
1
+ module Runbook::Extensions
2
+ module Setup
3
+ module DSL
4
+ def setup(*tags, labels: {}, &block)
5
+ Runbook::Entities::Setup.new(
6
+ tags: tags,
7
+ labels: labels,
8
+ ).tap do |setup|
9
+ parent.add(setup)
10
+ setup.dsl.instance_eval(&block) if block
11
+ end
12
+ end
13
+ end
14
+ end
15
+
16
+ Runbook::Entities::Book::DSL.prepend(Setup::DSL)
17
+ end
@@ -71,6 +71,8 @@ module Runbook::Extensions
71
71
  Runbook::Entities::Step::DSL.prepend(SSHConfig::DSL)
72
72
  Runbook::Entities::Section.prepend(SSHConfig)
73
73
  Runbook::Entities::Section::DSL.prepend(SSHConfig::DSL)
74
+ Runbook::Entities::Setup.prepend(SSHConfig)
75
+ Runbook::Entities::Setup::DSL.prepend(SSHConfig::DSL)
74
76
  Runbook::Entities::Book.prepend(SSHConfig)
75
77
  Runbook::Entities::Book::DSL.prepend(SSHConfig::DSL)
76
78
  end
@@ -5,6 +5,10 @@ module Runbook::Extensions
5
5
  if (klass = Statements::DSL._statement_class(name))
6
6
  klass.new(*args, &block).tap do |statement|
7
7
  parent.add(statement)
8
+
9
+ if statement.respond_to?(:into)
10
+ Runbook.runtime_methods << statement.into
11
+ end
8
12
  end
9
13
  else
10
14
  super
@@ -22,5 +26,6 @@ module Runbook::Extensions
22
26
  end
23
27
  end
24
28
 
29
+ Runbook::Entities::Setup::DSL.prepend(Statements::DSL)
25
30
  Runbook::Entities::Step::DSL.prepend(Statements::DSL)
26
31
  end
@@ -1,8 +1,17 @@
1
1
  module Runbook::Extensions
2
2
  module Steps
3
3
  module DSL
4
- def step(title=nil, &block)
5
- Runbook::Entities::Step.new(title).tap do |step|
4
+ def step(title=nil, *tags, labels: {}, &block)
5
+ if title.is_a?(Symbol)
6
+ tags.unshift(title)
7
+ title = nil
8
+ end
9
+
10
+ Runbook::Entities::Step.new(
11
+ title,
12
+ tags: tags,
13
+ labels: labels,
14
+ ).tap do |step|
6
15
  parent.add(step)
7
16
  step.dsl.instance_eval(&block) if block
8
17
  end
@@ -36,6 +36,10 @@ module Runbook
36
36
  metadata[:toolbox].output("Section #{metadata[:position]}: #{object.title}\n\n")
37
37
  end
38
38
 
39
+ def runbook__entities__setup(object, metadata)
40
+ metadata[:toolbox].output("Setup:\n\n")
41
+ end
42
+
39
43
  def runbook__entities__step(object, metadata)
40
44
  toolbox = metadata[:toolbox]
41
45
  title = " #{object.title}".rstrip
@@ -274,7 +278,8 @@ module Runbook
274
278
  :after,
275
279
  Runbook::Statement,
276
280
  ) do |object, metadata|
277
- if object.parent.is_a?(Runbook::Entities::Step)
281
+ if object.parent.is_a?(Runbook::Entities::Step) ||
282
+ object.parent.is_a?(Runbook::Entities::Setup)
278
283
  if object.parent.items.last == object
279
284
  metadata[:toolbox].output("\n")
280
285
  end
@@ -9,6 +9,12 @@ module Runbook::Runs
9
9
  module ClassMethods
10
10
  include Runbook::Helpers::SSHKitHelper
11
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
+
12
18
  def runbook__statements__assert(object, metadata)
13
19
  cmd_ssh_config = find_ssh_config(object, :cmd_ssh_config)
14
20
 
@@ -11,6 +11,10 @@ module Runbook
11
11
  _child_modules(Runbook::Runs)
12
12
  end
13
13
 
14
+ def self.views
15
+ _child_modules(Runbook::Views)
16
+ end
17
+
14
18
  def self.generators
15
19
  _child_classes(Runbook::Generators)
16
20
  end
@@ -1,3 +1,3 @@
1
1
  module Runbook
2
- VERSION = "0.15.0"
2
+ VERSION = "0.16.0"
3
3
  end
@@ -13,6 +13,14 @@ module Runbook::Views
13
13
  output << "#{heading} #{metadata[:index]+1}. #{object.title}\n\n"
14
14
  end
15
15
 
16
+ def self.runbook__entities__setup(object, output, metadata)
17
+ output << "[] #{object.title}\n\n"
18
+
19
+ ssh_config = find_ssh_config(object)
20
+ ssh_config_output = render_ssh_config_output(ssh_config)
21
+ output << "#{ssh_config_output}\n" unless ssh_config_output.empty?
22
+ end
23
+
16
24
  def self.runbook__entities__step(object, output, metadata)
17
25
  output << "#{metadata[:index]+1}. [] #{object.title}\n\n"
18
26
 
@@ -23,7 +31,7 @@ module Runbook::Views
23
31
 
24
32
  def self.runbook__statements__ask(object, output, metadata)
25
33
  default_msg = object.default ? " (default: #{object.default})" : ""
26
- output << " #{object.prompt} into #{object.into}#{default_msg}\n\n"
34
+ output << " #{object.prompt} into `#{object.into}`#{default_msg}\n\n"
27
35
  end
28
36
 
29
37
  def self.runbook__statements__assert(object, output, metadata)
@@ -41,11 +49,11 @@ module Runbook::Views
41
49
  end
42
50
 
43
51
  def self.runbook__statements__capture(object, output, metadata)
44
- output << " capture: `#{object.cmd}` into #{object.into}\n\n"
52
+ output << " capture: `#{object.cmd}` into `#{object.into}`\n\n"
45
53
  end
46
54
 
47
55
  def self.runbook__statements__capture_all(object, output, metadata)
48
- output << " capture_all: `#{object.cmd}` into #{object.into}\n\n"
56
+ output << " capture_all: `#{object.cmd}` into `#{object.into}`\n\n"
49
57
  end
50
58
 
51
59
  def self.runbook__statements__command(object, output, metadata)
@@ -35,7 +35,7 @@ Gem::Specification.new do |spec|
35
35
  spec.add_runtime_dependency "method_source", "~> 0.9"
36
36
  spec.add_runtime_dependency "sshkit", "1.16"
37
37
  spec.add_runtime_dependency "sshkit-sudo", "~> 0.1"
38
- spec.add_runtime_dependency "airbrussh", "~> 1.3"
38
+ spec.add_runtime_dependency "airbrussh", "~> 1.4"
39
39
  spec.add_runtime_dependency "thor", "~> 0.20"
40
40
  spec.add_runtime_dependency "tty-progressbar", "~> 0.14"
41
41
  spec.add_runtime_dependency "tty-prompt", "~> 0.16"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: runbook
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.15.0
4
+ version: 0.16.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - pblesi
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-09-29 00:00:00.000000000 Z
11
+ date: 2019-11-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -78,14 +78,14 @@ dependencies:
78
78
  requirements:
79
79
  - - "~>"
80
80
  - !ruby/object:Gem::Version
81
- version: '1.3'
81
+ version: '1.4'
82
82
  type: :runtime
83
83
  prerelease: false
84
84
  version_requirements: !ruby/object:Gem::Requirement
85
85
  requirements:
86
86
  - - "~>"
87
87
  - !ruby/object:Gem::Version
88
- version: '1.3'
88
+ version: '1.4'
89
89
  - !ruby/object:Gem::Dependency
90
90
  name: thor
91
91
  requirement: !ruby/object:Gem::Requirement
@@ -236,6 +236,7 @@ executables:
236
236
  extensions: []
237
237
  extra_rdoc_files: []
238
238
  files:
239
+ - ".dockerignore"
239
240
  - ".gitignore"
240
241
  - ".rspec"
241
242
  - ".ruby-gemset"
@@ -251,6 +252,11 @@ files:
251
252
  - TODO.md
252
253
  - bin/console
253
254
  - bin/setup
255
+ - dockerfiles/Dockerfile-runbook
256
+ - examples/hooks_runbook.rb
257
+ - examples/layout_runbook.rb
258
+ - examples/restart_nginx.rb
259
+ - examples/simple_runbook.rb
254
260
  - exe/runbook
255
261
  - gemfiles/.bundle/config
256
262
  - gemfiles/activesupport_5.gemfile
@@ -260,18 +266,21 @@ files:
260
266
  - images/runbook_execution_modes.png
261
267
  - lib/hacks/ssh_kit.rb
262
268
  - lib/runbook.rb
269
+ - lib/runbook/airbrussh_context.rb
263
270
  - lib/runbook/cli.rb
264
271
  - lib/runbook/cli_base.rb
265
272
  - lib/runbook/configuration.rb
266
273
  - lib/runbook/dsl.rb
267
274
  - lib/runbook/entities/book.rb
268
275
  - lib/runbook/entities/section.rb
276
+ - lib/runbook/entities/setup.rb
269
277
  - lib/runbook/entities/step.rb
270
278
  - lib/runbook/entity.rb
271
279
  - lib/runbook/errors.rb
272
280
  - lib/runbook/extensions/add.rb
273
281
  - lib/runbook/extensions/description.rb
274
282
  - lib/runbook/extensions/sections.rb
283
+ - lib/runbook/extensions/setup.rb
275
284
  - lib/runbook/extensions/shared_variables.rb
276
285
  - lib/runbook/extensions/ssh_config.rb
277
286
  - lib/runbook/extensions/statements.rb
@@ -327,10 +336,6 @@ files:
327
336
  - lib/runbook/viewer.rb
328
337
  - lib/runbook/views/markdown.rb
329
338
  - runbook.gemspec
330
- - samples/hooks_runbook.rb
331
- - samples/layout_runbook.rb
332
- - samples/restart_nginx.rb
333
- - samples/simple_runbook.rb
334
339
  homepage: https://github.com/braintree/runbook/
335
340
  licenses:
336
341
  - MIT