knife-wsfusion 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|