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
@@ -0,0 +1,2 @@
1
+ ---
2
+ BUNDLE_RETRY: "1"
@@ -0,0 +1,7 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "activesupport", "~> 5.0"
6
+
7
+ gemspec path: "../"
@@ -0,0 +1,7 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "activesupport", "~> 6.0"
6
+
7
+ gemspec path: "../"
data/lib/runbook.rb CHANGED
@@ -2,8 +2,10 @@ require "tmpdir"
2
2
  require "yaml"
3
3
  require "thread"
4
4
 
5
+ require "active_support/deprecation"
5
6
  require "active_support/inflector"
6
7
  require "method_source"
8
+ require "ruby2_keywords"
7
9
  require "pastel"
8
10
  require "sshkit"
9
11
  require "sshkit/sudo"
@@ -12,6 +14,8 @@ require "tty-progressbar"
12
14
  require "tty-prompt"
13
15
  require "thor/group"
14
16
 
17
+ require "runbook/airbrussh_context"
18
+
15
19
  require "runbook/configuration"
16
20
 
17
21
  require "hacks/ssh_kit"
@@ -30,6 +34,7 @@ require "runbook/node"
30
34
  require "runbook/entity"
31
35
  require "runbook/entities/book"
32
36
  require "runbook/entities/section"
37
+ require "runbook/entities/setup"
33
38
  require "runbook/entities/step"
34
39
 
35
40
  require "runbook/statement"
@@ -74,6 +79,7 @@ require "runbook/extensions/add"
74
79
  require "runbook/extensions/description"
75
80
  require "runbook/extensions/shared_variables"
76
81
  require "runbook/extensions/sections"
82
+ require "runbook/extensions/setup"
77
83
  require "runbook/extensions/ssh_config"
78
84
  require "runbook/extensions/statements"
79
85
  require "runbook/extensions/steps"
@@ -86,24 +92,36 @@ ActiveSupport::Inflector.inflections(:en) do |inflect|
86
92
  end
87
93
 
88
94
  module Runbook
89
- def self.book(title, &block)
95
+ def self.book(title, *tags, labels: {}, &block)
90
96
  Configuration.load_config
91
- Entities::Book.new(title).tap do |book|
97
+ Entities::Book.new(title, tags: tags, labels: labels).tap do |book|
92
98
  book.dsl.instance_eval(&block)
93
99
  register(book)
94
100
  end
95
101
  end
96
102
 
97
- def self.section(title, &block)
103
+ def self.section(title, *tags, labels: {}, &block)
98
104
  Configuration.load_config
99
- Entities::Section.new(title).tap do |section|
105
+ Entities::Section.new(title, tags: tags, labels: labels).tap do |section|
100
106
  section.dsl.instance_eval(&block)
101
107
  end
102
108
  end
103
109
 
104
- def self.step(title=nil, &block)
110
+ def self.setup(*tags, labels: {}, &block)
105
111
  Configuration.load_config
106
- Entities::Step.new(title).tap do |step|
112
+ Entities::Setup.new(tags: tags, labels: labels).tap do |setup|
113
+ setup.dsl.instance_eval(&block)
114
+ end
115
+ end
116
+
117
+ def self.step(title=nil, *tags, labels: {}, &block)
118
+ if title.is_a?(Symbol)
119
+ tags.unshift(title)
120
+ title = nil
121
+ end
122
+
123
+ Configuration.load_config
124
+ Entities::Step.new(title, tags: tags, labels: labels).tap do |step|
107
125
  step.dsl.instance_eval(&block) if block
108
126
  end
109
127
  end
@@ -115,4 +133,8 @@ module Runbook
115
133
  def self.books
116
134
  @books ||= []
117
135
  end
136
+
137
+ def self.runtime_methods
138
+ @runtime_methods ||= []
139
+ end
118
140
  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
data/lib/runbook/cli.rb CHANGED
@@ -82,17 +82,6 @@ module Runbook
82
82
  invoke(Runbook::Initializer)
83
83
  end
84
84
 
85
- desc "install", "Install Runbook in an existing project", hide: true
86
- Runbook::Initializer.class_options.values.each do |co|
87
- method_option co.name, desc: co.description, required: co.required,
88
- default: co.default, aliases: co.aliases, type: co.type,
89
- banner: co.banner, hide: co.hide
90
- end
91
- def install
92
- Runbook.deprecator.deprecation_warning(:install, :init)
93
- invoke(Runbook::Initializer)
94
- end
95
-
96
85
  desc "--version", "Print runbook's version"
97
86
  def __print_version
98
87
  puts "Runbook v#{Runbook::VERSION}"
@@ -104,8 +93,23 @@ module Runbook
104
93
  unless File.exist?(runbook)
105
94
  raise Thor::InvocationError, "#{cmd}: cannot access #{runbook}: No such file or directory"
106
95
  end
107
- load(runbook)
108
- Runbook.books.last || eval(File.read(runbook))
96
+
97
+ begin
98
+ load(runbook)
99
+ Runbook.books.last || eval(File.read(runbook))
100
+ rescue NameError => e
101
+ if Runbook.runtime_methods.include?(e.name)
102
+ message = (
103
+ "Runtime method `#{e.name}` cannot be referenced at " \
104
+ "compile time. Wrap statements referencing it in a " \
105
+ "`ruby_command` block in order to invoke the code at " \
106
+ "runtime."
107
+ )
108
+ raise e, message, e.backtrace
109
+ end
110
+
111
+ raise e
112
+ end
109
113
  end
