chef-metal 0.14.2 → 0.15

Sign up to get free protection for your applications and to get access to all the features.
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"