runbook 0.12.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (76) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +12 -0
  3. data/.rspec +2 -0
  4. data/.ruby-gemset +1 -0
  5. data/.ruby-version +1 -0
  6. data/.travis.yml +5 -0
  7. data/CHANGELOG.md +46 -0
  8. data/CODE_OF_CONDUCT.md +74 -0
  9. data/Gemfile +6 -0
  10. data/LICENSE.txt +21 -0
  11. data/README.md +999 -0
  12. data/Rakefile +6 -0
  13. data/TODO.md +38 -0
  14. data/bin/console +14 -0
  15. data/bin/setup +8 -0
  16. data/exe/runbook +5 -0
  17. data/images/runbook_anatomy_diagram.png +0 -0
  18. data/images/runbook_example.gif +0 -0
  19. data/images/runbook_execution_modes.png +0 -0
  20. data/lib/hacks/ssh_kit.rb +58 -0
  21. data/lib/runbook/cli.rb +90 -0
  22. data/lib/runbook/configuration.rb +110 -0
  23. data/lib/runbook/dsl.rb +21 -0
  24. data/lib/runbook/entities/book.rb +17 -0
  25. data/lib/runbook/entities/section.rb +7 -0
  26. data/lib/runbook/entities/step.rb +7 -0
  27. data/lib/runbook/entity.rb +127 -0
  28. data/lib/runbook/errors.rb +7 -0
  29. data/lib/runbook/extensions/add.rb +13 -0
  30. data/lib/runbook/extensions/description.rb +14 -0
  31. data/lib/runbook/extensions/sections.rb +15 -0
  32. data/lib/runbook/extensions/shared_variables.rb +51 -0
  33. data/lib/runbook/extensions/ssh_config.rb +76 -0
  34. data/lib/runbook/extensions/statements.rb +26 -0
  35. data/lib/runbook/extensions/steps.rb +14 -0
  36. data/lib/runbook/extensions/tmux.rb +13 -0
  37. data/lib/runbook/helpers/format_helper.rb +11 -0
  38. data/lib/runbook/helpers/ssh_kit_helper.rb +143 -0
  39. data/lib/runbook/helpers/tmux_helper.rb +174 -0
  40. data/lib/runbook/hooks.rb +88 -0
  41. data/lib/runbook/node.rb +23 -0
  42. data/lib/runbook/run.rb +283 -0
  43. data/lib/runbook/runner.rb +64 -0
  44. data/lib/runbook/runs/ssh_kit.rb +186 -0
  45. data/lib/runbook/statement.rb +22 -0
  46. data/lib/runbook/statements/ask.rb +11 -0
  47. data/lib/runbook/statements/assert.rb +25 -0
  48. data/lib/runbook/statements/capture.rb +14 -0
  49. data/lib/runbook/statements/capture_all.rb +14 -0
  50. data/lib/runbook/statements/command.rb +11 -0
  51. data/lib/runbook/statements/confirm.rb +10 -0
  52. data/lib/runbook/statements/description.rb +9 -0
  53. data/lib/runbook/statements/download.rb +12 -0
  54. data/lib/runbook/statements/layout.rb +10 -0
  55. data/lib/runbook/statements/note.rb +10 -0
  56. data/lib/runbook/statements/notice.rb +10 -0
  57. data/lib/runbook/statements/ruby_command.rb +9 -0
  58. data/lib/runbook/statements/tmux_command.rb +11 -0
  59. data/lib/runbook/statements/upload.rb +12 -0
  60. data/lib/runbook/statements/wait.rb +10 -0
  61. data/lib/runbook/toolbox.rb +43 -0
  62. data/lib/runbook/util/repo.rb +56 -0
  63. data/lib/runbook/util/runbook.rb +25 -0
  64. data/lib/runbook/util/sticky_hash.rb +26 -0
  65. data/lib/runbook/util/stored_pose.rb +54 -0
  66. data/lib/runbook/version.rb +3 -0
  67. data/lib/runbook/view.rb +24 -0
  68. data/lib/runbook/viewer.rb +24 -0
  69. data/lib/runbook/views/markdown.rb +109 -0
  70. data/lib/runbook.rb +110 -0
  71. data/runbook.gemspec +48 -0
  72. data/samples/hooks_runbook.rb +72 -0
  73. data/samples/layout_runbook.rb +26 -0
  74. data/samples/restart_nginx.rb +26 -0
  75. data/samples/simple_runbook.rb +41 -0
  76. metadata +324 -0
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
data/TODO.md ADDED
@@ -0,0 +1,38 @@
1
+ ## Desired feature list
2
+ * [X] Revise README.md
3
+ * [] capistrano-runbook gem that integrates runbook into capistrano tasks
4
+ * [] Create a generator for a runbook? Allow for custom generators?
5
+ * Generate runbook templates
6
+ * Generate runbook projects with Runbookfile, Gemfile, etc.
7
+ * Generate plugins
8
+ * [] Add Appraisal to test against multiple versions of Ruby
9
+ * [] Update output to be more log friendly, including timestamps for operations
10
+ * [] Allow for preventing echo when prompting for input
11
+ * [] Add an ability for skipping mutation commands that will have an affect on the system
12
+ * [] Add a revert section that does not get executed, but can be executed by passing a revert flag
13
+ * [] Specify version in runbook to allow for supporting backwards incompatible runbook DSL format changes
14
+ * [] Add support for sudo interaction handler for raw commands
15
+ * [] Replace Thor with a solution that is more easily extendable (adding new flags, etc.)
16
+ * [] Add goto statements for repeating steps (functionality exists in paranoid mode)
17
+ * [] Add test statement
18
+ * [] Feedback on completion of tmux commands (when they complete, return values, outputs)
19
+ * [] Add shorter aliases for tmux layout keys
20
+ * [] Add host aliases for ssh_config setters
21
+ * [] Allow for step dependencies that get executed before the step
22
+ * [] Add periodic flush for sshkit output
23
+ * Add a way to execute a series of commands in groups
24
+ * Docker testing story for more full-stack integration tests
25
+ * Test integration with sshkit
26
+ * Test sshkit-sudo functionality
27
+ * logging solution for alternate output
28
+ * Pattern for conditionally enabling plugins? Conditional plugins should be implemented as separate gems.
29
+ * Requiring a plugin is the same as enabling it
30
+ * Configuration can be added to toggle aspects of the plugin
31
+ * Add a setup step that always executes even if start_at is defined
32
+ * Yaml specification format (subset of Ruby)
33
+ * Will not contain as much flexibility as Ruby format
34
+ * Can convert from Ruby format to yaml (depending on compatibility) and yaml to Ruby
35
+ * Guard for view updates (How to handle arguments?)
36
+ * Be able to serve up markdown docs (web server) for easy viewing
37
+ * Could provide a rake task for compiling and nooping runbooks?
38
+ * Can specify input-format, output-format, input (file), and output (file)
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "runbook"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ require "pry"
11
+ Pry.start
12
+
13
+ # require "irb"
14
+ # IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
data/exe/runbook ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'runbook/cli'
4
+
5
+ Runbook::CLI.start
Binary file
Binary file
Binary file
@@ -0,0 +1,58 @@
1
+ ::SSHKit::Backend::Abstract.class_eval do
2
+ # Code taken from https://github.com/capistrano/sshkit/blob/v1.16.0/lib/sshkit/backends/abstract.rb#L98-L116
3
+ # Copyright (c) 2008- Lee Hambley & Contributors
4
+ # License link: https://github.com/capistrano/sshkit/blob/v1.16.0/LICENSE.md
5
+ #
6
+ # Full copyright notice and license:
7
+ #
8
+ # Copyright (c) 2008- Lee Hambley & Contributors
9
+ #
10
+ # Permission is hereby granted, free of charge, to any person obtaining a
11
+ # copy of this software and associated documentation files (the "Software"),
12
+ # to deal in the Software without restriction, including without limitation
13
+ # the rights to use, copy, modify, merge, publish, distribute, sublicense,
14
+ # and/or sell copies of the Software, and to permit persons to whom the
15
+ # Software is furnished to do so, subject to the following conditions:
16
+ #
17
+ # The above copyright notice and this permission notice shall be included
18
+ # in all copies or substantial portions of the Software.
19
+ #
20
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
25
+ # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
26
+ # DEALINGS IN THE SOFTWARE.
27
+
28
+ def as(who, &_block)
29
+ if who.is_a? Hash
30
+ @user = who[:user] || who["user"]
31
+ @group = who[:group] || who["group"]
32
+ else
33
+ @user = who
34
+ @group = nil
35
+ end
36
+
37
+ execute_args = {verbosity: Logger::DEBUG}
38
+ old_pty = ::SSHKit::Backend::Netssh.config.pty
39
+ begin
40
+ if Runbook.configuration.enable_sudo_prompt
41
+ execute_args[:interaction_handler] ||= ::SSHKit::Sudo::InteractionHandler.new
42
+ ::SSHKit::Backend::Netssh.config.pty = true
43
+ end
44
+ execute <<-EOTEST, execute_args
45
+ if ! sudo -u #{@user} whoami > /dev/null
46
+ then echo "You cannot switch to user '#{@user}' using sudo, please check the sudoers file" 1>&2
47
+ false
48
+ fi
49
+ EOTEST
50
+ yield
51
+ ensure
52
+ ::SSHKit::Backend::Netssh.config.pty = old_pty
53
+ end
54
+ ensure
55
+ remove_instance_variable(:@user)
56
+ remove_instance_variable(:@group)
57
+ end
58
+ end
@@ -0,0 +1,90 @@
1
+ require "thor"
2
+ require "runbook"
3
+
4
+ module Runbook
5
+ class CLI < Thor
6
+ map "--version" => :__print_version
7
+ class_option :config, aliases: :c, type: :string
8
+
9
+ def initialize(args = [], local_options = {}, config = {})
10
+ super(args, local_options, config)
11
+
12
+ cmd_name = config[:current_command].name
13
+ _set_cli_config(options[:config], cmd_name) if options[:config]
14
+ end
15
+
16
+ desc "view RUNBOOK", "Prints a formatted version of the runbook"
17
+ long_desc <<-LONGDESC
18
+ Prints the runbook.
19
+
20
+ With --view (-v), Prints the view using the specified view type
21
+ LONGDESC
22
+ option :view, aliases: :v, type: :string, default: :markdown
23
+ def view(runbook)
24
+ runbook = _retrieve_runbook(runbook, :view)
25
+ puts Runbook::Viewer.new(runbook).generate(
26
+ view: options[:view],
27
+ )
28
+ end
29
+
30
+ desc "exec RUNBOOK", "Executes the runbook"
31
+ long_desc <<-LONGDESC
32
+ Executes the runbook.
33
+
34
+ With --noop (-n), Runs the runbook in no-op mode, preventing
35
+ commands from executing.
36
+
37
+ With --auto (-a), Runs the runbook in auto mode. This
38
+ will prevent the execution from asking
39
+ for any user input (such as confirmations).
40
+ Not all runbooks are compatible with auto
41
+ mode (if they use the ask statement without
42
+ defaults for example).
43
+
44
+ With --run (-r), Runs the runbook with the specified run type
45
+
46
+ With --no-paranoid (-P), Runs the runbook without prompting to
47
+ continue at every step
48
+
49
+ With --start-at (-s), Runs the runbook starting at the specified
50
+ section or step.
51
+ LONGDESC
52
+ option :run, aliases: :r, type: :string, default: :ssh_kit
53
+ option :noop, aliases: :n, type: :boolean
54
+ option :auto, aliases: :a, type: :boolean
55
+ option :"no-paranoid", aliases: :P, type: :boolean
56
+ option :start_at, aliases: :s, type: :string, default: "0"
57
+ def exec(runbook)
58
+ runbook = _retrieve_runbook(runbook, :exec)
59
+ Runbook::Runner.new(runbook).run(
60
+ run: options[:run],
61
+ noop: options[:noop],
62
+ auto: options[:auto],
63
+ paranoid: options[:"no-paranoid"] == nil,
64
+ start_at: options[:start_at],
65
+ )
66
+ end
67
+
68
+ desc "--version", "Print runbook's version"
69
+ def __print_version
70
+ puts "Runbook v#{Runbook::VERSION}"
71
+ end
72
+
73
+ private
74
+
75
+ def _set_cli_config(config, cmd)
76
+ unless File.exist?(config)
77
+ raise Thor::InvocationError, "#{cmd}: cannot access #{config}: No such file or directory"
78
+ end
79
+ Runbook::Configuration.cli_config_file = config
80
+ end
81
+
82
+ def _retrieve_runbook(runbook, cmd)
83
+ unless File.exist?(runbook)
84
+ raise Thor::InvocationError, "#{cmd}: cannot access #{runbook}: No such file or directory"
85
+ end
86
+ load(runbook)
87
+ Runbook.books.last || eval(File.read(runbook))
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,110 @@
1
+ module Runbook
2
+ class << self
3
+ attr_accessor :configuration
4
+ end
5
+
6
+ def self.configure
7
+ Configuration.load_config
8
+ self.configuration ||= Configuration.new
9
+ yield(configuration) if block_given?
10
+ end
11
+
12
+ def self.reset_configuration
13
+ self.configuration = Configuration.new
14
+ Configuration.loaded = false
15
+ end
16
+
17
+ class Configuration
18
+ attr_accessor :ssh_kit
19
+ attr_accessor :enable_sudo_prompt
20
+ attr_reader :use_same_sudo_password
21
+
22
+ GlobalConfigFile = "/etc/runbook.conf"
23
+ ProjectConfigFile = "Runbookfile"
24
+ UserConfigFile = ".runbook.conf"
25
+
26
+ def self.cli_config_file
27
+ @cli_config_file
28
+ end
29
+
30
+ def self.cli_config_file=(cli_config_file)
31
+ @cli_config_file = cli_config_file
32
+ end
33
+
34
+ def self.loaded
35
+ @loaded
36
+ end
37
+
38
+ def self.loaded=(loaded)
39
+ @loaded = loaded
40
+ end
41
+
42
+ def self.load_config
43
+ return if @loaded
44
+ @loaded = true
45
+ _load_global_config
46
+ _load_project_config
47
+ _load_user_config
48
+ _load_cli_config
49
+ # Set defaults
50
+ Runbook.configure
51
+ end
52
+
53
+ def self.reconfigure
54
+ @loaded = false
55
+ load_config
56
+ end
57
+
58
+ def self._load_global_config
59
+ load(GlobalConfigFile) if File.exist?(GlobalConfigFile)
60
+ end
61
+
62
+ def self._load_project_config
63
+ dir = Dir.pwd
64
+ loop do
65
+ config_path = File.join(dir, ProjectConfigFile)
66
+ if File.exist?(config_path)
67
+ load(config_path)
68
+ return
69
+ end
70
+ break if File.identical?(dir, "/")
71
+ dir = File.join(dir, "..")
72
+ end
73
+ end
74
+
75
+ def self._load_user_config
76
+ user_config_file = File.join(ENV["HOME"], UserConfigFile)
77
+ load(user_config_file) if File.exist?(user_config_file)
78
+ end
79
+
80
+ def self._load_cli_config
81
+ if cli_config_file && File.exist?(cli_config_file)
82
+ load(cli_config_file)
83
+ end
84
+ end
85
+
86
+ def initialize
87
+ self.ssh_kit = SSHKit.config
88
+ ssh_kit.output = Airbrussh::Formatter.new(
89
+ $stdout,
90
+ banner: nil,
91
+ command_output: true,
92
+ )
93
+ self.enable_sudo_prompt = true
94
+ self.use_same_sudo_password = true
95
+ end
96
+
97
+ def use_same_sudo_password=(use_same_pwd)
98
+ @use_same_sudo_password = use_same_pwd
99
+ SSHKit::Sudo::InteractionHandler.class_eval do
100
+ if use_same_pwd
101
+ use_same_password!
102
+ else
103
+ def password_cache_key(host)
104
+ "#{host.user}@#{host.hostname}"
105
+ end
106
+ end
107
+ end
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,21 @@
1
+ module Runbook
2
+ module DSL
3
+ def self.class(*modules)
4
+ Class.new do
5
+ attr_reader :parent
6
+
7
+ def initialize(parent)
8
+ @parent = parent
9
+ end
10
+
11
+ modules.each do |mod|
12
+ prepend mod
13
+ end
14
+ end
15
+ end
16
+
17
+ def self.dsl_ivars
18
+ [:@parent]
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,17 @@
1
+ module Runbook::Entities
2
+ class Book < Runbook::Entity
3
+ def initialize(title)
4
+ super(title)
5
+ end
6
+
7
+ # Seed data for 'render' tree traversal method
8
+ def self.initial_render_metadata
9
+ {depth: 1, index: 0}
10
+ end
11
+
12
+ # Seed data for 'run' tree traversal method
13
+ def self.initial_run_metadata
14
+ {depth: 1, index: 0, position: ""}
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,7 @@
1
+ module Runbook::Entities
2
+ class Section < Runbook::Entity
3
+ def initialize(title)
4
+ super(title)
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ module Runbook::Entities
2
+ class Step < Runbook::Entity
3
+ def initialize(title=nil)
4
+ super(title)
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,127 @@
1
+ module Runbook
2
+ class Entity < Node
3
+ include Runbook::Hooks::Invoker
4
+ const_set(:DSL, Runbook::DSL.class)
5
+
6
+ def self.inherited(child_class)
7
+ child_class.const_set(:DSL, Runbook::DSL.class)
8
+ end
9
+
10
+ attr_accessor :parent
11
+ attr_reader :title, :dsl
12
+
13
+ def initialize(title, parent: nil)
14
+ @title = title
15
+ @parent = parent
16
+ @dsl = "#{self.class}::DSL".constantize.new(self)
17
+ end
18
+
19
+ def add(item)
20
+ items << item
21
+ item.parent = self
22
+ end
23
+
24
+ def items
25
+ @items ||= []
26
+ end
27
+
28
+ def method_missing(method, *args, &block)
29
+ if dsl.respond_to?(method)
30
+ dsl.send(method, *args, &block)
31
+ else
32
+ super
33
+ end
34
+ end
35
+
36
+ def respond_to?(name, include_private = false)
37
+ !!(dsl.respond_to?(name) || super)
38
+ end
39
+
40
+ def render(view, output, metadata)
41
+ invoke_with_hooks(view, self, output, metadata) do
42
+ view.render(self, output, metadata)
43
+ items.each_with_index do |item, index|
44
+ new_metadata = _render_metadata(items, item, metadata, index)
45
+ item.render(view, output, new_metadata)
46
+ end
47
+ end
48
+ end
49
+
50
+ def run(run, metadata)
51
+ return if _should_reverse?(run, metadata)
52
+ return if dynamic? && visited?
53
+
54
+ invoke_with_hooks(run, self, metadata) do
55
+ run.execute(self, metadata)
56
+ next if _should_reverse?(run, metadata)
57
+ loop do
58
+ items.each_with_index do |item, index|
59
+ new_metadata = _run_metadata(items, item, metadata, index)
60
+ # Optimization
61
+ break if _should_reverse?(run, new_metadata)
62
+ item.run(run, new_metadata)
63
+ end
64
+
65
+ if _should_retraverse?(run, metadata)
66
+ metadata[:reverse] = false
67
+ else
68
+ break
69
+ end
70
+ end
71
+ end
72
+ self.visited!
73
+ end
74
+
75
+ def dynamic!
76
+ items.each(&:dynamic!)
77
+ @dynamic = true
78
+ end
79
+
80
+ def _render_metadata(items, item, metadata, index)
81
+ index = items.select do |item|
82
+ item.is_a?(Entity)
83
+ end.index(item)
84
+
85
+ metadata.merge(
86
+ {
87
+ depth: metadata[:depth] + 1,
88
+ index: index,
89
+ }
90
+ )
91
+ end
92
+
93
+ def _run_metadata(items, item, metadata, index)
94
+ pos_index = items.select do |item|
95
+ item.is_a?(Entity)
96
+ end.index(item)
97
+
98
+ if pos_index
99
+ if metadata[:position].empty?
100
+ pos = "#{pos_index + 1}"
101
+ else
102
+ pos = "#{metadata[:position]}.#{pos_index + 1}"
103
+ end
104
+ else
105
+ pos = metadata[:position]
106
+ end
107
+
108
+ metadata.merge(
109
+ {
110
+ depth: metadata[:depth] + 1,
111
+ index: index,
112
+ position: pos,
113
+ }
114
+ )
115
+ end
116
+
117
+ def _should_reverse?(run, metadata)
118
+ return false unless metadata[:reverse]
119
+ run.past_position?(metadata[:position], metadata[:start_at])
120
+ end
121
+
122
+ def _should_retraverse?(run, metadata)
123
+ return false unless metadata[:reverse]
124
+ run.start_at_is_substep?(self, metadata)
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,7 @@
1
+ module Runbook
2
+ StandardError = Class.new(::StandardError)
3
+
4
+ class Runner
5
+ ExecutionError = Class.new(Runbook::StandardError)
6
+ end
7
+ end
@@ -0,0 +1,13 @@
1
+ module Runbook::Extensions
2
+ module Add
3
+ module DSL
4
+ def add(entity)
5
+ parent.add(entity)
6
+ end
7
+ end
8
+ end
9
+
10
+ Runbook::Entities::Book::DSL.prepend(Add::DSL)
11
+ Runbook::Entities::Section::DSL.prepend(Add::DSL)
12
+ Runbook::Entities::Step::DSL.prepend(Add::DSL)
13
+ end
@@ -0,0 +1,14 @@
1
+ module Runbook::Extensions
2
+ module Description
3
+ module DSL
4
+ def description(msg)
5
+ Runbook::Statements::Description.new(msg).tap do |desc|
6
+ parent.add(desc)
7
+ end
8
+ end
9
+ end
10
+ end
11
+
12
+ Runbook::Entities::Book::DSL.prepend(Description::DSL)
13
+ Runbook::Entities::Section::DSL.prepend(Description::DSL)
14
+ end
@@ -0,0 +1,15 @@
1
+ module Runbook::Extensions
2
+ module Sections
3
+ module DSL
4
+ def section(title, &block)
5
+ Runbook::Entities::Section.new(title).tap do |section|
6
+ parent.add(section)
7
+ section.dsl.instance_eval(&block)
8
+ end
9
+ end
10
+ end
11
+ end
12
+
13
+ Runbook::Entities::Book::DSL.prepend(Sections::DSL)
14
+ Runbook::Entities::Section::DSL.prepend(Sections::DSL)
15
+ end
@@ -0,0 +1,51 @@
1
+ module Runbook::Extensions
2
+ module SharedVariables
3
+ module RunHooks
4
+ def self.register_shared_variables_hooks(base)
5
+ base.register_hook(
6
+ :set_ivars_hook,
7
+ :before,
8
+ Runbook::Statement,
9
+ ) do |object, metadata|
10
+ target = SharedVariables::RunHooks._target(object)
11
+ metadata[:repo].each do |key, value|
12
+ target.singleton_class.class_eval { attr_accessor key }
13
+ target.send(SharedVariables::RunHooks._eqls_method(key), value)
14
+ end
15
+ end
16
+
17
+ base.register_hook(
18
+ :copy_ivars_to_repo_hook,
19
+ :after,
20
+ Runbook::Statement,
21
+ before: :save_repo_hook
22
+ ) do |object, metadata|
23
+ SharedVariables::RunHooks._copy_ivars_to_repo(object, metadata)
24
+ end
25
+ end
26
+
27
+ def self._copy_ivars_to_repo(object, metadata)
28
+ target = _target(object)
29
+ ivars = target.instance_variables - Runbook::DSL.dsl_ivars
30
+
31
+ ivars.each do |ivar|
32
+ repo_key = ivar.to_s[1..-1].to_sym
33
+ val = target.instance_variable_get(ivar)
34
+ metadata[:repo][repo_key] = val
35
+ end
36
+ end
37
+
38
+ def self._target(object)
39
+ object.parent.dsl
40
+ end
41
+
42
+ def self._eqls_method(key)
43
+ "#{key}=".to_sym
44
+ end
45
+ end
46
+ end
47
+
48
+ Runbook.runs.each do |run|
49
+ SharedVariables::RunHooks.register_shared_variables_hooks(run)
50
+ end
51
+ end