110
114
  end
111
115
  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
@@ -7,11 +7,12 @@ module Runbook
7
7
  child_class.const_set(:DSL, Runbook::DSL.class)
8
8
  end
9
9
 
10
- attr_accessor :parent
11
- attr_reader :title, :dsl
10
+ attr_reader :title, :tags, :labels, :dsl
12
11
 
13
- def initialize(title, parent: nil)
12
+ def initialize(title, tags: [], labels: {}, parent: nil)
14
13
  @title = title
14
+ @tags = tags
15
+ @labels = labels
15
16
  @parent = parent
16
17
  @dsl = "#{self.class}::DSL".constantize.new(self)
17
18
  end
@@ -25,7 +26,7 @@ module Runbook
25
26
  @items ||= []
26
27
  end
27
28
 
28
- def method_missing(method, *args, &block)
29
+ ruby2_keywords def method_missing(method, *args, &block)
29
30
  if dsl.respond_to?(method)
30
31
  dsl.send(method, *args, &block)
31
32
  else
@@ -92,7 +93,8 @@ module Runbook
92
93
 
93
94
  def _run_metadata(items, item, metadata, index)
94
95
  pos_index = items.select do |item|
95
- item.is_a?(Entity)
96
+ item.is_a?(Entity) &&
97
+ !item.is_a?(Runbook::Entities::Setup)
96
98
  end.index(item)
97
99
 
98
100
  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
@@ -1,10 +1,14 @@
1
1
  module Runbook::Extensions
2
2
  module Statements
3
3
  module DSL
4
- def method_missing(name, *args, &block)
4
+ ruby2_keywords def method_missing(name, *args, &block)
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
@@ -10,5 +19,6 @@ module Runbook::Extensions
10
19
  end
11
20
  end
12
21
 
22
+ Runbook::Entities::Book::DSL.prepend(Steps::DSL)
13
23
  Runbook::Entities::Section::DSL.prepend(Steps::DSL)
14
24
  end
@@ -22,14 +22,23 @@ module Runbook::Generators
22
22
  desc: "Target directory for shared runbook code"
23
23
  class_option :test, type: :string, enum: ["rspec", "minitest"],
24
24
  default: "rspec", desc: %Q{Test-suite, "rspec" or "minitest"}
25
+ class_option :ci, type: :string, enum: ["github", "travis", "gitlab", "circle"],
26
+ default: "github", desc: %Q{CI Service, "github", "travis", "gitlab", or "circle"}
25
27
 
26
28
  def init_gem
27
29
  bundle_exists = "which bundle 2>&1 1>/dev/null"
28
30
  raise "Please ensure bundle is installed" unless system(bundle_exists)
31
+ bundler_version = Gem::Version.new(Bundler::VERSION)
29
32
 
30
33
  inside(parent_options[:root]) do
31
34
  test = "--test #{options[:test]}"
32
- run("bundle gem #{_name} #{test} --no-coc --no-mit")
35
+ ci = "--ci #{options[:ci]}"
36
+ changelog = "--no-changelog" if bundler_version >= Gem::Version.new("2.2.8")
37
+ continue = (
38
+ run("bundle gem #{_name} #{test} #{ci} --rubocop #{changelog} --no-coc --no-mit") ||
39
+ options[:pretend]
40
+ )
41
+ exit 1 unless continue
33
42
  end
34
43
  end
35
44
 
@@ -49,6 +58,9 @@ module Runbook::Generators
49
58
  remove_file(readme)
50
59
 
51
60
  gemfile = File.join(*dirs, "Gemfile")
61
+ if File.exist?(gemfile)
62
+ @gemfile_file_contents = File.readlines(gemfile)
63
+ end
52
64
  remove_file(gemfile)
53
65
 
54
66
  base_file = File.join(*dirs, "lib", "#{_name}.rb")
@@ -98,14 +110,25 @@ module Runbook::Generators
98
110
  template("templates/Gemfile.tt", target)
99
111
 
100
112
  # Add development dependencies from gemspec
101
- return unless @gemspec_file_contents
102
- gems = @gemspec_file_contents.select do |line|
103
- line =~ / spec.add_development_dependency/
104
- end.map do |line|
105
- line.gsub(/ spec.add_development_dependency/, "gem")
106
- end.join
107
-
108
- append_to_file(target, "\n#{gems}", verbose: false)
113
+ if @gemspec_file_contents
114
+ gems = @gemspec_file_contents.select do |line|
115
+ line =~ / spec.add_development_dependency/
116
+ end.map do |line|
117
+ line.gsub(/ spec.add_development_dependency/, "gem")
118
+ end.join
119
+
120
+ append_to_file(target, "\n#{gems}", verbose: false)
121
+ end
122
+
123
+ # Add gemfile gems
124
+ if @gemfile_file_contents
125
+ gems = @gemfile_file_contents.select do |line|
126
+ line =~ /^gem /
127
+ end.join
128
+
129
+ append_to_file(target, "\n#{gems}", verbose: false)
130
+ end
131
+
109
132
  end
110
133
 
111
134
  def create_base_file