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,109 @@
1
+
2
+ module Dopi
3
+ module Cli
4
+
5
+ def self.command_show(base)
6
+ base.class_eval do
7
+
8
+ desc 'Show plan details and state'
9
+ arg_name 'name'
10
+ command :show do |c|
11
+ c.desc 'Do not exit and continuously update the display'
12
+ c.default_value false
13
+ c.switch [:follow, :f]
14
+
15
+ c.desc 'Display all details of the tree'
16
+ c.default_value false
17
+ c.switch [:detailed, :d]
18
+
19
+ c.action do |global_options,options,args|
20
+ help_now!('Specify a plan name to show') if args.empty?
21
+ help_now!('You can only show one plan') if args.length > 1
22
+ plan_name = args[0]
23
+ if options[:follow]
24
+ begin
25
+ Curses.noecho
26
+ Curses.curs_set(0)
27
+ Curses.init_screen
28
+ Curses.start_color
29
+ Curses.init_pair(1, Curses::COLOR_BLACK, Curses::COLOR_WHITE)
30
+ Curses.init_pair(2, Curses::COLOR_WHITE, Curses::COLOR_BLACK)
31
+ Curses.init_pair(3, Curses::COLOR_BLUE, Curses::COLOR_BLACK)
32
+ Curses.init_pair(4, Curses::COLOR_GREEN, Curses::COLOR_BLACK)
33
+ Curses.init_pair(5, Curses::COLOR_YELLOW, Curses::COLOR_BLACK)
34
+ Curses.init_pair(6, Curses::COLOR_RED, Curses::COLOR_BLACK)
35
+ draw_screen(plan_name, options[:detailed])
36
+ Curses.refresh
37
+ Dopi.on_state_change(plan_name) do
38
+ Curses.clear
39
+ draw_screen(plan_name, options[:detailed])
40
+ Curses.refresh
41
+ end
42
+ ensure
43
+ Curses.close_screen
44
+ end
45
+ else
46
+ print_state(plan_name, options[:detailed])
47
+ end
48
+ end
49
+ end
50
+
51
+ end
52
+ end
53
+
54
+ def self.str_state_color(state, string)
55
+ attr = case state
56
+ when :ready then Curses.color_pair(3)
57
+ when :done then Curses.color_pair(4)
58
+ when :partial then Curses.color_pair(5)
59
+ when :running, :started then Curses.color_pair(5) | Curses::A_BLINK
60
+ when :failed then Curses.color_pair(6)
61
+ else Curses.color_pair(2)
62
+ end
63
+ Curses.attrset(attr)
64
+ Curses.addstr(string)
65
+ Curses.attrset(Curses.color_pair(2))
66
+ end
67
+
68
+ def self.draw_screen(plan_name, detailed)
69
+ plan = Dopi.show(plan_name)
70
+ Curses.setpos(0, 0)
71
+ Curses.attrset(Curses.color_pair(1))
72
+ Curses.addstr("DOPi #{Dopi::VERSION} - #{plan.name} [ #{plan.state.to_s} ]".ljust(Curses.cols))
73
+ Curses.setpos(1, 0)
74
+ Curses.attrset(Curses.color_pair(2))
75
+ plan.step_sets.each do |step_set|
76
+ draw_step_set(step_set, detailed)
77
+ end
78
+ end
79
+
80
+ def self.draw_step_set(step_set, detailed)
81
+ str_state_color(step_set.state, ' - [' + step_set.state.to_s + '] ' + step_set.name + "\n")
82
+ if detailed or step_set.state_running? or step_set.state_children_partial?
83
+ step_set.steps.each do |step|
84
+ draw_step(step, detailed)
85
+ end
86
+ end
87
+ end
88
+
89
+ def self.draw_step(step, detailed)
90
+ str_state_color(step.state, ' - [' + step.state.to_s + '] ' + step.name + "\n")
91
+ if detailed or step.state_running? or step.state_children_partial?
92
+ step.command_sets.each do |command_set|
93
+ draw_command_set(command_set, detailed)
94
+ end
95
+ end
96
+ end
97
+
98
+ def self.draw_command_set(command_set, detailed)
99
+ str_state_color(command_set.state, " - [ #{command_set.state.to_s} ] #{command_set.node.name}\n")
100
+ if detailed or command_set.state_running? or command_set.state_children_partial?
101
+ command_set.commands.each do |command|
102
+ str_state_color(command.state, " - [ #{command.state.to_s} ] #{command.title}\n")
103
+ end
104
+ end
105
+ end
106
+
107
+ end
108
+ end
109
+
@@ -0,0 +1,37 @@
1
+ module Dopi
2
+ module Cli
3
+
4
+ def self.command_update(base)
5
+ base.class_eval do
6
+
7
+ desc 'Update the plan and/or the plan state for a given plan yaml or plan name.'
8
+ arg_name 'plan'
9
+ command :update do |c|
10
+ c.desc 'Remove the existing DOPi state and start with a clean state'
11
+ c.default_value false
12
+ c.switch [:clear, :c]
13
+
14
+ c.desc 'Ignore the update and keep the state as it is, only update the internal version string'
15
+ c.default_value false
16
+ c.switch [:ignore, :i]
17
+
18
+ c.action do |global_options,options,args|
19
+ help_now!('Specify a plan name or to update') if args.empty?
20
+ help_now!('You can only update one plan') if args.length > 1
21
+ plan = args[0]
22
+ if Dopi.list.include?(plan)
23
+ Dopi.update_state(plan, options)
24
+ elsif File.exists?(plan)
25
+ Dopi.update_plan(plan, options)
26
+ else
27
+ help_now!("the provided plan '#{plan}' is not an existing file or plan name")
28
+ end
29
+ end
30
+ end
31
+
32
+ end
33
+ end
34
+
35
+ end
36
+ end
37
+
@@ -0,0 +1,27 @@
1
+ module Dopi
2
+ module Cli
3
+
4
+ def self.command_validate(base)
5
+ base.class_eval do
6
+
7
+ desc 'Validate a plan file'
8
+ arg_name 'plan_file'
9
+ command :validate do |c|
10
+ c.action do |global_options,options,args|
11
+ help_now!('Specify a plan file to add') if args.empty?
12
+ help_now!('You can only add one plan') if args.length > 1
13
+ plan_file = args[0]
14
+ if Dopi.valid?(plan_file)
15
+ puts "Plan is valid"
16
+ else
17
+ exit_now!("Plan is NOT valid")
18
+ end
19
+ end
20
+ end
21
+
22
+ end
23
+ end
24
+
25
+ end
26
+ end
27
+
@@ -0,0 +1,55 @@
1
+ #
2
+ # DOPi CLI gloable options
3
+ #
4
+
5
+ module Dopi
6
+ module Cli
7
+
8
+ def self.global_options(base)
9
+ base.class_eval do
10
+ desc 'Use Hiera to get the role for the nodes'
11
+ default_value DopCommon.config.use_hiera
12
+ switch [:use_hiera, :h]
13
+
14
+ desc 'Specify the hiera configuration file'
15
+ default_value DopCommon.config.hiera_yaml
16
+ arg_name 'YAML'
17
+ flag [:hiera_yaml]
18
+
19
+ desc 'Try to load the scope for the nodes from existing facts'
20
+ default_value DopCommon.config.load_facts
21
+ switch [:load_facts]
22
+
23
+ desc 'Specify the directory where dopi can find facts'
24
+ default_value DopCommon.config.facts_dir
25
+ arg_name 'DIR'
26
+ flag [:facts_dir]
27
+
28
+ desc 'Set the name of the variable DOPi should use as the roles variable'
29
+ default_value DopCommon.config.role_variable
30
+ arg_name 'VARIABLE_NAME'
31
+ flag [:role_variable]
32
+
33
+ desc 'Set the default value for the node role'
34
+ default_value DopCommon.config.role_default
35
+ arg_name 'ROLE'
36
+ flag [:role_default]
37
+
38
+ desc 'Set the MCollective client configuration.'
39
+ default_value DopCommon.config.mco_config
40
+ arg_name 'FILE'
41
+ flag [:mco_config]
42
+
43
+ desc 'Use the DOPi logger to capture MCollective logs (this is enabled by default)'
44
+ default_value DopCommon.config.mco_dopi_logger
45
+ switch [:mco_dopi_logger]
46
+
47
+ desc 'Time until a connection check is marked as failure'
48
+ default_value DopCommon.config.connection_check_timeout
49
+ arg_name 'SECONDS'
50
+ flag [:connection_check_timeout]
51
+ end
52
+ end
53
+
54
+ end
55
+ end
@@ -0,0 +1,33 @@
1
+ #
2
+ # Initializes the logger for the CLI
3
+ #
4
+
5
+ module Dopi
6
+ module Cli
7
+
8
+ def self.state(plan_name, detailed = false)
9
+ plan = Dopi.show(plan_name)
10
+ result = "[#{plan.state.to_s}] #{plan.name}\n"
11
+ plan.step_sets.each do |step_set|
12
+ result << " [#{step_set.state.to_s}] #{step_set.name}\n"
13
+ step_set.steps.each do |step|
14
+ result << " [#{step.state.to_s}] #{step.name}\n"
15
+ if detailed or step.state_running? or step.state_children_partial?
16
+ step.command_sets.each do |command_set|
17
+ result << " [#{command_set.state.to_s}] #{command_set.name}\n"
18
+ command_set.commands.each do |command|
19
+ result << " [#{command.state.to_s}] #{command.title}\n"
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ return result
26
+ end
27
+
28
+ def self.print_state(plan_name, detailed = false)
29
+ puts state(plan_name, detailed)
30
+ end
31
+
32
+ end
33
+ end
data/lib/dopi/cli.rb ADDED
@@ -0,0 +1,57 @@
1
+ #
2
+ # The DOPi CLI main module
3
+ #
4
+ require 'gli'
5
+ require 'dopi'
6
+ require 'dop_common/cli/node_selection'
7
+ require 'dop_common/cli/log'
8
+ require 'dop_common/cli/global_options'
9
+ require 'dopi/cli/log'
10
+ require 'dopi/cli/global_options'
11
+ require 'dopi/cli/command_run'
12
+ require 'dopi/cli/command_validate'
13
+ require 'dopi/cli/command_add'
14
+ require 'dopi/cli/command_update'
15
+ require 'dopi/cli/command_list'
16
+ require 'dopi/cli/command_remove'
17
+ require 'dopi/cli/command_show'
18
+ require 'dopi/cli/command_reset'
19
+ require 'logger/colors'
20
+ require 'curses'
21
+ require 'yaml'
22
+ require 'fileutils'
23
+
24
+ module Dopi
25
+ module Cli
26
+ include GLI::App
27
+ extend self
28
+
29
+ program_desc 'DOPi Command line Client'
30
+ version Dopi::VERSION
31
+
32
+ subcommand_option_handling :normal
33
+ arguments :strict
34
+
35
+ config_file DopCommon.config.config_file
36
+
37
+ DopCommon::Cli.global_options(self)
38
+ global_options(self)
39
+
40
+ pre do |global,command,options,args|
41
+ DopCommon.configure = global
42
+ ENV['GLI_DEBUG'] = 'true' if global[:trace] == true
43
+ DopCommon::Cli.initialize_logger('dopi.log', global[:log_level], global[:verbosity], global[:trace])
44
+ true
45
+ end
46
+
47
+ command_run(self)
48
+ command_validate(self)
49
+ command_add(self)
50
+ command_update(self)
51
+ command_list(self)
52
+ command_remove(self)
53
+ command_show(self)
54
+ command_reset(self)
55
+
56
+ end
57
+ end
@@ -0,0 +1,52 @@
1
+ #
2
+ # DOPi Plugin: Custom Command
3
+ #
4
+
5
+ module Dopi
6
+ class Command
7
+ class Custom < Dopi::Command
8
+ include Dopi::Connector::Local
9
+ include Dopi::CommandParser::Exec
10
+ include Dopi::CommandParser::Env
11
+ include Dopi::CommandParser::Arguments
12
+ include Dopi::CommandParser::ExitCode
13
+ include Dopi::CommandParser::Output
14
+
15
+ public
16
+ def validate
17
+ #validate_exec
18
+ # remove after the refactoring is complete
19
+ unless Dopi::Command::Custom > self.class && self.method(:exec).owner == self.class
20
+ log_validation_method('exec_valid?', CommandParsingError)
21
+ end
22
+ validate_env
23
+ validate_arguments
24
+ validate_exit_code
25
+ validate_output
26
+ end
27
+
28
+ def run
29
+ result = []
30
+ cmd_stdout, cmd_stderr, cmd_exit_code = local_command(env, command_string)
31
+ # Output Parser
32
+ result << check_output(cmd_stdout)
33
+ result << check_output(cmd_stderr)
34
+ # Exit Code Parser
35
+ result << check_exit_code(cmd_exit_code)
36
+ result.all?
37
+ end
38
+
39
+ def run_noop
40
+ log(:info, "(NOOP) Executing '#{command_string}' for command #{name}")
41
+ log(:info, "(NOOP) Environment: #{env.to_s}")
42
+ end
43
+
44
+ private
45
+
46
+ def command_string
47
+ exec + ' ' + arguments
48
+ end
49
+
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,27 @@
1
+ #
2
+ # DOPi dummy command.
3
+ #
4
+ # this simply prints the command hash if there was one given
5
+ #
6
+
7
+ module Dopi
8
+ class Command
9
+ class Dummy < Dopi::Command
10
+
11
+ def validate
12
+ true
13
+ end
14
+
15
+ def run
16
+ log(:info, "Running dummy command")
17
+ if @command_hash.class == Hash
18
+ log(:info, "Command hash was: #{hash.inspect}")
19
+ end
20
+ true
21
+ end
22
+
23
+ end
24
+ end
25
+ end
26
+
27
+
@@ -0,0 +1,158 @@
1
+ #
2
+ # DOPi Plugin: MCO RPC
3
+ #
4
+ require 'mcollective'
5
+
6
+ module Dopi
7
+ class Command
8
+ module Mco
9
+ class Rpc < Dopi::Command
10
+ include MCollective::RPC
11
+ include Dopi::CommandParser::ExitCode
12
+
13
+ def validate
14
+ log_validation_method('options_valid?', CommandParsingError)
15
+ log_validation_method('arguments_valid?', CommandParsingError)
16
+ # Skip validation in subclasses that overwrite the non optional methods
17
+ unless Dopi::Command::Mco::Rpc > self.class && self.method(:agent).owner == self.class
18
+ log_validation_method('agent_valid?', CommandParsingError)
19
+ end
20
+ unless Dopi::Command::Mco::Rpc > self.class && self.method(:action).owner == self.class
21
+ log_validation_method('action_valid?', CommandParsingError)
22
+ end
23
+ validate_exit_code
24
+ end
25
+
26
+ def run
27
+ result_ok = true
28
+ flags = {
29
+ :configfile => DopCommon.config.mco_config,
30
+ :options => options,
31
+ :exit_on_failure => false
32
+ }
33
+ mc = rpcclient(agent, flags)
34
+ results = mc.custom_request(action, arguments, [@node.name], {'identity' => @node.name})
35
+ if results.empty?
36
+ log(:error, "No answer from node recieved")
37
+ result_ok = false
38
+ else
39
+ result_ok = false unless parse_mco_result(results.first)
40
+ end
41
+ mc.disconnect
42
+ result_ok
43
+ end
44
+
45
+ def run_noop
46
+ log(:info, "(NOOP) command #{name}: agent: #{agent}")
47
+ log(:info, "(NOOP) command #{name}: options: #{options}")
48
+ log(:info, "(NOOP) command #{name}: arguments: #{arguments}")
49
+ end
50
+
51
+ def agent
52
+ @agent ||= agent_valid? ?
53
+ hash[:agent] : nil
54
+ end
55
+
56
+ def options
57
+ @options ||= options_valid? ?
58
+ options_defaults.merge(hash[:options]) :
59
+ options_defaults
60
+ end
61
+
62
+ def action
63
+ @action ||= action_valid? ?
64
+ hash[:action] : nil
65
+ end
66
+
67
+ def arguments
68
+ @arguments ||= arguments_valid? ?
69
+ hash[:arguments] : {}
70
+ end
71
+
72
+ private
73
+
74
+ def expect_exit_codes_defaults
75
+ 0
76
+ end
77
+
78
+ def options_defaults
79
+ MCollective::Util.default_options.merge({
80
+ :config => DopCommon.config.mco_config
81
+ })
82
+ end
83
+
84
+ def parse_mco_result(result)
85
+ result_ok = true
86
+ unless check_exit_code(result[:statuscode])
87
+ log(:error, result[:statusmsg])
88
+ result_ok = false
89
+ end
90
+ result_ok = false unless parse_mco_result_data(result[:data])
91
+ result_ok
92
+ end
93
+
94
+ def parse_mco_result_data(data)
95
+ warning = "You are using the RPC plugin to run the #{agent} MCollective agent."
96
+ warning += " DOPi will not know what to expect in the resulting data as this is plugin specific."
97
+ warning += " Not all errors may be detected."
98
+ log(:warn, warning)
99
+ log(:debug, 'Receieved data: ' + data.inspect)
100
+ return true
101
+ end
102
+
103
+ def agent_valid?
104
+ hash[:agent] or
105
+ raise CommandParsingError, "No agent defined"
106
+ hash[:agent].kind_of?(String) or
107
+ raise CommandParsingError, "The value for 'agent' has to be a String"
108
+ begin
109
+ MCollective::DDL.new(hash[:agent])
110
+ rescue => e
111
+ raise CommandParsingError, "Unable to load the MCollective agent #{hash[:agent]}on this system: #{e.message}"
112
+ end
113
+ true
114
+ end
115
+
116
+ def options_valid?
117
+ return false if hash[:options].nil? # is optional
118
+ hash[:options].kind_of?(Hash) or
119
+ raise CommandParsingError, "The value for 'options' has to be a Hash"
120
+ end
121
+
122
+ def action_valid?
123
+ hash[:action] or
124
+ raise CommandParsingError, "No action defined"
125
+ hash[:action].kind_of?(String) or
126
+ raise CommandParsingError, "The value for 'action' has to be a String"
127
+ agent_ddl = nil
128
+ begin
129
+ agent_ddl = MCollective::DDL.new(agent)
130
+ rescue CommandParsingError
131
+ raise CommandParsingError, "Agent not valid, unable to verify the action #{hash[:action]}"
132
+ else
133
+ agent_ddl.actions.include?(hash[:action]) or
134
+ raise CommandParsingError, "The action #{hash[:action]} for agent #{agent} does not exist"
135
+ end
136
+ true
137
+ end
138
+
139
+ def arguments_valid?
140
+ hash.nil? or hash[:arguments].nil? or hash[:arguments].kind_of?(Hash) or
141
+ raise CommandParsingError, "The value for 'arguments' has to be a Hash"
142
+ begin
143
+ args = hash ? ( hash[:arguments] or {} ) : {}
144
+ agent_ddl = MCollective::DDL.new(agent)
145
+ agent_ddl.validate_rpc_request(action, args)
146
+ rescue CommandParsingError
147
+ raise CommandParsingError, "Agent and/or Action not valid, unable to verify the action #{hash[:action]}"
148
+ rescue MCollective::DDLValidationError => e
149
+ raise CommandParsingError, "Error while parsing arguments for agent #{agent} and action #{action}: #{e.message}"
150
+ end
151
+ return false if hash[:arguments].nil? # is completely optional if no exception was raised so far
152
+ true
153
+ end
154
+
155
+ end
156
+ end
157
+ end
158
+ end
@@ -0,0 +1,48 @@
1
+ #
2
+ # DOPi Plugin: Custom Command
3
+ #
4
+
5
+ module Dopi
6
+ class Command
7
+ class Ssh
8
+ class Custom < Dopi::Command
9
+ include Dopi::Connector::Ssh
10
+ include Dopi::CommandParser::Exec
11
+ include Dopi::CommandParser::Env
12
+ include Dopi::CommandParser::Arguments
13
+ include Dopi::CommandParser::ExitCode
14
+ include Dopi::CommandParser::Output
15
+
16
+ public
17
+
18
+ def validate
19
+ validate_ssh
20
+ validate_exec
21
+ validate_env
22
+ validate_arguments
23
+ validate_exit_code
24
+ validate_output
25
+ end
26
+
27
+ def run
28
+ cmd_stdout, cmd_stderr, cmd_exit_code = ssh_command(env, command_string)
29
+ check_output(cmd_stdout) &&
30
+ check_output(cmd_stderr) &&
31
+ check_exit_code(cmd_exit_code)
32
+ end
33
+
34
+ def run_noop
35
+ log(:info, "(NOOP) Executing '#{command_string}' for command #{name}")
36
+ log(:info, "(NOOP) Environment: #{env.to_s}")
37
+ end
38
+
39
+ private
40
+
41
+ def command_string
42
+ exec + ' ' + arguments
43
+ end
44
+
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,70 @@
1
+ #
2
+ # DOPi Plugin: File Contains
3
+ #
4
+ require 'pathname'
5
+
6
+ module Dopi
7
+ class Command
8
+ class Ssh
9
+ class FileContains < Dopi::Command
10
+ include Dopi::Connector::Ssh
11
+ include Dopi::CommandParser::ExitCode
12
+
13
+ public
14
+
15
+ def validate
16
+ validate_ssh
17
+ validate_exit_code
18
+ log_validation_method(:file_valid?, CommandParsingError)
19
+ log_validation_method(:pattern_valid?, CommandParsingError)
20
+ end
21
+
22
+ def run
23
+ cmd_stdout, cmd_stderr, cmd_exit_code = ssh_command({}, command_string)
24
+ check_exit_code(cmd_exit_code)
25
+ end
26
+
27
+ def run_noop
28
+ log(:info, "(NOOP) Executing '#{command_string}' for command #{name}")
29
+ end
30
+
31
+ def file
32
+ @file ||= file_valid? ? hash[:file] : nil
33
+ end
34
+
35
+ def pattern
36
+ @pattern ||= pattern_valid? ?
37
+ hash[:pattern] : nil
38
+ end
39
+
40
+ private
41
+
42
+ def command_string
43
+ "grep -e \"#{pattern}\" #{file}"
44
+ end
45
+
46
+ def file_valid?
47
+ hash[:file] or
48
+ raise CommandParsingError, "Plugin #{name}: The key 'file' needs to be specified"
49
+ begin
50
+ Pathname.new(hash[:file]).absolute? or
51
+ raise CommandParsingError, "Plugin #{name}: The path for 'file' has to be absolute"
52
+ rescue ArgumentError => e
53
+ raise CommandParsingError, "Plugin #{name}: The value in 'file' is not a valid file path: #{e.message}"
54
+ end
55
+ end
56
+
57
+ def pattern_valid?
58
+ hash[:pattern] or
59
+ raise CommandParsingError, "Plugin #{name}: The key 'pattern' needs to be specified"
60
+ begin
61
+ Regexp.new(hash[:pattern])
62
+ rescue
63
+ raise CommandParsingError, "Plugin #{name}: The value in 'pattern' is not a valid regexp: #{e.message}"
64
+ end
65
+ end
66
+
67
+ end
68
+ end
69
+ end
70
+ end