chef-metal 0.14.2 → 0.15

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 (41) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +5 -1
  3. data/README.md +2 -257
  4. data/Rakefile +0 -4
  5. data/lib/chef_metal.rb +1 -87
  6. data/lib/chef_metal/action_handler.rb +1 -66
  7. data/lib/chef_metal/add_prefix_action_handler.rb +1 -29
  8. data/lib/chef_metal/chef_image_spec.rb +1 -106
  9. data/lib/chef_metal/chef_machine_spec.rb +1 -82
  10. data/lib/chef_metal/chef_provider_action_handler.rb +1 -72
  11. data/lib/chef_metal/chef_run_data.rb +1 -125
  12. data/lib/chef_metal/convergence_strategy.rb +1 -26
  13. data/lib/chef_metal/convergence_strategy/install_cached.rb +1 -157
  14. data/lib/chef_metal/convergence_strategy/install_msi.rb +1 -56
  15. data/lib/chef_metal/convergence_strategy/install_sh.rb +1 -53
  16. data/lib/chef_metal/convergence_strategy/no_converge.rb +1 -37
  17. data/lib/chef_metal/convergence_strategy/precreate_chef_objects.rb +1 -181
  18. data/lib/chef_metal/driver.rb +1 -288
  19. data/lib/chef_metal/image_spec.rb +1 -70
  20. data/lib/chef_metal/machine.rb +1 -110
  21. data/lib/chef_metal/machine/basic_machine.rb +1 -82
  22. data/lib/chef_metal/machine/unix_machine.rb +1 -276
  23. data/lib/chef_metal/machine/windows_machine.rb +1 -102
  24. data/lib/chef_metal/machine_spec.rb +1 -78
  25. data/lib/chef_metal/recipe_dsl.rb +1 -94
  26. data/lib/chef_metal/transport.rb +1 -87
  27. data/lib/chef_metal/transport/ssh.rb +1 -288
  28. data/lib/chef_metal/transport/winrm.rb +1 -134
  29. data/lib/chef_metal/version.rb +1 -3
  30. metadata +19 -145
  31. data/bin/metal +0 -275
  32. data/lib/chef/provider/machine.rb +0 -171
  33. data/lib/chef/provider/machine_batch.rb +0 -186
  34. data/lib/chef/provider/machine_execute.rb +0 -30
  35. data/lib/chef/provider/machine_file.rb +0 -49
  36. data/lib/chef/provider/machine_image.rb +0 -54
  37. data/lib/chef/resource/machine.rb +0 -116
  38. data/lib/chef/resource/machine_batch.rb +0 -72
  39. data/lib/chef/resource/machine_execute.rb +0 -22
  40. data/lib/chef/resource/machine_file.rb +0 -28
  41. data/lib/chef/resource/machine_image.rb +0 -29
