ridley 0.12.4 → 1.0.0.rc1

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 (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