ridley 0.12.4 → 1.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. data/Gemfile +1 -1
  2. data/lib/ridley.rb +3 -3
  3. data/lib/ridley/bootstrap_context.rb +100 -0
  4. data/lib/ridley/bootstrap_context/unix.rb +74 -0
  5. data/lib/ridley/bootstrap_context/windows.rb +120 -0
  6. data/lib/ridley/chef_objects/node_object.rb +8 -5
  7. data/lib/ridley/host_commander.rb +207 -0
  8. data/lib/ridley/host_connector.rb +49 -87
  9. data/lib/ridley/host_connector/ssh.rb +153 -39
  10. data/lib/ridley/host_connector/winrm.rb +164 -39
  11. data/lib/ridley/resources/node_resource.rb +52 -56
  12. data/lib/ridley/version.rb +1 -1
  13. data/ridley.gemspec +0 -2
  14. data/spec/spec_helper.rb +4 -4
  15. data/spec/support/chef_server.rb +9 -3
  16. data/spec/unit/ridley/{bootstrap_bindings/unix_template_binding_spec.rb → bootstrap_context/unix_spec.rb} +2 -2
  17. data/spec/unit/ridley/{bootstrap_bindings/windows_template_binding_spec.rb → bootstrap_context/windows_spec.rb} +2 -2
  18. data/spec/unit/ridley/{mixin/bootstrap_binding_spec.rb → bootstrap_context_spec.rb} +2 -6
  19. data/spec/unit/ridley/host_commander_spec.rb +208 -0
  20. data/spec/unit/ridley/host_connector/ssh_spec.rb +37 -31
  21. data/spec/unit/ridley/host_connector/winrm_spec.rb +124 -31
  22. data/spec/unit/ridley/host_connector_spec.rb +23 -147
  23. data/spec/unit/ridley/resources/node_resource_spec.rb +55 -115
  24. metadata +17 -66
  25. data/lib/ridley/bootstrap_bindings.rb +0 -3
  26. data/lib/ridley/bootstrap_bindings/unix_template_binding.rb +0 -108
  27. data/lib/ridley/bootstrap_bindings/windows_template_binding.rb +0 -163
  28. data/lib/ridley/bootstrapper.rb +0 -89
  29. data/lib/ridley/bootstrapper/context.rb +0 -81
  30. data/lib/ridley/host_connector/response_set.rb +0 -98
  31. data/lib/ridley/host_connector/ssh/worker.rb +0 -135
  32. data/lib/ridley/host_connector/winrm/worker.rb +0 -159
  33. data/lib/ridley/log.rb +0 -10
  34. data/lib/ridley/mixin/bootstrap_binding.rb +0 -77
  35. data/spec/unit/ridley/bootstrapper/context_spec.rb +0 -45
  36. data/spec/unit/ridley/bootstrapper_spec.rb +0 -96
  37. data/spec/unit/ridley/host_connector/response_set_spec.rb +0 -112
  38. data/spec/unit/ridley/host_connector/ssh/worker_spec.rb +0 -57
  39. data/spec/unit/ridley/host_connector/winrm/worker_spec.rb +0 -139
