careacademy-runbook 1.2.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.
- checksums.yaml +7 -0
- data/.dockerignore +17 -0
- data/.github/workflows/ruby.yml +43 -0
- data/.gitignore +16 -0
- data/.rspec +2 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/Appraisals +8 -0
- data/CHANGELOG.md +143 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +21 -0
- data/README.md +1272 -0
- data/Rakefile +12 -0
- data/TODO.md +316 -0
- data/bin/console +16 -0
- data/bin/setup +8 -0
- data/dockerfiles/Dockerfile-runbook +18 -0
- data/dockerfiles/Dockerfile-sshd +4 -0
- data/examples/hooks_runbook.rb +72 -0
- data/examples/layout_runbook.rb +26 -0
- data/examples/restart_nginx.rb +26 -0
- data/examples/simple_runbook.rb +41 -0
- data/examples/suppress_capture_output.rb +47 -0
- data/exe/runbook +5 -0
- data/gemfiles/.bundle/config +2 -0
- data/gemfiles/activesupport_5.gemfile +7 -0
- data/gemfiles/activesupport_6.gemfile +7 -0
- data/images/runbook_anatomy_diagram.png +0 -0
- data/images/runbook_example.gif +0 -0
- data/images/runbook_execution_modes.png +0 -0
- data/lib/hacks/ssh_kit.rb +58 -0
- data/lib/runbook/airbrussh_context.rb +25 -0
- data/lib/runbook/cli.rb +115 -0
- data/lib/runbook/cli_base.rb +38 -0
- data/lib/runbook/configuration.rb +120 -0
- data/lib/runbook/dsl.rb +21 -0
- data/lib/runbook/entities/book.rb +17 -0
- data/lib/runbook/entities/section.rb +7 -0
- data/lib/runbook/entities/setup.rb +7 -0
- data/lib/runbook/entities/step.rb +7 -0
- data/lib/runbook/entity.rb +129 -0
- data/lib/runbook/errors.rb +7 -0
- data/lib/runbook/extensions/add.rb +14 -0
- data/lib/runbook/extensions/description.rb +14 -0
- data/lib/runbook/extensions/sections.rb +19 -0
- data/lib/runbook/extensions/setup.rb +17 -0
- data/lib/runbook/extensions/shared_variables.rb +51 -0
- data/lib/runbook/extensions/ssh_config.rb +78 -0
- data/lib/runbook/extensions/statements.rb +31 -0
- data/lib/runbook/extensions/steps.rb +24 -0
- data/lib/runbook/extensions/tmux.rb +13 -0
- data/lib/runbook/generator.rb +38 -0
- data/lib/runbook/generators/base.rb +45 -0
- data/lib/runbook/generators/dsl_extension/dsl_extension.rb +29 -0
- data/lib/runbook/generators/dsl_extension/templates/dsl_extension.tt +25 -0
- data/lib/runbook/generators/generator/generator.rb +43 -0
- data/lib/runbook/generators/generator/templates/generator.tt +53 -0
- data/lib/runbook/generators/project/project.rb +301 -0
- data/lib/runbook/generators/project/templates/Gemfile.tt +6 -0
- data/lib/runbook/generators/project/templates/README.md.tt +29 -0
- data/lib/runbook/generators/project/templates/Runbookfile.tt +11 -0
- data/lib/runbook/generators/project/templates/base_file.rb.tt +8 -0
- data/lib/runbook/generators/runbook/runbook.rb +22 -0
- data/lib/runbook/generators/runbook/templates/runbook.tt +20 -0
- data/lib/runbook/generators/statement/statement.rb +18 -0
- data/lib/runbook/generators/statement/templates/statement.tt +34 -0
- data/lib/runbook/helpers/format_helper.rb +11 -0
- data/lib/runbook/helpers/ssh_kit_helper.rb +143 -0
- data/lib/runbook/helpers/tmux_helper.rb +176 -0
- data/lib/runbook/hooks.rb +88 -0
- data/lib/runbook/initializer.rb +85 -0
- data/lib/runbook/node.rb +33 -0
- data/lib/runbook/run.rb +290 -0
- data/lib/runbook/runner.rb +66 -0
- data/lib/runbook/runs/ssh_kit.rb +193 -0
- data/lib/runbook/statement.rb +20 -0
- data/lib/runbook/statements/ask.rb +12 -0
- data/lib/runbook/statements/assert.rb +34 -0
- data/lib/runbook/statements/capture.rb +14 -0
- data/lib/runbook/statements/capture_all.rb +14 -0
- data/lib/runbook/statements/command.rb +11 -0
- data/lib/runbook/statements/confirm.rb +10 -0
- data/lib/runbook/statements/description.rb +9 -0
- data/lib/runbook/statements/download.rb +12 -0
- data/lib/runbook/statements/layout.rb +10 -0
- data/lib/runbook/statements/note.rb +10 -0
- data/lib/runbook/statements/notice.rb +10 -0
- data/lib/runbook/statements/ruby_command.rb +9 -0
- data/lib/runbook/statements/tmux_command.rb +11 -0
- data/lib/runbook/statements/upload.rb +12 -0
- data/lib/runbook/statements/wait.rb +10 -0
- data/lib/runbook/toolbox.rb +37 -0
- data/lib/runbook/util/repo.rb +57 -0
- data/lib/runbook/util/runbook.rb +43 -0
- data/lib/runbook/util/sticky_hash.rb +26 -0
- data/lib/runbook/util/stored_pose.rb +55 -0
- data/lib/runbook/version.rb +3 -0
- data/lib/runbook/view.rb +24 -0
- data/lib/runbook/viewer.rb +24 -0
- data/lib/runbook/views/markdown.rb +117 -0
- data/lib/runbook.rb +140 -0
- data/runbook.gemspec +53 -0
- metadata +419 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# <%= name.titleize %>
|
|
2
|
+
|
|
3
|
+
## Deployment
|
|
4
|
+
|
|
5
|
+
This code is best deployed by cloning this project using git.
|
|
6
|
+
|
|
7
|
+
## Usage
|
|
8
|
+
|
|
9
|
+
Set up this repository by running `bin/setup`. This will install all necessary dependencies for your runbooks.
|
|
10
|
+
|
|
11
|
+
Execute your runbooks using `bundle exec runbook exec <PATH_TO_YOUR_RUNBOOK>`
|
|
12
|
+
|
|
13
|
+
Get runbook help executing `bundle exec runbook help` or visiting https://github.com/braintree/runbook
|
|
14
|
+
|
|
15
|
+
## This Repository
|
|
16
|
+
|
|
17
|
+
Runbook code should live in the following locations:
|
|
18
|
+
|
|
19
|
+
`runbooks` - Place your runbooks in this directory
|
|
20
|
+
|
|
21
|
+
`<%= @shared_lib_dir %>` - Place code shared across your runbooks in this directory
|
|
22
|
+
|
|
23
|
+
`lib/runbook/extensions` - Place code that extends Runbook's functionality here. For example, adding a new statement to Runbook's DSL would live in this directory. Require these files in your `Runbookfile` so Runbook can load them when it is launched. See https://github.com/braintree/runbook#extending-runbook for more details on extending Runbook.
|
|
24
|
+
|
|
25
|
+
`lib/runbook/generators` - Place Runbook generators in this directory. Require these files in your `Runbookfile` to expose the generators via Runbook's command line interface.
|
|
26
|
+
|
|
27
|
+
## Development
|
|
28
|
+
|
|
29
|
+
Run `bin/setup` to install any necessary dependencies for this project. Run `bin/console` to use your code via an interactive prompt.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Your Runbookfile serves two purposes. Use your Runbookfile to
|
|
2
|
+
# (1) require any runbook plugins, extensions, or generators that
|
|
3
|
+
# you want available to your runbooks and (2) configure Runbook
|
|
4
|
+
# using a `Runbook.configure` block.
|
|
5
|
+
|
|
6
|
+
# Use this block to configure Runbook. See
|
|
7
|
+
# https://github.com/braintree/runbook#configuration for more
|
|
8
|
+
# details.
|
|
9
|
+
#
|
|
10
|
+
# Runbook.configure do |config|
|
|
11
|
+
# end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
module Runbook::Generators
|
|
2
|
+
class Runbook < Thor::Group
|
|
3
|
+
include ::Runbook::Generators::Base
|
|
4
|
+
|
|
5
|
+
source_root File.dirname(__FILE__)
|
|
6
|
+
|
|
7
|
+
def self.usage
|
|
8
|
+
"runbook NAME [options]"
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def self.description
|
|
12
|
+
"Generate a runbook named NAME, e.x. deploy_nginx"
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
argument :name, desc: "The name of your runbook, e.x. deploy_nginx"
|
|
16
|
+
|
|
17
|
+
def create_runbook
|
|
18
|
+
target = File.join(options[:root], "#{name.underscore}.rb")
|
|
19
|
+
template('templates/runbook.tt', target)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
require "runbook"
|
|
3
|
+
|
|
4
|
+
runbook = Runbook.book "<%= name.titleize %>" do
|
|
5
|
+
description <<-DESC
|
|
6
|
+
This is a runbook that...
|
|
7
|
+
DESC
|
|
8
|
+
|
|
9
|
+
section "SECTION" do
|
|
10
|
+
step "STEP" do
|
|
11
|
+
# Add statements here
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
if __FILE__ == $0
|
|
17
|
+
Runbook::Runner.new(runbook).run
|
|
18
|
+
else
|
|
19
|
+
runbook
|
|
20
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
module Runbook::Generators
|
|
2
|
+
class Statement < Thor::Group
|
|
3
|
+
include ::Runbook::Generators::Base
|
|
4
|
+
|
|
5
|
+
source_root File.dirname(__FILE__)
|
|
6
|
+
|
|
7
|
+
def self.description
|
|
8
|
+
"Generate a statement named NAME (e.x. ruby_command) that can be used in your runbooks"
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
argument :name, desc: "The name of your statement, e.x. ruby_command"
|
|
12
|
+
|
|
13
|
+
def create_statement
|
|
14
|
+
target = File.join(parent_options[:root], "#{name.underscore}.rb")
|
|
15
|
+
template('templates/statement.tt', target)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Remember to require this file in a runbook config file
|
|
2
|
+
# or in your project so it is available in your runbooks
|
|
3
|
+
module Runbook::Statements
|
|
4
|
+
class <%= name.classify %> < Runbook::Statement
|
|
5
|
+
# Add all attributes for your statement
|
|
6
|
+
attr_reader :attr1, :attr2
|
|
7
|
+
|
|
8
|
+
# Define the initialize method signature to
|
|
9
|
+
# match the method signature of your dsl statement
|
|
10
|
+
def initialize(attr1, attr2)
|
|
11
|
+
@attr1 = attr1
|
|
12
|
+
@attr2 = attr2
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# scope this module using your project's namespace
|
|
18
|
+
module MyProject::RunbookExtensions
|
|
19
|
+
module <%= "#{name.underscore}_markdown".classify %>
|
|
20
|
+
def runbook__statements__<%= name.underscore %>(object, output, metadata)
|
|
21
|
+
# Format how your statement will be displayed when rendered with markdown
|
|
22
|
+
output << "#{object.attr1}#{object.attr2}"
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
Runbook::Views::Markdown.singleton_class.prepend(<%= "#{name.underscore}_markdown".classify %>)
|
|
26
|
+
|
|
27
|
+
module <%= "#{name.underscore}_sshkit".classify %>
|
|
28
|
+
def runbook__statements__<%= name.underscore %>(object, metadata)
|
|
29
|
+
# Execute your behavior using object which is your instantiated statement
|
|
30
|
+
# and the current metadata for this step of the execution
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
Runbook::Runs::SSHKit.singleton_class.prepend(<%= "#{name.underscore}_sshkit".classify %>)
|
|
34
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
module Runbook::Helpers
|
|
2
|
+
module FormatHelper
|
|
3
|
+
def deindent(str, padding: 0)
|
|
4
|
+
lines = str.split("\n")
|
|
5
|
+
indentations = lines.map { |l| l.size - l.gsub(/^\s+/, "").size }
|
|
6
|
+
min_indentation = indentations.min
|
|
7
|
+
lines.map! { |line| " " * padding + line[min_indentation..-1] }
|
|
8
|
+
lines.join("\n")
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
module Runbook::Helpers
|
|
2
|
+
module SSHKitHelper
|
|
3
|
+
def ssh_kit_command(cmd, raw: false)
|
|
4
|
+
return [cmd] if raw
|
|
5
|
+
cmd, args = cmd.split(" ", 2)
|
|
6
|
+
[cmd.to_sym, args]
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def ssh_kit_command_options(ssh_config)
|
|
10
|
+
{}.tap do |options|
|
|
11
|
+
if ssh_config[:user] && Runbook.configuration.enable_sudo_prompt
|
|
12
|
+
options[:interaction_handler] ||= ::SSHKit::Sudo::InteractionHandler.new
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def find_ssh_config(object, ssh_config_method=:ssh_config)
|
|
18
|
+
blank_config = Runbook::Extensions::SSHConfig.blank_ssh_config
|
|
19
|
+
nil_or_blank = ->(config) { config.nil? || config == blank_config }
|
|
20
|
+
ssh_config = object.send(ssh_config_method)
|
|
21
|
+
return ssh_config unless nil_or_blank.call(ssh_config)
|
|
22
|
+
object = object.parent
|
|
23
|
+
|
|
24
|
+
while object
|
|
25
|
+
ssh_config = object.ssh_config
|
|
26
|
+
return ssh_config unless nil_or_blank.call(ssh_config)
|
|
27
|
+
object = object.parent
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
return blank_config
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def render_ssh_config_output(ssh_config)
|
|
34
|
+
"".tap do |output|
|
|
35
|
+
if (servers = ssh_config[:servers]).any?
|
|
36
|
+
server_str = servers.join(", ")
|
|
37
|
+
if server_str.size > 80
|
|
38
|
+
server_str = "#{server_str[0..38]}...#{server_str[-38..-1]}"
|
|
39
|
+
end
|
|
40
|
+
output << " on: #{server_str}\n"
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
if (strategy = ssh_config[:parallelization][:strategy])
|
|
44
|
+
limit = ssh_config[:parallelization][:limit]
|
|
45
|
+
wait = ssh_config[:parallelization][:wait]
|
|
46
|
+
in_str = " in: #{strategy}"
|
|
47
|
+
in_str << ", limit: #{limit}" if strategy == :groups
|
|
48
|
+
in_str << ", wait: #{wait}" if [:sequence, :groups].include?(strategy)
|
|
49
|
+
output << "#{in_str}\n"
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
if ssh_config[:user] || ssh_config[:group]
|
|
53
|
+
user = ssh_config[:user]
|
|
54
|
+
group = ssh_config[:group]
|
|
55
|
+
as_str = " as:"
|
|
56
|
+
as_str << " user: #{user}" if user
|
|
57
|
+
as_str << " group: #{group}" if group
|
|
58
|
+
output << "#{as_str}\n"
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
if (path = ssh_config[:path])
|
|
62
|
+
output << " within: #{path}\n"
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
if (env = ssh_config[:env])
|
|
66
|
+
env_str = env.map do |k, v|
|
|
67
|
+
"#{k.to_s.upcase}=#{v}"
|
|
68
|
+
end.join(" ")
|
|
69
|
+
output << " with: #{env_str}\n"
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
if (umask = ssh_config[:umask])
|
|
73
|
+
output << " umask: #{umask}\n"
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def with_ssh_config(ssh_config, &exec_block)
|
|
79
|
+
user = ssh_config[:user]
|
|
80
|
+
group = ssh_config[:group]
|
|
81
|
+
as_block = _as_block(user, group, &exec_block)
|
|
82
|
+
within_block = _within_block(ssh_config[:path], &as_block)
|
|
83
|
+
with_block = _with_block(ssh_config[:env], &within_block)
|
|
84
|
+
|
|
85
|
+
_with_umask(ssh_config[:umask]) do
|
|
86
|
+
servers = _servers(ssh_config[:servers])
|
|
87
|
+
parallelization = ssh_config[:parallelization]
|
|
88
|
+
coordinator_options = _coordinator_options(parallelization)
|
|
89
|
+
SSHKit::Coordinator.new(servers).each(coordinator_options) do
|
|
90
|
+
instance_exec(&with_block)
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def _servers(ssh_config_servers)
|
|
96
|
+
return :local if ssh_config_servers.empty?
|
|
97
|
+
return :local if ssh_config_servers == [:local]
|
|
98
|
+
ssh_config_servers
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def _coordinator_options(ssh_config_parallelization)
|
|
102
|
+
ssh_config_parallelization.clone.tap do |options|
|
|
103
|
+
if options[:strategy]
|
|
104
|
+
options[:in] = options.delete(:strategy)
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def _as_block(user, group, &block)
|
|
110
|
+
if user || group
|
|
111
|
+
-> { as({user: user, group: group}) { instance_exec(&block) } }
|
|
112
|
+
else
|
|
113
|
+
-> { instance_exec(&block) }
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def _with_block(env, &block)
|
|
118
|
+
if env
|
|
119
|
+
-> { with(env) { instance_exec(&block) } }
|
|
120
|
+
else
|
|
121
|
+
-> { instance_exec(&block) }
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def _within_block(path, &block)
|
|
126
|
+
if path
|
|
127
|
+
-> { within(path) { instance_exec(&block) } }
|
|
128
|
+
else
|
|
129
|
+
-> { instance_exec(&block) }
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def _with_umask(umask, &block)
|
|
134
|
+
old_umask = SSHKit.config.umask
|
|
135
|
+
begin
|
|
136
|
+
SSHKit.config.umask = umask if umask
|
|
137
|
+
instance_exec(&block)
|
|
138
|
+
ensure
|
|
139
|
+
SSHKit.config.umask = old_umask
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
module Runbook::Helpers
|
|
2
|
+
module TmuxHelper
|
|
3
|
+
FILE_PERMISSIONS = 0600
|
|
4
|
+
|
|
5
|
+
def setup_layout(structure, runbook_title:)
|
|
6
|
+
_remove_stale_layouts
|
|
7
|
+
layout_file = _layout_file(_slug(runbook_title))
|
|
8
|
+
if File.exist?(layout_file)
|
|
9
|
+
stored_layout = ::YAML::load_file(layout_file)
|
|
10
|
+
if _all_panes_exist?(stored_layout)
|
|
11
|
+
return stored_layout
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
_setup_layout(structure).tap do |layout_panes|
|
|
16
|
+
File.open(layout_file, 'w', FILE_PERMISSIONS) do |f|
|
|
17
|
+
f.write(layout_panes.to_yaml)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def send_keys(command, target)
|
|
23
|
+
`tmux send-keys -t #{target} #{_pager_escape_sequence} #{Shellwords.escape(command)} C-m`
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def kill_all_panes(layout_panes)
|
|
27
|
+
runbook_pane = _runbook_pane
|
|
28
|
+
layout_panes.values.each do |pane_id|
|
|
29
|
+
_kill_pane(pane_id) unless pane_id == runbook_pane
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def _pager_escape_sequence
|
|
34
|
+
"q C-u"
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def _kill_pane(pane_id)
|
|
38
|
+
`tmux kill-pane -t #{pane_id}`
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def _layout_file(runbook_title)
|
|
42
|
+
`tmux display-message -p -t $TMUX_PANE "#{Dir.tmpdir}/runbook_layout_\#{pid}_\#{session_name}_\#{pane_pid}_\#{pane_id}_#{runbook_title}.yml"`.strip
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def _slug(title)
|
|
46
|
+
title.titleize.gsub(/[\/\s]+/, "").underscore.dasherize
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def _all_panes_exist?(stored_layout)
|
|
50
|
+
(stored_layout.values - _session_panes).empty?
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def _remove_stale_layouts
|
|
54
|
+
session_panes = _session_panes
|
|
55
|
+
session_layout_files = _session_layout_files
|
|
56
|
+
session_layout_files.each do |file|
|
|
57
|
+
File.delete(file) unless session_panes.any? { |pane| /_#{pane}_/ =~ file }
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def _session_panes
|
|
62
|
+
`tmux list-panes -s -F '#D'`.split("\n")
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def _session_layout_files
|
|
66
|
+
session_layout_glob = `tmux display-message -p "#{Dir.tmpdir}/runbook_layout_\#{pid}_\#{session_name}_*.yml"`.strip
|
|
67
|
+
Dir[session_layout_glob]
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def _setup_layout(structure)
|
|
71
|
+
current_pane = _runbook_pane
|
|
72
|
+
panes_to_init = []
|
|
73
|
+
{}.tap do |layout_panes|
|
|
74
|
+
if structure.is_a?(Hash)
|
|
75
|
+
first_window = true
|
|
76
|
+
structure.each do |name, window|
|
|
77
|
+
if first_window
|
|
78
|
+
_rename_window(name)
|
|
79
|
+
first_window = false
|
|
80
|
+
else
|
|
81
|
+
current_pane = _new_window(name)
|
|
82
|
+
end
|
|
83
|
+
_setup_panes(layout_panes, panes_to_init, current_pane, window)
|
|
84
|
+
end
|
|
85
|
+
else
|
|
86
|
+
_setup_panes(layout_panes, panes_to_init, current_pane, structure)
|
|
87
|
+
end
|
|
88
|
+
_swap_runbook_pane(panes_to_init, layout_panes)
|
|
89
|
+
_initialize_panes(panes_to_init, layout_panes)
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def _setup_panes(layout_panes, panes_to_init, current_pane, structure, depth=0)
|
|
94
|
+
return if structure.empty?
|
|
95
|
+
case structure
|
|
96
|
+
when Array
|
|
97
|
+
case structure.size
|
|
98
|
+
when 1
|
|
99
|
+
_setup_panes(layout_panes, panes_to_init, current_pane, structure.shift, depth+1)
|
|
100
|
+
else
|
|
101
|
+
size = 100 - 100 / structure.size
|
|
102
|
+
new_pane = _split(current_pane, depth, size)
|
|
103
|
+
_setup_panes(layout_panes, panes_to_init, current_pane, structure.shift, depth+1)
|
|
104
|
+
_setup_panes(layout_panes, panes_to_init, new_pane, structure, depth)
|
|
105
|
+
end
|
|
106
|
+
when Hash
|
|
107
|
+
if structure.values.all? { |v| v.is_a?(Numeric) }
|
|
108
|
+
total_size = structure.values.reduce(:+)
|
|
109
|
+
case structure.size
|
|
110
|
+
when 1
|
|
111
|
+
_setup_panes(layout_panes, panes_to_init, current_pane, structure.keys[0], depth+1)
|
|
112
|
+
else
|
|
113
|
+
size = (total_size - structure.values[0]) * 100 / total_size
|
|
114
|
+
new_pane = _split(current_pane, depth, size)
|
|
115
|
+
first_struct = structure.keys[0]
|
|
116
|
+
structure.delete(first_struct)
|
|
117
|
+
_setup_panes(layout_panes, panes_to_init, current_pane, first_struct, depth+1)
|
|
118
|
+
_setup_panes(layout_panes, panes_to_init, new_pane, structure, depth)
|
|
119
|
+
end
|
|
120
|
+
else
|
|
121
|
+
layout_panes[structure[:name]] = current_pane
|
|
122
|
+
panes_to_init << structure
|
|
123
|
+
end
|
|
124
|
+
when Symbol
|
|
125
|
+
layout_panes[structure] = current_pane
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def _swap_runbook_pane(panes_to_init, layout_panes)
|
|
130
|
+
if (runbook_pane = panes_to_init.find { |pane| pane[:runbook_pane] })
|
|
131
|
+
current_runbook_pane_name = layout_panes.keys.find do |k|
|
|
132
|
+
layout_panes[k] == _runbook_pane
|
|
133
|
+
end
|
|
134
|
+
target_pane_id = layout_panes[runbook_pane[:name]]
|
|
135
|
+
layout_panes[runbook_pane[:name]] = _runbook_pane
|
|
136
|
+
layout_panes[current_runbook_pane_name] = target_pane_id
|
|
137
|
+
_swap_panes(target_pane_id, _runbook_pane)
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def _initialize_panes(panes_to_init, layout_panes)
|
|
142
|
+
panes_to_init.each do |pane|
|
|
143
|
+
target = layout_panes[pane[:name]]
|
|
144
|
+
_set_directory(pane[:directory], target) if pane[:directory]
|
|
145
|
+
send_keys(pane[:command], target) if pane[:command]
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def _runbook_pane
|
|
150
|
+
@runbook_pane ||= `tmux display-message -p '#D'`.strip
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def _rename_window(name)
|
|
154
|
+
`tmux rename-window "#{name}"`
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def _new_window(name)
|
|
158
|
+
`tmux new-window -n "#{name}" -P -F '#D' -d`.strip
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def _split(current_pane, depth, size)
|
|
162
|
+
direction = depth.even? ? "h" : "v"
|
|
163
|
+
command = "tmux split-window"
|
|
164
|
+
args = "-#{direction} -t #{current_pane} -p #{size} -P -F '#D' -d"
|
|
165
|
+
`#{command} #{args}`.strip
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def _swap_panes(target_pane, source_pane)
|
|
169
|
+
`tmux swap-pane -d -t #{target_pane} -s #{source_pane}`
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def _set_directory(directory, target)
|
|
173
|
+
send_keys("cd #{directory}; clear", target)
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
end
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
module Runbook
|
|
2
|
+
module Hooks
|
|
3
|
+
def hooks
|
|
4
|
+
@hooks ||= []
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
def register_hook(name, type, klass, before: nil, &block)
|
|
8
|
+
hook = {
|
|
9
|
+
name: name,
|
|
10
|
+
type: type,
|
|
11
|
+
klass: klass,
|
|
12
|
+
block: block,
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if before
|
|
16
|
+
hooks.insert(_hook_index(before), hook)
|
|
17
|
+
else
|
|
18
|
+
hooks << hook
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def hooks_for(type, klass)
|
|
23
|
+
hooks.select do |hook|
|
|
24
|
+
hook[:type] == type && klass <= hook[:klass]
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def _hook_index(hook_name)
|
|
29
|
+
hooks.index { |hook| hook[:name] == hook_name } || -1
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
module Invoker
|
|
33
|
+
def invoke_with_hooks(executor, object, *args, &block)
|
|
34
|
+
skip_before = skip_around = skip_after = false
|
|
35
|
+
if executor <= Runbook::Run
|
|
36
|
+
if executor.should_skip?(args[0])
|
|
37
|
+
if executor.start_at_is_substep?(object, args[0])
|
|
38
|
+
skip_before = skip_around = true
|
|
39
|
+
else
|
|
40
|
+
skip_before = skip_around = skip_after = true
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
unless skip_before
|
|
46
|
+
_execute_before_hooks(executor, object, *args)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
if skip_around
|
|
50
|
+
block.call
|
|
51
|
+
else
|
|
52
|
+
_execute_around_hooks(executor, object, *args, &block)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
unless skip_after
|
|
56
|
+
_execute_after_hooks(executor, object, *args)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def _execute_before_hooks(executor, object, *args)
|
|
61
|
+
executor.hooks_for(:before, object.class).each do |hook|
|
|
62
|
+
executor.instance_exec(object, *args, &hook[:block])
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def _execute_around_hooks(executor, object, *args)
|
|
67
|
+
executor.hooks_for(:around, object.class).reverse.reduce(
|
|
68
|
+
-> (object, *args) {
|
|
69
|
+
yield
|
|
70
|
+
}
|
|
71
|
+
) do |inner_method, hook|
|
|
72
|
+
-> (object, *args) {
|
|
73
|
+
inner_block = Proc.new do |object, *args|
|
|
74
|
+
inner_method.call(object, *args)
|
|
75
|
+
end
|
|
76
|
+
executor.instance_exec(object, *args, inner_block, &hook[:block])
|
|
77
|
+
}
|
|
78
|
+
end.call(object, *args)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def _execute_after_hooks(executor, object, *args)
|
|
82
|
+
executor.hooks_for(:after, object.class).each do |hook|
|
|
83
|
+
executor.instance_exec(object, *args, &hook[:block])
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
module Runbook
|
|
2
|
+
class Initializer < Thor::Group
|
|
3
|
+
include Thor::Actions
|
|
4
|
+
|
|
5
|
+
source_root File.join(
|
|
6
|
+
File.dirname(__FILE__),
|
|
7
|
+
"generators",
|
|
8
|
+
"project",
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
add_runtime_options!
|
|
12
|
+
check_unknown_options!
|
|
13
|
+
|
|
14
|
+
def create_runbookfile
|
|
15
|
+
template(
|
|
16
|
+
"templates/Runbookfile.tt",
|
|
17
|
+
"Runbookfile",
|
|
18
|
+
)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def create_runbooks_directory
|
|
22
|
+
target = "runbooks"
|
|
23
|
+
empty_directory(target)
|
|
24
|
+
_keep_dir(target)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def create_lib_directory
|
|
28
|
+
dirs = [
|
|
29
|
+
"lib",
|
|
30
|
+
"runbook",
|
|
31
|
+
]
|
|
32
|
+
target = File.join(*dirs)
|
|
33
|
+
|
|
34
|
+
empty_directory(target)
|
|
35
|
+
_keep_dir(target)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def create_extensions_directory
|
|
39
|
+
dirs = [
|
|
40
|
+
"lib",
|
|
41
|
+
"runbook",
|
|
42
|
+
"extensions",
|
|
43
|
+
]
|
|
44
|
+
target = File.join(*dirs)
|
|
45
|
+
|
|
46
|
+
empty_directory(target)
|
|
47
|
+
_keep_dir(target)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def create_generators_directory
|
|
51
|
+
dirs = [
|
|
52
|
+
"lib",
|
|
53
|
+
"runbook",
|
|
54
|
+
"generators",
|
|
55
|
+
]
|
|
56
|
+
target = File.join(*dirs)
|
|
57
|
+
|
|
58
|
+
empty_directory(target)
|
|
59
|
+
_keep_dir(target)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def runbook_initialization_overview
|
|
63
|
+
msg = [
|
|
64
|
+
"",
|
|
65
|
+
"Runbook was successfully initialized.",
|
|
66
|
+
"Add runbooks to the `runbooks` directory.",
|
|
67
|
+
"Add shared code to `lib/runbook`.",
|
|
68
|
+
"Execute runbooks using `bundle exec runbook exec <RUNBOOK_PATH>`",
|
|
69
|
+
"from your project root.",
|
|
70
|
+
"\n",
|
|
71
|
+
]
|
|
72
|
+
|
|
73
|
+
say(msg.join("\n"))
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
private
|
|
77
|
+
|
|
78
|
+
def _keep_dir(dir)
|
|
79
|
+
create_file(
|
|
80
|
+
File.join(dir, ".keep"),
|
|
81
|
+
verbose: false,
|
|
82
|
+
)
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
data/lib/runbook/node.rb
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
module Runbook
|
|
2
|
+
class Node
|
|
3
|
+
attr_accessor :parent
|
|
4
|
+
|
|
5
|
+
def initialize
|
|
6
|
+
raise "Should not be initialized"
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def dynamic!
|
|
10
|
+
@dynamic = true
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def visited!
|
|
14
|
+
@visited = true
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def dynamic?
|
|
18
|
+
@dynamic
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def visited?
|
|
22
|
+
@visited
|
|
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
|
|
32
|
+
end
|
|
33
|
+
end
|