chef-metal-vsphere 0.2.0 → 0.3.0.beta.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b8ea8bc26ba359b811ea19cfa5876b688437362b
4
- data.tar.gz: 3e37abdd15aed60c1b5ff549ee55b06aaaaac116
3
+ metadata.gz: 1ab4cff7dadfa8f7cd8fdd608877d2fa4157542f
4
+ data.tar.gz: 48ec7ddcc3607dd222c59ab81dd7574255d60701
5
5
  SHA512:
6
- metadata.gz: 5c67f07c76d42ba8804f369651b7ba88a60d9ac14584cd79ebfc8df25138597606872a9150fb8edd3a6cf055162d4937baf629797f204cd98009885f75d9af47
7
- data.tar.gz: cd75aac3d3273599f7499e4603155d6cdea53b4135d047c20fa07f9188b36854eb4ad9b93096f35d5d4b4ee18a3ab9e508f808aa8d7e1235399dbec24c5a4eeb
6
+ metadata.gz: cb74dc9033f4309fbab2f639a44cf82bb48d4182d5344356a2577e27495a35d81ae8d38f687e606987566a357e44bd7566b41c8a5a4e48da4a628ad7574846e5
7
+ data.tar.gz: 5894d2d4ee3ff44ac50971e4f78c462d55c5cc3e5dce0ba344d01e59d6f7d8622afc10b4f3ce0d950c50a357ea8dd5b24561d210fac261fa92b3e61c84b92e9c
data/.gitignore ADDED
@@ -0,0 +1,27 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.bundle
19
+ *.so
20
+ *.o
21
+ *.a
22
+ mkmf.log
23
+ .vagrant
24
+ **/local-mode-cache
25
+ test/nodes
26
+ test/clients
27
+ .vmonkey
data/CHANGELOG.md ADDED
@@ -0,0 +1,6 @@
1
+ # Changelog
2
+
3
+ ## 0.3.0 (6/15/2014)
4
+
5
+ - from scratch for chef-metal 0.11
6
+ - uses vmonkey gem for vsphere interactions
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
@@ -1,4 +1,6 @@
1
- Copyright (c) 2014 Rally Software Development Corp
1
+ Copyright (c) 2014 Brian Dupras
2
+
3
+ MIT License
2
4
 
3
5
  Permission is hereby granted, free of charge, to any person obtaining
