runbook 0.14.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
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