runbook 0.14.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/.dockerignore +17 -0
  3. data/.gitignore +4 -0
  4. data/.ruby-version +1 -1
  5. data/.travis.yml +23 -7
  6. data/Appraisals +8 -0
  7. data/CHANGELOG.md +71 -0
  8. data/README.md +159 -25
  9. data/Rakefile +7 -1
  10. data/TODO.md +316 -46
  11. data/dockerfiles/Dockerfile-runbook +18 -0
  12. data/dockerfiles/Dockerfile-sshd +4 -0
  13. data/{samples → examples}/hooks_runbook.rb +0 -0
  14. data/{samples → examples}/layout_runbook.rb +0 -0
  15. data/{samples → examples}/restart_nginx.rb +0 -0
  16. data/{samples → examples}/simple_runbook.rb +0 -0
  17. data/examples/suppress_capture_output.rb +47 -0
  18. data/gemfiles/.bundle/config +2 -0
  19. data/gemfiles/activesupport_5.gemfile +7 -0
  20. data/gemfiles/activesupport_6.gemfile +7 -0
  21. data/lib/runbook.rb +28 -6
  22. data/lib/runbook/airbrussh_context.rb +25 -0
  23. data/lib/runbook/cli.rb +17 -13
  24. data/lib/runbook/configuration.rb +7 -1
  25. data/lib/runbook/entities/book.rb +2 -2
  26. data/lib/runbook/entities/section.rb +2 -2
  27. data/lib/runbook/entities/setup.rb +7 -0
  28. data/lib/runbook/entities/step.rb +2 -2
  29. data/lib/runbook/entity.rb +7 -5
  30. data/lib/runbook/extensions/add.rb +1 -0
  31. data/lib/runbook/extensions/sections.rb +6 -2
  32. data/lib/runbook/extensions/setup.rb +17 -0
  33. data/lib/runbook/extensions/ssh_config.rb +2 -0
  34. data/lib/runbook/extensions/statements.rb +6 -1
  35. data/lib/runbook/extensions/steps.rb +12 -2
  36. data/lib/runbook/generators/project/project.rb +32 -9
  37. data/lib/runbook/helpers/tmux_helper.rb +6 -4
  38. data/lib/runbook/node.rb +10 -0
  39. data/lib/runbook/run.rb +14 -8
  40. data/lib/runbook/runner.rb +2 -0
  41. data/lib/runbook/runs/ssh_kit.rb +20 -13
  42. data/lib/runbook/statement.rb +0 -2
  43. data/lib/runbook/statements/ask.rb +3 -2
  44. data/lib/runbook/statements/assert.rb +11 -2
  45. data/lib/runbook/toolbox.rb +3 -9
  46. data/lib/runbook/util/repo.rb +4 -3
  47. data/lib/runbook/util/runbook.rb +4 -0
  48. data/lib/runbook/util/stored_pose.rb +4 -3
  49. data/lib/runbook/version.rb +1 -1
  50. data/lib/runbook/views/markdown.rb +15 -7
  51. data/runbook.gemspec +12 -8
  52. metadata +108 -29
@@ -1,9 +1,11 @@
1
1
  module Runbook::Helpers
2
2
  module TmuxHelper
3
+ FILE_PERMISSIONS = 0600
4
+
3
5
  def setup_layout(structure, runbook_title:)
4
6
  _remove_stale_layouts
5
7
  layout_file = _layout_file(_slug(runbook_title))
6
- if File.exists?(layout_file)
8
+ if File.exist?(layout_file)
7
9
  stored_layout = ::YAML::load_file(layout_file)
8
10
  if _all_panes_exist?(stored_layout)
9
11
  return stored_layout
@@ -11,14 +13,14 @@ module Runbook::Helpers
11
13
  end
12
14
 
13
15
  _setup_layout(structure).tap do |layout_panes|
14
- File.open(layout_file, 'w') do |f|
16
+ File.open(layout_file, 'w', FILE_PERMISSIONS) do |f|
15
17
  f.write(layout_panes.to_yaml)
16
18
  end
17
19
  end
18
20
  end
19
21
 
20
22
  def send_keys(command, target)
21
- `tmux send-keys -t #{target} #{_pager_escape_sequence} '#{command}' C-m`
23
+ `tmux send-keys -t #{target} #{_pager_escape_sequence} #{Shellwords.escape(command)} C-m`
22
24
  end
