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
@@ -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