chef-metal 0.5 → 0.6

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