dopi 0.17.0

Sign up to get free protection for your applications and to get access to all the features.
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