@@ -1,163 +0,0 @@
1
- module Ridley
2
- # Represents a binding that will be evaluated as an ERB template. When bootstrapping
3
- # nodes, an instance of this class represents the customizable and necessary configurations
4
- # needed by the Host in order to install and connect to Chef. By default, this class will be used
5
- # when WinRM is the best way to connect to the node.
6
- #
7
- # @author Kyle Allan <kallan@riotgames.com>
8
- #
9
- # Windows Specific code written by Seth Chisamore (<schisamo@opscode.com>) in knife-windows
10
- # https://github.com/opscode/knife-windows/blob/3b8886ddcfb928ca0958cd05b22f8c3d78bee86e/lib/chef/knife/bootstrap/windows-chef-client-msi.erb
11
- # https://github.com/opscode/knife-windows/blob/78d38bbed358ac20107fc2b5b427f4b5e52e5cb2/lib/chef/knife/core/windows_bootstrap_context.rb
12
- class WindowsTemplateBinding
13
- include Ridley::BootstrapBinding
14
-
15
- attr_reader :template_file
16
-
17
- # @option options [String] :validator_client
18
- # @option options [String] :validator_path
19
- # filepath to the validator used to bootstrap the node (required)
20
- # @option options [String] :bootstrap_proxy (nil)
21
- # URL to a proxy server to bootstrap through
22
- # @option options [String] :encrypted_data_bag_secret
23
- # your organizations encrypted data bag secret
24
- # @option options [Hash] :hints (Hash.new)
25
- # a hash of Ohai hints to place on the bootstrapped node
26
- # @option options [Hash] :attributes (Hash.new)
27
- # a hash of attributes to use in the first Chef run
28
- # @option options [Array] :run_list (Array.new)
29
- # an initial run list to bootstrap with
30
- # @option options [String] :chef_version (nil)
31
- # version of Chef to install on the node
32
- # @option options [String] :environment ('_default')
33
- # environment to join the node to
34
- # @option options [Boolean] :sudo (true)
35
- # bootstrap with sudo (default: true)
36
- # @option options [String] :template ('windows_omnibus')
37
- # bootstrap template to use
38
- def initialize(options)
39
- options = self.class.default_options.merge(options)
40
- options[:template] ||= default_template
41
- self.class.validate_options(options)
42
-
43
- @template_file = options[:template]
44
- @bootstrap_proxy = options[:bootstrap_proxy]
45
- @chef_version = options[:chef_version] ? options[:chef_version] : "latest"
46
- @validator_path = options[:validator_path]
47
- @encrypted_data_bag_secret = options[:encrypted_data_bag_secret]
48
- @server_url = options[:server_url]
49
- @validator_client = options[:validator_client]
50
- @node_name = options[:node_name]
51
- @attributes = options[:attributes]
52
- @run_list = options[:run_list]
53
- @environment = options[:environment]
54
- end
55
-
56
- # @return [String]
57
- def boot_command
58
- template.evaluate(self)
59
- end
60
-
61
- # @return [String]
62
- def chef_config
63
- body = <<-CONFIG
64
- log_level :info
65
- log_location STDOUT
66
- chef_server_url "#{server_url}"
67
- validation_client_name "#{validator_client}"
68
- CONFIG
69
-
70
- if node_name.present?
71
- body << %Q{node_name "#{node_name}"\n}
72
- else
73
- body << "# Using default node name (fqdn)\n"
74
- end
75
-
76
- if bootstrap_proxy.present?
77
- body << %Q{http_proxy "#{bootstrap_proxy}"\n}
78
- body << %Q{https_proxy "#{bootstrap_proxy}"\n}
79
- end
80
-
81
- if encrypted_data_bag_secret.present?
82
- body << %Q{encrypted_data_bag_secret '#{bootstrap_directory}\\encrypted_data_bag_secret'\n}
83
- end
84
-
85
- escape_and_echo(body)
86
- end
87
-
88
- # @return [String]
89
- def bootstrap_directory
90
- "C:\\chef"
91
- end
92
-
93
- # @return [String]
94
- def validation_key
95
- escape_and_echo(IO.read(File.expand_path(validator_path)).chomp)
96
- rescue Errno::ENOENT
97
- raise Errors::ValidatorNotFound, "Error bootstrapping: Validator not found at '#{validator_path}'"
98
- end
99
-
100
- # @return [String]
101
- def chef_run
102
- "chef-client -j #{bootstrap_directory}\\first-boot.json -E #{environment}"
103
- end
104
-
105
- # @return [String]
106
- def default_template
107
- templates_path.join('windows_omnibus.erb').to_s
108
- end
109
-
110
- # @return [String]
111
- def encrypted_data_bag_secret
112
- return unless @encrypted_data_bag_secret
113
-
114
- escape_and_echo(@encrypted_data_bag_secret)
115
- end
116
-
117
- # Implements a Powershell script that attempts a simple
118
- # 'wget' to download the Chef msi
119
- #
120
- # @return [String]
121
- def windows_wget_powershell
122
- win_wget_ps = <<-WGET_PS
123
- param(
124
- [String] $remoteUrl,
125
- [String] $localPath
126
- )
127
-
128
- $webClient = new-object System.Net.WebClient;
129
-
130
- $webClient.DownloadFile($remoteUrl, $localPath);
131
- WGET_PS
132
-
133
- escape_and_echo(win_wget_ps)
134
- end
135
-
136
- # @return [String]
137
- def install_chef
138
- 'msiexec /qb /i "%LOCAL_DESTINATION_MSI_PATH%"'
139
- end
140
-
141
- # @return [String]
142
- def first_boot
143
- escape_and_echo(JSON.fast_generate(attributes.merge(run_list: run_list)))
144
- end
145
-
146
- # @return [String]
147
- def set_path
148
- "SET \"PATH=%PATH%;C:\\ruby\\bin;C:\\opscode\\chef\\bin;C:\\opscode\\chef\\embedded\\bin\"\n"
149
- end
150
-
151
- # @return [String]
152
- def local_download_path
153
- "%TEMP%\\chef-client-#{chef_version}.msi"
154
- end
155
-
156
- # escape WIN BATCH special chars
157
- # and prefixes each line with an
158
- # echo
159
- def escape_and_echo(file_contents)
160
- file_contents.gsub(/^(.*)$/, 'echo.\1').gsub(/([(<|>)^])/, '^\1')
161
- end
162
- end
163
- end
@@ -1,89 +0,0 @@
1
- require_relative 'bootstrapper/context'
2
- require_relative 'logging'
3
-
4
- module Ridley
5
- # @author Jamie Winsor <reset@riotgames.com>
6
- class Bootstrapper
7
- include Celluloid
8
- include Ridley::Logging
9
-
10
- # @return [Array<String>]
11
- attr_reader :hosts
12
-
13
- # @return [Hash]
14
- attr_reader :options
15
-
16
- # @param [Array<#to_s>] hosts
17
- # @option options [Hash] :ssh
18
- # * :user (String) a shell user that will login to each node and perform the bootstrap command on (required)
19
- # * :password (String) the password for the shell user that will perform the bootstrap
20
- # * :keys (Array, String) an array of keys (or a single key) to authenticate the ssh user with instead of a password
21
- # * :timeout (Float) [5.0] timeout value for SSH bootstrap
22
- # @option options [Hash] :winrm
23
- # * :user (String) a user that will login to each node and perform the bootstrap command on (required)
24
- # * :password (String) the password for the user that will perform the bootstrap
25
- # * :port (Fixnum) the winrm port to connect on the node the bootstrap will be performed on (5985)
26
- # @option options [String] :validator_client
27
- # @option options [String] :validator_path
28
- # filepath to the validator used to bootstrap the node (required)
29
- # @option options [String] :bootstrap_proxy (nil)
30
- # URL to a proxy server to bootstrap through
31
- # @option options [String] :encrypted_data_bag_secret
32
- # your organizations encrypted data bag secret
33
- # @option options [Hash] :hints (Hash.new)
34
- # a hash of Ohai hints to place on the bootstrapped node
35
- # @option options [Hash] :attributes (Hash.new)
36
- # a hash of attributes to use in the first Chef run
37
- # @option options [Array] :run_list (Array.new)
38
- # an initial run list to bootstrap with
39
- # @option options [String] :chef_version (nil)
40
- # version of Chef to install on the node
41
- # @option options [String] :environment ('_default')
42
- # environment to join the node to
43
- # @option options [Boolean] :sudo (true)
44
- # bootstrap with sudo (default: true)
45
- # @option options [String] :template
46
- # bootstrap template to use
47
- def initialize(hosts, options = {})
48
- @hosts = Array(hosts).flatten.collect(&:to_s).uniq
49
- @options = options.dup
50
- @options[:ssh] ||= Hash.new
51
- @options[:ssh] = {
52
- timeout: 5.0,
53
- sudo: true
54
- }.merge(@options[:ssh])
55
-
56
- @options[:sudo] = @options[:ssh][:sudo]
57
- end
58
-
59
- # @raise [Errors::HostConnectionError] if a node is unreachable
60
- #
61
- # @return [Array<Bootstrapper::Context>]
62
- def contexts
63
- @contexts ||= @hosts.collect { |host| Context.create(host, options) }
64
- end
65
-
66
- # @raise [Errors::HostConnectionError] if a node is unreachable
67
- #
68
- # @return [HostConnector::ResponseSet]
69
- def run
70
- workers = Array.new
71
- futures = contexts.collect do |context|
72
- log.info { "Running bootstrap command on #{context.host}" }
73
-
74
- workers << worker = context.host_connector::Worker.new(context.host, self.options.freeze)
75
-
76
- worker.future.run(context.template_binding.boot_command)
77
- end
78
-
79
- HostConnector::ResponseSet.new.tap do |response_set|
80
- futures.each do |future|
81
- status, response = future.value
82
- response_set.add_response(response)
83
- end
84
- end
85
- ensure
86
- workers.map(&:terminate)
87
- end
88
- end
89
- end
@@ -1,81 +0,0 @@
1
- require 'erubis'
2
-
3
- module Ridley
4
- class Bootstrapper
5
- # @author Jamie Winsor <reset@riotgames.com>
6
- class Context
7
- class << self
8
- # @param [String] host
9
- # @option options [Hash] :ssh
10
- # * :user (String) a shell user that will login to each node and perform the bootstrap command on (required)
11
- # * :password (String) the password for the shell user that will perform the bootstrap
12
- # * :port (Fixnum) the ssh port to connect on the node the bootstrap will be performed on (22)
13
- # * :keys (Array, String) an array of keys (or a single key) to authenticate the ssh user with instead of a password
14
- # * :timeout (Float) [5.0] timeout value for SSH bootstrap
15
- # @option options [Hash] :winrm
16
- # * :user (String) a user that will login to each node and perform the bootstrap command on (required)
17
- # * :password (String) the password for the user that will perform the bootstrap
18
- # * :port (Fixnum) the winrm port to connect on the node the bootstrap will be performed on (5985)
19
- # @option options [String] :validator_client
20
- # @option options [String] :validator_path
21
- # filepath to the validator used to bootstrap the node (required)
22
- # @option options [String] :bootstrap_proxy (nil)
23
- # URL to a proxy server to bootstrap through
24
- # @option options [String] :encrypted_data_bag_secret
25
- # your organizations encrypted data bag secret
26
- # @option options [Hash] :hints (Hash.new)
27
- # a hash of Ohai hints to place on the bootstrapped node
28
- # @option options [Hash] :attributes (Hash.new)
29
- # a hash of attributes to use in the first Chef run
30
- # @option options [Array] :run_list (Array.new)
31
- # an initial run list to bootstrap with
32
- # @option options [String] :chef_version (nil)
33
- # version of Chef to install on the node
34
- # @option options [String] :environment ('_default')
35
- # environment to join the node to
36
- # @option options [Boolean] :sudo (true)
37
- # bootstrap with sudo (default: true)
38
- # @option options [String] :template ('omnibus')
39
- # bootstrap template to use
40
- #
41
- # @raise [Errors::HostConnectionError] if a node is unreachable
42
- def create(host, options = {})
43
- host_connector = HostConnector.best_connector_for(host, options)
44
- template_binding = case host_connector.to_s
45
- when Ridley::HostConnector::SSH.to_s
46
- Ridley::UnixTemplateBinding.new(options)
47
- when Ridley::HostConnector::WinRM.to_s
48
- Ridley::WindowsTemplateBinding.new(options)
49
- else
50
- raise Ridley::Errors::HostConnectionError, "Cannot find an appropriate Template Binding for an unknown connector."
51
- end
52
- new(host, host_connector, template_binding)
53
- end
54
- end
55
-
56
- # @return [String]
57
- attr_reader :host
58
- # @return [Ridley::HostConnector]
59
- attr_reader :host_connector
60
- # @return [Ridley::Binding]
61
- attr_reader :template_binding
62
-
63
- # @param [String] host
64
- # name of the node as identified in Chef
65
- # @param [Ridley::HostConnector] host_connector
66
- # either the SSH or WinRM Connector class
67
- # @param [Ridley::Binding] template_binding
68
- # an instance of either the UnixTemplateBinding or WindowsTemplateBinding class
69
- def initialize(host, host_connector, template_binding)
70
- @host = host
71
- @host_connector = host_connector
72
- @template_binding = template_binding
73
- end
74
-
75
- # @return [String]
76
- def clean_command
77
- "rm /etc/chef/first-boot.json; rm /etc/chef/validation.pem"
78
- end
79
- end
80
- end
81
- end
@@ -1,98 +0,0 @@
1
- module Ridley
2
- module HostConnector
3
- # @author Jamie Winsor <reset@riotgames.com>
4
- class ResponseSet
5
- class << self
6
- # Merges the responses of the other ResponseSet with the target ResponseSet
7
- # and returns the mutated target
8
- #
9
- # @param [HostConnector::ResponseSet] target
10
- # @param [HostConnector::ResponseSet] other
11
- #
12
- # @return [HostConnector::ResponseSet]
13
- def merge!(target, other)
14
- if other.is_a?(self)
15
- target.add_response(other.responses)
16
- end
17
-
18
- target
19
- end
20
- end
21
-
22
- extend Forwardable
23
- include Enumerable
24
-
25
- attr_reader :failures
26
- attr_reader :successes
27
-
28
- def_delegator :responses, :each
29
-
30
- def initialize(responses = Array.new)
31
- @failures = Array.new
32
- @successes = Array.new
33
-
34
- add_response Array(responses)
35
- end
36
-
37
- # @param [HostConnector::Response, Array<HostConnector::Response>] response
38
- #
39
- # @return [Array<HostConnector::Response>]
40
- def add_response(response)
41
- if response.is_a?(Array)
42
- until response.empty?
43
- add_response(response.pop)
44
- end
45
- return responses
46
- end
47
-
48
- response.error? ? add_failure(response) : add_success(response)
49
- responses
50
- end
51
- alias_method :<<, :add_response
52
-
53
- def responses
54
- successes + failures
55
- end
56
-
57
- # Return true if the response set contains any errors
58
- #
59
- # @return [Boolean]
60
- def has_errors?
61
- self.failures.any?
62
- end
63
-
64
- # Merges the responses of another ResponseSet with self and returns
65
- # a new instance of ResponseSet
66
- #
67
- # @param [Ridley::HostConnector::ResponseSet] other
68
- #
69
- # @return [Ridley::HostConnector::ResponseSet]
70
- def merge(other)
71
- target = self.class.new(self.responses) # Why the fuck can't I use #dup here?
72
- self.class.merge!(target, other)
73
- end
74
-
75
- # Merges the respones of another ResponseSet with self and returns
76
- # mutated self
77
- #
78
- # @param [Ridley::HostConnector::ResponseSet] other
79
- #
80
- # @return [self]
81
- def merge!(other)
82
- self.class.merge!(self, other)
83
- end
84
-
85
- private
86
-
87
- # @param [HostConnector::Response] response
88
- def add_failure(response)
89
- self.failures << response
90
- end
91
-
92
- # @param [HostConnector::Response] response
93
- def add_success(response)
94
- self.successes << response
95
- end
96
- end
97
- end
98
- end
@@ -1,135 +0,0 @@
1
- module Ridley
2
- module HostConnector
3
- class SSH
4
- # @author Jamie Winsor <reset@riotgames.com>
5
- # @api private
6
- class Worker
7
- include Celluloid
8
- include Celluloid::Logger
9
-
10
- attr_reader :sudo
11
- attr_reader :user
12
- attr_reader :host
13
- # @return [Hashie::Mash]
14
- attr_reader :options
15
-
16
- EMBEDDED_RUBY_PATH = '/opt/chef/embedded/bin/ruby'.freeze
17
-
18
- # @param [Hash] options
19
- def initialize(host, options = {})
20
- options = options.deep_symbolize_keys
21
- @options = options[:ssh] || Hash.new
22
- @host = host
23
- @sudo = @options[:sudo]
24
- @user = @options[:user]
25
-
26
- @options[:paranoid] = false
27
- end
28
-
29
- # @param [String] command
30
- #
31
- # @return [Array]
32
- def run(command)
33
- response = Ridley::HostConnector::Response.new(host)
34
- debug "Running SSH command: '#{command}' on: '#{host}' as: '#{user}'"
35
-
36
- channel_exec = ->(channel, command) do
37
- channel.exec(command) do |ch, success|
38
- unless success
39
- raise "Channel execution failed while executing command #{command}"
40
- end
41
-
42
- channel.on_data do |ch, data|
43
- response.stdout += data
44
- info "NODE[#{host}] #{data}" if data.present? and data != "\r\n"
45
- end
46
-
47
- channel.on_extended_data do |ch, type, data|
48
- response.stderr += data
49
- info "NODE[#{host}] #{data}" if data.present? and data != "\r\n"
50
- end
51
-
52
- channel.on_request("exit-status") do |ch, data|
53
- response.exit_code = data.read_long
54
- end
55
-
56
- channel.on_request("exit-signal") do |ch, data|
57
- response.exit_signal = data.read_string
58
- end
59
- end
60
- end
61
-
62
- Net::SSH.start(host, user, options.slice(*Net::SSH::VALID_OPTIONS)) do |ssh|
63
- ssh.open_channel do |channel|
64
- if self.sudo
65
- channel.request_pty do |channel, success|
66
- raise "Could not aquire pty: A pty is required for running sudo commands." unless success
67
-
68
- channel_exec.call(channel, command)
69
- end
70
- else
71
- channel_exec.call(channel, command)
72
- end
73
- end
74
-
75
- ssh.loop
76
- end
77
-
78
- case response.exit_code
79
- when 0
80
- debug "Successfully ran SSH command on: '#{host}' as: '#{user}'"
81
- [ :ok, response ]
82
- else
83
- error "Successfully ran SSH command on: '#{host}' as: '#{user}', but it failed"
84
- error response.stdout
85
- [ :error, response ]
86
- end
87
- rescue => e
88
- error "Failed to run SSH command on: '#{host}' as: '#{user}'"
89
- error "#{e.class}: #{e.message}"
90
- response.exit_code = -1
91
- response.stderr = e.message
92
- [ :error, response ]
93
- end
94
-
95
- # Executes a chef-client command on the nodes
96
- #
97
- # @return [#run]
98
- def chef_client
99
- command = "chef-client"
100
- if sudo
101
- command = "sudo #{command}"
102
- end
103
-
104
- run(command)
105
- end
106
-
107
- # Writes the given encrypted data bag secret to the node
108
- #
109
- # @param [String] secret
110
- # your organization's encrypted data bag secret
111
- #
112
- # @return [#run]
113
- def put_secret(secret)
114
- command = "echo '#{secret}' > /etc/chef/encrypted_data_bag_secret; chmod 0600 /etc/chef/encrypted_data_bag_secret"
115
- run(command)
116
- end
117
-
118
- # Executes a provided Ruby script in the embedded Ruby installation
119
- #
120
- # @param [Array<String>] command_lines
121
- # An Array of lines of the command to be executed
122
- #
123
- # @return [#run]
124
- def ruby_script(command_lines)
125
- command = "#{EMBEDDED_RUBY_PATH} -e \"#{command_lines.join(';')}\""
126
- run(command)
127
- end
128
-
129
- private
130
-
131
- attr_reader :runner
132
- end
133
- end
134
- end
135
- end