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.
@@ -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
@@ -0,0 +1,3 @@
1
+ module KnifeWsFusion
2
+ VERSION = "0.1"
3
+ 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: []