runsheets 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +98 -0
  3. data/LICENSE.txt +21 -0
  4. data/README.md +277 -0
  5. data/bin/runsheets +12 -0
  6. data/examples/about-the-examples.md +40 -0
  7. data/examples/directory_based_runbooks/hello/rollback.md +11 -0
  8. data/examples/directory_based_runbooks/hello/runbook.md +40 -0
  9. data/examples/directory_based_runbooks/hello/steps/010-say-hello.md +33 -0
  10. data/examples/directory_based_runbooks/hello/steps/020-inspect-ruby.md +28 -0
  11. data/examples/directory_based_runbooks/hello/steps/030-take-a-breath.md +11 -0
  12. data/examples/directory_based_runbooks/hello/steps/035-keep-a-clock-running.md +21 -0
  13. data/examples/directory_based_runbooks/hello/steps/040-exercise-failure.md +26 -0
  14. data/examples/directory_based_runbooks/hello/steps/045-check-the-greeting.md +18 -0
  15. data/examples/directory_based_runbooks/hello/verify.md +18 -0
  16. data/examples/directory_based_runbooks/staging-teardown/rollback.md +28 -0
  17. data/examples/directory_based_runbooks/staging-teardown/runbook.md +57 -0
  18. data/examples/directory_based_runbooks/staging-teardown/steps/010-confirm-nothing-to-preserve.md +18 -0
  19. data/examples/directory_based_runbooks/staging-teardown/steps/020-snapshot-the-database.md +47 -0
  20. data/examples/directory_based_runbooks/staging-teardown/steps/030-stop-the-pipeline.md +32 -0
  21. data/examples/directory_based_runbooks/staging-teardown/steps/040-drain-the-ecs-services.md +35 -0
  22. data/examples/directory_based_runbooks/staging-teardown/steps/050-record-the-final-row-counts.md +41 -0
  23. data/examples/directory_based_runbooks/staging-teardown/steps/060-delete-the-stack.md +56 -0
  24. data/examples/directory_based_runbooks/staging-teardown/verify.md +40 -0
  25. data/examples/single_md_file_runbooks/db-maintenance.md +163 -0
  26. data/examples/single_md_file_runbooks/disk-space-triage.md +90 -0
  27. data/examples/single_md_file_runbooks/disk-usage-glossary.md +48 -0
  28. data/examples/single_md_file_runbooks/policies/cleanup-policy.md +33 -0
  29. data/examples/single_md_file_runbooks/policies/images/triage-flow.svg +44 -0
  30. data/examples/single_md_file_runbooks/runbooks_with_scripts/.loki +17 -0
  31. data/examples/single_md_file_runbooks/runbooks_with_scripts/asgard-tasks.md +135 -0
  32. data/examples/single_md_file_runbooks/runbooks_with_scripts/checks.loki +27 -0
  33. data/examples/single_md_file_runbooks/runbooks_with_scripts/greet.loki +6 -0
  34. data/lib/runsheets/assets.rb +780 -0
  35. data/lib/runsheets/block.rb +118 -0
  36. data/lib/runsheets/cli.rb +424 -0
  37. data/lib/runsheets/config/defaults.yml +44 -0
  38. data/lib/runsheets/config.rb +139 -0
  39. data/lib/runsheets/diff.rb +67 -0
  40. data/lib/runsheets/execution.rb +144 -0
  41. data/lib/runsheets/executor.rb +157 -0
  42. data/lib/runsheets/expectation.rb +101 -0
  43. data/lib/runsheets/fences.rb +172 -0
  44. data/lib/runsheets/front_matter.rb +29 -0
  45. data/lib/runsheets/library.rb +268 -0
  46. data/lib/runsheets/pages/chooser.rb +408 -0
  47. data/lib/runsheets/pages/search.rb +119 -0
  48. data/lib/runsheets/pages/tree.rb +88 -0
  49. data/lib/runsheets/pages.rb +947 -0
  50. data/lib/runsheets/redactor.rb +88 -0
  51. data/lib/runsheets/renderer.rb +229 -0
  52. data/lib/runsheets/run.rb +330 -0
  53. data/lib/runsheets/run_record.rb +371 -0
  54. data/lib/runsheets/runbook.rb +301 -0
  55. data/lib/runsheets/search.rb +129 -0
  56. data/lib/runsheets/session.rb +307 -0
  57. data/lib/runsheets/session_log.rb +114 -0
  58. data/lib/runsheets/single_file.rb +93 -0
  59. data/lib/runsheets/step.rb +91 -0
  60. data/lib/runsheets/version.rb +5 -0
  61. data/lib/runsheets/web.rb +537 -0
  62. data/lib/runsheets.rb +90 -0
  63. data/sig/runsheets.rbs +4 -0
  64. metadata +226 -0
