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