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,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
|
data/lib/dopi/cli/log.rb
ADDED
|
@@ -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
|