23
25
 
24
26
  def kill_all_panes(layout_panes)
@@ -41,7 +43,7 @@ module Runbook::Helpers
41
43
  end
42
44
 
43
45
  def _slug(title)
44
- title.titleize.gsub(/\s+/, "").underscore.dasherize
46
+ title.titleize.gsub(/[\/\s]+/, "").underscore.dasherize
45
47
  end
46
48
 
47
49
  def _all_panes_exist?(stored_layout)
data/lib/runbook/node.rb CHANGED
@@ -1,5 +1,7 @@
1
1
  module Runbook
2
2
  class Node
3
+ attr_accessor :parent
4
+
3
5
  def initialize
4
6
  raise "Should not be initialized"
5
7
  end
@@ -19,5 +21,13 @@ module Runbook
19
21
  def visited?
20
22
  @visited
21
23
  end
24
+
25
+ def parent_entity
26
+ node = self
27
+ while(node && !node.is_a?(Runbook::Entity))
28
+ node = node.parent
29
+ end
30
+ node
31
+ end
22
32
  end
23
33
  end
data/lib/runbook/run.rb CHANGED
@@ -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
@@ -66,11 +70,12 @@ module Runbook
66
70
 
67
71
  if metadata[:noop]
68
72
  default_msg = default ? " (default: #{default})" : ""
69
- metadata[:toolbox].output("[NOOP] Ask: #{object.prompt} (store in: #{object.into})#{default_msg}")
73
+ echo_msg = object.echo ? "" : " (echo: false)"
74
+ metadata[:toolbox].output("[NOOP] Ask: #{object.prompt} (store in: #{object.into})#{default_msg}#{echo_msg}")
70
75
  return
71
76
  end
72
77
 
73
- result = metadata[:toolbox].ask(object.prompt, default: default)
78
+ result = metadata[:toolbox].ask(object.prompt, default: default, echo: object.echo)
74
79
 
75
80
  target = object.parent.dsl
76
81
  target.singleton_class.class_eval { attr_accessor object.into }
@@ -152,7 +157,7 @@ module Runbook
152
157
  next_index = metadata[:index] + 1
153
158
  parent_items = object.parent.items
154
159
  remaining_items = parent_items.slice!(next_index..-1)
155
- object.parent.dsl.instance_exec(object, metadata, &object.block)
160
+ object.parent.dsl.instance_exec(object, metadata, self, &object.block)
156
161
  parent_items[next_index..-1].each { |item| item.dynamic! }
157
162
  parent_items.push(*remaining_items)
158
163
  end
@@ -228,9 +233,9 @@ module Runbook
228
233
  return
229
234
  when :skip
230
235
  position = metadata[:position]
231
- current_step = position.split(".")[-1].to_i
232
- new_step = current_step + 1
233
- start_at = position.gsub(/\.#{current_step}$/, ".#{new_step}")
236
+ split_position = position.split(".")
237
+ new_step = (split_position.pop.to_i + 1).to_s
238
+ start_at = (split_position << new_step).join(".")
234
239
  metadata[:start_at] = start_at
235
240
  when :jump
236
241
  result = toolbox.ask("What position would you like to jump to?")
@@ -253,7 +258,7 @@ module Runbook
253
258
  :after,
254
259
  Runbook::Entities::Book,
255
260
  ) do |object, metadata|
256
- next if metadata[:noop] || metadata[:layout_panes].none?
261
+ next if metadata[:noop] || metadata[:layout_panes].none? || metadata[:keep_panes]
257
262
  if metadata[:auto]
258
263
  metadata[:toolbox].output("Killing all opened tmux panes...")
259
264
  kill_all_panes(metadata[:layout_panes])
@@ -273,7 +278,8 @@ module Runbook
273
278
  :after,
274
279
  Runbook::Statement,
275
280
  ) do |object, metadata|
276
- 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)
277
283
  if object.parent.items.last == object
278
284
  metadata[:toolbox].output("\n")
279
285
  end
@@ -11,6 +11,7 @@ module Runbook
11
11
  noop: false,
12
12
  auto: false,
13
13
  paranoid: true,
14
+ keep_panes: false,
14
15
  start_at: "0"
15
16
  )