4
6
  a copy of this software and associated documentation files (the
@@ -17,4 +19,4 @@ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
19
  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
20
  LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
21
  OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
- WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md CHANGED
@@ -17,51 +17,40 @@ Create or obtain a unix/linux VM template. The VM template must:
17
17
  - provide access via ssh
18
18
  - provide a user account with NOPASSWD sudo
19
19
 
20
+
21
+ ### vSphere Credentials
22
+ Create a file called $HOME/.vmonkey. chef-metal-vsphere uses [vmonkey](https://github.com/vmtricks/vmonkey) to connect to vSphere.
23
+
24
+ host: vcenter_host_name
25
+ user: vcenter_user_name
26
+ password: your_mothers_maiden_name
27
+ datacenter: datacenter_name
28
+ cluster: cluster_name
29
+ insecure: true
30
+ ssl: true
31
+
32
+
20
33
  ### Example recipe
21
34
  require 'chef_metal_vsphere'
22
-
23
- with_vsphere_provisioner vsphere_host: 'vcenter-host-name',
24
- vsphere_insecure: true,
25
- vsphere_user: 'you_user_name',
26
- vsphere_password: 'your_mothers_maiden_name' # consider using a chef-vault
27
-
28
- with_provisioner_options('bootstrap_options' => {
29
- datacenter: 'datacenter_name',
30
- cluster: 'cluster_name',
31
- resource_pool: 'resource_pool_name', # often the same as the cluster_name
32
- datastore: 'datastore_name',
33
- template_name: 'name_of_template_vm', # may be a VM or a VM Template
34
- template_folder: 'folder_containing_template_vm',
35
- vm_folder: 'folder_to_clone_vms_into',
36
- customization_spec: 'standard-config', # optional
37
-
38
- ssh: { # net-ssh start() options
39
- user: 'username_on_vm', # must have nopasswd sudo
40
- password: 'name_of_your_first_pet', # consider using a chef-vault
35
+ with_driver 'vsphere'
36
+
37
+ with_machine_options({
38
+ bootstrap_options: {
39
+ template: '/path/to/a/vm/template', # vCenter "VMs and Templates" path to a VM Template
40
+ folder: '/path/to/a/folder/to/place/new/vms' # vCenter "VMs and Templates" path to a Folder. New VMs are created in this folder.
41
+ },
42
+ ssh_options: {
43
+ user: 'root', # root or a user with ssh access and NOPASSWD sudo on a VM cloned from the template
44
+ password: 'your_first_pet', # consisder using chef-vault
41
45
  port: 22,
42
- auth_methods: ['password'],
43
- user_known_hosts_file: '/dev/null', # don't do this in production
44
- paranoid: false, # don't do this in production, either
45
- keys: [ ], # consider using a chef-vault
46
- keys_only: false
47
- }
48
- })
46
+ user_known_hosts_file: '/dev/null', # don't do this in production
47
+ paranoid: false # don't do this in production, either
48
+ }
49
+ })
49
50
 
50
51
  1.upto 2 do |n|
51
52
  machine "metal_#{n}" do
52
53
  action [:create]
53
-
54
- ## optionally add options per-machine customizations
55
- add_provisioner_options('bootstrap_options' => {
56
- num_cpus: n,
57
- memory_mb: 1024 * n,
58
- annotation: "metal_#{n} created by chef-metal-vsphere"
59
- })
60
- end
61
-
62
- machine_file "/tmp/metal_#{n}.txt" do
63
- machine "metal_#{n}"
64
- content "Hello machine #{n}!"
65
54
  end
66
55
 
67
56
  machine "metal_#{n}" do
@@ -75,7 +64,7 @@ Create or obtain a unix/linux VM template. The VM template must:
75
64
 
76
65
  end
77
66
 
78
- This will clone your VM template to create two VMware Virtual Machines, "metal_1" and "metal_2", in the vSphere Folder specified by vm_folder, bootstrapped to an empty runlist. It will then stop (guest shutdown) and delete the vms.
67
+ This will clone your VM template to create two VMware Virtual Machines, "metal_1" and "metal_2", in the vSphere Folder specified by bootstrap_options => folder, bootstrapped to an empty runlist. It will then stop (guest shutdown) and delete the VMs.
79
68
 
80
69
  Roadmap
81
70
  -------
@@ -85,6 +74,4 @@ Check out [TODO.md](TODO.md)
85
74
  Bugs and Contact
86
75
  ----------------
87
76
 
88
- Please submit bugs at [chef-metal-vpshere](https://github.com/RallySoftware-cookbooks/chef-metal-vsphere), contact Brian Dupras on Twitter at @briandupras, email at rallysoftware-cookbooks@rallydev.com.
89
-
90
- *Warning* if you get an rbvmomi error regarding VMODL::AnyType, add `gem 'nokogiri', '1.5.5'` to your dependencies.
77
+ Please submit bugs at [chef-metal-vpshere](https://github.com/vmtricks/chef-metal-vsphere), contact Brian Dupras on Twitter at @briandupras, email at brian@duprasville.com.
data/Rakefile CHANGED
@@ -1,30 +1,39 @@
1
- require 'bundler'
2
1
  require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+ require 'mixlib/shellout'
3
4
 
4
- def gem_server
5
- @gem_server ||= ENV['GEM_SERVER'] || 'rubygems.org'
5
+ def shell_out(*command_args)
6
+ cmd = Mixlib::ShellOut.new(command_args)
7
+ cmd.live_stream = STDOUT
8
+ cmd.run_command
9
+ cmd
6
10
  end
7
11
 
8
- module Bundler
9
- class GemHelper
10
- unless gem_server == 'rubygems.org'
11
- unless method_defined?(:rubygem_push)
12
- raise NoMethodError, "Monkey patching Bundler::GemHelper#rubygem_push failed: did the Bundler API change???"
13
- end
14
-
15
- def rubygem_push(path)
16
- print "Username: "
17
- username = STDIN.gets.chomp
18
- print "Password: "
19
- password = STDIN.gets.chomp
12
+ def shell_out!(*command_args)
13
+ cmd = shell_out(*command_args)
14
+ cmd.error!
15
+ cmd
16
+ end
20
17
 
21
- gem_server_url = "https://#{username}:#{password}@#{gem_server}/"
22
- sh %{gem push #{path} --host #{gem_server_url}}
18
+ namespace :test do
19
+ desc 'Run test cookbook in vSphere'
20
+ task :vsphere do |t|
21
+ shell_out! %Q{(cd test && chef-client -z -o test::vsphere,test::simple)}
22
+ end
23
23
 
24
- Bundler.ui.confirm "Pushed #{name} #{version} to #{gem_server}"
25
- end
24
+ desc 'Run test cookbook in vagrant'
25
+ task :vagrant do |t|
26
+ shell_out! %Q{(cd test && chef-client -z -o test::vagrant,test::simple)}
27
+ end
26
28
 
27
- puts "Monkey patched Bundler::GemHelper#rubygem_push to push to #{gem_server}."
28
- end
29
+ desc 'Destroy test machines'
30
+ task :clean do |t|
31
+ shell_out! %Q{(cd test && chef-client -z -o test::vsphere,test::destroy_all)}
29
32
  end
30
- end
33
+ end
34
+
35
+ desc 'Run RSpec specs'
36
+ RSpec::Core::RakeTask.new(:spec)
37
+
38
+ desc 'Run all tests using vSphere'
39
+ task test: [:spec, 'test:vsphere']
data/TODO.md ADDED
@@ -0,0 +1,41 @@
1
+ To Do Items
2
+ ===========
3
+
4
+ Clone features
5
+ --------------
6
+
7
+ :bootstrap_options
8
+ :num_cpus
9
+ :memory_mb
10
+ :annotation
11
+
12
+ :datastore_cluster
13
+ :linked_clone
14
+
15
+ :customizations
16
+ :vlan
17
+ :ips
18
+ :dns_ips
19
+ :dns_suffixes
20
+ :gw
21
+ :hostname
22
+ :domain
23
+ :tz
24
+
25
+ vApp features
26
+ -------------
27
+
28
+ - create/start/stop/delete vApps in recipes
29
+ - clone from existing vApp
30
+ - replace machines in vApp
31
+
32
+ VM features
33
+ -----------
34
+
35
+ - markAsTemplate
36
+
37
+ Usability
38
+ ---------
39
+
40
+ - create example cookbooks
41
+ - with test-kitchen tests
@@ -0,0 +1,34 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'chef_metal_vsphere/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'chef-metal-vsphere'
8
+ spec.version = ChefMetalVsphere::VERSION
9
+ spec.platform = Gem::Platform::RUBY
10
+ spec.authors = ['Brian Dupras']
11
+ spec.email = ['brian@duprasville.com']
12
+ spec.summary = %q{chef-metal vSphere driver}
13
+ spec.description = spec.summary
14
+ spec.homepage = 'https://github.com/vmtricks/chef-metal-vsphere'
15
+ spec.license = 'MIT'
16
+
17
+ spec.require_path = 'lib'
18
+ spec.files = `git ls-files -z`.split("\x0")
19
+ spec.extra_rdoc_files = ['README.md', 'LICENSE.txt' ]
20
+ spec.bindir = 'bin'
21
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
22
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
23
+ spec.require_paths = ['lib']
24
+
25
+ spec.add_dependency 'chef', '~> 11.12'
26
+ spec.add_dependency 'chef-metal', '~> 0.11'
27
+ spec.add_dependency 'vmonkey', '~> 0.4'
28
+
29
+ spec.add_development_dependency 'bundler', '~> 1.6'
30
+ spec.add_development_dependency 'rspec', '~> 3.0'
31
+ spec.add_development_dependency 'rake', '~> 10.3'
32
+ spec.add_development_dependency 'mixlib-shellout', '~> 1.4.0'
33
+ spec.add_development_dependency 'chef-metal-vagrant', '~> 0.4'
34
+ end
@@ -0,0 +1,3 @@
1
+ require 'chef_metal_vsphere/vsphere_driver'
2
+
3
+ ChefMetal.register_driver_class('vsphere', ChefMetalVsphere::VsphereDriver)
@@ -1,12 +1,2 @@
1
1
  require 'chef_metal'
2
- require 'chef_metal_vsphere/vsphere_provisioner'
3
-
4
- class Chef
5
- module DSL
6
- module Recipe
7
- def with_vsphere_provisioner(options = {}, &block)
8
- run_context.chef_metal.with_provisioner(ChefMetalVsphere::VsphereProvisioner.new(options), &block)
9
- end
10
- end
11
- end
12
- end
2
+ require 'chef_metal_vsphere/vsphere_driver'
@@ -1,3 +1,3 @@
1
1
  module ChefMetalVsphere
2
- VERSION = '0.2.0'
2
+ VERSION = '0.3.0.beta.1'
3
3
  end
@@ -0,0 +1,325 @@
1
+ require 'chef_metal/driver'
2
+ require 'chef_metal/machine/windows_machine'
3
+ require 'chef_metal/machine/unix_machine'
4
+ require 'chef_metal/machine_spec'
5
+ require 'chef_metal/convergence_strategy/install_msi'
6
+ require 'chef_metal/convergence_strategy/install_sh'
7
+ require 'chef_metal/convergence_strategy/install_cached'
8
+ require 'chef_metal/convergence_strategy/no_converge'
9
+ require 'chef_metal/transport/ssh'
10
+ require 'chef_metal/transport/winrm'
11
+ require 'chef_metal_vsphere/version'
12
+ require 'vmonkey'
13
+
14
+ module ChefMetalVsphere
15
+ # Provisions machines in VMware vSphere.
16
+ class VsphereDriver < ChefMetal::Driver
17
+ DEFAULT_OPTIONS = {
18
+ :create_timeout => 300,
19
+ :start_timeout => 180,
20
+ :ssh_timeout => 20
21
+ }
22
+
23
+ def self.from_url(url, config)
24
+ VsphereDriver.new(url, config)
25
+ end
26
+
27
+ def self.canonicalize_url(driver_url, config)
28
+ _, host, datacenter, cluster = driver_url.split(':', 4)
29
+ vmonkey_opts = VMonkey.default_opts
30
+
31
+ host ||= vmonkey_opts[:host]
32
+ datacenter ||= vmonkey_opts[:datacenter]
33
+ cluster ||= vmonkey_opts[:cluster]
34
+
35
+ [ "vsphere:#{host}:#{datacenter}:#{cluster}", config ]
36
+ end
37
+
38
+ def initialize(url, config)
39
+ super(url, config)
40
+ end
41
+
42
+ def allocate_machine(action_handler, machine_spec, machine_options)
43
+ create_vm(action_handler, machine_spec, machine_options)
44
+ end
45
+
46
+ def ready_machine(action_handler, machine_spec, machine_options)
47
+ vm = vm_for(machine_spec)
48
+
49
+ if vm.nil?
50
+ raise "Machine #{machine_spec.name} does not have a vm associated with it, or vm does not exist."
51
+ end
52
+
53
+ # Start the vm if needed, and wait for it to start
54
+ start_vm(action_handler, machine_spec, vm)
55
+ wait_until_ready(action_handler, machine_spec, machine_options, vm)
56
+ wait_for_transport(action_handler, machine_spec, machine_options, vm)
57
+
58
+ machine_for(machine_spec, machine_options, vm)
59
+ end
60
+
61
+ def connect_to_machine(machine_spec, machine_options)
62
+ machine_for(machine_spec, machine_options)
63
+ end
64
+
65
+ def destroy_machine(action_handler, machine_spec, machine_options)
66
+ vm = vm_for(machine_spec)
67
+ if vm
68
+ action_handler.perform_action "destroy machine #{machine_spec.name} (#{machine_spec.location['instance_uuid']} at #{driver_url})" do
69
+ vm.destroy
70
+ machine_spec.location = nil
71
+ end
72
+ end
73
+ strategy = convergence_strategy_for(machine_spec, machine_options)
74
+ strategy.cleanup_convergence(action_handler, machine_spec)
75
+ end
76
+
77
+ def transport_for(machine_spec, machine_options, vm)
78
+ # TODO winrm
79
+ create_ssh_transport(machine_spec, machine_options, vm)
80
+ end
81
+
82
+ protected
83
+ def option_for(machine_options, key)
84
+ machine_options[key] || DEFAULT_OPTIONS[key]
85
+ end
86
+
87
+ def monkey
88
+ ## TODO - test @vim and reconnect on error - idle sessions get silently timed-out by vSphere :P
89
+ @vim ||= VMonkey.connect
90
+ end
91
+
92
+ def create_vm(action_handler, machine_spec, machine_options)
93
+ if machine_spec.location
94
+ if machine_spec.location['driver_url'] != driver_url
95
+ raise "Switching a machine's driver from #{machine_spec.location['driver_url']} to #{driver_url} for is not currently supported! Use machine :destroy and then re-create the machine on the new driver."
96
+ end
97
+
98
+ vm = vm_for!(machine_spec)
99
+ if vm
100
+ return vm
101
+ else
102
+ Chef::Log.warn "Machine #{machine_spec.name} (#{machine_spec.location['instance_uuid']} on #{driver_url}) no longer exists. Recreating ..."
103
+ end
104
+ end
105
+
106
+ bootstrap_options = bootstrap_options_for(action_handler, machine_spec, machine_options)
107
+
108
+ description = [ "creating machine #{machine_spec.name} on #{driver_url}" ]
109
+ bootstrap_options.each_pair { |key,value| description << " #{key}: #{value.inspect}" }
110
+ action_handler.report_progress description
111
+ vm = nil
112
+ if action_handler.should_perform_actions
113
+ template = monkey.vm! bootstrap_options[:template]
114
+ vm_path = "#{bootstrap_options[:folder]}/#{machine_spec.name}"
115
+ vm = template.clone_to vm_path
116
+
117
+ machine_spec.location = {
118
+ 'driver_url' => driver_url,
119
+ 'driver_version' => ChefMetalVsphere::VERSION,
120
+ 'cloned_from' => bootstrap_options[:template],
121
+ 'path' => vm_path,
122
+ 'instance_uuid' => vm.config.instanceUuid,
123
+ 'allocated_at' => Time.now.to_i
124
+ }
125
+ machine_spec.location['key_name'] = bootstrap_options[:key_name] if bootstrap_options[:key_name]
126
+ %w(is_windows ssh_username sudo ssh_gateway).each do |key|
127
+ machine_spec.location[key] = machine_options[key.to_sym] if machine_options[key.to_sym]
128
+ end
129
+
130
+ action_handler.performed_action "machine #{machine_spec.name} created as #{vm.config.instanceUuid} on #{driver_url}"
131
+ end
132
+ vm
133
+ end
134
+
135
+ def start_vm(action_handler, machine_spec, vm)
136
+ unless vm.started?
137
+ action_handler.perform_action "start machine #{machine_spec.name} (#{vm.config.instanceUuid} on #{driver_url})" do
138
+ vm.start
139
+ machine_spec.location['started_at'] = Time.now.to_i
140
+ end
141
+ machine_spec.save(action_handler)
142
+ end
143
+ end
144
+
145
+ def remaining_wait_time(machine_spec, machine_options)
146
+ if machine_spec.location['started_at']
147
+ timeout = option_for(machine_options, :start_timeout) - (Time.now.utc - parse_time(machine_spec.location['started_at']))
148
+ else
149
+ timeout = option_for(machine_options, :create_timeout) - (Time.now.utc - parse_time(machine_spec.location['allocated_at']))
150
+ end
151
+ timeout > 0 ? timeout : 0.01
152
+ end
153
+
154
+ def parse_time(value)
155
+ if value.is_a?(String)
156
+ Time.parse(value)
157
+ else
158
+ Time.at(value)
159
+ end
160
+ end
161
+
162
+ def wait_until_ready(action_handler, machine_spec, machine_options, vm)
163
+ unless vm.ready?
164
+ if action_handler.should_perform_actions
165
+ action_handler.report_progress "waiting for #{machine_spec.name} (#{vm.config.instanceUuid} on #{driver_url}) to be ready ..."
166
+ vm.wait_for(remaining_wait_time(machine_spec, machine_options)) { vm.ready? }
167
+ action_handler.report_progress "#{machine_spec.name} is now ready"
168
+ end
169
+ end
170
+ end
171
+
172
+ def wait_for_transport(action_handler, machine_spec, machine_options, vm)
173
+ transport = transport_for(machine_spec, machine_options, vm)
174
+ if !transport.available?
175
+ if action_handler.should_perform_actions
176
+ action_handler.report_progress "waiting for #{machine_spec.name} (#{vm.config.instanceUuid} on #{driver_url}) to be connectable (transport up and running) ..."
177
+
178
+ _self = self
179
+
180
+ vm.wait_for(remaining_wait_time(machine_spec, machine_options)) do
181
+ transport.available?
182
+ end
183
+ action_handler.report_progress "#{machine_spec.name} is now connectable"
184
+ end
185
+ end
186
+ end
187
+
188
+ def symbolize_keys(options)
189
+ options.inject({}) do |result,(key,value)|
190
+ result[key.to_sym] = value
191
+ result
192
+ end
193
+ end
194
+
195
+ def vm_for(machine_spec)
196
+ if machine_spec.location
197
+ monkey.vm_by_instance_uuid machine_spec.location['instance_uuid']
198
+ else
199
+ nil
200
+ end
201
+ end
202
+
203
+ def vm_for!(machine_spec)
204
+ monkey.vm_by_instance_uuid! machine_spec.location['instance_uuid']
205
+ end
206
+
207
+ def bootstrap_options_for(action_handler, machine_spec, machine_options)
208
+ bootstrap_options = symbolize_keys(machine_options[:bootstrap_options] || {})
209
+
210
+ bootstrap_options[:tags] = default_tags(machine_spec, bootstrap_options[:tags] || {})
211
+
212
+ bootstrap_options[:name] ||= machine_spec.name
213
+
214
+ bootstrap_options
215
+ end
216
+
217
+ def default_tags(machine_spec, bootstrap_tags = {})
218
+ tags = {
219
+ 'Name' => machine_spec.name,
220
+ 'BootstrapId' => machine_spec.id,
221
+ 'BootstrapHost' => Socket.gethostname,
222
+ 'BootstrapUser' => Etc.getlogin
223
+ }
224
+ # User-defined tags override the ones we set
225
+ tags.merge(bootstrap_tags)
226
+ end
227
+
228
+ def machine_for(machine_spec, machine_options, vm = nil)
229
+ vm ||= vm_for(machine_spec)
230
+ if !vm
231
+ raise "VM for node #{machine_spec.name} has not been created!"
232
+ end
233
+
234
+ if machine_spec.location['is_windows']
235
+ ChefMetal::Machine::WindowsMachine.new(machine_spec, transport_for(machine_spec, machine_options, vm), convergence_strategy_for(machine_spec, machine_options))
236
+ else
237
+ ChefMetal::Machine::UnixMachine.new(machine_spec, transport_for(machine_spec, machine_options, vm), convergence_strategy_for(machine_spec, machine_options))
238
+ end
239
+ end
240
+
241
+ def convergence_strategy_for(machine_spec, machine_options)
242
+ # Defaults
243
+ if !machine_spec.location
244
+ return ChefMetal::ConvergenceStrategy::NoConverge.new(machine_options[:convergence_options], config)
245
+ end
246
+
247
+ if machine_spec.location['is_windows']
248
+ ChefMetal::ConvergenceStrategy::InstallMsi.new(machine_options[:convergence_options], config)
249
+ elsif machine_options[:cached_installer] == true
250
+ ChefMetal::ConvergenceStrategy::InstallCached.new(machine_options[:convergence_options], config)
251
+ else
252
+ ChefMetal::ConvergenceStrategy::InstallSh.new(machine_options[:convergence_options], config)
253
+ end
254
+ end
255
+
256
+ def ssh_options_for(machine_spec, machine_options, vm)
257
+ result = {
258
+ :auth_methods => [ 'publickey' ],
259
+ :keys_only => true,
260
+ :host_key_alias => vm.config.instanceUuid
261
+ }.merge(machine_options[:ssh_options] || {})
262
+ if vm.respond_to?(:private_key) && vm.private_key
263
+ result[:key_data] = [ vm.private_key ]
264
+ elsif vm.respond_to?(:key_name) && vm.key_name
265
+ key = get_private_key(vm.key_name)
266
+ if !key
267
+ raise "VM has key name '#{vm.key_name}', but the corresponding private key was not found locally. Check if the key is in Chef::Config.private_key_paths: #{Chef::Config.private_key_paths.join(', ')}"
268
+ end
269
+ result[:key_data] = [ key ]
270
+ elsif machine_spec.location['key_name']
271
+ key = get_private_key(machine_spec.location['key_name'])
272
+ if !key
273
+ raise "VM was created with key name '#{machine_spec.location['key_name']}', but the corresponding private key was not found locally. Check if the key is in Chef::Config.private_key_paths: #{Chef::Config.private_key_paths.join(', ')}"
274
+ end
275
+ result[:key_data] = [ key ]
276
+ elsif machine_options[:bootstrap_options] && machine_options[:bootstrap_options][:key_path]
277
+ result[:key_data] = [ IO.read(machine_options[:bootstrap_options][:key_path]) ]
278
+ elsif machine_options[:bootstrap_options] && machine_options[:bootstrap_options][:key_name]
279
+ result[:key_data] = [ get_private_key(machine_options[:bootstrap_options][:key_name]) ]
280
+ elsif machine_options[:ssh_options] && machine_options[:ssh_options][:password]
281
+ result[:password] = machine_options[:ssh_options][:password]
282
+ result[:auth_methods] = [ 'password' ]
283
+ result[:keys] = [ ]
284
+ result[:keys_only] = false
285
+ else
286
+ # TODO make a way to suggest other keys to try ...
287
+ raise "No key or password found to connect to #{machine_spec.name} (#{machine_spec.location.inspect})! machine_options #{machine_options.inspect}"
288
+ end
289
+ result
290
+ end
291
+
292
+ def default_ssh_username(machine_options)
293
+ if machine_options[:ssh_options] && machine_options[:ssh_options][:user]
294
+ machine_options[:ssh_options][:user]
295
+ else
296
+ 'root'
297
+ end
298
+ end
299
+
300
+ def create_ssh_transport(machine_spec, machine_options, vm)
301
+ ssh_options = ssh_options_for(machine_spec, machine_options, vm)
302
+ username = machine_spec.location['ssh_username'] || default_ssh_username(machine_options)
303
+ if machine_options.has_key?(:ssh_username) && machine_options[:ssh_username] != machine_spec.location['ssh_username']
304
+ Chef::Log.warn("VM #{machine_spec.name} was created with SSH username #{machine_spec.location['ssh_username']} and machine_options specifies username #{machine_options[:ssh_username]}. Using #{machine_spec.location['ssh_username']}. Please edit the node and change the metal.location.ssh_username attribute if you want to change it.")
305
+ end
306
+ options = {}
307
+ if machine_spec.location[:sudo] || (!machine_spec.location.has_key?(:sudo) && username != 'root')
308
+ options[:prefix] = 'sudo '
309
+ end
310
+
311
+ if vm.guest_ip.nil?
312
+ raise "VM #{machine_spec.name} (#{vm.config.instanceUuid} has no IP address!"
313
+ end
314
+
315
+ #Enable pty by default
316
+ options[:ssh_pty_enable] = true
317
+ options[:ssh_gateway] = machine_spec.location['ssh_gateway'] if machine_spec.location.has_key?('ssh_gateway')
318
+
319
+ ChefMetal::Transport::SSH.new(vm.guest_ip, username, ssh_options, options, config)
320
+ end
321
+
322
+ end
323
+ end
324
+
325
+