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.
- 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
|
+
|