16
17
  run = "Runbook::Runs::#{run.to_s.camelize}".constantize
@@ -21,6 +22,7 @@ module Runbook
21
22
  paranoid: Util::Glue.new(paranoid),
22
23
  start_at: Util::Glue.new(start_at || "0"),
23
24
  toolbox: Util::Glue.new(toolbox),
25
+ keep_panes: keep_panes,
24
26
  book_title: book.title,
25
27
  }).
26
28
  merge(Runbook::Entities::Book.initial_run_metadata).
@@ -1,13 +1,20 @@
1
1
  module Runbook::Runs
2
2
  module SSHKit
3
3
  include Runbook::Run
4
- extend Runbook::Helpers::SSHKitHelper
5
4
 
6
5
  def self.included(base)
7
6
  base.extend(ClassMethods)
8
7
  end
9
8
 
10
9
  module ClassMethods
10
+ include Runbook::Helpers::SSHKitHelper
11
+
12
+ def runbook__entities__step(object, metadata)
13
+ airbrussh_context = Runbook.configuration._airbrussh_context
14
+ airbrussh_context.set_current_task_name(object.title)
15
+ super
16
+ end
17
+
11
18
  def runbook__statements__assert(object, metadata)
12
19
  cmd_ssh_config = find_ssh_config(object, :cmd_ssh_config)
13
20
 
@@ -19,18 +26,18 @@ module Runbook::Runs
19
26
  if object.timeout > 0 || object.attempts > 0
20
27
  timeout_msg = object.timeout > 0 ? "#{object.timeout} second(s)" : nil
21
28
  attempts_msg = object.attempts > 0 ? "#{object.attempts} attempts" : nil
22
- giveup_msg = "after #{[timeout_msg, attempts_msg].compact.join(" or ")}, give up..."
23
- metadata[:toolbox].output(giveup_msg)
24
- if object.timeout_statement
25
- object.timeout_statement.parent = object.parent
26
- object.timeout_statement.run(self, metadata.dup)
29
+ abort_msg = "after #{[timeout_msg, attempts_msg].compact.join(" or ")}, abort..."
30
+ metadata[:toolbox].output(abort_msg)
31
+ if object.abort_statement
32
+ object.abort_statement.parent = object.parent
33
+ object.abort_statement.run(self, metadata.dup)
27
34
  end
28
35
  metadata[:toolbox].output("and exit")
29
36
  end
30
37
  return
31
38
  end
32
39
 
33
- gave_up = false
40
+ should_abort = false
34
41
  test_args = ssh_kit_command(object.cmd, raw: object.cmd_raw)
35
42
  test_options = ssh_kit_command_options(cmd_ssh_config)
36
43
 
@@ -39,12 +46,12 @@ module Runbook::Runs
39
46
  count = object.attempts
40
47
  while !(test(*test_args, test_options))
41
48
  if ((count -= 1) == 0)
42
- gave_up = true
49
+ should_abort = true
43
50
  break
44
51
  end
45
52
 
46
53
  if (object.timeout > 0 && Time.now - time > object.timeout)
47
- gave_up = true
54
+ should_abort = true
48
55
  break
49
56
  end
50
57
 
@@ -52,12 +59,12 @@ module Runbook::Runs
52
59
  end
53
60
  end
54
61
 
55
- if gave_up
62
+ if should_abort
56
63
  error_msg = "Error! Assertion `#{object.cmd}` failed"
57
64
  metadata[:toolbox].error(error_msg)
58
- if object.timeout_statement
59
- object.timeout_statement.parent = object.parent
60
- object.timeout_statement.run(self, metadata.dup)
65
+ if object.abort_statement
66
+ object.abort_statement.parent = object.parent
67
+ object.abort_statement.run(self, metadata.dup)
61
68
  end
62
69
  raise Runbook::Runner::ExecutionError, error_msg
63
70
  end
@@ -2,8 +2,6 @@ module Runbook
2
2
  class Statement < Node
3
3
  include Runbook::Hooks::Invoker
4
4
 
5
- attr_accessor :parent
6
-
7
5
  def render(view, output, metadata)
8
6
  invoke_with_hooks(view, self, output, metadata) do
9
7
  view.render(self, output, metadata)
@@ -1,11 +1,12 @@
1
1
  module Runbook::Statements
