dopi 0.17.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +20 -0
- data/.rspec +2 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +322 -0
- data/Gemfile +7 -0
- data/Gemfile.lock +102 -0
- data/LICENSE.txt +177 -0
- data/README.md +309 -0
- data/Rakefile +44 -0
- data/Vagrantfile +64 -0
- data/bin/dopi +4 -0
- data/doc/getting_started.md +247 -0
- data/doc/getting_started_examples/001_hello_world.yaml +17 -0
- data/doc/getting_started_examples/002_connecting_over_ssh.yaml +35 -0
- data/doc/plugins/custom.md +88 -0
- data/doc/plugins/mco/rpc.md +82 -0
- data/doc/plugins/ssh/custom.md +141 -0
- data/doc/plugins/ssh/file_contains.md +37 -0
- data/doc/plugins/ssh/file_deploy.md +52 -0
- data/doc/plugins/ssh/file_exists.md +31 -0
- data/doc/plugins/ssh/file_replace.md +37 -0
- data/doc/plugins/ssh/puppet_agent_run.md +50 -0
- data/doc/plugins/ssh/reboot.md +22 -0
- data/doc/plugins/ssh/wait_for_login.md +53 -0
- data/doc/plugins/winrm/cmd.md +161 -0
- data/doc/plugins/winrm/file_contains.md +39 -0
- data/doc/plugins/winrm/file_exists.md +31 -0
- data/doc/plugins/winrm/powershell.md +27 -0
- data/doc/plugins/winrm/puppet_agent_run.md +49 -0
- data/doc/plugins/winrm/reboot.md +17 -0
- data/doc/plugins/winrm/wait_for_login.md +55 -0
- data/dopi.gemspec +42 -0
- data/lib/dopi/cli/command_add.rb +35 -0
- data/lib/dopi/cli/command_list.rb +19 -0
- data/lib/dopi/cli/command_remove.rb +31 -0
- data/lib/dopi/cli/command_reset.rb +27 -0
- data/lib/dopi/cli/command_run.rb +68 -0
- data/lib/dopi/cli/command_show.rb +109 -0
- data/lib/dopi/cli/command_update.rb +37 -0
- data/lib/dopi/cli/command_validate.rb +27 -0
- data/lib/dopi/cli/global_options.rb +55 -0
- data/lib/dopi/cli/log.rb +33 -0
- data/lib/dopi/cli.rb +57 -0
- data/lib/dopi/command/custom.rb +52 -0
- data/lib/dopi/command/dummy.rb +27 -0
- data/lib/dopi/command/mco/rpc.rb +158 -0
- data/lib/dopi/command/ssh/custom.rb +48 -0
- data/lib/dopi/command/ssh/file_contains.rb +70 -0
- data/lib/dopi/command/ssh/file_deploy.rb +71 -0
- data/lib/dopi/command/ssh/file_exists.rb +54 -0
- data/lib/dopi/command/ssh/file_replace.rb +96 -0
- data/lib/dopi/command/ssh/puppet_agent_run.rb +63 -0
- data/lib/dopi/command/ssh/reboot.rb +50 -0
- data/lib/dopi/command/ssh/wait_for_login.rb +68 -0
- data/lib/dopi/command/winrm/cmd.rb +44 -0
- data/lib/dopi/command/winrm/file_contains.rb +66 -0
- data/lib/dopi/command/winrm/file_exists.rb +51 -0
- data/lib/dopi/command/winrm/powershell.rb +16 -0
- data/lib/dopi/command/winrm/puppet_agent_run.rb +61 -0
- data/lib/dopi/command/winrm/reboot.rb +33 -0
- data/lib/dopi/command/winrm/wait_for_login.rb +49 -0
- data/lib/dopi/command.rb +239 -0
- data/lib/dopi/command_parser/arguments.rb +38 -0
- data/lib/dopi/command_parser/credentials.rb +59 -0
- data/lib/dopi/command_parser/env.rb +37 -0
- data/lib/dopi/command_parser/exec.rb +27 -0
- data/lib/dopi/command_parser/exit_code.rb +73 -0
- data/lib/dopi/command_parser/output.rb +126 -0
- data/lib/dopi/command_set.rb +66 -0
- data/lib/dopi/connector/local.rb +77 -0
- data/lib/dopi/connector/ssh.rb +170 -0
- data/lib/dopi/connector/winrm.rb +167 -0
- data/lib/dopi/error.rb +43 -0
- data/lib/dopi/log.rb +18 -0
- data/lib/dopi/node.rb +70 -0
- data/lib/dopi/plan.rb +99 -0
- data/lib/dopi/pluginmanager.rb +62 -0
- data/lib/dopi/state.rb +226 -0
- data/lib/dopi/state_store.rb +155 -0
- data/lib/dopi/step.rb +227 -0
- data/lib/dopi/step_set.rb +70 -0
- data/lib/dopi/version.rb +3 -0
- data/lib/dopi.rb +165 -0
- data/spec/command_helper.rb +11 -0
- data/spec/fixtures/mco_client.cfg +26 -0
- data/spec/fixtures/plans/fail_on_timeout.yaml +20 -0
- data/spec/fixtures/plans/hello_world.yaml +34 -0
- data/spec/fixtures/plans/non_existing_node.yaml +26 -0
- data/spec/fixtures/plans/test_role_variable.yaml +29 -0
- data/spec/fixtures/puppet/Puppetfile +8 -0
- data/spec/fixtures/puppet/Puppetfile.lock +57 -0
- data/spec/fixtures/puppet/hiera.yaml +6 -0
- data/spec/fixtures/puppet/manifests/site.pp +52 -0
- data/spec/fixtures/test_configuration.yaml +54 -0
- data/spec/fixtures/test_credentials.yaml +11 -0
- data/spec/fixtures/test_deloyed_file.txt +5 -0
- data/spec/fixtures/test_infrastructure.yaml +12 -0
- data/spec/fixtures/test_nodes.yaml +45 -0
- data/spec/fixtures/testenv_plan.yaml +159 -0
- data/spec/integration/dopi/addrun_spec.rb +31 -0
- data/spec/integration/dopi/cli/command_run_spec.rb +38 -0
- data/spec/integration/dopi/cli/global_options_spec.rb +128 -0
- data/spec/integration/dopi/command_spec.rb +66 -0
- data/spec/integration/dopi/fail_check_plans/file_exists_fails.yaml +38 -0
- data/spec/integration/dopi/fail_check_plans/output_parser.yaml +39 -0
- data/spec/integration/dopi/fail_check_plans/powershell_fail.yaml +25 -0
- data/spec/integration/dopi/fail_check_plans/timeout.yaml +29 -0
- data/spec/integration/dopi/fail_check_plans/verify_commands.yaml +33 -0
- data/spec/integration/dopi/failplan.rb +27 -0
- data/spec/integration/dopi/plan.rb +27 -0
- data/spec/integration/dopi/plans/dummy.yaml +29 -0
- data/spec/integration/dopi/plans/max_per_role.yaml +55 -0
- data/spec/integration/dopi/plans/no_timeout.yaml +29 -0
- data/spec/integration/dopi/plans/node_and_role_patterns.yaml +58 -0
- data/spec/integration/dopi/plans/node_by_config.yaml +116 -0
- data/spec/integration/dopi/plans/plugin_defaults.yaml +86 -0
- data/spec/integration/dopi/plans/plugins/mco/rpc.yaml +33 -0
- data/spec/integration/dopi/plans/plugins/ssh/custom.yaml +97 -0
- data/spec/integration/dopi/plans/plugins/ssh/file_contains.yaml +51 -0
- data/spec/integration/dopi/plans/plugins/ssh/file_deploy.yaml +82 -0
- data/spec/integration/dopi/plans/plugins/ssh/file_exists.yaml +69 -0
- data/spec/integration/dopi/plans/plugins/ssh/file_replace.yaml +55 -0
- data/spec/integration/dopi/plans/plugins/ssh/puppet_agent_run.yaml +45 -0
- data/spec/integration/dopi/plans/plugins/ssh/reboot.yaml +43 -0
- data/spec/integration/dopi/plans/plugins/ssh/wait_for_login.yaml +45 -0
- data/spec/integration/dopi/plans/plugins/winrm/cmd.yaml +39 -0
- data/spec/integration/dopi/plans/plugins/winrm/file_contains.yaml +51 -0
- data/spec/integration/dopi/plans/plugins/winrm/file_exists.yaml +69 -0
- data/spec/integration/dopi/plans/plugins/winrm/reboot.yaml +31 -0
- data/spec/integration/dopi/plans/resolve_roles_on_validate.yaml +23 -0
- data/spec/integration/dopi/plans/ssh_parallel.yaml +37 -0
- data/spec/integration/dopi/plans/verify_commands.yaml +49 -0
- data/spec/spec_helper.rb +104 -0
- data/spec/unit/dopi/command/custom_spec.rb +58 -0
- data/spec/unit/dopi/command/mco/rpc_spec.rb +157 -0
- data/spec/unit/dopi/command/ssh/custom_spec.rb +30 -0
- data/spec/unit/dopi/command/ssh/file_deploy_spec.rb +42 -0
- data/spec/unit/dopi/command/ssh/file_replace_spec.rb +35 -0
- data/spec/unit/dopi/command_parser/credentials_spec.rb +53 -0
- data/spec/unit/dopi/command_parser/exit_code_spec.rb +63 -0
- data/spec/unit/dopi/command_parser/output_spec.rb +129 -0
- data/spec/unit/dopi/command_spec.rb +14 -0
- data/spec/unit/dopi/connector/winrm_spec.rb +111 -0
- data/spec/unit/dopi/node_spec.rb +24 -0
- data/spec/unit/dopi/plan_spec.rb +31 -0
- data/spec/unit/dopi/state_spec.rb +109 -0
- data/spec/unit/dopi/step_spec.rb +13 -0
- 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
|
data/lib/dopi/version.rb
ADDED
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,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
|
+
|