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