2
2
  class Ask < Runbook::Statement
3
- attr_reader :prompt, :into, :default
3
+ attr_reader :prompt, :into, :default, :echo
4
4
 
5
- def initialize(prompt, into:, default: nil)
5
+ def initialize(prompt, into:, default: nil, echo: true)
6
6
  @prompt = prompt
7
7
  @into = into
8
8
  @default = default
9
+ @echo = echo
9
10
  end
10
11
  end
11
12
  end
@@ -2,7 +2,12 @@ module Runbook::Statements
2
2
  class Assert < Runbook::Statement
3
3
  attr_reader :cmd, :cmd_ssh_config, :cmd_raw
4
4
  attr_reader :interval, :timeout, :attempts
5
- attr_reader :timeout_statement
5
+ attr_reader :abort_statement
6
+
7
+ def timeout_statement
8
+ Runbook.deprecator.deprecation_warning(:timeout_statement, :abort_statement)
9
+ @abort_statement
10
+ end
6
11
 
7
12
  def initialize(
8
13
  cmd,
@@ -11,6 +16,7 @@ module Runbook::Statements
11
16
  interval: 1,
12
17
  timeout: 0,
13
18
  attempts: 0,
19
+ abort_statement: nil,
14
20
  timeout_statement: nil
15
21
  )
16
22
  @cmd = cmd
@@ -19,7 +25,10 @@ module Runbook::Statements
19
25
  @interval = interval
20
26
  @timeout = timeout
21
27
  @attempts = attempts
22
- @timeout_statement = timeout_statement
28
+ if timeout_statement
29
+ Runbook.deprecator.deprecation_warning(:timeout_statement, :abort_statement)
30
+ end
31
+ @abort_statement = abort_statement || timeout_statement
23
32
  end
24
33
  end
25
34
  end
@@ -6,8 +6,8 @@ module Runbook
6
6
  @prompt = TTY::Prompt.new
7
7
  end
8
8
 
9
- def ask(msg, default: nil)
10
- prompt.ask(msg, default: default)
9
+ def ask(msg, default: nil, echo: true)
10
+ prompt.ask(msg, default: default, echo: echo)
11
11
  end
12
12
 
13
13
  def expand(msg, choices)
@@ -15,13 +15,7 @@ module Runbook
15
15
  end
16
16
 
17
17
  def yes?(msg)
18
- begin
19
- prompt.yes?(msg)
20
- rescue TTY::Prompt::ConversionError
21
- err_msg = "Unknown input: Please type 'y' or 'n'."
22
- warn(err_msg)
23
- retry
24
- end
18
+ prompt.yes?(msg)
25
19
  end
26
20
 
27
21
  def output(msg)
@@ -1,11 +1,12 @@
1
1
  module Runbook::Util
2
2
  module Repo
3
3
  FILE_ID = "data"
4
+ FILE_PERMISSIONS = 0600
4
5
 
5
6
  def self.load(metadata)
6
7
  title = metadata[:book_title]
7
8
  file = _file(title)
8
- if File.exists?(file)
9
+ if File.exist?(file)
9
10
  msg = "Repo file #{file} detected. Loading previous state..."
10
11
  metadata[:toolbox].output(msg)
11
12
  metadata[:repo] = ::YAML::load_file(file)
@@ -13,7 +14,7 @@ module Runbook::Util
13
14
  end
14
15
 
15
16
  def self.save(repo, book_title:)
16
- File.open(_file(book_title), 'w') do |f|
17
+ File.open(_file(book_title), 'w', FILE_PERMISSIONS) do |f|
17
18
  f.write(repo.to_yaml)
18
19
  end
19
20
  end
@@ -27,7 +28,7 @@ module Runbook::Util
27
28
  end
28
29
 
29
30
  def self._slug(title)
30
- title.titleize.gsub(/\s+/, "").underscore.dasherize
31
+ title.titleize.gsub(/[\/\s]+/, "").underscore.dasherize
31
32
  end
32
33
 
33
34
  def self.register_save_repo_hook(base)
@@ -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,17 +1,18 @@
1
1
  module Runbook::Util
2
2
  module StoredPose
3
3
  FILE_ID = "current_position"
4
+ FILE_PERMISSIONS = 0600
4
5
 
5
6
  def self.load(metadata)
