knife-wsfusion 0.1.0
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.
- data/lib/chef/knife/base_command.rb +207 -0
- data/lib/chef/knife/wsfusion_create.rb +202 -0
- data/lib/knife-wsfusion/version.rb +3 -0
- metadata +63 -0
@@ -0,0 +1,207 @@
|
|
1
|
+
require 'open3'
|
2
|
+
require 'chef/knife'
|
3
|
+
|
4
|
+
|
5
|
+
module KnifeWsFusion
|
6
|
+
class BaseCommand < Chef::Knife
|
7
|
+
@@fusion_search_paths = [
|
8
|
+
File.join('/', 'Applications', 'VMware Fusion Tech Preview.app'),
|
9
|
+
File.join('/', 'Applications', 'VMware Fusion.app'),
|
10
|
+
]
|
11
|
+
|
12
|
+
def is_fusion?
|
13
|
+
@@fusion_search_paths.each do |path|
|
14
|
+
if File.directory?(path)
|
15
|
+
return true
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
return false
|
20
|
+
end
|
21
|
+
|
22
|
+
def get_vmrun_path
|
23
|
+
if config[:vmrun_path]
|
24
|
+
return config[:vmrun_path]
|
25
|
+
end
|
26
|
+
|
27
|
+
path = get_exe_from_path('vmrun')
|
28
|
+
|
29
|
+
if path
|
30
|
+
return path
|
31
|
+
end
|
32
|
+
|
33
|
+
if is_fusion?
|
34
|
+
@@fusion_search_paths.each do |search_path|
|
35
|
+
vmrun_path = File.join(search_path, 'Contents', 'Library',
|
36
|
+
'vmrun')
|
37
|
+
|
38
|
+
if File.executable?(vmrun_path)
|
39
|
+
return vmrun_path
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
return nil
|
45
|
+
end
|
46
|
+
|
47
|
+
def get_exe_from_path(exe)
|
48
|
+
exts = ENV['PATHEXT'] ? ENV['PATHEXT'].split(';') : ['']
|
49
|
+
ENV['PATH'].split(File::PATH_SEPARATOR).each do |path|
|
50
|
+
exts.each do |ext|
|
51
|
+
exe_path = File.join(path, "#{exe}#{ext}")
|
52
|
+
return exe_path if File.executable?(exe_path)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
return nil
|
57
|
+
end
|
58
|
+
|
59
|
+
def create_vm_directory(vms_dir, vm_name)
|
60
|
+
if is_fusion?
|
61
|
+
vm_name += '.vmwarevm'
|
62
|
+
end
|
63
|
+
|
64
|
+
vm_dir = File.join(vms_dir, vm_name)
|
65
|
+
Dir.mkdir(vm_dir, 0755)
|
66
|
+
|
67
|
+
return vm_dir
|
68
|
+
end
|
69
|
+
|
70
|
+
def normalize_vmx_path(path)
|
71
|
+
path = File.expand_path(path)
|
72
|
+
|
73
|
+
if not File.exists?(path)
|
74
|
+
return nil
|
75
|
+
end
|
76
|
+
|
77
|
+
if path.end_with?('.vmx')
|
78
|
+
return path
|
79
|
+
end
|
80
|
+
|
81
|
+
if not File.directory?(path)
|
82
|
+
# Well, it had better be a VMX file then.
|
83
|
+
return path
|
84
|
+
end
|
85
|
+
|
86
|
+
Dir.foreach(path) do |filename|
|
87
|
+
if filename.end_with?('.vmx')
|
88
|
+
return File.join(path, filename)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
return nil
|
93
|
+
end
|
94
|
+
|
95
|
+
def run_vmrun(args)
|
96
|
+
vmrun_path = get_vmrun_path()
|
97
|
+
cmd = [vmrun_path] + args
|
98
|
+
out = nil
|
99
|
+
err = nil
|
100
|
+
exit_status = nil
|
101
|
+
|
102
|
+
Chef::Log.debug("Running #{cmd.join(' ')}")
|
103
|
+
|
104
|
+
Open3.popen3(*cmd) do |stdin, stdout, stderr, wait_thr|
|
105
|
+
wait_thr.join()
|
106
|
+
|
107
|
+
out = stdout.read()
|
108
|
+
err = stderr.read()
|
109
|
+
|
110
|
+
exit_status = wait_thr.value
|
111
|
+
end
|
112
|
+
|
113
|
+
Chef::Log.debug("... stdout: {#{out}}")
|
114
|
+
Chef::Log.debug("... stderr: {#{err}}")
|
115
|
+
Chef::Log.debug("... exit code: {#{exit_status}}")
|
116
|
+
|
117
|
+
return exit_status, out, err
|
118
|
+
end
|
119
|
+
|
120
|
+
def set_vmx_variable(vmx_path, key, value)
|
121
|
+
lines = []
|
122
|
+
|
123
|
+
File.open(vmx_path, 'r') do |file|
|
124
|
+
lines = file.readlines()
|
125
|
+
end
|
126
|
+
|
127
|
+
line_re = Regexp.new("^#{key}\s*=")
|
128
|
+
|
129
|
+
File.open(vmx_path, 'w') do |file|
|
130
|
+
lines.each do |line|
|
131
|
+
if line_re.match(line)
|
132
|
+
file.write("#{key} = \"#{value}\"\n")
|
133
|
+
else
|
134
|
+
file.write(line)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def clone_vm(src_vmx_path, dest_vmx_path, clone_type, clone_name,
|
141
|
+
snapshot_name=nil)
|
142
|
+
args = [
|
143
|
+
'clone',
|
144
|
+
src_vmx_path,
|
145
|
+
dest_vmx_path,
|
146
|
+
clone_type,
|
147
|
+
#"-cloneName=#{clone_name}",
|
148
|
+
]
|
149
|
+
|
150
|
+
if snapshot_name
|
151
|
+
args.push("-snapshot=#{snapshot_name}")
|
152
|
+
end
|
153
|
+
|
154
|
+
exit_status, out, err = run_vmrun(args)
|
155
|
+
|
156
|
+
return exit_status == 0
|
157
|
+
end
|
158
|
+
|
159
|
+
def wait_for_ssh(ip_address, port)
|
160
|
+
tcp_socket = TCPSocket.new(ip_address, port)
|
161
|
+
readable = IO.select([tcp_socket], nil, nil, 5)
|
162
|
+
|
163
|
+
if readable
|
164
|
+
Chef::Log.debug("Connected to sshd on #{ip_address}")
|
165
|
+
yield
|
166
|
+
return true
|
167
|
+
else
|
168
|
+
return false
|
169
|
+
end
|
170
|
+
rescue Errno::ETIMEDOUT, Errno::EPERM
|
171
|
+
return false
|
172
|
+
rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Errno::ENETUNREACH
|
173
|
+
sleep 2
|
174
|
+
return false
|
175
|
+
ensure
|
176
|
+
tcp_socket && tcp_socket.close
|
177
|
+
end
|
178
|
+
|
179
|
+
def power_on_vm(vmx_path)
|
180
|
+
exit_status, out, err = run_vmrun(['start', vmx_path, 'nogui'])
|
181
|
+
|
182
|
+
return exit_status == 0
|
183
|
+
end
|
184
|
+
|
185
|
+
def get_tools_state(vmx_path)
|
186
|
+
exit_status, out, err = run_vmrun(['checkToolsState', vmx_path])
|
187
|
+
|
188
|
+
return out.strip()
|
189
|
+
end
|
190
|
+
|
191
|
+
def get_guest_ip_address(vmx_path)
|
192
|
+
exit_status, out, err = \
|
193
|
+
run_vmrun(['getGuestIPAddress', vmx_path, '-wait'])
|
194
|
+
|
195
|
+
if exit_status == 0
|
196
|
+
return out.strip()
|
197
|
+
else
|
198
|
+
return ''
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
def locate_config_value(key)
|
203
|
+
key = key.to_sym
|
204
|
+
return Chef::Config[:knife][key] || config[key]
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
@@ -0,0 +1,202 @@
|
|
1
|
+
require 'chef/knife'
|
2
|
+
require 'chef/knife/base_command'
|
3
|
+
|
4
|
+
|
5
|
+
module KnifeWsFusion
|
6
|
+
class WsfusionCreate < BaseCommand
|
7
|
+
deps do
|
8
|
+
require 'chef/json_compat'
|
9
|
+
require 'chef/knife/bootstrap'
|
10
|
+
Chef::Knife::Bootstrap.load_deps
|
11
|
+
end
|
12
|
+
|
13
|
+
banner "knife wsfusion create (options)"
|
14
|
+
|
15
|
+
option :bootstrap_version,
|
16
|
+
:long => '--bootstrap-version VERSION',
|
17
|
+
:description => 'The version of Chef to install',
|
18
|
+
:proc => Proc.new { |v| Chef::Config[:knife][:bootstrap_version] = v }
|
19
|
+
|
20
|
+
option :use_chef_solo,
|
21
|
+
:long => "--chef-solo",
|
22
|
+
:description => "Use Chef Solo instead of Chef Server",
|
23
|
+
:default => false,
|
24
|
+
:proc => Proc.new { |mode| Chef::Config[:knife][:chef_mode] = mode }
|
25
|
+
|
26
|
+
option :chef_node_name,
|
27
|
+
:short => "-N NAME",
|
28
|
+
:long => "--node-name NAME",
|
29
|
+
:description => "The Chef node name for your new node",
|
30
|
+
:proc => Proc.new { |key| Chef::Config[:knife][:chef_node_name] = key }
|
31
|
+
|
32
|
+
option :clone_snapshot_name,
|
33
|
+
:long => '--clone-snapshot-name NAME',
|
34
|
+
:description => 'The name of the snapshot from the template to ' \
|
35
|
+
'clone'
|
36
|
+
|
37
|
+
option :clone_type,
|
38
|
+
:long => '--clone-type [full|linked]',
|
39
|
+
:description => 'The type of clone to make (full or linked)',
|
40
|
+
:default => 'linked'
|
41
|
+
|
42
|
+
option :distro,
|
43
|
+
:short => "-d DISTRO",
|
44
|
+
:long => "--distro DISTRO",
|
45
|
+
:description => "Bootstrap a distro using a template; default is " \
|
46
|
+
"'chef-full'",
|
47
|
+
:proc => Proc.new { |d| Chef::Config[:knife][:distro] = d }
|
48
|
+
|
49
|
+
option :identity_file,
|
50
|
+
:short => "-i IDENTITY_FILE",
|
51
|
+
:long => "--identity-file IDENTITY_FILE",
|
52
|
+
:description => "The SSH identity file used for authentication"
|
53
|
+
|
54
|
+
option :json_attributes,
|
55
|
+
:short => "-j JSON",
|
56
|
+
:long => "--json-attributes JSON",
|
57
|
+
:description => "A JSON string to be added to the first run of " \
|
58
|
+
"chef-client",
|
59
|
+
:proc => lambda { |o| JSON.parse(o) }
|
60
|
+
|
61
|
+
option :run_list,
|
62
|
+
:short => "-r RUN_LIST",
|
63
|
+
:long => "--run-list RUN_LIST",
|
64
|
+
:description => "Comma-separated list of roles/recipes to apply",
|
65
|
+
:proc => lambda { |o| o.split(/[\s,]+/) }
|
66
|
+
|
67
|
+
option :ssh_password,
|
68
|
+
:short => "-P PASSWORD",
|
69
|
+
:long => "--ssh-password PASSWORD",
|
70
|
+
:description => "The ssh password"
|
71
|
+
|
72
|
+
option :ssh_port,
|
73
|
+
:short => "-p PORT",
|
74
|
+
:long => "--ssh-port PORT",
|
75
|
+
:description => "The ssh port",
|
76
|
+
:default => "22",
|
77
|
+
:proc => Proc.new { |key| Chef::Config[:knife][:ssh_port] = key }
|
78
|
+
|
79
|
+
option :ssh_user,
|
80
|
+
:short => "-x USERNAME",
|
81
|
+
:long => "--ssh-user USERNAME",
|
82
|
+
:description => "The ssh username",
|
83
|
+
:default => "root"
|
84
|
+
|
85
|
+
option :template_file,
|
86
|
+
:long => "--template-file TEMPLATE",
|
87
|
+
:description => "Full path to the location of the template to use",
|
88
|
+
:proc => Proc.new { |t| Chef::Config[:knife][:template_file] = t },
|
89
|
+
:default => false
|
90
|
+
|
91
|
+
option :vm_source_path,
|
92
|
+
:long => '--vm-source-path PATH',
|
93
|
+
:description => 'Path to the VM used for cloning',
|
94
|
+
:required => true
|
95
|
+
|
96
|
+
option :vm_name,
|
97
|
+
:long => '--vm-name NAME',
|
98
|
+
:description => 'Name of the new VM',
|
99
|
+
:required => true
|
100
|
+
|
101
|
+
option :vmrun_path,
|
102
|
+
:long => '--vmrun-path PATH',
|
103
|
+
:description => 'Custom path to vmrun'
|
104
|
+
|
105
|
+
attr_accessor :initial_sleep_delay
|
106
|
+
|
107
|
+
def run
|
108
|
+
vm_source_path = config[:vm_source_path]
|
109
|
+
src_vmx_path = normalize_vmx_path(vm_source_path)
|
110
|
+
|
111
|
+
if not src_vmx_path
|
112
|
+
ui.fatal("No VM was found at #{vm_source_path}")
|
113
|
+
exit 1
|
114
|
+
end
|
115
|
+
|
116
|
+
vms_dir = File.dirname(File.dirname(src_vmx_path))
|
117
|
+
|
118
|
+
dest_vm_name = config[:vm_name]
|
119
|
+
dest_vm_dir = create_vm_directory(vms_dir, dest_vm_name)
|
120
|
+
|
121
|
+
if not dest_vm_dir
|
122
|
+
ui.fatal('Unable to find a place to create the new VM')
|
123
|
+
exit 1
|
124
|
+
end
|
125
|
+
|
126
|
+
# Check for Tools in the source VM.
|
127
|
+
tools_state = get_tools_state(src_vmx_path)
|
128
|
+
|
129
|
+
if tools_state != 'installed' and tools_state != 'running'
|
130
|
+
ui.fatal("The source VM doesn't appear to have " +
|
131
|
+
"VMware Tools installed")
|
132
|
+
exit 1
|
133
|
+
end
|
134
|
+
|
135
|
+
dest_vmx_path = File.join(dest_vm_dir, "#{dest_vm_name}.vmx")
|
136
|
+
|
137
|
+
# Create the virtual machine by cloning the existing one.
|
138
|
+
puts "Creating VM #{dest_vm_name} from #{vm_source_path}..."
|
139
|
+
|
140
|
+
success = clone_vm(src_vmx_path, dest_vmx_path, config[:clone_type],
|
141
|
+
dest_vm_name, config[:clone_snapshot_name])
|
142
|
+
|
143
|
+
if not success
|
144
|
+
ui.fatal("Unable to clone the VM. See the log for details.")
|
145
|
+
exit 1
|
146
|
+
end
|
147
|
+
|
148
|
+
# Now power on the VM.
|
149
|
+
puts "Powering on the new VM..."
|
150
|
+
if not power_on_vm(dest_vmx_path)
|
151
|
+
ui.fatal("Unable to power on the VM. See the log for details.")
|
152
|
+
exit 1
|
153
|
+
end
|
154
|
+
|
155
|
+
# Wait for the guest's IP address.
|
156
|
+
puts "Waiting for an IP address from the guest..."
|
157
|
+
ip_address = get_guest_ip_address(dest_vmx_path)
|
158
|
+
|
159
|
+
# Wait for SSH access.
|
160
|
+
puts "Waiting for sshd..."
|
161
|
+
print ('.') until wait_for_ssh(ip_address, config[:ssh_port]) do
|
162
|
+
sleep @initial_sleep_delay ||= 10;
|
163
|
+
print 'done'
|
164
|
+
end
|
165
|
+
|
166
|
+
if Chef::Config[:knife][:chef_mode] != "solo"
|
167
|
+
puts "Bootstrapping VM"
|
168
|
+
bootstrap_for_node(dest_vm_name, ip_address).run()
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
def bootstrap_for_node(vm_name, ip_address)
|
173
|
+
knife_config = Chef::Config[:knife]
|
174
|
+
|
175
|
+
bootstrap = Chef::Knife::Bootstrap.new
|
176
|
+
bootstrap.name_args = [ip_address]
|
177
|
+
bootstrap.config[:run_list] = config[:run_list]
|
178
|
+
bootstrap.config[:chef_node_name] = \
|
179
|
+
config[:chef_node_name] || vm_name
|
180
|
+
bootstrap.config[:first_boot_attributes] = \
|
181
|
+
config[:json_attributes] || {}
|
182
|
+
bootstrap.config[:bootstrap_version] = \
|
183
|
+
locate_config_value(:bootstrap_version)
|
184
|
+
bootstrap.config[:distro] = locate_config_value(:distro)
|
185
|
+
bootstrap.config[:template_file] = \
|
186
|
+
locate_config_value(:template_file)
|
187
|
+
bootstrap.config[:environment] = locate_config_value(:environment)
|
188
|
+
bootstrap.config[:ssh_user] = config[:ssh_user]
|
189
|
+
bootstrap.config[:ssh_password] = config[:ssh_password]
|
190
|
+
bootstrap.config[:use_sudo] = (config[:ssh_user] != 'root')
|
191
|
+
bootstrap.config[:identity_file] = config[:identity_file]
|
192
|
+
bootstrap.config[:no_host_key_verify] = \
|
193
|
+
locate_config_value(:no_host_key_verify)
|
194
|
+
bootstrap.config[:encrypted_data_bag_secret] = \
|
195
|
+
locate_config_value(:encrypted_data_bag_secret)
|
196
|
+
bootstrap.config[:encrypted_data_bag_secret_file] = \
|
197
|
+
locate_config_value(:encrypted_data_bag_secret_file)
|
198
|
+
|
199
|
+
return bootstrap
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
metadata
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: knife-wsfusion
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Christian Hammond
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-08-22 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: chef
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 0.10.0
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 0.10.0
|
30
|
+
description: VMware Workstation/Fusion support for Chef's Knife command.
|
31
|
+
email: chipx86@chipx86.com
|
32
|
+
executables: []
|
33
|
+
extensions: []
|
34
|
+
extra_rdoc_files: []
|
35
|
+
files:
|
36
|
+
- lib/chef/knife/base_command.rb
|
37
|
+
- lib/chef/knife/wsfusion_create.rb
|
38
|
+
- lib/knife-wsfusion/version.rb
|
39
|
+
homepage: https://github.com/chipx86/knife-wsfusion
|
40
|
+
licenses: []
|
41
|
+
post_install_message:
|
42
|
+
rdoc_options: []
|
43
|
+
require_paths:
|
44
|
+
- lib
|
45
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
46
|
+
none: false
|
47
|
+
requirements:
|
48
|
+
- - ! '>='
|
49
|
+
- !ruby/object:Gem::Version
|
50
|
+
version: '0'
|
51
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
52
|
+
none: false
|
53
|
+
requirements:
|
54
|
+
- - ! '>='
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: '0'
|
57
|
+
requirements: []
|
58
|
+
rubyforge_project:
|
59
|
+
rubygems_version: 1.8.23
|
60
|
+
signing_key:
|
61
|
+
specification_version: 3
|
62
|
+
summary: VMware Workstation/Fusion support for Knife
|
63
|
+
test_files: []
|