dopi 0.17.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 (149) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +20 -0
  3. data/.rspec +2 -0
  4. data/.ruby-version +1 -0
  5. data/CHANGELOG.md +322 -0
  6. data/Gemfile +7 -0
  7. data/Gemfile.lock +102 -0
  8. data/LICENSE.txt +177 -0
  9. data/README.md +309 -0
  10. data/Rakefile +44 -0
  11. data/Vagrantfile +64 -0
  12. data/bin/dopi +4 -0
  13. data/doc/getting_started.md +247 -0
  14. data/doc/getting_started_examples/001_hello_world.yaml +17 -0
  15. data/doc/getting_started_examples/002_connecting_over_ssh.yaml +35 -0
  16. data/doc/plugins/custom.md +88 -0
  17. data/doc/plugins/mco/rpc.md +82 -0
  18. data/doc/plugins/ssh/custom.md +141 -0
  19. data/doc/plugins/ssh/file_contains.md +37 -0
  20. data/doc/plugins/ssh/file_deploy.md +52 -0
  21. data/doc/plugins/ssh/file_exists.md +31 -0
  22. data/doc/plugins/ssh/file_replace.md +37 -0
  23. data/doc/plugins/ssh/puppet_agent_run.md +50 -0
  24. data/doc/plugins/ssh/reboot.md +22 -0
  25. data/doc/plugins/ssh/wait_for_login.md +53 -0
  26. data/doc/plugins/winrm/cmd.md +161 -0
  27. data/doc/plugins/winrm/file_contains.md +39 -0
  28. data/doc/plugins/winrm/file_exists.md +31 -0
  29. data/doc/plugins/winrm/powershell.md +27 -0
  30. data/doc/plugins/winrm/puppet_agent_run.md +49 -0
  31. data/doc/plugins/winrm/reboot.md +17 -0
  32. data/doc/plugins/winrm/wait_for_login.md +55 -0
  33. data/dopi.gemspec +42 -0
  34. data/lib/dopi/cli/command_add.rb +35 -0
  35. data/lib/dopi/cli/command_list.rb +19 -0
  36. data/lib/dopi/cli/command_remove.rb +31 -0
  37. data/lib/dopi/cli/command_reset.rb +27 -0
  38. data/lib/dopi/cli/command_run.rb +68 -0
  39. data/lib/dopi/cli/command_show.rb +109 -0
  40. data/lib/dopi/cli/command_update.rb +37 -0
  41. data/lib/dopi/cli/command_validate.rb +27 -0
  42. data/lib/dopi/cli/global_options.rb +55 -0
  43. data/lib/dopi/cli/log.rb +33 -0
  44. data/lib/dopi/cli.rb +57 -0
  45. data/lib/dopi/command/custom.rb +52 -0
  46. data/lib/dopi/command/dummy.rb +27 -0
  47. data/lib/dopi/command/mco/rpc.rb +158 -0
  48. data/lib/dopi/command/ssh/custom.rb +48 -0
  49. data/lib/dopi/command/ssh/file_contains.rb +70 -0
  50. data/lib/dopi/command/ssh/file_deploy.rb +71 -0
  51. data/lib/dopi/command/ssh/file_exists.rb +54 -0
  52. data/lib/dopi/command/ssh/file_replace.rb +96 -0
  53. data/lib/dopi/command/ssh/puppet_agent_run.rb +63 -0
  54. data/lib/dopi/command/ssh/reboot.rb +50 -0
  55. data/lib/dopi/command/ssh/wait_for_login.rb +68 -0
  56. data/lib/dopi/command/winrm/cmd.rb +44 -0
  57. data/lib/dopi/command/winrm/file_contains.rb +66 -0
  58. data/lib/dopi/command/winrm/file_exists.rb +51 -0
  59. data/lib/dopi/command/winrm/powershell.rb +16 -0
  60. data/lib/dopi/command/winrm/puppet_agent_run.rb +61 -0
  61. data/lib/dopi/command/winrm/reboot.rb +33 -0
  62. data/lib/dopi/command/winrm/wait_for_login.rb +49 -0
  63. data/lib/dopi/command.rb +239 -0
  64. data/lib/dopi/command_parser/arguments.rb +38 -0
  65. data/lib/dopi/command_parser/credentials.rb +59 -0
  66. data/lib/dopi/command_parser/env.rb +37 -0
  67. data/lib/dopi/command_parser/exec.rb +27 -0
  68. data/lib/dopi/command_parser/exit_code.rb +73 -0
  69. data/lib/dopi/command_parser/output.rb +126 -0
  70. data/lib/dopi/command_set.rb +66 -0
  71. data/lib/dopi/connector/local.rb +77 -0
  72. data/lib/dopi/connector/ssh.rb +170 -0
  73. data/lib/dopi/connector/winrm.rb +167 -0
  74. data/lib/dopi/error.rb +43 -0
  75. data/lib/dopi/log.rb +18 -0
  76. data/lib/dopi/node.rb +70 -0
  77. data/lib/dopi/plan.rb +99 -0
  78. data/lib/dopi/pluginmanager.rb +62 -0
  79. data/lib/dopi/state.rb +226 -0
  80. data/lib/dopi/state_store.rb +155 -0
  81. data/lib/dopi/step.rb +227 -0
  82. data/lib/dopi/step_set.rb +70 -0
  83. data/lib/dopi/version.rb +3 -0
  84. data/lib/dopi.rb +165 -0
  85. data/spec/command_helper.rb +11 -0
  86. data/spec/fixtures/mco_client.cfg +26 -0
  87. data/spec/fixtures/plans/fail_on_timeout.yaml +20 -0
  88. data/spec/fixtures/plans/hello_world.yaml +34 -0
  89. data/spec/fixtures/plans/non_existing_node.yaml +26 -0
  90. data/spec/fixtures/plans/test_role_variable.yaml +29 -0
  91. data/spec/fixtures/puppet/Puppetfile +8 -0
  92. data/spec/fixtures/puppet/Puppetfile.lock +57 -0
  93. data/spec/fixtures/puppet/hiera.yaml +6 -0
  94. data/spec/fixtures/puppet/manifests/site.pp +52 -0
  95. data/spec/fixtures/test_configuration.yaml +54 -0
  96. data/spec/fixtures/test_credentials.yaml +11 -0
  97. data/spec/fixtures/test_deloyed_file.txt +5 -0
  98. data/spec/fixtures/test_infrastructure.yaml +12 -0
  99. data/spec/fixtures/test_nodes.yaml +45 -0
  100. data/spec/fixtures/testenv_plan.yaml +159 -0
  101. data/spec/integration/dopi/addrun_spec.rb +31 -0
  102. data/spec/integration/dopi/cli/command_run_spec.rb +38 -0
  103. data/spec/integration/dopi/cli/global_options_spec.rb +128 -0
  104. data/spec/integration/dopi/command_spec.rb +66 -0
  105. data/spec/integration/dopi/fail_check_plans/file_exists_fails.yaml +38 -0
  106. data/spec/integration/dopi/fail_check_plans/output_parser.yaml +39 -0
  107. data/spec/integration/dopi/fail_check_plans/powershell_fail.yaml +25 -0
  108. data/spec/integration/dopi/fail_check_plans/timeout.yaml +29 -0
  109. data/spec/integration/dopi/fail_check_plans/verify_commands.yaml +33 -0
  110. data/spec/integration/dopi/failplan.rb +27 -0
  111. data/spec/integration/dopi/plan.rb +27 -0
  112. data/spec/integration/dopi/plans/dummy.yaml +29 -0
  113. data/spec/integration/dopi/plans/max_per_role.yaml +55 -0
  114. data/spec/integration/dopi/plans/no_timeout.yaml +29 -0
  115. data/spec/integration/dopi/plans/node_and_role_patterns.yaml +58 -0
  116. data/spec/integration/dopi/plans/node_by_config.yaml +116 -0
  117. data/spec/integration/dopi/plans/plugin_defaults.yaml +86 -0
  118. data/spec/integration/dopi/plans/plugins/mco/rpc.yaml +33 -0
  119. data/spec/integration/dopi/plans/plugins/ssh/custom.yaml +97 -0
  120. data/spec/integration/dopi/plans/plugins/ssh/file_contains.yaml +51 -0
  121. data/spec/integration/dopi/plans/plugins/ssh/file_deploy.yaml +82 -0
  122. data/spec/integration/dopi/plans/plugins/ssh/file_exists.yaml +69 -0
  123. data/spec/integration/dopi/plans/plugins/ssh/file_replace.yaml +55 -0
  124. data/spec/integration/dopi/plans/plugins/ssh/puppet_agent_run.yaml +45 -0
  125. data/spec/integration/dopi/plans/plugins/ssh/reboot.yaml +43 -0
  126. data/spec/integration/dopi/plans/plugins/ssh/wait_for_login.yaml +45 -0
  127. data/spec/integration/dopi/plans/plugins/winrm/cmd.yaml +39 -0
  128. data/spec/integration/dopi/plans/plugins/winrm/file_contains.yaml +51 -0
  129. data/spec/integration/dopi/plans/plugins/winrm/file_exists.yaml +69 -0
  130. data/spec/integration/dopi/plans/plugins/winrm/reboot.yaml +31 -0
  131. data/spec/integration/dopi/plans/resolve_roles_on_validate.yaml +23 -0
  132. data/spec/integration/dopi/plans/ssh_parallel.yaml +37 -0
  133. data/spec/integration/dopi/plans/verify_commands.yaml +49 -0
  134. data/spec/spec_helper.rb +104 -0
  135. data/spec/unit/dopi/command/custom_spec.rb +58 -0
  136. data/spec/unit/dopi/command/mco/rpc_spec.rb +157 -0
  137. data/spec/unit/dopi/command/ssh/custom_spec.rb +30 -0
  138. data/spec/unit/dopi/command/ssh/file_deploy_spec.rb +42 -0
  139. data/spec/unit/dopi/command/ssh/file_replace_spec.rb +35 -0
  140. data/spec/unit/dopi/command_parser/credentials_spec.rb +53 -0
  141. data/spec/unit/dopi/command_parser/exit_code_spec.rb +63 -0
  142. data/spec/unit/dopi/command_parser/output_spec.rb +129 -0
  143. data/spec/unit/dopi/command_spec.rb +14 -0
  144. data/spec/unit/dopi/connector/winrm_spec.rb +111 -0
  145. data/spec/unit/dopi/node_spec.rb +24 -0
  146. data/spec/unit/dopi/plan_spec.rb +31 -0
  147. data/spec/unit/dopi/state_spec.rb +109 -0
  148. data/spec/unit/dopi/step_spec.rb +13 -0
  149. metadata +448 -0