6
7
  title = metadata[:book_title]
7
8
  file = _file(title)
8
- if File.exists?(file)
9
+ if File.exist?(file)
9
10
  ::YAML::load_file(file)
10
11
  end
11
12
  end
12
13
 
13
14
  def self.save(repo, book_title:)
14
- File.open(_file(book_title), 'w') do |f|
15
+ File.open(_file(book_title), 'w', FILE_PERMISSIONS) do |f|
15
16
  f.write(repo.to_yaml)
16
17
  end
17
18
  end
@@ -25,7 +26,7 @@ module Runbook::Util
25
26
  end
26
27
 
27
28
  def self._slug(title)
28
- title.titleize.gsub(/\s+/, "").underscore.dasherize
29
+ title.titleize.gsub(/[\/\s]+/, "").underscore.dasherize
29
30
  end
30
31
 
31
32
  def self.register_save_pose_hook(base)
@@ -1,3 +1,3 @@
1
1
  module Runbook
2
- VERSION = "0.14.0"
2
+ VERSION = "1.1.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)
@@ -31,21 +39,21 @@ module Runbook::Views
31
39
  if object.timeout > 0 || object.attempts > 0
32
40
  timeout_msg = object.timeout > 0 ? "#{object.timeout} second(s)" : nil
33
41
  attempts_msg = object.attempts > 0 ? "#{object.attempts} attempts" : nil
34
- giveup_msg = "after #{[timeout_msg, attempts_msg].compact.join(" or ")}, give up..."
35
- output << " #{giveup_msg}\n\n"
36
- if object.timeout_statement
37
- object.timeout_statement.render(self, output, metadata.dup)
42
+ abort_msg = "after #{[timeout_msg, attempts_msg].compact.join(" or ")}, abort..."
43
+ output << " #{abort_msg}\n\n"
44
+ if object.abort_statement
45
+ object.abort_statement.render(self, output, metadata.dup)
38
46
  end
39
47
  output << " and exit\n\n"
40
48
  end
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)
data/runbook.gemspec CHANGED
@@ -31,19 +31,23 @@ Gem::Specification.new do |spec|
31
31
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
32
32
  spec.require_paths = ["lib"]
33
33
 
34
- spec.add_runtime_dependency 'activesupport', '~> 5.0', '>= 5.0.0.1'
35
- spec.add_runtime_dependency "method_source", "~> 0.9"
36
- spec.add_runtime_dependency "sshkit", "1.16"
34
+ spec.add_runtime_dependency "activesupport", ">= 5.0.1.x", "< 7.0"
35
+ spec.add_runtime_dependency "method_source", "~> 1.0"
36
+ spec.add_runtime_dependency "ruby2_keywords", "~> 0.0.4"
37
+ spec.add_runtime_dependency "sshkit", "1.21.0"
37
38
  spec.add_runtime_dependency "sshkit-sudo", "~> 0.1"
38
- spec.add_runtime_dependency "airbrussh", "~> 1.3"
39
+ spec.add_runtime_dependency "airbrussh", "~> 1.4"
39
40
  spec.add_runtime_dependency "thor", "~> 0.20"
40
41
  spec.add_runtime_dependency "tty-progressbar", "~> 0.14"
41
- spec.add_runtime_dependency "tty-prompt", "~> 0.16"
42
+ spec.add_runtime_dependency "tty-prompt", "~> 0.20"
42
43
 
44
+ spec.add_development_dependency "appraisal", "~> 2.2"
43
45
  spec.add_development_dependency "aruba", "~> 0.14"
44
- spec.add_development_dependency "bundler", "~> 1.15"
45
- spec.add_development_dependency "pry", "~> 0.11"
46
+ spec.add_development_dependency "bundler", "~> 2.2"
47
+ spec.add_development_dependency "bcrypt_pbkdf", ">= 1.0", "< 2.0"
48
+ spec.add_development_dependency "ed25519", ">= 1.2", "< 2.0"
49
+ spec.add_development_dependency "pry", "~> 0.13"
46
50
  spec.add_development_dependency "pry-byebug", "~> 3.6"
47
- spec.add_development_dependency "rake", "~> 10.0"
51
+ spec.add_development_dependency "rake", "~> 12.3"
48
52
  spec.add_development_dependency "rspec", "~> 3.0"
49
53
  end