@@ -0,0 +1,33 @@
1
+ # Cleanup policy
2
+
3
+ What may be deleted to free space, and what may not. Used by the
4
+ [disk space triage](../disk-space-triage.md) runbook. Terms are explained
5
+ in the [glossary](../disk-usage-glossary.md).
6
+
7
+ ![Triage: measure, find, check the policy, clean, verify](images/triage-flow.svg)
8
+
9
+ ## Safe to delete
10
+
11
+ - Build caches and intermediate artifacts the next build recreates.
12
+ - Package manager caches (`~/.cache`, `~/.npm/_cacache`, gem and bundler
13
+ caches).
14
+ - Rotated and compressed logs older than 14 days.
15
+ - Anything under `/tmp` older than a day that no running process holds open.
16
+
17
+ ## Ask first
18
+
19
+ - Uncompressed logs of a service that is still running. Truncate them
20
+ instead of deleting, so the service keeps writing to the same file.
21
+ - Container images and volumes. An unused-looking volume may hold the only
22
+ copy of something.
23
+
24
+ ## Never delete
25
+
26
+ - Database files, even when the database is stopped.
27
+ - Anything under a backup or snapshot directory.
28
+ - Files you cannot identify. Find their owner first.
29
+
30
+ ## When something was deleted by mistake
31
+
32
+ Stop, do not write more to that file system, and contact the owner of the
33
+ host. Restore from backup before trying anything clever.
@@ -0,0 +1,44 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 760 150" width="760" height="150" role="img" aria-labelledby="t d">
2
+ <title id="t">Disk space triage</title>
3
+ <desc id="d">Measure with df, find with du, check the cleanup policy, clean by hand, then verify use is under the threshold; loop back to find if it is not.</desc>
4
+ <defs>
5
+ <marker id="arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
6
+ <path d="M0 0 10 5 0 10z" fill="#8b95a8"/>
7
+ </marker>
8
+ </defs>
9
+ <g font-family="-apple-system, Segoe UI, Helvetica, Arial, sans-serif" font-size="14" text-anchor="middle">
10
+ <g>
11
+ <rect x="10" y="40" width="120" height="56" rx="10" fill="#10263d" stroke="#5ab0ff" stroke-width="1.5"/>
12
+ <text x="70" y="64" fill="#d9dee8" font-weight="600">Measure</text>
13
+ <text x="70" y="83" fill="#8b95a8" font-size="12" font-family="ui-monospace, Menlo, monospace">df -h</text>
14
+ </g>
15
+ <g>
16
+ <rect x="160" y="40" width="120" height="56" rx="10" fill="#10263d" stroke="#5ab0ff" stroke-width="1.5"/>
17
+ <text x="220" y="64" fill="#d9dee8" font-weight="600">Find</text>
18
+ <text x="220" y="83" fill="#8b95a8" font-size="12" font-family="ui-monospace, Menlo, monospace">du -sh</text>
19
+ </g>
20
+ <g>
21
+ <rect x="310" y="40" width="130" height="56" rx="10" fill="#211a3a" stroke="#a78bfa" stroke-width="1.5"/>
22
+ <text x="375" y="64" fill="#d9dee8" font-weight="600">Check policy</text>
23
+ <text x="375" y="83" fill="#8b95a8" font-size="12">plain document</text>
24
+ </g>
25
+ <g>
26
+ <rect x="470" y="40" width="120" height="56" rx="10" fill="#33240f" stroke="#ffb454" stroke-width="1.5"/>
27
+ <text x="530" y="64" fill="#d9dee8" font-weight="600">Clean</text>
28
+ <text x="530" y="83" fill="#8b95a8" font-size="12">by hand</text>
29
+ </g>
30
+ <g>
31
+ <rect x="620" y="40" width="130" height="56" rx="10" fill="#0f2e24" stroke="#3ddc97" stroke-width="1.5"/>
32
+ <text x="685" y="64" fill="#d9dee8" font-weight="600">Verify</text>
33
+ <text x="685" y="83" fill="#8b95a8" font-size="12">use ≤ threshold</text>
34
+ </g>
35
+ <g stroke="#8b95a8" stroke-width="1.5" fill="none" marker-end="url(#arrow)">
36
+ <path d="M130 68h28"/>
37
+ <path d="M280 68h28"/>
38
+ <path d="M440 68h28"/>
39
+ <path d="M590 68h28"/>
40
+ <path d="M685 96v28H220v-26" stroke-dasharray="5 4"/>
41
+ </g>
42
+ <text x="452" y="140" fill="#8b95a8" font-size="12">still over: find more</text>
43
+ </g>
44
+ </svg>
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+ # Asgard entry point for the asgard-tasks.md example runbook.
3
+ #
4
+ # asgard looks for a .loki file in its working directory, then in each
5
+ # directory above it. runsheets runs a single-file runbook's blocks in the
6
+ # runbook's own directory, so `asgard` in one of its blocks finds this file
7
+ # first, ahead of the repository's own .loki further up.
8
+ #
9
+ # This file loads the task files beside it (the glob skips .loki itself):
10
+ # greet.loki greet NAME
11
+ # checks.loki ruby_check, disk_check [DIR], and health, which runs both
12
+
13
+ import "*.loki"
14
+
15
+ class Tasks
16
+ header "runbook tasks: defined beside asgard-tasks.md"
17
+ end
@@ -0,0 +1,135 @@
1
+ ---
2
+ title: Runbook tasks from local .loki files
3
+ when_to_use: >
4
+ An example of a runbook whose blocks call asgard tasks defined in .loki
5
+ files kept beside it, so a long or reusable script lives in a task file
6
+ and the runbook block stays one line.
7
+ prerequisites:
8
+ - The `asgard` command on PATH (`gem install asgard`)
9
+ escalation: Nobody; this runbook only reads.
10
+ tags: [asgard, loki, scripts, safe, example]
11
+ inputs:
12
+ - name: NAME
13
+ prompt: Who to greet
14
+ default: operator
15
+ - name: TARGET_DIR
16
+ prompt: Directory whose file system disk_check measures
17
+ default: /tmp
18
+ - name: MAX_USE
19
+ prompt: Highest acceptable disk use, in percent
20
+ default: "95"
21
+ ---
22
+
23
+ # Runbook tasks from local .loki files
24
+
25
+ This runbook is one markdown file, and its scripts are
26
+ [asgard](https://github.com/MadBomber/asgard) tasks in `.loki` files in
27
+ the same directory:
28
+
29
+ ```text
30
+ runbooks_with_scripts/
31
+ asgard-tasks.md this runbook
32
+ .loki asgard's entry point: imports the other .loki files
33
+ greet.loki greet NAME
34
+ checks.loki ruby_check, disk_check [DIR], and health, which runs both
35
+ ```
36
+
37
+ Two facts make it work:
38
+
39
+ - runsheets runs a single-file runbook's blocks in the directory holding
40
+ the runbook (a step's `cwd` can change that).
41
+ - asgard looks for a `.loki` file in its working directory first, then in
42
+ each directory above. The `.loki` here is found before any other, such as
43
+ the one at the root of the runsheets repository.
44
+
45
+ The `.loki` file marks the directory as asgard's project root and loads
46
+ the task files beside it:
47
+
48
+ ```ruby
49
+ import "*.loki"
50
+ ```
51
+
52
+ Each task file reopens `class Tasks`, as in `greet.loki`:
53
+
54
+ ```ruby
55
+ class Tasks
56
+ desc "greet NAME", "Say hello to NAME"
57
+ def greet(name = "World") = puts "Hello, #{name}, from a task beside the runbook!"
58
+ end
59
+ ```
60
+
61
+ A block then calls the task by name, with runbook inputs as arguments or
62
+ in the environment. Every block in this runbook only reads.
63
+
64
+ ## Find the local task file
65
+ <!-- kind: verify, timeout: 30 -->
66
+
67
+ `asgard --doctor` reports which `.loki` file it is using. It should be the
68
+ one beside this runbook. The doctor's full report also lists every
69
+ imported file and the tasks each defines; the block prints only the line
70
+ that names the file in use.
71
+
72
+ ```bash run
73
+ asgard --doctor | grep -o 'using .loki marker: [^[:cntrl:]]*'
74
+ ```
75
+
76
+ ```text expect
77
+ using .loki marker: {{any}}/runbooks_with_scripts/.loki
78
+ ```
79
+
80
+ ## List the tasks
81
+
82
+ The task list comes from the local `.loki` files, together with asgard's
83
+ built-in commands (`help`, `schedule`, `tree`).
84
+
85
+ ```bash run
86
+ asgard tree
87
+ ```
88
+
89
+ ## Run a task with an argument
90
+ <!-- timeout: 30 -->
91
+
92
+ `greet` is defined in `greet.loki`. The `NAME` input is passed as its
93
+ argument.
94
+
95
+ ```bash run
96
+ asgard greet "$NAME"
97
+ ```
98
+
99
+ ```text expect
100
+ Hello, {{any}}, from a task beside the runbook!
101
+ ```
102
+
103
+ ## Run a task with dependencies
104
+ <!-- timeout: 60 -->
105
+
106
+ `health` in `checks.loki` declares `depends_on [:ruby_check, :disk_check]`,
107
+ so asgard runs both checks in parallel before it. A dependency gets no
108
+ arguments, so `disk_check` reads its directory from `TARGET_DIR` and its
109
+ limit from `MAX_USE`, both runbook inputs in the environment. If either
110
+ check fails, `health` does not run and the block fails.
111
+
112
+ ```bash run
113
+ asgard health
114
+ ```
115
+
116
+ The two checks run in parallel and may print in either order.
117
+
118
+ ```text expect
119
+ {{any}}_check: {{any}}
120
+ {{any}}_check: {{any}}
121
+ health: all checks passed
122
+ ```
123
+
124
+ ## Verify
125
+
126
+ Disk use under `TARGET_DIR` is at or below `MAX_USE`, checked by the same
127
+ task the health check runs, this time called directly with an argument.
128
+
129
+ ```bash run
130
+ asgard disk_check "$TARGET_DIR"
131
+ ```
132
+
133
+ ## Rollback
134
+
135
+ Nothing to undo: no task in this directory changes anything.
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "shellwords"
4
+
5
+ class Tasks
6
+ desc "ruby_check", "Report the Ruby that asgard runs on"
7
+ def ruby_check = puts "ruby_check: ruby #{RUBY_VERSION}"
8
+
9
+ # DIR defaults to TARGET_DIR from the environment, which is how a runbook
10
+ # input reaches a task run as a dependency, with no arguments.
11
+ desc "disk_check [DIR]", "Fail when the file system holding DIR is more than MAX_USE percent full"
12
+ def disk_check(dir = env(:target_dir, "."))
13
+ used = disk_use(dir)
14
+ limit = env(:max_use, "95").to_i
15
+ puts "disk_check: #{dir} is #{used}% used, limit #{limit}%"
16
+ exit 1 if used > limit
17
+ end
18
+
19
+ depends_on [:ruby_check, :disk_check]
20
+ desc "Run every check, in parallel"
21
+ def health = puts "health: all checks passed"
22
+
23
+ private
24
+
25
+ # Percent used of the file system holding +dir+, from POSIX df output.
26
+ def disk_use(dir) = `df -P #{Shellwords.escape(dir)}`.lines.last.split[4].to_i
27
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Tasks
4
+ desc "greet NAME", "Say hello to NAME"
5
+ def greet(name = "World") = puts "Hello, #{name}, from a task beside the runbook!"
6
+ end