knife-wsfusion 0.1.0

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