chef-metal 0.5 → 0.6

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.
@@ -1,348 +0,0 @@
1
- require 'chef/mixin/shell_out'
2
- require 'chef_metal/provisioner'
3
-
4
- module ChefMetal
5
- class Provisioner
6
-
7
- # Provisions machines in vagrant.
8
- class VagrantProvisioner < Provisioner
9
-
10
- include Chef::Mixin::ShellOut
11
- # Create a new vagrant provisioner.
12
- #
13
- # ## Parameters
14
- # cluster_path - path to the directory containing the vagrant files, which
15
- # should have been created with the vagrant_cluster resource.
16
- def initialize(cluster_path)
17
- @cluster_path = cluster_path
18
- end
19
-
20
- attr_reader :cluster_path
21
-
22
- # Inflate a provisioner from node information; we don't want to force the
23
- # driver to figure out what the provisioner really needs, since it varies
24
- # from provisioner to provisioner.
25
- #
26
- # ## Parameters
27
- # node - node to inflate the provisioner for
28
- #
29
- # returns a VagrantProvisioner
30
- def self.inflate(node)
31
- node_url = node['normal']['provisioner_output']['provisioner_url']
32
- cluster_path = node_url.split(':', 2)[1].sub(/^\/\//, "")
33
- self.new(cluster_path)
34
- end
35
-
36
- # Acquire a machine, generally by provisioning it. Returns a Machine
37
- # object pointing at the machine, allowing useful actions like setup,
38
- # converge, execute, file and directory. The Machine object will have a
39
- # "node" property which must be saved to the server (if it is any
40
- # different from the original node object).
41
- #
42
- # ## Parameters
43
- # action_handler - the action_handler object that is calling this method; this
44
- # is generally a action_handler, but could be anything that can support the
45
- # ChefMetal::ActionHandler interface (i.e., in the case of the test
46
- # kitchen metal driver for acquiring and destroying VMs; see the base
47
- # class for what needs providing).
48
- # node - node object (deserialized json) representing this machine. If
49
- # the node has a provisioner_options hash in it, these will be used
50
- # instead of options provided by the provisioner. TODO compare and
51
- # fail if different?
52
- # node will have node['normal']['provisioner_options'] in it with any options.
53
- # It is a hash with this format:
54
- #
55
- # -- provisioner_url: vagrant:<cluster_path>
56
- # -- vagrant_options: hash of properties of the "config"
57
- # object, i.e. "vm.box" => "ubuntu12" and "vm.box_url"
58
- # -- vagrant_config: string containing other vagrant config.
59
- # Should assume the variable "config" represents machine config.
60
- # Will be written verbatim into the vm's Vagrantfile.
61
- # -- transport_options: hash of options specifying the transport.
62
- # :type => :ssh
63
- # :type => :winrm
64
- # If not specified, ssh is used unless vm.guest is :windows. If that is
65
- # the case, the windows options are used and the port forward for 5985
66
- # is detected.
67
- # -- up_timeout: maximum time, in seconds, to wait for vagrant
68
- # to bring up the machine. Defaults to 10 minutes.
69
- #
70
- # node['normal']['provisioner_output'] will be populated with information
71
- # about the created machine. For vagrant, it is a hash with this
72
- # format:
73
- #
74
- # -- provisioner_url: vagrant_cluster://<current_node>/<cluster_path>
75
- # -- vm_name: name of vagrant vm created
76
- # -- vm_file_path: path to machine-specific vagrant config file
77
- # on disk
78
- # -- forwarded_ports: hash with key as guest_port => host_port
79
- #
80
- def acquire_machine(action_handler, node)
81
- # Set up the modified node data
82
- provisioner_options = node['normal']['provisioner_options']
83
- vm_name = node['name']
84
- old_provisioner_output = node['normal']['provisioner_output']
85
- node['normal']['provisioner_output'] = provisioner_output = {
86
- 'provisioner_url' => provisioner_url(action_handler),
87
- 'vm_name' => vm_name,
88
- 'vm_file_path' => File.join(cluster_path, "#{vm_name}.vm")
89
- }
90
- # Preserve existing forwarded ports
91
- provisioner_output['forwarded_ports'] = old_provisioner_output['forwarded_ports'] if old_provisioner_output
92
-
93
- # TODO compare new options to existing and fail if we cannot change it
94
- # over (perhaps introduce a boolean that will force a delete and recreate
95
- # in such a case)
96
-
97
- # Determine contents of vm file
98
- vm_file_content = "Vagrant.configure('2') do |outer_config|\n"
99
- vm_file_content << " outer_config.vm.define #{vm_name.inspect} do |config|\n"
100
- merged_vagrant_options = { 'vm.hostname' => node['name'] }
101
- merged_vagrant_options.merge!(provisioner_options['vagrant_options']) if provisioner_options['vagrant_options']
102
- merged_vagrant_options.each_pair do |key, value|
103
- vm_file_content << " config.#{key} = #{value.inspect}\n"
104
- end
105
- vm_file_content << provisioner_options['vagrant_config'] if provisioner_options['vagrant_config']
106
- vm_file_content << " end\nend\n"
107
-
108
- # Set up vagrant file
109
- vm_file = ChefMetal.inline_resource(action_handler) do
110
- file provisioner_output['vm_file_path'] do
111
- content vm_file_content
112
- action :create
113
- end
114
- end
115
-
116
- # Check current status of vm
117
- current_status = vagrant_status(vm_name)
118
- up_timeout = provisioner_options['up_timeout'] || 10*60
119
-
120
- if current_status != 'running'
121
- # Run vagrant up if vm is not running
122
- action_handler.perform_action "run vagrant up #{vm_name} (status was '#{current_status}')" do
123
- result = shell_out("vagrant up #{vm_name}", :cwd => cluster_path, :timeout => up_timeout)
124
- if result.exitstatus != 0
125
- raise "vagrant up #{vm_name} failed!\nSTDOUT:#{result.stdout}\nSTDERR:#{result.stderr}"
126
- end
127
- parse_vagrant_up(result.stdout, node)
128
- end
129
- elsif vm_file.updated_by_last_action?
130
- # Run vagrant reload if vm is running and vm file changed
131
- action_handler.perform_action "run vagrant reload #{vm_name}" do
132
- result = shell_out("vagrant reload #{vm_name}", :cwd => cluster_path, :timeout => up_timeout)
133
- if result.exitstatus != 0
134
- raise "vagrant reload #{vm_name} failed!\nSTDOUT:#{result.stdout}\nSTDERR:#{result.stderr}"
135
- end
136
- parse_vagrant_up(result.stdout, node)
137
- end
138
- end
139
-
140
- # Create machine object for callers to use
141
- machine_for(node)
142
- end
143
-
144
- # Connect to machine without acquiring it
145
- def connect_to_machine(node)
146
- machine_for(node)
147
- end
148
-
149
- def delete_machine(action_handler, node)
150
- if node['normal'] && node['normal']['provisioner_output']
151
- provisioner_output = node['normal']['provisioner_output']
152
- else
153
- provisioner_output = {}
154
- end
155
- vm_name = provisioner_output['vm_name'] || node['name']
156
- current_status = vagrant_status(vm_name)
157
- if current_status != 'not created'
158
- action_handler.perform_action "run vagrant destroy -f #{vm_name} (status was '#{current_status}')" do
159
- result = shell_out("vagrant destroy -f #{vm_name}", :cwd => cluster_path)
160
- if result.exitstatus != 0
161
- raise "vagrant destroy failed!\nSTDOUT:#{result.stdout}\nSTDERR:#{result.stderr}"
162
- end
163
- end
164
- end
165
-
166
- convergence_strategy_for(node).cleanup_convergence(action_handler, node)
167
-
168
- vm_file_path = provisioner_output['vm_file_path'] || File.join(cluster_path, "#{vm_name}.vm")
169
- ChefMetal.inline_resource(action_handler) do
170
- file vm_file_path do
171
- action :delete
172
- end
173
- end
174
- end
175
-
176
- def stop_machine(action_handler, node)
177
- if node['normal'] && node['normal']['provisioner_output']
178
- provisioner_output = node['normal']['provisioner_output']
179
- else
180
- provisioner_output = {}
181
- end
182
- vm_name = provisioner_output['vm_name'] || node['name']
183
- current_status = vagrant_status(vm_name)
184
- if current_status == 'running'
185
- action_handler.perform_action "run vagrant halt #{vm_name} (status was '#{current_status}')" do
186
- result = shell_out("vagrant halt #{vm_name}", :cwd => cluster_path)
187
- if result.exitstatus != 0
188
- raise "vagrant halt failed!\nSTDOUT:#{result.stdout}\nSTDERR:#{result.stderr}"
189
- end
190
- end
191
- end
192
- end
193
-
194
-
195
- # Used by vagrant_cluster and machine to get the string used to configure vagrant
196
- def self.vagrant_config_string(vagrant_config, variable, line_prefix)
197
- hostname = name.gsub(/[^A-Za-z0-9\-]/, '-')
198
-
199
- result = ''
200
- vagrant_config.each_pair do |key, value|
201
- result += "#{line_prefix}#{variable}.#{key} = #{value.inspect}\n"
202
- end
203
- result
204
- end
205
-
206
- protected
207
-
208
- def provisioner_url(action_handler)
209
- "vagrant_cluster://#{action_handler.node['name']}#{cluster_path}"
210
- end
211
-
212
- def parse_vagrant_up(output, node)
213
- # Grab forwarded port info
214
- in_forwarding_ports = false
215
- output.lines.each do |line|
216
- if in_forwarding_ports
217
- if line =~ /-- (\d+) => (\d+)/
218
- node['normal']['provisioner_output']['forwarded_ports'][$1] = $2
219
- else
220
- in_forwarding_ports = false
221
- end
222
- elsif line =~ /Forwarding ports...$/
223
- node['normal']['provisioner_output']['forwarded_ports'] = {}
224
- in_forwarding_ports = true
225
- end
226
- end
227
- end
228
-
229
- def machine_for(node)
230
- if vagrant_option(node, 'vm.guest').to_s == 'windows'
231
- require 'chef_metal/machine/windows_machine'
232
- ChefMetal::Machine::WindowsMachine.new(node, transport_for(node), convergence_strategy_for(node))
233
- else
234
- require 'chef_metal/machine/unix_machine'
235
- ChefMetal::Machine::UnixMachine.new(node, transport_for(node), convergence_strategy_for(node))
236
- end
237
- end
238
-
239
- def convergence_strategy_for(node)
240
- if vagrant_option(node, 'vm.guest').to_s == 'windows'
241
- @windows_convergence_strategy ||= begin
242
- require 'chef_metal/convergence_strategy/install_msi'
243
- ChefMetal::ConvergenceStrategy::InstallMsi.new
244
- end
245
- else
246
- @unix_convergence_strategy ||= begin
247
- require 'chef_metal/convergence_strategy/install_cached'
248
- ChefMetal::ConvergenceStrategy::InstallCached.new
249
- end
250
- end
251
- end
252
-
253
- def transport_for(node)
254
- if vagrant_option(node, 'vm.guest').to_s == 'windows'
255
- create_winrm_transport(node)
256
- else
257
- create_ssh_transport(node)
258
- end
259
- end
260
-
261
- def vagrant_option(node, option)
262
- if node['normal']['provisioner_options'] &&
263
- node['normal']['provisioner_options']['vagrant_options']
264
- node['normal']['provisioner_options']['vagrant_options'][option]
265
- else
266
- nil
267
- end
268
- end
269
-
270
- def vagrant_status(name)
271
- status_output = shell_out("vagrant status #{name}", :cwd => cluster_path).stdout
272
- if status_output =~ /^#{name}\s+([^\n]+)\s+\(([^\n]+)\)$/m
273
- $1
274
- else
275
- 'not created'
276
- end
277
- end
278
-
279
- def create_winrm_transport(node)
280
- require 'chef_metal/transport/winrm'
281
-
282
- provisioner_output = node['default']['provisioner_output'] || {}
283
- forwarded_ports = provisioner_output['forwarded_ports'] || {}
284
-
285
- # TODO IPv6 loopback? What do we do for that?
286
- hostname = vagrant_option(node, 'winrm.host') || '127.0.0.1'
287
- port = vagrant_option(node, 'winrm.port') || forwarded_ports[5985] || 5985
288
- endpoint = "http://#{hostname}:#{port}/wsman"
289
- type = :plaintext
290
- options = {
291
- :user => vagrant_option(node, 'winrm.username') || 'vagrant',
292
- :pass => vagrant_option(node, 'winrm.password') || 'vagrant',
293
- :disable_sspi => true
294
- }
295
-
296
- ChefMetal::Transport::WinRM.new(endpoint, type, options)
297
- end
298
-
299
- def create_ssh_transport(node)
300
- require 'chef_metal/transport/ssh'
301
-
302
- vagrant_ssh_config = vagrant_ssh_config_for(node)
303
- hostname = vagrant_ssh_config['HostName']
304
- username = vagrant_ssh_config['User']
305
- ssh_options = {
306
- :port => vagrant_ssh_config['Port'],
307
- :auth_methods => ['publickey'],
308
- :user_known_hosts_file => vagrant_ssh_config['UserKnownHostsFile'],
309
- :paranoid => yes_or_no(vagrant_ssh_config['StrictHostKeyChecking']),
310
- :keys => [ strip_quotes(vagrant_ssh_config['IdentityFile']) ],
311
- :keys_only => yes_or_no(vagrant_ssh_config['IdentitiesOnly'])
312
- }
313
- ssh_options[:auth_methods] = %w(password) if yes_or_no(vagrant_ssh_config['PasswordAuthentication'])
314
- options = {
315
- :prefix => 'sudo '
316
- }
317
- ChefMetal::Transport::SSH.new(hostname, username, ssh_options, options)
318
- end
319
-
320
- def vagrant_ssh_config_for(node)
321
- vagrant_ssh_config = {}
322
- result = shell_out("vagrant ssh-config #{node['normal']['provisioner_output']['vm_name']}", :cwd => cluster_path)
323
- result.stdout.lines.inject({}) do |result, line|
324
- line =~ /^\s*(\S+)\s+(.+)/
325
- vagrant_ssh_config[$1] = $2
326
- end
327
- vagrant_ssh_config
328
- end
329
-
330
- def yes_or_no(str)
331
- case str
332
- when 'yes'
333
- true
334
- else
335
- false
336
- end
337
- end
338
-
339
- def strip_quotes(str)
340
- if str[0] == '"' && str[-1] == '"' && str.size >= 2
341
- str[1..-2]
342
- else
343
- str
344
- end
345
- end
346
- end
347
- end
348
- end
@@ -1,4 +0,0 @@
1
- require 'chef_metal/provisioner/fog_provisioner'
2
-
3
- ChefMetal.add_registered_provisioner_class("fog",
4
- ChefMetal::Provisioner::FogProvisioner)
@@ -1,4 +0,0 @@
1
- require 'chef_metal/provisioner/vagrant_provisioner'
2
-
3
- ChefMetal.add_registered_provisioner_class("vagrant_cluster",
4
- ChefMetal::Provisioner::VagrantProvisioner)
@@ -1,39 +0,0 @@
1
- require 'chef_metal'
2
- require 'chef/resource/vagrant_cluster'
3
- require 'chef/provider/vagrant_cluster'
4
- require 'chef/resource/vagrant_box'
5
- require 'chef/provider/vagrant_box'
6
- require 'chef_metal/provisioner/vagrant_provisioner'
7
-
8
- module ChefMetal
9
- def self.with_vagrant_cluster(cluster_path, &block)
10
- with_provisioner(ChefMetal::Provisioner::VagrantProvisioner.new(cluster_path), &block)
11
- end
12
-
13
- def self.with_vagrant_box(box_name, provisioner_options = nil, &block)
14
- if box_name.is_a?(Chef::Resource::VagrantBox)
15
- provisioner_options ||= box_name.provisioner_options || {}
16
- provisioner_options['vagrant_options'] ||= {}
17
- provisioner_options['vagrant_options']['vm.box'] = box_name.name
18
- provisioner_options['vagrant_options']['vm.box_url'] = box_name.url if box_name.url
19
- else
20
- provisioner_options ||= {}
21
- provisioner_options['vagrant_options'] ||= {}
22
- provisioner_options['vagrant_options']['vm.box'] = box_name
23
- end
24
-
25
- with_provisioner_options(provisioner_options, &block)
26
- end
27
- end
28
-
29
- class Chef
30
- class Recipe
31
- def with_vagrant_cluster(cluster_path, &block)
32
- ChefMetal.with_vagrant_cluster(cluster_path, &block)
33
- end
34
-
35
- def with_vagrant_box(box_name, vagrant_options = {}, &block)
36
- ChefMetal.with_vagrant_box(box_name, vagrant_options, &block)
37
- end
38
- end
39
- end