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,66 @@
1
+ #
2
+ #
3
+ # This class represents the set commands for a
4
+ # specific node and step.
5
+ #
6
+ # It will also manage the run group restrictions
7
+ #
8
+
9
+ module Dopi
10
+ class CommandSet
11
+ include Dopi::State
12
+ attr_reader :plan, :step, :node
13
+
14
+ def initialize(step_parser, step, node)
15
+ @step_parser = step_parser
16
+ @step = step
17
+ @plan = step.plan
18
+ @node = node
19
+
20
+ commands.each{|command| state_add_child(command)}
21
+ end
22
+
23
+ def name
24
+ @node.name
25
+ end
26
+
27
+ def commands
28
+ @commands ||= @step_parser.commands.map do |command|
29
+ Dopi::Command.create_plugin_instance(command, @step, node)
30
+ end
31
+ end
32
+
33
+ def valid?
34
+ begin
35
+ commands.all?{|command| command.meta_valid?}
36
+ rescue PluginLoaderError => e
37
+ Dopi.log.error("Step '#{name}': Can't load plugin : #{e.message}")
38
+ false
39
+ end
40
+ end
41
+
42
+ def run(noop)
43
+ commands.each do |command|
44
+ break if state_failed? or signals[:stop]
45
+ command.meta_run(noop)
46
+ break unless command.state_done?
47
+ end
48
+ end
49
+
50
+ def load_state(state_hash)
51
+ return if state_hash.empty?
52
+ commands.each_with_index do |command, i|
53
+ command.load_state(state_hash[i])
54
+ end
55
+ end
56
+
57
+ def state_hash
58
+ commands.map do |command|
59
+ command.state_hash
60
+ end
61
+ end
62
+
63
+ end
64
+ end
65
+
66
+
@@ -0,0 +1,77 @@
1
+ #
2
+ # This connector simply executes commands on the local
3
+ # machine. It does all the signal handling and makes
4
+ # sure processes are stopped or killed.
5
+ #
6
+ require 'pty'
7
+ require 'open3'
8
+
9
+ module Dopi
10
+ module Connector
11
+ module Local
12
+
13
+ # The command method executes the command of the step.
14
+ # Returns an array with stdio, sterror and exit code.
15
+ def local_command(env, command_string)
16
+ master, slave = PTY.open
17
+ stdout_r, stdout_w = IO.pipe
18
+ stderr_r, stderr_w = IO.pipe
19
+ cmd_stdout = ''
20
+ cmd_stderr = ''
21
+ options = {
22
+ :pgroup => true,
23
+ :unsetenv_others => true,
24
+ :in => slave,
25
+ :out => stdout_w,
26
+ :err => stderr_w,
27
+ }
28
+ log(:debug, "Executing #{command_string} for command #{name}")
29
+ log(:debug, "Environment: #{env.to_s}")
30
+
31
+ pid = Process.spawn(merged_env(env), command_string, options)
32
+ slave.close
33
+ stdout_w.close
34
+ stderr_w.close
35
+
36
+ signal_handler = Proc.new do |signal|
37
+ case signal
38
+ when :abort then Process.kill(:TERM, pid)
39
+ when :kill then Process.kill(:KILL, pid)
40
+ end
41
+ end
42
+
43
+ on_signal(signal_handler)
44
+ stdout_thread = Thread.new do
45
+ until ( line = stdout_r.gets ).nil? do
46
+ cmd_stdout << line
47
+ log(:debug, line.gsub("\n", '').gsub("\r", ''))
48
+ end
49
+ end
50
+
51
+ stderr_thread = Thread.new do
52
+ until ( line = stderr_r.gets ).nil? do
53
+ cmd_stderr << line
54
+ log(:error, line.gsub("\n", '').gsub("\r", ''))
55
+ end
56
+ end
57
+
58
+ _, status = Process.wait2(pid)
59
+ stdout_thread.join
60
+ stderr_thread.join
61
+ delete_on_signal(signal_handler)
62
+ [ cmd_stdout, cmd_stderr, status.exitstatus ]
63
+ end
64
+
65
+ private
66
+
67
+ def merged_env(env)
68
+ {
69
+ 'HOME' => ENV['HOME'],
70
+ 'PATH' => '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin'
71
+ }.merge(env)
72
+ end
73
+
74
+ end
75
+ end
76
+ end
77
+
@@ -0,0 +1,170 @@
1
+ #
2
+ # This connector will execute commands over ssh
3
+ #
4
+ #
5
+ module Dopi
6
+ module Connector
7
+ module Ssh
8
+ include Dopi::Connector::Local
9
+ include Dopi::CommandParser::Credentials
10
+
11
+ public
12
+
13
+ def validate_ssh
14
+ log_validation_method(:port_valid?, CommandParsingError)
15
+ log_validation_method(:quiet_valid?, CommandParsingError)
16
+ log_validation_method(:check_host_key_valid?, CommandParsingError)
17
+ log_validation_method(:base64_valid?, CommandParsingError)
18
+ log_validation_method(:ssh_options_valid?, CommandParsingError)
19
+ validate_credentials
20
+ end
21
+
22
+ def ssh_command(env, command_string)
23
+ credential = working_ssh_credential
24
+ ssh_command_string = create_ssh_command_string(credential, env, command_string)
25
+ local_env = create_local_env(credential)
26
+ local_env.merge!(env) unless base64 # keep old behaviour for escaping
27
+ local_command(local_env, ssh_command_string)
28
+ end
29
+
30
+ def port
31
+ @port ||= port_valid? ? hash[:port].to_s : '22'
32
+ end
33
+
34
+ def quiet
35
+ @quiet || quiet_valid? ? hash[:quiet] : true
36
+ end
37
+
38
+ def check_host_key
39
+ @check_host_key || check_host_key_valid? ? hash[:check_host_key] : false
40
+ end
41
+
42
+ def base64
43
+ @base64 || base64_valid? ? hash[:base64] : true
44
+ end
45
+
46
+ def ssh_options
47
+ #TBD
48
+ []
49
+ end
50
+
51
+ def supported_credential_types
52
+ [:username_password, :ssh_key]
53
+ end
54
+
55
+ private
56
+
57
+ # this method checks for every credential if a login is possible
58
+ # and will return the first one where it is possible.
59
+ # If none of the credentials work it will raise a CommandConnectionError.
60
+ def working_ssh_credential
61
+ credentials.find do |credential|
62
+ ssh_command_string = create_ssh_command_string(credential, {}, 'exit')
63
+ local_env = create_local_env(credential)
64
+ local_command(local_env, ssh_command_string)[2] == 0
65
+ end or raise CommandConnectionError,
66
+ "Can't establish connection with node #{@node.name} with any of the given" +
67
+ "credentials #{credentials.map{|c| c.name}.join(', ')}"
68
+ end
69
+
70
+ def create_local_env(credential)
71
+ if credential.type == :username_password
72
+ { 'SSHPASS' => credential.password }
73
+ else
74
+ {}
75
+ end
76
+ end
77
+
78
+ def create_ssh_command_string(credential, env, command_string)
79
+ address = @node.address(port)
80
+ opts = options(credential)
81
+ prefix = credential.type == :username_password ? sshpass_cmd : ''
82
+ cmd = "#{ssh_env_string(env)} #{command_string}"
83
+ real_cmd = if base64
84
+ log(:debug, "Unencoded command: '#{cmd}'")
85
+ ssh_encode_command(cmd)
86
+ else
87
+ ssh_escape_command(cmd)
88
+ end
89
+ "#{prefix}ssh #{opts} #{credential.username}@#{address} \"#{real_cmd}\""
90
+ end
91
+
92
+ def options(credential)
93
+ opts = []
94
+ opts << "-p #{port}"
95
+ opts << '-q' if quiet
96
+ opts << '-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null' unless check_host_key
97
+ if credential.type == :ssh_key
98
+ opts << '-o ChallengeResponseAuthentication=no'
99
+ opts << '-o PasswordAuthentication=no'
100
+ opts << "-i #{credential.private_key}"
101
+ end
102
+ # Force allocation of tty, needed to propagate signals to remotely
103
+ # spawned processes
104
+ opts << '-tt'
105
+ opts += ssh_options
106
+ opts += ssh_options_defaults if respond_to?(:ssh_options_defaults)
107
+ opts.join(' ')
108
+ end
109
+
110
+ def sshpass_bin
111
+ @sshpass_bin ||= ENV['PATH'].split(':').map{|p| File.join(p, 'sshpass')}.find do |f|
112
+ File.exists?(f) && File.executable?(f)
113
+ end
114
+ end
115
+
116
+ def sshpass_cmd
117
+ @sshpass_cmd ||= sshpass_bin ? sshpass_bin + ' -e ' : ""
118
+ end
119
+
120
+ def ssh_env_string(env)
121
+ env.map{|variable,value| "export #{variable}=#{value};" }.join(' ')
122
+ end
123
+
124
+ def ssh_escape_command(cmd)
125
+ cmd.gsub("\\", "\\\\\\\\").gsub('"', '\\"')
126
+ end
127
+
128
+ def ssh_encode_command(cmd)
129
+ "echo -n #{Base64.strict_encode64(cmd)} | base64 -d | bash"
130
+ end
131
+
132
+ def port_valid?
133
+ return false unless hash.kind_of?(Hash)
134
+ return false if hash[:port].nil? # is optional
135
+ hash[:port].kind_of?(Fixnum) or
136
+ raise CommandParsingError, "Plugin #{name}: The value for port must be a number"
137
+ hash[:port].between?(0, 65536) or
138
+ raise CommandParsingError, "Plugin #{name}: The value for port must bigger than 0 and below 65536"
139
+ true
140
+ end
141
+
142
+ def quiet_valid?
143
+ return false unless hash.kind_of?(Hash)
144
+ return false if hash[:quiet].nil? # is optional
145
+ hash[:quiet].kind_of?(TrueClass) or hash[:quiet].kind_of?(FalseClass) or
146
+ raise CommandParsingError, "Plugin #{name}: The value for 'quiet' must be boolean"
147
+ end
148
+
149
+ def check_host_key_valid?
150
+ return false unless hash.kind_of?(Hash)
151
+ return false if hash[:check_host_key].nil? # is optional
152
+ hash[:check_host_key].kind_of?(TrueClass) or hash[:check_host_key].kind_of?(FalseClass) or
153
+ raise CommandParsingError, "Plugin #{name}: The value for 'check_host_key' must be boolean"
154
+ end
155
+
156
+ def base64_valid?
157
+ return false unless hash.kind_of?(Hash)
158
+ return false if hash[:base64].nil? # is optional
159
+ hash[:base64].kind_of?(TrueClass) or hash[:base64].kind_of?(FalseClass) or
160
+ raise CommandParsingError, "Plugin #{name}: The value for 'base64' must be boolean"
161
+ end
162
+
163
+ def ssh_options_valid?
164
+ #TBD
165
+ false
166
+ end
167
+
168
+ end
169
+ end
170
+ end
@@ -0,0 +1,167 @@
1
+ #
2
+ # DOPi Plugin: WinRM Command
3
+ #
4
+ require 'winrm'
5
+ require 'gssapi'
6
+
7
+ module Dopi
8
+ module Connector
9
+ module Winrm
10
+ include Dopi::CommandParser::Credentials
11
+
12
+ def validate_winrm
13
+ log_validation_method(:port_valid?)
14
+ log_validation_method(:ssl_valid?)
15
+ log_validation_method(:ca_trust_path_valid?)
16
+ log_validation_method(:disable_sspi_valid?)
17
+ log_validation_method(:basic_auth_only_valid?)
18
+ validate_credentials
19
+ end
20
+
21
+ def winrm_command(command_string)
22
+ cmd_stdout = ""
23
+ cmd_stderr = ""
24
+ log(:debug, "Executing '#{command_string}' for command #{name}")
25
+ result = winrm.run_cmd(command_string) do |stdout, stderr|
26
+ unless stdout.nil? or stdout.empty?
27
+ cmd_stdout << stdout
28
+ log(:debug, stdout)
29
+ end
30
+ unless stderr.nil? or stderr.empty?
31
+ cmd_stderr << stderr
32
+ log(:error, stderr)
33
+ end
34
+ end
35
+ winrm.close
36
+ [cmd_stdout, cmd_stderr, result[:exitcode]]
37
+ end
38
+
39
+ def winrm_powershell_command(command_string)
40
+ log(:debug, "Unencoded Powershell command '#{command_string}'")
41
+ script = WinRM::PowershellScript.new(command_string)
42
+ winrm_command("powershell -encodedCommand #{script.encoded()}")
43
+ end
44
+
45
+ def winrm
46
+ working_command_executor = nil
47
+ credentials.each do |credential|
48
+ begin
49
+ winrm_web_service = WinRM::WinRMWebService.new(
50
+ endpoint,
51
+ auth_method(credential),
52
+ :realm => credential.realm,
53
+ :service => credential.service,
54
+ :keytab => credential.keytab,
55
+ :user => credential.username,
56
+ :pass => credential.password,
57
+ :disable_sspi => disable_sspi,
58
+ :basic_auth_only => basic_auth_only,
59
+ :ca_trust_path => ca_trust_path
60
+ )
61
+ winrm_web_service.set_timeout(operation_timeout)
62
+ command_executor = WinRM::CommandExecutor.new(winrm_web_service)
63
+ command_executor.open
64
+ command_executor.run_cmd('exit') # test connection
65
+ rescue WinRM::WinRMAuthorizationError, GSSAPI::GssApiError => e
66
+ log(:warn, "Unable to login with credential #{credential.name} : #{e.message}")
67
+ rescue SocketError => e
68
+ raise CommandConnectionError,
69
+ "A problem occurred while trying to connect to node #{@node.name} : #{e.message}"
70
+ else working_command_executor = command_executor
71
+ end
72
+ end
73
+ working_command_executor or
74
+ raise CommandExecutionError,
75
+ "Unable to login with any of the given credentials: #{credentials.map{|c| c.name}.join(', ')}"
76
+ end
77
+
78
+ def endpoint
79
+ "http://#{@node.address(port)}:#{port}/wsman"
80
+ end
81
+
82
+ def port
83
+ port_valid? ? hash[:port] : 5985
84
+ end
85
+
86
+ def ssl
87
+ ssl_valid? ? hash[:ssl] : true
88
+ end
89
+
90
+ def ca_trust_path
91
+ ca_trust_path_valid? ? hash[:ca_trust_path] : nil
92
+ end
93
+
94
+ def disable_sspi
95
+ disable_sspi_valid? ? hash[:disable_sspi] : nil
96
+ end
97
+
98
+ def basic_auth_only
99
+ basic_auth_only_valid? ? hash[:basic_auth_only] : nil
100
+ end
101
+
102
+ def operation_timeout
103
+ operation_timeout_valid? ? hash[:operation_timeout] : ( plugin_timeout - 5 )
104
+ end
105
+
106
+ def supported_credential_types
107
+ [:username_password, :kerberos]
108
+ end
109
+
110
+ private
111
+
112
+ def auth_method(credential)
113
+ case credential.type
114
+ when :kerberos then :kerberos
115
+ when :username_password then ssl ? :ssl : :plaintext
116
+ end
117
+ end
118
+
119
+ def port_valid?
120
+ return false if hash.nil?
121
+ return false if hash[:port].nil?
122
+ hash[:port].kind_of?(Fixnum) and (hash[:port] > 0) and (hash[:port] < 65536) or
123
+ raise CommandParsingError, "The value for 'port' has to be a number in the range of 1-65535"
124
+ end
125
+
126
+ def ssl_valid?
127
+ return false if hash.nil?
128
+ return false if hash[:ssl].nil?
129
+ hash[:ssl].kind_of?(TrueClass) or hash[:ssl].kind_of?(FalseClass) or
130
+ raise CommandParsingError, "The value for 'ssl_valid' has to be true or false"
131
+ end
132
+
133
+ def ca_trust_path_valid?
134
+ return false if hash.nil?
135
+ return false if hash[:ca_trust_path].nil?
136
+ hash[:ca_trust_path].kind_of?(String) or
137
+ raise CommandParsingError, "The value for ca_trust_path has to be a string"
138
+ File.directory?(hash[:ca_trust_path]) or
139
+ raise CommandParsingError, "The directory in 'ca_trust_path' does not exist"
140
+ end
141
+
142
+ def disable_sspi_valid?
143
+ return false if hash.nil?
144
+ return false if hash[:disable_sspi].nil?
145
+ hash[:disable_sspi].kind_of?(TrueClass) or hash[:disable_sspi].kind_of?(FalseClass) or
146
+ raise CommandParsingError, "The value for 'disable_sspi' has to be true or false"
147
+ end
148
+
149
+ def basic_auth_only_valid?
150
+ return false if hash.nil?
151
+ return false if hash[:basic_auth_only].nil?
152
+ hash[:basic_auth_only].kind_of?(TrueClass) or hash[:basic_auth_only].kind_of?(FalseClass) or
153
+ raise CommandParsingError, "The value for 'basic_auth_only' has to be true or false"
154
+ end
155
+
156
+ def operation_timeout_valid?
157
+ return false if hash.nil?
158
+ return false if hash[:operation_timeout].nil?
159
+ hash[:operation_timeout].kind_of?(Fixnum) or
160
+ raise CommandParsingError, 'The value for operation_timeout has to be a number'
161
+ hash[:operation_timeout] >= 0 or
162
+ raise CommandParsingError, 'The value for operation_timeout has to be positive number'
163
+ end
164
+
165
+ end
166
+ end
167
+ end
data/lib/dopi/error.rb ADDED
@@ -0,0 +1,43 @@
1
+ #
2
+ # Various error classes for DOPi
3
+ #
4
+ # Error hierarchy:
5
+ #
6
+ # PluginLoaderError
7
+ # StateTransitionError
8
+ # NoRoleFoundError
9
+ # CommandParsingError
10
+ # CommandExecutionError
11
+ # CommandExecutionError
12
+ # ConnectionError
13
+ # NodeConnectionError
14
+ # CommandConnectionError
15
+ #
16
+ module Dopi
17
+ class PluginLoaderError < StandardError
18
+ end
19
+
20
+ class StateTransitionError < StandardError
21
+ end
22
+
23
+ class NoRoleFoundError < StandardError
24
+ end
25
+
26
+ class CommandParsingError < StandardError
27
+ end
28
+
29
+ class CommandExecutionError < StandardError
30
+ end
31
+
32
+ class GracefulExit < StandardError
33
+ end
34
+
35
+ class ConnectionError < CommandExecutionError
36
+ end
37
+
38
+ class NodeConnectionError < ConnectionError
39
+ end
40
+
41
+ class CommandConnectionError < ConnectionError
42
+ end
43
+ end
data/lib/dopi/log.rb ADDED
@@ -0,0 +1,18 @@
1
+ #
2
+ # the logger stuff
3
+ #
4
+ require 'logger'
5
+ require 'dop_common/log'
6
+
7
+ module Dopi
8
+
9
+ def self.log
10
+ @log ||= DopCommon.log
11
+ end
12
+
13
+ def self.logger=(logger)
14
+ @log = logger
15
+ DopCommon.logger = logger
16
+ end
17
+
18
+ end
data/lib/dopi/node.rb ADDED
@@ -0,0 +1,70 @@
1
+ #
2
+ # This class loades a deployment plan
3
+ #
4
+ require 'hiera'
5
+ require 'yaml'
6
+ require 'socket'
7
+ require 'timeout'
8
+ require 'forwardable'
9
+
10
+ module Dopi
11
+ class Node
12
+ extend Forwardable
13
+
14
+ attr_accessor :node_info
15
+
16
+ def initialize(node_parser, plan)
17
+ @node_parser = node_parser
18
+ @plan = plan
19
+ @addresses = {}
20
+ @node_info = {}
21
+ end
22
+
23
+ def_delegators :@node_parser,
24
+ :name,
25
+ :fqdn,
26
+ :has_name?,
27
+ :config,
28
+ :has_config?,
29
+ :config_includes?,
30
+ :fact,
31
+ :has_fact?,
32
+ :role,
33
+ :has_role?
34
+
35
+ def addresses
36
+ [ fqdn, plan_ip_addresses, node_info_ip_addresses ].flatten.uniq
37
+ end
38
+
39
+ def address(port)
40
+ @addresses[port] ||= addresses.find {|addr| connection_possible?(addr,port)} or
41
+ raise NodeConnectionError, "Unable to establish a connection for node #{name} on port #{port} over #{addresses.join(', ')}"
42
+ end
43
+
44
+ def reset_address(port = nil)
45
+ port.nil? ? @addresses = {} : @addresses.delete(port)
46
+ end
47
+
48
+ private
49
+
50
+ def plan_ip_addresses
51
+ @node_parser.interfaces.map{|i| [:dhcp, :none].include?(i.ip) ? nil : i.ip}.compact
52
+ end
53
+
54
+ def node_info_ip_addresses
55
+ node_info[:ip_addresses] || []
56
+ end
57
+
58
+ def connection_possible?(address, port)
59
+ Timeout::timeout(DopCommon.config.connection_check_timeout.to_i) do
60
+ TCPSocket.new(address, port).close
61
+ end
62
+ Dopi.log.debug("Connection test with #{address}:#{port} for node #{name} ok")
63
+ true
64
+ rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Timeout::Error, SocketError
65
+ Dopi.log.debug("Connection test with #{address}:#{port} for node #{name} failed")
66
+ false
67
+ end
68
+
69
+ end
70
+ end