@@ -1,78 +1 @@
1
- module ChefMetal
2
- #
3
- # Specification for a machine. Sufficient information to find and contact it
4
- # after it has been set up.
5
- #
6
- class MachineSpec
7
- def initialize(node)
8
- @node = node
9
- end
10
-
11
- attr_reader :node
12
-
13
- #
14
- # Globally unique identifier for this machine. Does not depend on the machine's
15
- # location or existence.
16
- #
17
- def id
18
- raise "id unimplemented"
19
- end
20
-
21
- #
22
- # Name of the machine. Corresponds to the name in "machine 'name' do" ...
23
- #
24
- def name
25
- node['name']
26
- end
27
-
28
- #
29
- # Location of this machine. This should be a freeform hash, with enough
30
- # information for the driver to look it up and create a Machine object to
31
- # access it.
32
- #
33
- # This MUST include a 'driver_url' attribute with the driver's URL in it.
34
- #
35
- # chef-metal will do its darnedest to not lose this information.
36
- #
37
- def location
38
- metal_attr('location')
39
- end
40
-
41
- #
42
- # Set the location for this machine.
43
- #
44
- def location=(value)
45
- set_metal_attr('location', value)
46
- end
47
-
48
- # URL to the driver. Convenience for location['driver_url']
49
- def driver_url
50
- location ? location['driver_url'] : nil
51
- end
52
-
53
- #
54
- # Save this node to the server. If you have significant information that
55
- # could be lost, you should do this as quickly as possible. Data will be
56
- # saved automatically for you after allocate_machine and ready_machine.
57
- #
58
- def save(action_handler)
59
- raise "save unimplemented"
60
- end
61
-
62
- protected
63
-
64
- def metal_attr(attr)
65
- if node['normal'] && node['normal']['metal']
66
- node['normal']['metal'][attr]
67
- else
68
- nil
69
- end
70
- end
71
-
72
- def set_metal_attr(attr, value)
73
- node['normal'] ||= {}
74
- node['normal']['metal'] ||= {}
75
- node['normal']['metal'][attr] = value
76
- end
77
- end
78
- end
1
+ require "chef/provisioning/machine_spec"
@@ -1,94 +1 @@
1
- require 'chef_metal/chef_run_data'
2
- require 'chef/resource_collection'
3
-
4
- require 'chef/resource/machine'
5
- require 'chef/provider/machine'
6
- require 'chef/resource/machine_batch'
7
- require 'chef/provider/machine_batch'
8
- require 'chef/resource/machine_file'
9
- require 'chef/provider/machine_file'
10
- require 'chef/resource/machine_execute'
11
- require 'chef/provider/machine_execute'
12
- require 'chef/resource/machine_image'
13
- require 'chef/provider/machine_image'
14
-
15
- class Chef
16
- module DSL
17
- module Recipe
18
- def with_driver(driver, options = nil, &block)
19
- run_context.chef_metal.with_driver(driver, options, &block)
20
- end
21
-
22
- def with_machine_options(machine_options, &block)
23
- run_context.chef_metal.with_machine_options(machine_options, &block)
24
- end
25
-
26
- def current_machine_options
27
- run_context.chef_metal.current_machine_options
28
- end
29
-
30
- def add_machine_options(options, &block)
31
- run_context.chef_metal.add_machine_options(options, &block)
32
- end
33
-
34
- def with_image_options(image_options, &block)
35
- run_context.chef_metal.with_image_options(image_options, &block)
36
- end
37
-
38
- def current_image_options
39
- run_context.chef_metal.current_image_options
40
- end
41
-
42
- NOT_PASSED = Object.new
43
-
44
- @@next_machine_batch_index = 0
45
-
46
- def machine_batch_default_name
47
- @@next_machine_batch_index += 1
48
- if @@next_machine_batch_index > 1
49
- "default#{@@next_machine_batch_index}"
50
- else
51
- "default"
52
- end
53
- end
54
-
55
- def machine_batch(name = nil, &block)
56
- name ||= machine_batch_default_name
57
- recipe = self
58
- declare_resource(:machine_batch, name, caller[0]) do
59
- from_recipe recipe
60
- instance_eval(&block)
61
- end
62
- end
63
-
64
- end
65
- end
66
-
67
- class Config
68
- default(:driver) { ENV['CHEF_DRIVER'] }
69
- # config_context :drivers do
70
- # # each key is a driver_url, and each value can have driver, driver_options and machine_options
71
- # config_strict_mode false
72
- # end
73
- # config_context :driver_options do
74
- # # open ended for whatever the driver wants
75
- # config_strict_mode false
76
- # end
77
- # config_context :machine_options do
78
- # # open ended for whatever the driver wants
79
- # config_strict_mode false
80
- # end
81
- end
82
-
83
- class RunContext
84
- def chef_metal
85
- @chef_metal ||= ChefMetal::ChefRunData.new(config)
86
- end
87
- end
88
-
89
- class ResourceCollection
90
- def previous_index
91
- @insert_after_idx ? @insert_after_idx : @resources.length - 1
92
- end
93
- end
94
- end
1
+ require "chef/provisioning/recipe_dsl"
@@ -1,87 +1 @@
1
- require 'timeout'
2
-
3
- module ChefMetal
4
- class Transport
5
- DEFAULT_TIMEOUT = 15*60
6
-
7
- # Execute a program on the remote host.
8
- #
9
- # == Arguments
10
- # command: command to run. May be a shell-escaped string or a pre-split array containing [PROGRAM, ARG1, ARG2, ...].
11
- # options: hash of options, including but not limited to:
12
- # :timeout => NUM_SECONDS - time to wait before program finishes (throws an exception otherwise). Set to nil or 0 to run with no timeout. Defaults to 15 minutes.
13
- # :stream => BOOLEAN - true to stream stdout and stderr to the console.
14
- # :stream => BLOCK - block to stream stdout and stderr to (block.call(stdout_chunk, stderr_chunk))
15
- # :stream_stdout => FD - FD to stream stdout to (defaults to IO.stdout)
16
- # :stream_stderr => FD - FD to stream stderr to (defaults to IO.stderr)
17
- # :read_only => BOOLEAN - true if command is guaranteed not to change system state (useful for Docker)
18
- def execute(command, options = {})
19
- raise "execute not overridden on #{self.class}"
20
- end
21
-
22
- def read_file(path)
23
- raise "read_file not overridden on #{self.class}"
24
- end
25
-
26
- def write_file(path, content)
27
- raise "write_file not overridden on #{self.class}"
28
- end
29
-
30
- def download_file(path, local_path)
31
- IO.write(local_path, read_file(path))
32
- end
33
-
34
- def upload_file(local_path, path)
35
- write_file(path, IO.read(local_path))
36
- end
37
-
38
- def make_url_available_to_remote(local_url)
39
- raise "make_url_available_to_remote not overridden on #{self.class}"
40
- end
41
-
42
- def disconnect
43
- raise "disconnect not overridden on #{self.class}"
44
- end
45
-
46
- def available?
47
- raise "available? not overridden on #{self.class}"
48
- end
49
-
50
- # Config hash, including :log_level and :logger as keys
51
- def config
52
- raise "config not overridden on #{self.class}"
53
- end
54
-
55
- protected
56
-
57
- # Helper to implement stdout/stderr streaming in execute
58
- def stream_chunk(options, stdout_chunk, stderr_chunk)
59
- if options[:stream].is_a?(Proc)
60
- options[:stream].call(stdout_chunk, stderr_chunk)
61
- else
62
- if stdout_chunk
63
- if options[:stream_stdout]
64
- options[:stream_stdout].print stdout_chunk
65
- elsif options[:stream] || config[:log_level] == :debug
66
- STDOUT.print stdout_chunk
67
- end
68
- end
69
- if stderr_chunk
70
- if options[:stream_stderr]
71
- options[:stream_stderr].print stderr_chunk
72
- elsif options[:stream] || config[:log_level] == :debug
73
- STDERR.print stderr_chunk
74
- end
75
- end
76
- end
77
- end
78
-
79
- def with_execute_timeout(options, &block)
80
- Timeout::timeout(execute_timeout(options), &block)
81
- end
82
-
83
- def execute_timeout(options)
84
- options.has_key?(:timeout) ? options[:timeout] : DEFAULT_TIMEOUT
85
- end
86
- end
87
- end
1
+ require "chef/provisioning/transport"
@@ -1,288 +1 @@
1
- require 'chef_metal/transport'
2
- require 'chef/log'
3
- require 'uri'
4
- require 'socket'
5
- require 'timeout'
6
- require 'net/ssh'
7
- require 'net/scp'
8
- require 'net/ssh/gateway'
9
-
10
- module ChefMetal
11
- class Transport
12
- class SSH < ChefMetal::Transport
13
- def initialize(host, username, ssh_options, options, global_config)
14
- @host = host
15
- @username = username
16
- @ssh_options = ssh_options
17
- @options = options
18
- @config = global_config
19
- end
20
-
21
- attr_reader :host
22
- attr_reader :username
23
- attr_reader :ssh_options
24
- attr_reader :options
25
- attr_reader :config
26
-
27
- def execute(command, execute_options = {})
28
- Chef::Log.info("Executing #{options[:prefix]}#{command} on #{username}@#{host}")
29
- stdout = ''
30
- stderr = ''
31
- exitstatus = nil
32
- session # grab session outside timeout, it has its own timeout
33
- with_execute_timeout(execute_options) do
34
- channel = session.open_channel do |channel|
35
- # Enable PTY unless otherwise specified, some instances require this
36
- unless options[:ssh_pty_enable] == false
37
- channel.request_pty do |chan, success|
38
- raise "could not get pty" if !success && options[:ssh_pty_enable]
39
- end
40
- end
41
-
42
- channel.exec("#{options[:prefix]}#{command}") do |ch, success|
43
- raise "could not execute command: #{command.inspect}" unless success
44
-
45
- channel.on_data do |ch2, data|
46
- stdout << data
47
- stream_chunk(execute_options, data, nil)
48
- end
49
-
50
- channel.on_extended_data do |ch2, type, data|
51
- stderr << data
52
- stream_chunk(execute_options, nil, data)
53
- end
54
-
55
- channel.on_request "exit-status" do |ch, data|
56
- exitstatus = data.read_long
57
- end
58
- end
59
- end
60
-
61
- channel.wait
62
- end
63
-
64
- Chef::Log.info("Completed #{command} on #{username}@#{host}: exit status #{exitstatus}")
65
- Chef::Log.debug("Stdout was:\n#{stdout}") if stdout != '' && !options[:stream] && !options[:stream_stdout] && config[:log_level] != :debug
66
- Chef::Log.info("Stderr was:\n#{stderr}") if stderr != '' && !options[:stream] && !options[:stream_stderr] && config[:log_level] != :debug
67
- SSHResult.new(command, execute_options, stdout, stderr, exitstatus)
68
- end
69
-
70
- def read_file(path)
71
- Chef::Log.debug("Reading file #{path} from #{username}@#{host}")
72
- result = StringIO.new
73
- download(path, result)
74
- result.string
75
- end
76
-
77
- def download_file(path, local_path)
78
- Chef::Log.debug("Downloading file #{path} from #{username}@#{host} to local #{local_path}")
79
- download(path, local_path)
80
- end
81
-
82
- def write_file(path, content)
83
- execute("mkdir -p #{File.dirname(path)}").error!
84
- if options[:prefix]
85
- # Make a tempfile on the other side, upload to that, and sudo mv / chown / etc.
86
- remote_tempfile = "/tmp/#{File.basename(path)}.#{Random.rand(2**32)}"
87
- Chef::Log.debug("Writing #{content.length} bytes to #{remote_tempfile} on #{username}@#{host}")
88
- Net::SCP.new(session).upload!(StringIO.new(content), remote_tempfile)
89
- execute("mv #{remote_tempfile} #{path}").error!
90
- else
91
- Chef::Log.debug("Writing #{content.length} bytes to #{path} on #{username}@#{host}")
92
- Net::SCP.new(session).upload!(StringIO.new(content), path)
93
- end
94
- end
95
-
96
- def upload_file(local_path, path)
97
- execute("mkdir -p #{File.dirname(path)}").error!
98
- if options[:prefix]
99
- # Make a tempfile on the other side, upload to that, and sudo mv / chown / etc.
100
- remote_tempfile = "/tmp/#{File.basename(path)}.#{Random.rand(2**32)}"
101
- Chef::Log.debug("Uploading #{local_path} to #{remote_tempfile} on #{username}@#{host}")
102
- Net::SCP.new(session).upload!(local_path, remote_tempfile)
103
- execute("mv #{remote_tempfile} #{path}").error!
104
- else
105
- Chef::Log.debug("Uploading #{local_path} to #{path} on #{username}@#{host}")
106
- Net::SCP.new(session).upload!(local_path, path)
107
- end
108
- end
109
-
110
- def make_url_available_to_remote(local_url)
111
- uri = URI(local_url)
112
- if is_local_machine(uri.host)
113
- port, host = forward_port(uri.port, uri.host, uri.port, 'localhost')
114
- if !port
115
- # Try harder if the port is already taken
116
- port, host = forward_port(uri.port, uri.host, 0, 'localhost')
117
- if !port
118
- raise "Error forwarding port: could not forward #{uri.port} or 0"
119
- end
120
- end
121
- uri.host = host
122
- uri.port = port
123
- end
124
- Chef::Log.info("Port forwarded: local URL #{local_url} is available to #{self.host} as #{uri.to_s} for the duration of this SSH connection.")
125
- uri.to_s
126
- end
127
-
128
- def disconnect
129
- if @session
130
- begin
131
- Chef::Log.debug("Closing SSH session on #{username}@#{host}")
132
- @session.close
133
- rescue
134
- ensure
135
- @session = nil
136
- end
137
- end
138
- end
139
-
140
- def available?
141
- # If you can't pwd within 10 seconds, you can't pwd
142
- execute('pwd', :timeout => 10)
143
- true
144
- rescue Timeout::Error, Errno::EHOSTUNREACH, Errno::ENETUNREACH, Errno::EHOSTDOWN, Errno::ETIMEDOUT, Errno::ECONNREFUSED, Errno::ECONNRESET, Net::SSH::Disconnect
145
- Chef::Log.debug("#{username}@#{host} unavailable: network connection failed or broke: #{$!.inspect}")
146
- disconnect
147
- false
148
- rescue Net::SSH::AuthenticationFailed, Net::SSH::HostKeyMismatch
149
- Chef::Log.debug("#{username}@#{host} unavailable: SSH authentication error: #{$!.inspect} ")
150
- disconnect
151
- false
152
- end
153
-
154
- protected
155
-
156
- def session
157
- @session ||= begin
158
- ssh_start_opts = { timeout:10 }.merge(ssh_options)
159
- Chef::Log.debug("Opening SSH connection to #{username}@#{host} with options #{ssh_start_opts.inspect}")
160
- # Small initial connection timeout (10s) to help us fail faster when server is just dead
161
- begin
162
- if gateway? then gateway.ssh(host, username, ssh_start_opts)
163
- else Net::SSH.start(host, username, ssh_start_opts)
164
- end
165
- rescue Timeout::Error
166
- Chef::Log.debug("Timed out connecting to SSH: #{$!}")
167
- raise InitialConnectTimeout.new($!)
168
- end
169
- end
170
- end
171
-
172
- def download(path, local_path)
173
- channel = Net::SCP.new(session).download(path, local_path)
174
- begin
175
- channel.wait
176
- rescue Net::SCP::Error => e
177
- # TODO we need a way to distinguish between "directory of file does not exist" and "SCP did not finish successfully"
178
- nil
179
- # ensure the channel is closed when a rescue happens above
180
- ensure
181
- channel.close
182
- channel.wait
183
- end
184
- nil
185
- end
186
-
187
- class SSHResult
188
- def initialize(command, options, stdout, stderr, exitstatus)
189
- @command = command
190
- @options = options
191
- @stdout = stdout
192
- @stderr = stderr
193
- @exitstatus = exitstatus
194
- end
195
-
196
- attr_reader :command
197
- attr_reader :options
198
- attr_reader :stdout
199
- attr_reader :stderr
200
- attr_reader :exitstatus
201
-
202
- def error!
203
- if exitstatus != 0
204
- # TODO stdout/stderr is already printed at info/debug level. Let's not print it twice, it's a lot.
205
- msg = "Error: command '#{command}' exited with code #{exitstatus}.\n"
206
- raise msg
207
- end
208
- end
209
- end
210
-
211
- class InitialConnectTimeout < Timeout::Error
212
- def initialize(original_error)
213
- super(original_error.message)
214
- @original_error = original_error
215
- end
216
-
217
- attr_reader :original_error
218
- end
219
-
220
- private
221
-
222
- def gateway?
223
- options.key?(:ssh_gateway) and ! options[:ssh_gateway].nil?
224
- end
225
-
226
- def gateway
227
- gw_host, gw_user = options[:ssh_gateway].split('@').reverse
228
- gw_host, gw_port = gw_host.split(':')
229
- gw_user = ssh_options[:ssh_username] unless gw_user
230
-
231
- ssh_start_opts = { timeout:10 }.merge(ssh_options)
232
- ssh_start_opts[:port] = gw_port || 22
233
-
234
- Chef::Log.debug("Opening SSH gateway to #{gw_user}@#{gw_host} with options #{ssh_start_opts.inspect}")
235
- begin
236
- Net::SSH::Gateway.new(gw_host, gw_user, ssh_start_opts)
237
- rescue Errno::ETIMEDOUT
238
- Chef::Log.debug("Timed out connecting to gateway: #{$!}")
239
- raise InitialConnectTimeout.new($!)
240
- end
241
- end
242
-
243
- def is_local_machine(host)
244
- local_addrs = Socket.ip_address_list
245
- host_addrs = Addrinfo.getaddrinfo(host, nil)
246
- local_addrs.any? do |local_addr|
247
- host_addrs.any? do |host_addr|
248
- local_addr.ip_address == host_addr.ip_address
249
- end
250
- end
251
- end
252
-
253
- # Forwards a port over the connection, and returns the
254
- def forward_port(local_port, local_host, remote_port, remote_host)
255
- # This bit is from the documentation.
256
- if session.forward.respond_to?(:active_remote_destinations)
257
- got_remote_port, remote_host = session.forward.active_remote_destinations[[local_port, local_host]]
258
- if !got_remote_port
259
- Chef::Log.debug("Forwarding local server #{local_host}:#{local_port} to #{username}@#{self.host}")
260
-
261
- session.forward.remote(local_port, local_host, remote_port, remote_host) do |actual_remote_port|
262
- got_remote_port = actual_remote_port || :error
263
- :no_exception # I'll take care of it myself, thanks
264
- end
265
- # Kick SSH until we get a response
266
- session.loop { !got_remote_port }
267
- if got_remote_port == :error
268
- return nil
269
- end
270
- end
271
- [ got_remote_port, remote_host ]
272
- else
273
- @forwarded_ports ||= {}
274
- remote_port, remote_host = @forwarded_ports[[local_port, local_host]]
275
- if !remote_port
276
- Chef::Log.debug("Forwarding local server #{local_host}:#{local_port} to #{username}@#{self.host}")
277
- old_active_remotes = session.forward.active_remotes
278
- session.forward.remote(local_port, local_host, local_port)
279
- session.loop { !(session.forward.active_remotes.length > old_active_remotes.length) }
280
- remote_port, remote_host = (session.forward.active_remotes - old_active_remotes).first
281
- @forwarded_ports[[local_port, local_host]] = [ remote_port, remote_host ]
282
- end
283
- [ remote_port, remote_host ]
284
- end
285
- end
286
- end
287
- end
288
- end
1
+ require "chef/provisioning/transport/ssh"