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
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
#
|
|
2
|
+
# DOPi Plugin: Wait For Login
|
|
3
|
+
#
|
|
4
|
+
|
|
5
|
+
module Dopi
|
|
6
|
+
class Command
|
|
7
|
+
class Winrm
|
|
8
|
+
class WaitForLogin < Dopi::Command
|
|
9
|
+
include Dopi::Connector::Winrm
|
|
10
|
+
include Dopi::CommandParser::ExitCode
|
|
11
|
+
|
|
12
|
+
DEFAULT_INTERVAL = 10
|
|
13
|
+
|
|
14
|
+
def validate
|
|
15
|
+
validate_winrm
|
|
16
|
+
validate_exit_code
|
|
17
|
+
log_validation_method(:interval_valid?, CommandParsingError)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def run
|
|
21
|
+
connected = false
|
|
22
|
+
until connected
|
|
23
|
+
begin connected = check_exit_code(winrm_command('exit')[2])
|
|
24
|
+
rescue Dopi::NodeConnectionError, Dopi::CommandConnectionError
|
|
25
|
+
end
|
|
26
|
+
unless connected
|
|
27
|
+
sleep interval
|
|
28
|
+
raise GracefulExit if signals[:stop]
|
|
29
|
+
log(:info, "Retrying connect to node")
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
true
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def interval
|
|
36
|
+
@interval ||= interval_valid? ?
|
|
37
|
+
hash[:interval] : DEFAULT_INTERVAL
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def interval_valid?
|
|
41
|
+
return false if hash[:interval].nil? # is optional
|
|
42
|
+
hash[:interval].class == Fixnum or
|
|
43
|
+
raise CommandParsingError, "Plugin #{name}: the value of 'interval' has to be a number"
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
data/lib/dopi/command.rb
ADDED
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
#
|
|
2
|
+
# This class loades the dopi command plugins
|
|
3
|
+
#
|
|
4
|
+
require 'dop_common'
|
|
5
|
+
require 'forwardable'
|
|
6
|
+
require 'timeout'
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
module Dopi
|
|
10
|
+
class Command
|
|
11
|
+
extend Forwardable
|
|
12
|
+
include Dopi::State
|
|
13
|
+
include DopCommon::Validator
|
|
14
|
+
|
|
15
|
+
def self.inherited(klass)
|
|
16
|
+
PluginManager << klass
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def self.create_plugin_instance(command_parser, step, node, is_verify_command = false)
|
|
20
|
+
plugin_type = PluginManager.get_plugin_name(self) + '/'
|
|
21
|
+
plugin_full_name = plugin_type + command_parser.plugin
|
|
22
|
+
Dopi.log.debug("Creating instance of plugin #{plugin_full_name}")
|
|
23
|
+
PluginManager.create_instance(plugin_full_name, command_parser, step, node, is_verify_command)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def self.set_plugin_defaults(node_name, hash)
|
|
27
|
+
@plugin_defaults ||= {}
|
|
28
|
+
@plugin_defaults[node_name] ||= {}
|
|
29
|
+
@plugin_defaults[node_name].merge!(DopCommon::HashParser.symbolize_keys(hash))
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def self.plugin_defaults(node_name)
|
|
33
|
+
@plugin_defaults ||= {}
|
|
34
|
+
@plugin_defaults[node_name] ||= {}
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# wipe all the defaults for this plugin
|
|
38
|
+
def self.wipe_plugin_defaults
|
|
39
|
+
@plugin_defaults = {}
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# delete all the defaults on this plugin for the node
|
|
43
|
+
def self.delete_plugin_defaults(node_name)
|
|
44
|
+
@plugin_defaults ||= {}
|
|
45
|
+
@plugin_defaults[node_name] = {}
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# delete a specific default for the node
|
|
49
|
+
def self.delete_plugin_default(node_name, key)
|
|
50
|
+
@plugin_defaults ||= {}
|
|
51
|
+
@plugin_defaults[node_name] ||= {}
|
|
52
|
+
@plugin_defaults[node_name].delete(key)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
attr_reader :node, :hash, :is_verify_command
|
|
56
|
+
|
|
57
|
+
def initialize(command_parser, step, node, is_verify_command)
|
|
58
|
+
@command_parser = command_parser
|
|
59
|
+
@step = step
|
|
60
|
+
@node = node
|
|
61
|
+
@is_verify_command = is_verify_command
|
|
62
|
+
@hash = merged_hash
|
|
63
|
+
log(:debug, "Plugin created with merged command hash: #{hash.inspect}")
|
|
64
|
+
# make sure verify commands are initialized as well
|
|
65
|
+
verify_commands
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def merged_hash
|
|
69
|
+
if @command_parser.hash.kind_of?(Hash)
|
|
70
|
+
self.class.plugin_defaults(@node.name).merge(@command_parser.hash)
|
|
71
|
+
else
|
|
72
|
+
self.class.plugin_defaults(@node.name)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def_delegator :@command_parser, :plugin, :name
|
|
77
|
+
def_delegator :@command_parser, :title, :title
|
|
78
|
+
|
|
79
|
+
def meta_run(noop = false)
|
|
80
|
+
return if skip_run?(noop)
|
|
81
|
+
state_run unless noop
|
|
82
|
+
# Nest timeout in itself to fix working in combination with popen3() used
|
|
83
|
+
# by command connectors
|
|
84
|
+
Timeout::timeout(plugin_timeout) do
|
|
85
|
+
Timeout::timeout(plugin_timeout) do
|
|
86
|
+
log(:info, "Running command #{name}") unless @is_verify_command
|
|
87
|
+
if noop
|
|
88
|
+
run_noop
|
|
89
|
+
else
|
|
90
|
+
if run
|
|
91
|
+
if verify_after_run
|
|
92
|
+
verify_commands_ok? or
|
|
93
|
+
raise CommandExecutionError, "Verify commands failed to confirm a successful run"
|
|
94
|
+
end
|
|
95
|
+
state_finish
|
|
96
|
+
log(:info, "#{name} [OK]") if state_done?
|
|
97
|
+
else
|
|
98
|
+
state_fail
|
|
99
|
+
log(:info, "#{name} [FAILED]")
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
rescue GracefulExit
|
|
105
|
+
log(:info, "Command excited gracefuly, resetting to ready")
|
|
106
|
+
state_reset(true) unless noop
|
|
107
|
+
rescue Timeout::Error
|
|
108
|
+
log(:error, "Command timed out (plugin_timeout is set to #{plugin_timeout})", false)
|
|
109
|
+
state_fail unless noop
|
|
110
|
+
send_signal(:abort)
|
|
111
|
+
rescue CommandExecutionError => e
|
|
112
|
+
log(:error, "Command failed: #{e.message}", false)
|
|
113
|
+
Dopi.log.error(e) if DopCommon.config.trace
|
|
114
|
+
state_fail unless noop
|
|
115
|
+
rescue => e
|
|
116
|
+
log(:error, "Unexpected error!!! This is a Bug", false)
|
|
117
|
+
Dopi.log.error(e.message)
|
|
118
|
+
Dopi.log.error(e.backtrace)
|
|
119
|
+
state_fail unless noop
|
|
120
|
+
raise e
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def meta_valid?
|
|
124
|
+
validity = valid?
|
|
125
|
+
validity = false unless verify_commands.all? do |verify_command|
|
|
126
|
+
begin
|
|
127
|
+
verify_command.meta_valid?
|
|
128
|
+
rescue PluginLoaderError => e
|
|
129
|
+
Dopi.log.error("Step '#{@step.name}': Can't load plugin #{verify_command.plugin}: #{e.message}")
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
validity
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def_delegator :@command_parser, :verify_commands, :parsed_verify_commands
|
|
136
|
+
def_delegators :@command_parser, :plugin_timeout, :verify_after_run
|
|
137
|
+
|
|
138
|
+
def load_state(state_hash)
|
|
139
|
+
command_state = state_hash[:command_state] || :ready
|
|
140
|
+
unless command_state == :ready
|
|
141
|
+
@state = command_state
|
|
142
|
+
state_changed
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def state_hash
|
|
147
|
+
{:command_state => @state}
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
private
|
|
151
|
+
|
|
152
|
+
def run
|
|
153
|
+
raise Dopi::CommandExecutionError, "No run method implemented in plugin #{name}"
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def run_noop
|
|
157
|
+
Dopi.log.error("The plugin #{name} does not support noop runs and will not show the command")
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def validate
|
|
161
|
+
Dopi.log.warn("No 'validate' method implemented in plugin #{name}. Validation not possible")
|
|
162
|
+
true
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def skip_run?(noop = false)
|
|
166
|
+
if state_done?
|
|
167
|
+
log(:info, "Is already in state 'done'. Skipping")
|
|
168
|
+
true
|
|
169
|
+
elsif verify_commands.any? && verify_commands_ok?
|
|
170
|
+
if noop
|
|
171
|
+
log(:info, "All verify commands ok. Skipping")
|
|
172
|
+
else
|
|
173
|
+
log(:info, "All verify commands ok. Skipping and marked as 'done'")
|
|
174
|
+
state_run
|
|
175
|
+
state_finish
|
|
176
|
+
end
|
|
177
|
+
true
|
|
178
|
+
else
|
|
179
|
+
false
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def verify_commands
|
|
184
|
+
@verify_commands ||= parsed_verify_commands.map do |command|
|
|
185
|
+
Dopi::Command.create_plugin_instance(command, @step, @node, true)
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def verify_commands_ok?
|
|
190
|
+
verify_commands.all? do |command|
|
|
191
|
+
command.state_reset(true)
|
|
192
|
+
command.meta_run
|
|
193
|
+
command.state_done?
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def log_prefix
|
|
198
|
+
if @is_verify_command
|
|
199
|
+
" [Verify] #{@node.name} : "
|
|
200
|
+
else
|
|
201
|
+
" [Command] #{@node.name} : "
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def log(severity, message, overwrite = true)
|
|
206
|
+
# Ignore verify command errors, because they are expected
|
|
207
|
+
if @is_verify_command && overwrite
|
|
208
|
+
severity = :debug if severity == :error || severity == :warn
|
|
209
|
+
end
|
|
210
|
+
# TODO: implement Node specific logging
|
|
211
|
+
# for now we simply forward to the global DOPi logger
|
|
212
|
+
Dopi.log.log(Logger.const_get(severity.upcase), log_prefix + message)
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
# load standard command plugins
|
|
220
|
+
require 'dopi/command/dummy'
|
|
221
|
+
require 'dopi/command/custom'
|
|
222
|
+
require 'dopi/command/ssh/custom'
|
|
223
|
+
require 'dopi/command/ssh/puppet_agent_run'
|
|
224
|
+
require 'dopi/command/ssh/wait_for_login'
|
|
225
|
+
require 'dopi/command/ssh/file_contains'
|
|
226
|
+
require 'dopi/command/ssh/file_exists'
|
|
227
|
+
require 'dopi/command/ssh/file_replace'
|
|
228
|
+
require 'dopi/command/ssh/file_deploy'
|
|
229
|
+
require 'dopi/command/ssh/reboot'
|
|
230
|
+
require 'dopi/command/mco/rpc'
|
|
231
|
+
require 'dopi/command/winrm/cmd'
|
|
232
|
+
require 'dopi/command/winrm/powershell'
|
|
233
|
+
require 'dopi/command/winrm/puppet_agent_run'
|
|
234
|
+
require 'dopi/command/winrm/wait_for_login'
|
|
235
|
+
require 'dopi/command/winrm/file_contains'
|
|
236
|
+
require 'dopi/command/winrm/file_exists'
|
|
237
|
+
require 'dopi/command/winrm/reboot'
|
|
238
|
+
|
|
239
|
+
# TODO: load plugins from the plugin paths
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Simple command parser class to parse arguments
|
|
3
|
+
#
|
|
4
|
+
module Dopi
|
|
5
|
+
module CommandParser
|
|
6
|
+
module Arguments
|
|
7
|
+
|
|
8
|
+
def validate_arguments
|
|
9
|
+
log_validation_method('arguments_valid?', CommandParsingError)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def arguments
|
|
13
|
+
arguments_valid? ? parse_arguments : ""
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
private
|
|
17
|
+
|
|
18
|
+
def arguments_valid?
|
|
19
|
+
return false unless hash.kind_of?(Hash) # plugin may not have parameters
|
|
20
|
+
return false if hash[:arguments].nil? # arguments are optional
|
|
21
|
+
hash[:arguments].kind_of?(Hash) or
|
|
22
|
+
hash[:arguments].kind_of?(Array) or
|
|
23
|
+
hash[:arguments].kind_of?(String) or
|
|
24
|
+
raise CommandParsingError, "The value for 'arguments' hast to be an Array, Hash or String"
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def parse_arguments
|
|
28
|
+
case hash[:arguments]
|
|
29
|
+
when Hash then hash[:arguments].to_a.flatten.join(' ')
|
|
30
|
+
when Array then hash[:arguments].flatten.join(' ')
|
|
31
|
+
when String then hash[:arguments]
|
|
32
|
+
else ""
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Dopi Credentials helper module
|
|
3
|
+
#
|
|
4
|
+
# This module will provide a credentials method which contains all the credential
|
|
5
|
+
# objects for the plugin.
|
|
6
|
+
#
|
|
7
|
+
# Make sure you call "validate_credentials" method from your validation method.
|
|
8
|
+
#
|
|
9
|
+
# Implement a method "supported_credential_types" in your plugin if you want to limit
|
|
10
|
+
# the types which are supported and trow an error during validation if some other type
|
|
11
|
+
# is assigned
|
|
12
|
+
#
|
|
13
|
+
module Dopi
|
|
14
|
+
module CommandParser
|
|
15
|
+
module Credentials
|
|
16
|
+
include DopCommon::Validator
|
|
17
|
+
include DopCommon::HashParser
|
|
18
|
+
|
|
19
|
+
public
|
|
20
|
+
|
|
21
|
+
def validate_credentials
|
|
22
|
+
log_validation_method('credentials_valid?', CommandParsingError)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def credentials
|
|
26
|
+
@credentials ||= credentials_valid? ? parse_credentials : []
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def credentials_valid?
|
|
32
|
+
return false unless hash.kind_of?(Hash) # plugin may not have parameters
|
|
33
|
+
key_aliases(hash, :credentials, ['credentials', :credential, 'credential'])
|
|
34
|
+
return false if hash[:credentials].nil? # credentials is optional
|
|
35
|
+
hash[:credentials].kind_of?(String) or hash[:credentials].kind_of?(Array) or
|
|
36
|
+
raise CommandParsingError, "the value for 'credentials' has to be a string or an array of strings"
|
|
37
|
+
[hash[:credentials]].flatten.each do |c|
|
|
38
|
+
c.kind_of?(String) or
|
|
39
|
+
raise CommandParsingError, "All values in the 'credentials' array have to be strings"
|
|
40
|
+
@step.plan.credentials.has_key?(c) or
|
|
41
|
+
raise CommandParsingError, "Credentials #{c} are not configured"
|
|
42
|
+
if self.methods.include?(:supported_credential_types)
|
|
43
|
+
cred_type = @step.plan.credentials[c].type
|
|
44
|
+
supported_credential_types.include?(cred_type) or
|
|
45
|
+
raise CommandParsingError, "Credential #{c} is of type #{cred_type}, which is not supported by the plugin"
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
true
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def parse_credentials
|
|
52
|
+
[hash[:credentials]].flatten.map{|c| @step.plan.credentials[c]}
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Simple command parser module for environment variable hashes
|
|
3
|
+
#
|
|
4
|
+
# To set plugin specific defaults for the environment create
|
|
5
|
+
# a 'env_defaults' method which returns a hash with:
|
|
6
|
+
# { var => val, var2 => val2, ... }
|
|
7
|
+
# This hash will be merged with the user specified hash.
|
|
8
|
+
#
|
|
9
|
+
module Dopi
|
|
10
|
+
module CommandParser
|
|
11
|
+
module Env
|
|
12
|
+
|
|
13
|
+
def validate_env
|
|
14
|
+
log_validation_method(:env_valid?, CommandParsingError)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def env
|
|
18
|
+
env_valid? ? create_env.merge(hash[:env]) : create_env
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def env_valid?
|
|
24
|
+
return false unless hash.kind_of?(Hash) # plugin may not have parameters
|
|
25
|
+
return false if hash[:env].nil? # env is optional
|
|
26
|
+
hash[:env].kind_of?(Hash) or
|
|
27
|
+
raise CommandParsingError, "The value for 'env' has to be a hash"
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def create_env
|
|
31
|
+
defaults = respond_to?(:env_defaults) ? env_defaults : {}
|
|
32
|
+
{ 'DOP_NODE_FQDN' => @node.name }.merge(defaults)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Simple command parser module for exec
|
|
3
|
+
#
|
|
4
|
+
module Dopi
|
|
5
|
+
module CommandParser
|
|
6
|
+
module Exec
|
|
7
|
+
|
|
8
|
+
def validate_exec
|
|
9
|
+
log_validation_method('exec_valid?', CommandParsingError)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def exec
|
|
13
|
+
exec_valid? ? hash[:exec] : nil
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
private
|
|
17
|
+
|
|
18
|
+
def exec_valid?
|
|
19
|
+
hash[:exec] or
|
|
20
|
+
raise CommandParsingError, "#Step #{@step.name} | Plugin #{name} | No command to execute in 'exec' defined"
|
|
21
|
+
hash[:exec].kind_of?(String) or
|
|
22
|
+
raise CommandParsingError, "#Step #{@step.name} | Plugin #{name} | The value for 'exec' has to be a String"
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
#
|
|
2
|
+
# This is a mixin for command plugins that need to parse an exit Code of some sort
|
|
3
|
+
#
|
|
4
|
+
# To set plugin specific defaults you can implement the 'expect_exit_codes_defaults'
|
|
5
|
+
# method which returns an array of expected exit codes
|
|
6
|
+
#
|
|
7
|
+
module Dopi
|
|
8
|
+
module CommandParser
|
|
9
|
+
module ExitCode
|
|
10
|
+
|
|
11
|
+
public
|
|
12
|
+
|
|
13
|
+
def validate_exit_code
|
|
14
|
+
log_validation_method('expect_exit_codes_valid?', CommandParsingError)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def expect_exit_codes
|
|
18
|
+
@expect_exit_codes ||= expect_exit_codes_valid? ?
|
|
19
|
+
hash[:expect_exit_codes] : create_exit_codes
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Returns true if the exit code is one we expected, otherwise false
|
|
23
|
+
def check_exit_code(cmd_exit_code)
|
|
24
|
+
log(:debug, "Checking exit code '#{cmd_exit_code}'")
|
|
25
|
+
exit_code_ok = case expect_exit_codes
|
|
26
|
+
when 'all', 'ALL', 'All', :all then true
|
|
27
|
+
when Array then expect_exit_codes.include?(cmd_exit_code)
|
|
28
|
+
when Fixnum then expect_exit_codes == cmd_exit_code
|
|
29
|
+
else false
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
unless exit_code_ok
|
|
33
|
+
log(:error, "Wrong exit code in command #{name}")
|
|
34
|
+
if expect_exit_codes.kind_of?(Array)
|
|
35
|
+
log(:error, "Exit code was #{cmd_exit_code.to_s} should be one of #{expect_exit_codes.join(', ')}")
|
|
36
|
+
elsif expect_exit_codes.kind_of?(Fixnum)
|
|
37
|
+
log(:error, "Exit code was #{cmd_exit_code.to_s} should be #{expect_exit_codes.to_s}")
|
|
38
|
+
else
|
|
39
|
+
log(:error, "Exit code was #{cmd_exit_code.to_s} #{expect_exit_codes}")
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
exit_code_ok
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private
|
|
47
|
+
|
|
48
|
+
def expect_exit_codes_valid?
|
|
49
|
+
return false unless hash.kind_of?(Hash) # plugin may not have parameters
|
|
50
|
+
return false if hash[:expect_exit_codes].nil? # expect_exit_codes is optional
|
|
51
|
+
hash[:expect_exit_codes].kind_of?(Fixnum) or
|
|
52
|
+
hash[:expect_exit_codes].kind_of?(String) or
|
|
53
|
+
hash[:expect_exit_codes].kind_of?(Symbol) or
|
|
54
|
+
hash[:expect_exit_codes].kind_of?(Array) or
|
|
55
|
+
raise CommandParsingError, "The value for 'expect_exit_codes' hast to be a number or an array of numbers or :all"
|
|
56
|
+
if hash[:expect_exit_codes].kind_of?(String) || hash[:expect_exit_codes].kind_of?(Symbol)
|
|
57
|
+
['all', 'All', 'ALL', :all].include? hash[:expect_exit_codes] or
|
|
58
|
+
raise CommandParsingError, "Unknown keyword for expect_exit_codes. This has to be a number, an array or :all"
|
|
59
|
+
end
|
|
60
|
+
if hash[:expect_exit_codes].kind_of?(Array)
|
|
61
|
+
hash[:expect_exit_codes].all?{|exit_code| exit_code.kind_of?(Fixnum)} or
|
|
62
|
+
raise CommandParsingError, "The array in 'expect_exit_codes' can only contain numbers"
|
|
63
|
+
end
|
|
64
|
+
true
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def create_exit_codes
|
|
68
|
+
respond_to?(:expect_exit_codes_defaults) ? expect_exit_codes_defaults : 0
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
#
|
|
2
|
+
# This is a mixin for command plugins that need to parse an output of some sort
|
|
3
|
+
#
|
|
4
|
+
# Make sure to call the validation method from the class you use the module
|
|
5
|
+
#
|
|
6
|
+
# To set plugin specific output parser patterns, implement the method
|
|
7
|
+
# 'parse_output_defaults' which should return a hash with the patterns.
|
|
8
|
+
#
|
|
9
|
+
module Dopi
|
|
10
|
+
module CommandParser
|
|
11
|
+
module Output
|
|
12
|
+
include DopCommon::Validator
|
|
13
|
+
|
|
14
|
+
public
|
|
15
|
+
|
|
16
|
+
def validate_output
|
|
17
|
+
log_validation_method('parse_output_valid?', CommandParsingError)
|
|
18
|
+
unless parse_output.empty?
|
|
19
|
+
log_validation_method('error_patterns_valid?', CommandParsingError)
|
|
20
|
+
log_validation_method('warning_patterns_valid?', CommandParsingError)
|
|
21
|
+
end
|
|
22
|
+
log_validation_method('fail_on_warning_valid?', CommandParsingError)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def check_output(raw_output)
|
|
26
|
+
if error_patterns.empty? && warning_patterns.empty?
|
|
27
|
+
log(:debug, "No patterns defined to parse the output")
|
|
28
|
+
return true
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
output_ok = true
|
|
32
|
+
|
|
33
|
+
error_patterns.each do |pattern|
|
|
34
|
+
lines_with_matches(raw_output, pattern).each do |line_with_error|
|
|
35
|
+
log(:error, line_with_error)
|
|
36
|
+
output_ok = false
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
warning_patterns.each do |pattern|
|
|
41
|
+
lines_with_matches(raw_output, pattern).each do |line_with_warning|
|
|
42
|
+
if fail_on_warning
|
|
43
|
+
log(:error, line_with_warning)
|
|
44
|
+
output_ok = false
|
|
45
|
+
else
|
|
46
|
+
log(:warn, line_with_warning)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
output_ok
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def parse_output
|
|
55
|
+
if parse_output_valid?
|
|
56
|
+
Hash[hash[:parse_output].map{|k,v| [k.to_sym, v]}]
|
|
57
|
+
else
|
|
58
|
+
respond_to?(:parse_output_defaults) ? parse_output_defaults : {}
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def error_patterns
|
|
63
|
+
@error_patterns ||= parser_patterns_valid?(parse_output[:error]) ?
|
|
64
|
+
[ parse_output[:error] ].flatten : []
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def warning_patterns
|
|
68
|
+
@warning_patterns ||= parser_patterns_valid?(parse_output[:warning]) ?
|
|
69
|
+
[ parse_output[:warning] ].flatten : []
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def fail_on_warning
|
|
73
|
+
@fail_on_warning ||= fail_on_warning_valid? ?
|
|
74
|
+
hash[:fail_on_warning] : false
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def lines_with_matches(raw_output, pattern)
|
|
78
|
+
regexp = Regexp.new(pattern)
|
|
79
|
+
raw_output.lines.find_all{ |line| line.scan(regexp).any? }
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
private
|
|
83
|
+
|
|
84
|
+
def parse_output_valid?
|
|
85
|
+
return false unless hash.kind_of?(Hash)
|
|
86
|
+
return false if hash[:parse_output].nil? # optional
|
|
87
|
+
hash[:parse_output].kind_of?(Hash) or
|
|
88
|
+
raise CommandParsingError, "The value for 'parse_output' has to be a Hash"
|
|
89
|
+
true
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def error_patterns_valid?
|
|
93
|
+
parser_patterns_valid?(parse_output[:error])
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def warning_patterns_valid?
|
|
97
|
+
parser_patterns_valid?(parse_output[:warning])
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def parser_patterns_valid?(pattern)
|
|
101
|
+
return false if pattern.nil? # optional
|
|
102
|
+
pattern.kind_of?(Array) or
|
|
103
|
+
raise CommandParsingError, "The value of 'error' and 'warning' in 'parse_output' has to be an Array"
|
|
104
|
+
pattern.each do |entry|
|
|
105
|
+
begin
|
|
106
|
+
Regexp.new(entry)
|
|
107
|
+
rescue
|
|
108
|
+
raise CommandParsingError, "The pattern #{entry} in 'parse_output' is not a valid regular expression"
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
true
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def fail_on_warning_valid?
|
|
115
|
+
return false unless hash.kind_of?(Hash)
|
|
116
|
+
return false if hash[:fail_on_warning].nil? # is optional
|
|
117
|
+
hash[:fail_on_warning].kind_of?(TrueClass) or hash[:fail_on_warning].kind_of?(FalseClass) or
|
|
118
|
+
raise CommandParsingError, "The value for 'fail_on_warning' must be boolean"
|
|
119
|
+
true
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
|