data/lib/dopi/step.rb ADDED
@@ -0,0 +1,227 @@
1
+ #
2
+ # Step
3
+ #
4
+ require 'parallel'
5
+
6
+ module Dopi
7
+ class Step
8
+ include Dopi::State
9
+ include DopCommon::NodeFilter
10
+
11
+ DEFAULT_MAX_IN_FLIGHT = 3
12
+ DEFAULT_MAX_PER_ROLE = -1
13
+
14
+ attr_accessor :plan, :nodes
15
+
16
+ def initialize(step_parser, plan)
17
+ @step_parser = step_parser
18
+ @plan = plan
19
+ @nodes = filter_nodes(plan.nodes, step_parser)
20
+
21
+ @next_mutex = Mutex.new
22
+ @notify_mutex = Mutex.new
23
+ @queue = Queue.new
24
+
25
+ command_sets.each{|command_set| state_add_child(command_set)}
26
+ end
27
+
28
+ # Loading queue object from yaml files results in not properly initialized
29
+ # queue and a type error when using it. Skip queue when converting to yaml.
30
+ # Will be nil after loading from yaml and must be re-created.
31
+ def to_yaml_properties
32
+ super - [:@queue]
33
+ end
34
+
35
+ def name
36
+ @step_parser.name
37
+ end
38
+
39
+ def valid?
40
+ if @nodes.empty?
41
+ Dopi.log.error("Step '#{name}': Nodes list is empty")
42
+ return false
43
+ end
44
+ # since they are identical in respect to parsing
45
+ # we only have to check one of them
46
+ command_sets.first.valid?
47
+ end
48
+
49
+ def command_sets
50
+ @command_sets ||= @nodes.map do |node|
51
+ delete_plugin_defaults
52
+ set_plugin_defaults
53
+ Dopi::CommandSet.new(@step_parser, self, node)
54
+ end
55
+ end
56
+
57
+ def run(run_options)
58
+ if state_done?
59
+ Dopi.log.info("Step '#{name}' is in state 'done'. Skipping")
60
+ return
61
+ end
62
+ Dopi.log.info("Starting to run step '#{name}'")
63
+
64
+ nodes_to_run = filter_nodes(@nodes, run_options[:run_for_nodes])
65
+ command_sets_to_run = command_sets.select {|cs| nodes_to_run.include?(cs.node)}
66
+
67
+ unless run_options[:noop]
68
+ run_canary(run_options, command_sets_to_run) if canary_host
69
+ run_command_sets(run_options, command_sets_to_run) unless state_failed?
70
+ else
71
+ command_sets_to_run.each{|command_set| command_set.run(run_options[:noop])}
72
+ end
73
+
74
+ Dopi.log.info("Step '#{name}' successfully finished.") if state_done?
75
+ Dopi.log.error("Step '#{name}' failed! Stopping execution.") if state_failed?
76
+ end
77
+
78
+ def load_state(state_hash)
79
+ command_sets.each do |command_set|
80
+ command_set_state = state_hash[command_set.name] || []
81
+ command_set.load_state(command_set_state)
82
+ end
83
+ end
84
+
85
+ def state_hash
86
+ command_sets_hash = {}
87
+ command_sets.each do |command_set|
88
+ command_sets_hash[command_set.name] = command_set.state_hash
89
+ end
90
+ command_sets_hash
91
+ end
92
+
93
+ private
94
+
95
+ def run_canary(run_options, command_sets_to_run)
96
+ pick = rand(command_sets_to_run.length - 1)
97
+ command_sets_to_run[pick].run(run_options[:noop])
98
+ end
99
+
100
+ def run_command_sets(run_options, command_sets_to_run)
101
+ in_threads = max_in_flight == -1 ? command_sets_to_run.length : max_in_flight
102
+ pick = lambda { next_command_set(command_sets_to_run) || Parallel::Stop }
103
+ Parallel.each(pick, :in_threads => in_threads) do |command_set|
104
+ plan.context_logger.log_context = command_set.node.name
105
+ command_set.run(run_options[:noop])
106
+ notify_done
107
+ end
108
+ end
109
+
110
+ # notify the waiting thread that a command_set has finished it's run
111
+ def notify_done
112
+ @notify_mutex.synchronize do
113
+ queue.push(1)
114
+ end
115
+ end
116
+
117
+ # This method returns the next command_set which is ready
118
+ # to run. If no node is ready because of constrains
119
+ # it will block the thread until notify_done was called
120
+ # from a finishing thread. If no command_set is in the state
121
+ # ready it will return nil.
122
+ def next_command_set(command_sets_to_run)
123
+ @next_mutex.synchronize do
124
+ ready_command_sets = command_sets_to_run.select{|n| n.state == :ready}
125
+ return nil if ready_command_sets.empty?
126
+ loop do
127
+ return nil if state_failed? or signals[:stop]
128
+ @notify_mutex.synchronize do
129
+ queue.clear
130
+ next_command_set = ready_command_sets.find{|cs| is_runnable?(cs.node)}
131
+ unless next_command_set.nil?
132
+ next_command_set.state_start
133
+ return next_command_set
134
+ end
135
+ end
136
+ queue.pop # wait until a thread notifies it has finished
137
+ end
138
+ end
139
+ end
140
+
141
+ # check if a node is runnable or if there are constrains
142
+ # which prevent it from running
143
+ def is_runnable?(node)
144
+ if max_per_role > 0
145
+ running_groups[node.role] < max_per_role
146
+ else
147
+ true
148
+ end
149
+ end
150
+
151
+ # return a hash with the group names as keys and the
152
+ # amount of running nodes as value
153
+ def running_groups
154
+ role_counter = Hash.new(0)
155
+ command_sets.each do |command_set|
156
+ if [:running, :starting].include? command_set.state
157
+ role_counter[command_set.node.role] += 1
158
+ end
159
+ end
160
+ role_counter
161
+ end
162
+
163
+ def max_in_flight
164
+ @max_in_flight ||= @step_parser.max_in_flight || @plan.max_in_flight || DEFAULT_MAX_IN_FLIGHT
165
+ end
166
+
167
+ def max_per_role
168
+ @max_per_role ||= @step_parser.max_per_role || @plan.max_per_role || DEFAULT_MAX_PER_ROLE
169
+ end
170
+
171
+ def canary_host
172
+ @canary_host ||= @step_parser.canary_host || @plan.canary_host
173
+ end
174
+
175
+ def delete_plugin_defaults
176
+ if @step_parser.delete_plugin_defaults == :all
177
+ # Wipe all the defaults
178
+ PluginManager.plugin_klass_list('^dopi/command/').each do |plugin_klass|
179
+ @nodes.each{|node| plugin_klass.delete_plugin_defaults(node.name)}
180
+ end
181
+ else
182
+ @step_parser.delete_plugin_defaults.each do |entry|
183
+ plugin_list(entry[:plugins]).each do |plugin_klass|
184
+ if entry[:delete_keys] == :all
185
+ @nodes.each{|node| plugin_klass.delete_plugin_defaults(node.name)}
186
+ else
187
+ entry[:delete_keys].each do |key|
188
+ @nodes.each{|node| plugin_klass.delete_plugin_default(node.name, key)}
189
+ end
190
+ end
191
+ end
192
+ end
193
+ end
194
+ end
195
+
196
+ def set_plugin_defaults
197
+ @step_parser.set_plugin_defaults.each do |entry|
198
+ defaults_hash = entry.dup
199
+ defaults_hash.delete(:plugins)
200
+ plugin_list(entry[:plugins]).each do |plugin_klass|
201
+ @nodes.each{|node| plugin_klass.set_plugin_defaults(node.name, defaults_hash)}
202
+ end
203
+ end
204
+ end
205
+
206
+ def plugin_list(plugin_filter_list)
207
+ if plugin_filter_list == :all
208
+ PluginManager.plugin_klass_list('^dopi/command/')
209
+ else
210
+ all_plugin_names = PluginManager.plugin_name_list('^dopi/command/').map{|p| p.sub('dopi/command/', '')}
211
+ selected_plugin_names = plugin_filter_list.map do |filter|
212
+ case filter
213
+ when Regexp then all_plugin_names.select{|p| p =~ filter}
214
+ else all_plugin_names.select{|p| p == filter}
215
+ end
216
+ end
217
+ selected_plugin_names.flatten.uniq.map{|p| PluginManager.plugin_klass('dopi/command/' + p)}
218
+ end
219
+ end
220
+
221
+ # Will be skipped when dumping yaml, therefore nil after loading from yaml.
222
+ def queue
223
+ @queue ||= Queue.new
224
+ end
225
+
226
+ end
227
+ end
@@ -0,0 +1,70 @@
1
+ #
2
+ # This class parses loades step sets
3
+ #
4
+ require 'yaml'
5
+ require 'dop_common'
6
+
7
+ module Dopi
8
+ class StepSet
9
+ include Dopi::State
10
+
11
+ def initialize(parsed_step_set, plan)
12
+ @parsed_step_set = parsed_step_set
13
+ @plan = plan
14
+ steps.each{|step| state_add_child(step)}
15
+ end
16
+
17
+ def name
18
+ @parsed_step_set.name
19
+ end
20
+
21
+ def run(run_options)
22
+ if state_done?
23
+ Dopi.log.info("Step set #{name} is in state 'done'. Nothing to do")
24
+ return
25
+ end
26
+ unless state_ready?
27
+ raise StandardError, "Step set #{name} is not in state 'ready'. Try to reset the plan"
28
+ end
29
+ steps.each do |step|
30
+ step.run(run_options)
31
+ break if signals[:stop] || state_failed?
32
+ end
33
+ end
34
+
35
+ # The main validation work is done in the dop_common
36
+ # parser. We just add the command plugin parsers
37
+ def valid?
38
+ validity = true
39
+ validity = false unless steps.all?{|step| step.valid? }
40
+ validity
41
+ rescue Dopi::NoRoleFoundError => e
42
+ Dopi.log.warn(e.message)
43
+ end
44
+
45
+ def steps
46
+ # Before all the new commands get parsed we have to make sure we
47
+ # Reset all the plugin defaults
48
+ PluginManager.plugin_klass_list('^dopi/command/').each do |plugin_klass|
49
+ plugin_klass.wipe_plugin_defaults
50
+ end
51
+ @steps ||= @parsed_step_set.steps.map do |parsed_step|
52
+ ::Dopi::Step.new(parsed_step, @plan)
53
+ end
54
+ end
55
+
56
+ def load_state(state_hash)
57
+ return if state_hash.empty?
58
+ steps.each_with_index do |step, i|
59
+ step.load_state(state_hash[i])
60
+ end
61
+ end
62
+
63
+ def state_hash
64
+ steps.map do |step|
65
+ step.state_hash
66
+ end
67
+ end
68
+
69
+ end
70
+ end
@@ -0,0 +1,3 @@
1
+ module Dopi
2
+ VERSION = '0.17.0'
3
+ end
data/lib/dopi.rb ADDED
@@ -0,0 +1,165 @@
1
+ require 'dop_common'
2
+ require "dopi/error"
3
+ require "dopi/log"
4
+ require "dopi/pluginmanager"
5
+ require "dopi/state"
6
+ require "dopi/state_store"
7
+ require "dopi/command_parser/exec"
8
+ require "dopi/command_parser/env"
9
+ require "dopi/command_parser/arguments"
10
+ require "dopi/command_parser/credentials"
11
+ require "dopi/command_parser/exit_code"
12
+ require "dopi/command_parser/output"
13
+ require "dopi/connector/local"
14
+ require "dopi/connector/ssh"
15
+ require "dopi/connector/winrm"
16
+ require "dopi/command"
17
+ require "dopi/command_set"
18
+ require "dopi/node"
19
+ require "dopi/plan"
20
+ require "dopi/step"
21
+ require "dopi/step_set"
22
+ require "dopi/version"
23
+
24
+ module Dopi
25
+
26
+ def self.valid?(raw_plan)
27
+ hash, _ = plan_store.read_plan_file(raw_plan)
28
+ plan_parser = DopCommon::Plan.new(hash)
29
+ plan = Dopi::Plan.new(plan_parser)
30
+ plan.valid?
31
+ end
32
+
33
+ def self.add(raw_plan)
34
+ raise StandardError, 'Plan not valid; did not add' unless valid?(raw_plan)
35
+ plan_store.add(raw_plan)
36
+ end
37
+
38
+ def self.update_plan(raw_plan, options = {})
39
+ raise StandardError, 'Plan not valid; did not add' unless valid?(raw_plan)
40
+ plan_name = plan_store.update(raw_plan)
41
+ update_state(plan_name, options)
42
+ plan_name
43
+ end
44
+
45
+ def self.update_state(plan_name, options = {})
46
+ plan_store.run_lock(plan_name) do
47
+ state_store = Dopi::StateStore.new(plan_name, plan_store)
48
+ state_store.update(options)
49
+ end
50
+ end
51
+
52
+ def self.remove(plan_name, remove_dopi_state = true, remove_dopv_state = false)
53
+ plan_store.remove(plan_name, remove_dopi_state, remove_dopv_state)
54
+ end
55
+
56
+ def self.list
57
+ plan_store.list
58
+ end
59
+
60
+ # TODO: this returns a plan with loaded state at the moment.
61
+ # THIS MAY BE CHANGED IN THE FUTURE!!
62
+ def self.show(plan_name)
63
+ ensure_plan_exists(plan_name)
64
+ state_store = Dopi::StateStore.new(plan_name, plan_store)
65
+ plan = get_plan(plan_name)
66
+ plan.load_state(state_store.state_hash)
67
+ plan
68
+ end
69
+
70
+ def self.run(plan_name, options = {})
71
+ ensure_plan_exists(plan_name)
72
+ update_state(plan_name)
73
+ plan_store.run_lock(plan_name) do
74
+ state_store = Dopi::StateStore.new(plan_name, plan_store)
75
+ dopv_state_store = plan_store.state_store(plan_name, 'dopv')
76
+ dopv_state_store.transaction(true) do
77
+ dopv_node_info = dopv_state_store[:nodes] || {}
78
+ api_node_info = options[:node_info] || {}
79
+ options[:node_info] = dopv_node_info.merge(api_node_info)
80
+ end
81
+ plan = get_plan(plan_name)
82
+ plan.load_state(state_store.state_hash)
83
+ manager = nil
84
+ if block_given?
85
+ manager = Thread.new { yield(plan) }
86
+ else
87
+ run_signal_handler(plan)
88
+ end
89
+ begin
90
+ state_store_observer = Dopi::StateStoreObserver.new(plan, state_store)
91
+ plan.add_observer(state_store_observer)
92
+ plan.run(options)
93
+ manager.join if manager
94
+ ensure
95
+ state_store_observer.update
96
+ end
97
+ end
98
+ end
99
+
100
+ def self.reset(plan_name, force = false)
101
+ ensure_plan_exists(plan_name)
102
+ plan_store.run_lock(plan_name) do
103
+ state_store = Dopi::StateStore.new(plan_name, plan_store)
104
+ plan = get_plan(plan_name)
105
+ plan.load_state(state_store.state_hash)
106
+ plan.state_reset_with_children(force)
107
+ state_store.persist_state(plan)
108
+ end
109
+ end
110
+
111
+ def self.on_state_change(plan_name)
112
+ ensure_plan_exists(plan_name)
113
+ state_store = Dopi::StateStore.new(plan_name, plan_store)
114
+ state_store.on_change do
115
+ yield
116
+ end
117
+ end
118
+
119
+ private
120
+
121
+ def self.plan_store
122
+ @plan_store ||= DopCommon::PlanStore.new(DopCommon.config.plan_store_dir)
123
+ end
124
+
125
+ def self.get_plan(plan_name)
126
+ raise StandardError, 'Please update the plan state, there are pending updates' if pending_updates?(plan_name)
127
+ plan_parser = plan_store.get_plan(plan_name)
128
+ Dopi::Plan.new(plan_parser)
129
+ end
130
+
131
+ def self.pending_updates?(plan_name)
132
+ state_store = Dopi::StateStore.new(plan_name, plan_store)
133
+ state_store.pending_updates?
134
+ end
135
+
136
+ def self.run_signal_handler(plan)
137
+ plan.reset_signals
138
+ signal_handler_thread = Thread.new do
139
+ Dopi.log.info("Starting signal handling")
140
+ signal_counter = 0
141
+ DopCommon::SignalHandler.new.handle_signals(:INT, :TERM) do
142
+ signal_counter += 1
143
+ case signal_counter
144
+ when 1
145
+ Dopi.log.warn("Signal received! The run will halt after all currently running commands are finished")
146
+ plan.send_signal(:stop)
147
+ when 2
148
+ Dopi.log.error("Signal received! Sending termination signal to all the processes!")
149
+ plan.send_signal(:abort)
150
+ when 3
151
+ Dopi.log.error("Signal received! Sending KILL signal to all the processes!")
152
+ plan.send_signal(:kill)
153
+ end
154
+ end
155
+ end
156
+ signal_handler_thread.abort_on_exception = true
157
+ end
158
+
159
+ def self.ensure_plan_exists(plan_name)
160
+ unless plan_store.list.include?(plan_name)
161
+ raise StandardError, "The plan #{plan_name} does not exist in the plan store"
162
+ end
163
+ end
164
+
165
+ end
@@ -0,0 +1,11 @@
1
+
2
+ module CommandHelper
3
+
4
+ def create_command(hash)
5
+ node = instance_double('Dopi::Node', :name => 'test.example.com')
6
+ step = instance_double('Dopi::Step', :name => 'Fake step for tests')
7
+ command_parser = DopCommon::Command.new(hash)
8
+ Dopi::Command.create_plugin_instance(command_parser, step, node)
9
+ end
10
+
11
+ end
@@ -0,0 +1,26 @@
1
+ main_collective = mcollective
2
+ collectives = mcollective
3
+ #libdir = /usr/libexec/mcollective
4
+ logfile = /dev/null
5
+ loglevel = info
6
+ direct_addressing = 0
7
+
8
+ # Plugins
9
+ securityprovider = psk
10
+ plugin.psk = vagrant
11
+
12
+ connector = activemq
13
+
14
+ plugin.activemq.pool.size = 1
15
+ plugin.activemq.pool.1.host = 192.168.56.102
16
+ plugin.activemq.pool.1.port = 61614
17
+ plugin.activemq.pool.1.user = mcollective
18
+ plugin.activemq.pool.1.password = vagrant
19
+
20
+ plugin.activemq.pool.1.ssl = false
21
+
22
+ # Facts
23
+ #factsource = yaml
24
+ #plugin.yaml = /etc/mcollective/facts.yaml
25
+
26
+ #default_discovery_method = mc
@@ -0,0 +1,20 @@
1
+ name: 'fail_on_timeout'
2
+
3
+ infrastructures:
4
+ baremetal:
5
+ type: 'baremetal'
6
+
7
+ nodes:
8
+ linux01.example.com:
9
+ infrastructure: 'baremetal'
10
+
11
+ steps:
12
+ default:
13
+ - name: 'fail on timeout'
14
+ nodes: 'all'
15
+ max_per_role: 1
16
+ command:
17
+ - plugin: 'custom'
18
+ plugin_timeout: 1
19
+ exec: 'sleep 4'
20
+
@@ -0,0 +1,34 @@
1
+ name: 'hello_world'
2
+ max_in_flight: 1
3
+
4
+ infrastructures:
5
+ 'test':
6
+ type: 'baremetal'
7
+
8
+ nodes:
9
+ 'linux01.example.com':
10
+ infrastructure: 'test'
11
+
12
+ credentials:
13
+ 'linux_login':
14
+ type: 'username_password'
15
+ username: 'root'
16
+ password: 'puppet'
17
+
18
+ steps:
19
+ - name: 'write hello world'
20
+ nodes: 'all'
21
+ command:
22
+ plugin: 'custom'
23
+ exec: 'echo'
24
+ arguments: '"hello world"'
25
+ - name: 'list the local dir'
26
+ nodes: 'all'
27
+ command:
28
+ plugin: 'custom'
29
+ exec: 'ls'
30
+ - name: 'list the environment'
31
+ nodes: 'all'
32
+ command:
33
+ plugin: 'custom'
34
+ exec: 'env'
@@ -0,0 +1,26 @@
1
+ name: 'hello_world'
2
+ max_in_flight: 1
3
+
4
+ infrastructures:
5
+ 'test':
6
+ type: 'baremetal'
7
+
8
+ nodes:
9
+ 'nonexisting01.example.com':
10
+ infrastructure: 'test'
11
+
12
+ credentials:
13
+ 'linux_login':
14
+ type: 'username_password'
15
+ username: 'root'
16
+ password: 'puppet'
17
+
18
+ steps:
19
+ - name: 'Hello world on node'
20
+ nodes: 'all'
21
+ command:
22
+ plugin: 'custom'
23
+ credentials: 'linux_login'
24
+ exec: 'echo'
25
+ arguments: '"hello world"'
26
+
@@ -0,0 +1,29 @@
1
+ name: 'test_role_variable'
2
+ max_in_flight: 1
3
+
4
+ infrastructures:
5
+ 'test':
6
+ type: 'baremetal'
7
+
8
+ nodes:
9
+ 'linux01.example.com':
10
+ infrastructure: 'test'
11
+
12
+ credentials:
13
+ 'linux_login':
14
+ type: 'username_password'
15
+ username: 'root'
16
+ password: 'puppet'
17
+
18
+ configuration:
19
+ nodes:
20
+ 'linux01.example.com':
21
+ test_role: 'testnode'
22
+
23
+ steps:
24
+ - name: 'write hello world'
25
+ roles: 'testnode'
26
+ command:
27
+ plugin: 'custom'
28
+ exec: 'echo'
29
+ arguments: '"hello world"'
@@ -0,0 +1,8 @@
1
+ forge "https://forgeapi.puppetlabs.com"
2
+
3
+ mod 'theforeman-puppet'
4
+ mod 'theforeman-foreman'
5
+ mod 'puppetlabs-firewall'
6
+ mod 'puppetlabs-java'
7
+ mod 'jbussdieker-activemq'
8
+ mod 'camptocamp-mcollective'
@@ -0,0 +1,57 @@
1
+ FORGE
2
+ remote: https://forgeapi.puppetlabs.com
3
+ specs:
4
+ adrien-alternatives (0.3.0)
5
+ camptocamp-buildenv (0.3.17)
6
+ camptocamp-mcollective (3.1.3)
7
+ camptocamp-ruby (< 2.0.0, >= 1.0.0)
8
+ puppetlabs-concat (< 2.0.0, >= 1.0.0)
9
+ puppetlabs-stdlib (< 5.0.0, >= 3.2.0)
10
+ camptocamp-ruby (1.1.11)
11
+ camptocamp-buildenv (< 1.0.0, >= 0.0.2)
12
+ puppetlabs-stdlib (< 5.0.0, >= 3.2.0)
13
+ jbussdieker-activemq (0.0.3)
14
+ puppet-extlib (0.11.0)
15
+ puppetlabs-stdlib (>= 0)
16
+ puppetlabs-apache (1.8.1)
17
+ puppetlabs-concat (< 3.0.0, >= 1.1.1)
18
+ puppetlabs-stdlib (< 5.0.0, >= 2.4.0)
19
+ puppetlabs-apt (2.2.2)
20
+ puppetlabs-stdlib (< 5.0.0, >= 4.5.0)
21
+ puppetlabs-concat (1.2.5)
22
+ puppetlabs-stdlib (< 5.0.0, >= 3.2.0)
23
+ puppetlabs-firewall (1.8.0)
24
+ puppetlabs-java (1.4.3)
25
+ puppetlabs-stdlib (< 5.0.0, >= 2.4.0)
26
+ puppetlabs-postgresql (4.7.1)
27
+ puppetlabs-apt (< 3.0.0, >= 1.8.0)
28
+ puppetlabs-concat (< 3.0.0, >= 1.1.0)
29
+ puppetlabs-stdlib (~> 4.0)
30
+ puppetlabs-stdlib (4.11.0)
31
+ puppetlabs-xinetd (1.5.0)
32
+ puppetlabs-stdlib (>= 2.2.1)
33
+ theforeman-foreman (5.1.0)
34
+ adrien-alternatives (< 1.0.0, >= 0.3.0)
35
+ puppet-extlib (< 1.0.0, >= 0.10.4)
36
+ puppetlabs-apache (< 2.0.0, >= 1.2.0)
37
+ puppetlabs-apt (< 3.0.0)
38
+ puppetlabs-concat (< 3.0.0, >= 1.0.0)
39
+ puppetlabs-postgresql (< 5.0.0, >= 3.0.0)
40
+ puppetlabs-stdlib (< 5.0.0, >= 4.2.0)
41
+ theforeman-tftp (< 2.0.0, >= 1.4.0)
42
+ theforeman-puppet (4.3.1)
43
+ puppetlabs-apache (< 2.0.0, >= 1.2.0)
44
+ puppetlabs-concat (< 3.0.0, >= 1.0.0)
45
+ puppetlabs-stdlib (< 5.0.0, >= 4.2.0)
46
+ theforeman-tftp (1.7.0)
47
+ puppetlabs-stdlib (< 5.0.0, >= 2.3.0)
48
+ puppetlabs-xinetd (< 2.0.0, >= 1.1.0)
49
+
50
+ DEPENDENCIES
51
+ camptocamp-mcollective (>= 0)
52
+ jbussdieker-activemq (>= 0)
53
+ puppetlabs-firewall (>= 0)
54
+ puppetlabs-java (>= 0)
55
+ theforeman-foreman (>= 0)
56
+ theforeman-puppet (>= 0)
57
+
@@ -0,0 +1,6 @@
1
+ :backends:
2
+ - dop
3
+ :hierarchy:
4
+ - nodes/%{::clientcert}
5
+ - roles/%{::role}
6
+ - defaults