realityforge-vagrant-windows 0.1.4
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/.gitignore +9 -0
- data/.ruby-version +1 -0
- data/Gemfile +5 -0
- data/README.md +115 -0
- data/Rakefile +2 -0
- data/lib/vagrant-windows/communication/winrm.rb +185 -0
- data/lib/vagrant-windows/config/windows.rb +27 -0
- data/lib/vagrant-windows/config/winrm.rb +31 -0
- data/lib/vagrant-windows/errors.rb +22 -0
- data/lib/vagrant-windows/guest/windows.rb +116 -0
- data/lib/vagrant-windows/monkey_patches/driver.rb +57 -0
- data/lib/vagrant-windows/monkey_patches/vm.rb +16 -0
- data/lib/vagrant-windows/provisioners/puppet.rb +72 -0
- data/lib/vagrant-windows/provisioners/winrm.rb +35 -0
- data/lib/vagrant-windows/scripts/command_alias.ps1 +23 -0
- data/lib/vagrant-windows/scripts/mount_volume.ps1.erb +49 -0
- data/lib/vagrant-windows/version.rb +3 -0
- data/lib/vagrant-windows/winrm.rb +82 -0
- data/lib/vagrant_init.rb +24 -0
- data/vagrant-windows.gemspec +21 -0
- metadata +115 -0
data/.gitignore
ADDED
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
1.9.3-p327
|
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,115 @@
|
|
1
|
+
TODO:
|
2
|
+
vagrant rdp && vagrant ps to connect to VM
|
3
|
+
|
4
|
+
|
5
|
+
Installing
|
6
|
+
==========
|
7
|
+
|
8
|
+
```
|
9
|
+
gem install vagrant-windows
|
10
|
+
```
|
11
|
+
|
12
|
+
Building a Base Box
|
13
|
+
===================
|
14
|
+
|
15
|
+
All Windows Machines
|
16
|
+
--------------------
|
17
|
+
-Enable WinRM
|
18
|
+
|
19
|
+
```
|
20
|
+
winrm quickconfig -q
|
21
|
+
winrm set winrm/config/winrs @{MaxMemoryPerShellMB="512"}
|
22
|
+
winrm set winrm/config @{MaxTimeoutms="1800000"}
|
23
|
+
winrm set winrm/config/service @{AllowUnencrypted="true"}
|
24
|
+
winrm set winrm/config/service/auth @{Basic="true"}
|
25
|
+
```
|
26
|
+
- Create a vagrant user
|
27
|
+
- For things to work out of the box, username and password should both be vagrant
|
28
|
+
|
29
|
+
- Turn off UAC (Msconfig)
|
30
|
+
- Disable complex passwords
|
31
|
+
|
32
|
+
Servers
|
33
|
+
--------
|
34
|
+
- Disable Shutdown Tracker (http://www.jppinto.com/2010/01/how-to-disable-the-shutdown-event-tracker-in-server-20032008/)
|
35
|
+
- Disable "Server Manager" Starting at login (http://www.elmajdal.net/win2k8/How_to_Turn_Off_The_Automatic_Display_of_Server_Manager_At_logon.aspx)
|
36
|
+
|
37
|
+
The Vagrant File
|
38
|
+
================
|
39
|
+
|
40
|
+
Add the following to your Vagrantfile
|
41
|
+
|
42
|
+
```ruby
|
43
|
+
config.vm.guest = :windows
|
44
|
+
|
45
|
+
config.vm.forward_port 3389, 3390, :name => "rdp", :auto => true
|
46
|
+
config.vm.forward_port 5985, 5985, :name => "winrm", :auto => true
|
47
|
+
```
|
48
|
+
|
49
|
+
Example:
|
50
|
+
```ruby
|
51
|
+
Vagrant::Config.run do |config|
|
52
|
+
|
53
|
+
#The following timeout configuration is option, however if have
|
54
|
+
#any large remote_file resources in your chef recipes, you may
|
55
|
+
#experience timeouts (reported as 500 responses)
|
56
|
+
config.winrm.timeout = 1800 #Set WinRM Timeout in seconds (Default 30)
|
57
|
+
|
58
|
+
# Configure base box parameters
|
59
|
+
config.vm.box = "windows2008r2"
|
60
|
+
config.vm.box_url = "./windows-2008-r2.box"
|
61
|
+
config.vm.guest = :windows
|
62
|
+
|
63
|
+
config.vm.forward_port 3389, 3390, :name => "rdp", :auto => true
|
64
|
+
config.vm.forward_port 5985, 5985, :name => "winrm", :auto => true
|
65
|
+
|
66
|
+
config.vm.provision :chef_solo do |chef|
|
67
|
+
chef.add_recipe("your::recipe")
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
````
|
72
|
+
|
73
|
+
What Works?
|
74
|
+
===========
|
75
|
+
- vagrant up|hault|reload|provision
|
76
|
+
- Chef Vagrant Provisioner
|
77
|
+
- Puppet standalone provisioning
|
78
|
+
|
79
|
+
What has not been tested
|
80
|
+
========================
|
81
|
+
- Everything Else!!!
|
82
|
+
- Shell provisioning
|
83
|
+
- Shell should work, though I have not vetted it yet.
|
84
|
+
|
85
|
+
What does not work
|
86
|
+
==================
|
87
|
+
- <s>Complex networking setups</s> - Fixed in 0.0.3
|
88
|
+
- Note that I have not tested the Virtual Box 4.0 Driver, all _should_ work. Can someone please confirm?
|
89
|
+
|
90
|
+
What Can I do to help?
|
91
|
+
======================
|
92
|
+
1. Contribute Code (See Below)
|
93
|
+
2. Test Various Scenarios and file bugs for things that dont work
|
94
|
+
|
95
|
+
Contributing
|
96
|
+
============
|
97
|
+
1. Fork it.
|
98
|
+
2. Create a branch (git checkout -b my_feature_branch)
|
99
|
+
3. Commit your changes (git commit -am "Added a sweet feature")
|
100
|
+
4. Push to the branch (git push origin my_feature_branch)
|
101
|
+
5. Create a pull requst from your branch into master (Please be sure to provide enough detail for us to cipher what this change is doing)
|
102
|
+
|
103
|
+
|
104
|
+
References and Shout Outs
|
105
|
+
=========================
|
106
|
+
- Chris McClimans - Vagrant Branch (https://github.com/hh/vagrant/blob/feature/winrm/)
|
107
|
+
-Dan Wanek - WinRM GEM (https://github.com/zenchild/WinRM)
|
108
|
+
- +1 For being super responsive to pull requests.
|
109
|
+
|
110
|
+
|
111
|
+
Changelog
|
112
|
+
=========
|
113
|
+
0.1.1 - Remove extra debug information from command output.
|
114
|
+
0.1.2 - added virtual box 4.2 support
|
115
|
+
0.1.3 - added puppet provisioner
|
data/Rakefile
ADDED
@@ -0,0 +1,185 @@
|
|
1
|
+
require 'timeout'
|
2
|
+
|
3
|
+
require 'log4r'
|
4
|
+
#require 'em-winrm'
|
5
|
+
require 'winrm'
|
6
|
+
require 'highline'
|
7
|
+
|
8
|
+
require 'vagrant/util/ansi_escape_code_remover'
|
9
|
+
require 'vagrant/util/file_mode'
|
10
|
+
require 'vagrant/util/platform'
|
11
|
+
require 'vagrant/util/retryable'
|
12
|
+
|
13
|
+
module Vagrant
|
14
|
+
module Communication
|
15
|
+
# Provides communication with the VM via WinRM.
|
16
|
+
class WinRM < Base
|
17
|
+
|
18
|
+
include Util::ANSIEscapeCodeRemover
|
19
|
+
include Util::Retryable
|
20
|
+
|
21
|
+
attr_reader :logger
|
22
|
+
attr_reader :vm
|
23
|
+
|
24
|
+
def initialize(vm)
|
25
|
+
@vm = vm
|
26
|
+
@logger = Log4r::Logger.new("vagrant::communication::winrm")
|
27
|
+
@co = nil
|
28
|
+
end
|
29
|
+
|
30
|
+
def ready?
|
31
|
+
logger.debug("Checking whether WinRM is ready...")
|
32
|
+
|
33
|
+
Timeout.timeout(@vm.config.winrm.timeout) do
|
34
|
+
execute "hostname"
|
35
|
+
end
|
36
|
+
|
37
|
+
# If we reached this point then we successfully connected
|
38
|
+
logger.info("WinRM is ready!")
|
39
|
+
true
|
40
|
+
rescue Timeout::Error, HTTPClient::KeepAliveDisconnected => e
|
41
|
+
#, Errors::SSHConnectionRefused, Net::SSH::Disconnect => e
|
42
|
+
# The above errors represent various reasons that WinRM may not be
|
43
|
+
# ready yet. Return false.
|
44
|
+
logger.info("WinRM not up yet: #{e.inspect}")
|
45
|
+
|
46
|
+
return false
|
47
|
+
end
|
48
|
+
|
49
|
+
# Wrap Sudo in execute.... One day we could integrate with UAC, but Icky
|
50
|
+
def sudo(command, opts=nil, &block)
|
51
|
+
execute(command,opts,&block)
|
52
|
+
end
|
53
|
+
|
54
|
+
def execute(command, opts=nil, &block)
|
55
|
+
|
56
|
+
# Connect to WinRM, giving it a few tries
|
57
|
+
logger.info("Connecting to WinRM: #{@vm.winrm.info[:host]}:#{@vm.winrm.info[:port]}")
|
58
|
+
|
59
|
+
opts = {
|
60
|
+
:error_check => true,
|
61
|
+
:error_class => Errors::VagrantError,
|
62
|
+
:error_key => :winrm_bad_exit_status,
|
63
|
+
:command => command,
|
64
|
+
:sudo => false,
|
65
|
+
:shell => :powershell
|
66
|
+
}.merge(opts || {})
|
67
|
+
|
68
|
+
# Connect via WinRM and execute the command in the shell.
|
69
|
+
exceptions = [HTTPClient::KeepAliveDisconnected]
|
70
|
+
exit_status = retryable(:tries => @vm.config.winrm.max_tries, :on => exceptions, :sleep => 10) do
|
71
|
+
logger.debug "WinRM Trying to connect"
|
72
|
+
shell_execute(command,opts[:shell], &block)
|
73
|
+
end
|
74
|
+
|
75
|
+
logger.debug("#{command} EXIT STATUS #{exit_status.inspect}")
|
76
|
+
|
77
|
+
# Check for any errors
|
78
|
+
if opts[:error_check] && exit_status != 0
|
79
|
+
# The error classes expect the translation key to be _key,
|
80
|
+
# but that makes for an ugly configuration parameter, so we
|
81
|
+
# set it here from `error_key`
|
82
|
+
error_opts = opts.merge(:_key => opts[:error_key])
|
83
|
+
raise opts[:error_class], error_opts
|
84
|
+
end
|
85
|
+
|
86
|
+
# Return the exit status
|
87
|
+
exit_status
|
88
|
+
end
|
89
|
+
|
90
|
+
def new_session
|
91
|
+
opts = {
|
92
|
+
:user => vm.config.winrm.username,
|
93
|
+
:pass => vm.config.winrm.password,
|
94
|
+
:host => vm.config.winrm.host,
|
95
|
+
:port => vm.winrm.info[:port],
|
96
|
+
:operation_timeout => vm.config.winrm.timeout,
|
97
|
+
:basic_auth_only => true
|
98
|
+
}.merge ({})
|
99
|
+
|
100
|
+
# create a session
|
101
|
+
logger.info("Attempting WinRM session with options: #{opts}")
|
102
|
+
begin
|
103
|
+
endpoint = "http://#{opts[:host]}:#{opts[:port]}/wsman"
|
104
|
+
client = ::WinRM::WinRMWebService.new(endpoint, :plaintext, opts)
|
105
|
+
client.set_timeout(opts[:operation_timeout]) if opts[:operation_timeout]
|
106
|
+
rescue ::WinRM::WinRMAuthorizationError => error
|
107
|
+
raise ::WinRM::WinRMAuthorizationError.new("#{error.message}@#{opts[:host]}")
|
108
|
+
end
|
109
|
+
|
110
|
+
client
|
111
|
+
end
|
112
|
+
|
113
|
+
def session
|
114
|
+
@session ||= new_session
|
115
|
+
end
|
116
|
+
|
117
|
+
def h
|
118
|
+
@highline ||= HighLine.new
|
119
|
+
end
|
120
|
+
|
121
|
+
def print_data(data, color = :green)
|
122
|
+
if data =~ /\n/
|
123
|
+
data.split(/\n/).each { |d| print_data(d, color) }
|
124
|
+
else
|
125
|
+
puts h.color(data.chomp, color)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def upload(from, to)
|
130
|
+
file = "winrm-upload-#{rand()}"
|
131
|
+
file_name = (session.cmd("echo %TEMP%\\#{file}"))[:data][0][:stdout].chomp
|
132
|
+
session.powershell <<-EOH
|
133
|
+
if(Test-Path #{to})
|
134
|
+
{
|
135
|
+
rm #{to}
|
136
|
+
}
|
137
|
+
EOH
|
138
|
+
Base64.encode64(IO.binread(from)).gsub("\n",'').chars.to_a.each_slice(8000-file_name.size) do |chunk|
|
139
|
+
out = session.cmd( "echo #{chunk.join} >> \"#{file_name}\"" )
|
140
|
+
end
|
141
|
+
execute "mkdir [System.IO.Path]::GetDirectoryName(\"#{to}\")"
|
142
|
+
execute <<-EOH
|
143
|
+
$base64_string = Get-Content \"#{file_name}\"
|
144
|
+
$bytes = [System.Convert]::FromBase64String($base64_string)
|
145
|
+
$new_file = [System.IO.Path]::GetFullPath(\"#{to}\")
|
146
|
+
[System.IO.File]::WriteAllBytes($new_file,$bytes)
|
147
|
+
EOH
|
148
|
+
end
|
149
|
+
|
150
|
+
protected
|
151
|
+
|
152
|
+
# Executes the command on an SSH connection within a login shell.
|
153
|
+
def shell_execute(command,shell = :powershell)
|
154
|
+
logger.info("Execute: #{command}")
|
155
|
+
exit_status = nil
|
156
|
+
|
157
|
+
if shell.eql? :cmd
|
158
|
+
output = session.cmd(command) do |out,error|
|
159
|
+
print_data(out) if out
|
160
|
+
print_data(error, :red) if error
|
161
|
+
end
|
162
|
+
elsif shell.eql? :powershell
|
163
|
+
new_command = File.read(File.expand_path("#{File.dirname(__FILE__)}/../scripts/command_alias.ps1"))
|
164
|
+
new_command << "\r\n"
|
165
|
+
new_command << command
|
166
|
+
output = session.powershell(new_command) do |out,error|
|
167
|
+
print_data(out) if out
|
168
|
+
print_data(error, :red) if error
|
169
|
+
end
|
170
|
+
else
|
171
|
+
raise Vagrant::Errors::WinRMInvalidShell, "#{shell} is not a valid type of shell"
|
172
|
+
end
|
173
|
+
|
174
|
+
|
175
|
+
exit_status = output[:exitcode]
|
176
|
+
logger.debug exit_status.inspect
|
177
|
+
|
178
|
+
# Return the final exit status
|
179
|
+
return exit_status
|
180
|
+
rescue ::WinRM::WinRMHTTPTransportError => e
|
181
|
+
raise Vagrant::Errors::WinRMTimeout, e.message
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Vagrant
|
2
|
+
module Config
|
3
|
+
class Windows < Vagrant::Config::Base
|
4
|
+
attr_accessor :winrm_user
|
5
|
+
attr_accessor :winrm_password
|
6
|
+
attr_accessor :halt_timeout
|
7
|
+
attr_accessor :halt_check_interval
|
8
|
+
attr_accessor :device
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@winrm_user = 'vagrant'
|
12
|
+
@winrm_password = 'vagrant'
|
13
|
+
@halt_timeout = 30
|
14
|
+
@halt_check_interval = 1
|
15
|
+
@device = "e1000g"
|
16
|
+
end
|
17
|
+
|
18
|
+
def validate(env, errors)
|
19
|
+
[ :winrm_user, :winrm_password].each do |field|
|
20
|
+
errors.add(I18n.t("vagrant.config.common.error_empty", :field => field)) if !instance_variable_get("@#{field}".to_sym)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
Vagrant.config_keys.register(:windows) { Vagrant::Config::Windows }
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Vagrant
|
2
|
+
module Config
|
3
|
+
class WinRM < Vagrant::Config::Base
|
4
|
+
attr_accessor :username
|
5
|
+
attr_accessor :password
|
6
|
+
attr_accessor :host
|
7
|
+
attr_accessor :port
|
8
|
+
attr_accessor :guest_port
|
9
|
+
attr_accessor :max_tries
|
10
|
+
attr_accessor :timeout
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
@username = "vagrant"
|
14
|
+
@password = "vagrant"
|
15
|
+
@guest_port = 5985
|
16
|
+
@host = "localhost"
|
17
|
+
@max_tries = 12
|
18
|
+
@timeout = 1800
|
19
|
+
end
|
20
|
+
|
21
|
+
def validate(env, errors)
|
22
|
+
[:username, :password, :host, :max_tries, :timeout].each do |field|
|
23
|
+
errors.add(I18n.t("vagrant.config.common.error_empty", :field => field)) if !instance_variable_get("@#{field}".to_sym)
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
Vagrant.config_keys.register(:winrm) { Vagrant::Config::WinRM }
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Vagrant
|
2
|
+
module Errors
|
3
|
+
class WinRMPortNotDetected < VagrantError
|
4
|
+
status_code(550)
|
5
|
+
error_key(:winrm_port_not_detected)
|
6
|
+
end
|
7
|
+
|
8
|
+
class WinRMInvalidShell < VagrantError
|
9
|
+
status_code(551)
|
10
|
+
error_key(:winrm_invalid_shell)
|
11
|
+
end
|
12
|
+
class WinRMTransferError < VagrantError
|
13
|
+
status_code(552)
|
14
|
+
error_key(:winrm_upload_error)
|
15
|
+
end
|
16
|
+
class WinRMTimeout < VagrantError
|
17
|
+
status_code(553)
|
18
|
+
error_key(:winrm_timeout)
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
module Vagrant
|
2
|
+
module Guest
|
3
|
+
# A general Vagrant system implementation for "windows".
|
4
|
+
#
|
5
|
+
# Contributed by Chris McClimans <chris@hippiehacker.org>
|
6
|
+
class Windows < Base
|
7
|
+
# A custom config class which will be made accessible via `config.windows`
|
8
|
+
# Here for whenever it may be used.
|
9
|
+
class WindowsError < Errors::VagrantError
|
10
|
+
error_namespace("vagrant.guest.windows")
|
11
|
+
end
|
12
|
+
|
13
|
+
def change_host_name(name)
|
14
|
+
#### on windows, renaming a computer seems to require a reboot
|
15
|
+
@vm.channel.session.cmd("netdom renamecomputer %COMPUTERNAME% /force /reb:2 /newname:#{name}")
|
16
|
+
sleep 5
|
17
|
+
wait_for_state :running, @vm.config.windows.halt_timeout, @vm.config.windows.halt_check_interval
|
18
|
+
end
|
19
|
+
|
20
|
+
# TODO: I am sure that ciphering windows versions will be important at some point
|
21
|
+
def distro_dispatch
|
22
|
+
:windows
|
23
|
+
end
|
24
|
+
|
25
|
+
def halt
|
26
|
+
@vm.channel.execute("shutdown /s /t 1 /c \"Vagrant Halt\" /f /d p:4:1")
|
27
|
+
# Wait until the VM's state is actually powered off. If this doesn't
|
28
|
+
# occur within a reasonable amount of time (15 seconds by default),
|
29
|
+
# then simply return and allow Vagrant to kill the machine.
|
30
|
+
wait_for_state :poweroff, @vm.config.windows.halt_timeout, @vm.config.windows.halt_check_interval
|
31
|
+
end
|
32
|
+
|
33
|
+
def wait_for_state(state, timeout, interval)
|
34
|
+
count = 0
|
35
|
+
while @vm.state != state
|
36
|
+
count += 1
|
37
|
+
return if count > timeout
|
38
|
+
sleep interval
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def mount_shared_folder(name, guestpath, options)
|
43
|
+
mount_script = TemplateRenderer.render(File.expand_path("#{File.dirname(__FILE__)}/../scripts/mount_volume.ps1"),
|
44
|
+
:options => {:mount_point => guestpath, :name => name})
|
45
|
+
@vm.channel.execute(mount_script,{:shell => :powershell})
|
46
|
+
end
|
47
|
+
|
48
|
+
def mount_nfs(ip, folders)
|
49
|
+
raise NotImplementedError, "Mounting NFS Shares on windows is not implemented"
|
50
|
+
# TODO: Maybe check for nfs support on the guest, since its often
|
51
|
+
# not installed by default
|
52
|
+
#folders.each do |name, opts|
|
53
|
+
# # Expand the guestpath, so we can handle things like "~/vagrant"
|
54
|
+
# real_guestpath = expanded_guest_path(opts[:guestpath])
|
55
|
+
|
56
|
+
# Do the actual creating and mounting
|
57
|
+
# @vm.channel.sudo("mkdir -p #{real_guestpath}")
|
58
|
+
# @vm.channel.sudo("mount -o vers=#{opts[:nfs_version]} #{ip}:'#{opts[:hostpath]}' #{real_guestpath}",
|
59
|
+
# :error_class => LinuxError,
|
60
|
+
# :error_key => :mount_nfs_fail)
|
61
|
+
#end
|
62
|
+
end
|
63
|
+
|
64
|
+
def configure_networks(networks)
|
65
|
+
### HACK!!!!!
|
66
|
+
Nori.advanced_typecasting = false
|
67
|
+
if driver_mac_address = @vm.driver.read_mac_addresses
|
68
|
+
driver_mac_address = driver_mac_address.invert
|
69
|
+
end
|
70
|
+
|
71
|
+
vm_interface_map = {}
|
72
|
+
@vm.channel.session.wql("SELECT * FROM Win32_NetworkAdapter WHERE NetConnectionStatus=2")[:win32_network_adapter].each do |nic|
|
73
|
+
naked_mac = nic[:mac_address].gsub(':','')
|
74
|
+
|
75
|
+
if driver_mac_address[naked_mac]
|
76
|
+
vm_interface_map[driver_mac_address[naked_mac]] = {
|
77
|
+
:name => nic[:net_connection_id],
|
78
|
+
:mac_address => naked_mac,
|
79
|
+
:index => nic[:interface_index]
|
80
|
+
}
|
81
|
+
end
|
82
|
+
end
|
83
|
+
networks.each do |network|
|
84
|
+
interface_name = vm_interface_map[network[:interface]+1][:name]
|
85
|
+
interface_index = vm_interface_map[network[:interface]+1][:index]
|
86
|
+
|
87
|
+
if network[:type].to_sym == :static
|
88
|
+
@vm.channel.execute("netsh interface ip set address \"#{interface_name}\" static #{network[:ip]} #{network[:netmask]}")
|
89
|
+
|
90
|
+
elsif network[:type].to_sym == :dhcp
|
91
|
+
|
92
|
+
if !vm.channel.test("if (-not (netsh interface ip show address \"#{interface_name}\" | where \{$_ -match \"DHCP enabled:\s+Yes\"\})) \{exit 1\} ")
|
93
|
+
vm.channel.execute("netsh interface ip set address \"#{interface_name}\" dhcp")
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
|
100
|
+
def windows_path(path)
|
101
|
+
p = ''
|
102
|
+
if path =~ /^\//
|
103
|
+
p << 'C:\\'
|
104
|
+
end
|
105
|
+
p << path
|
106
|
+
p.gsub! /\//, "\\"
|
107
|
+
p.gsub /\\\\{0,}/, "\\"
|
108
|
+
end
|
109
|
+
|
110
|
+
|
111
|
+
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
Vagrant.guests.register(:windows) { Vagrant::Guest::Windows }
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'vagrant/driver/virtualbox_base'
|
2
|
+
require 'vagrant/driver/virtualbox'
|
3
|
+
|
4
|
+
|
5
|
+
module Vagrant
|
6
|
+
module Driver
|
7
|
+
|
8
|
+
class VirtualBox_4_2 < VirtualBoxBase
|
9
|
+
def read_mac_addresses
|
10
|
+
macs = {}
|
11
|
+
info = execute("showvminfo", @uuid, "--machinereadable", :retryable => true)
|
12
|
+
info.split("\n").each do |line|
|
13
|
+
if matcher = /^macaddress(\d+)="(.+?)"$/.match(line)
|
14
|
+
adapter = matcher[1].to_i
|
15
|
+
mac = matcher[2].to_s
|
16
|
+
macs[adapter] = mac
|
17
|
+
end
|
18
|
+
end
|
19
|
+
macs
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class VirtualBox_4_1 < VirtualBoxBase
|
24
|
+
def read_mac_addresses
|
25
|
+
macs = {}
|
26
|
+
info = execute("showvminfo", @uuid, "--machinereadable", :retryable => true)
|
27
|
+
info.split("\n").each do |line|
|
28
|
+
if matcher = /^macaddress(\d+)="(.+?)"$/.match(line)
|
29
|
+
adapter = matcher[1].to_i
|
30
|
+
mac = matcher[2].to_s
|
31
|
+
macs[adapter] = mac
|
32
|
+
end
|
33
|
+
end
|
34
|
+
macs
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
class VirtualBox_4_0 < VirtualBoxBase
|
39
|
+
def read_mac_addresses
|
40
|
+
macs = {}
|
41
|
+
info = execute("showvminfo", @uuid, "--machinereadable", :retryable => true)
|
42
|
+
info.split("\n").each do |line|
|
43
|
+
if matcher = /^macaddress(\d+)="(.+?)"$/.match(line)
|
44
|
+
adapter = matcher[1].to_i
|
45
|
+
mac = matcher[2].to_s
|
46
|
+
macs[adapter] = mac
|
47
|
+
end
|
48
|
+
end
|
49
|
+
macs
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
class VirtualBox < VirtualBoxBase
|
54
|
+
def_delegator :@driver, :read_mac_addresses
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Vagrant
|
2
|
+
class VM
|
3
|
+
def winrm
|
4
|
+
@winrm ||= WinRM.new(self)
|
5
|
+
end
|
6
|
+
|
7
|
+
def channel
|
8
|
+
if @guest.class.eql? Vagrant::Guest::Windows
|
9
|
+
@channel ||= Communication::WinRM.new(self)
|
10
|
+
else
|
11
|
+
@channel ||= Communication::SSH.new(self)
|
12
|
+
end
|
13
|
+
@channel
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module Vagrant
|
2
|
+
module Provisioners
|
3
|
+
class Puppet < Base
|
4
|
+
def run_puppet_client
|
5
|
+
options = [config.options].flatten
|
6
|
+
if env[:vm].config.vm.guest == :windows
|
7
|
+
options << "--modulepath '#{@module_paths.values.join(';')}'" if !@module_paths.empty?
|
8
|
+
else
|
9
|
+
options << "--modulepath '#{@module_paths.values.join(':')}'" if !@module_paths.empty?
|
10
|
+
end
|
11
|
+
|
12
|
+
options << @manifest_file
|
13
|
+
options = options.join(" ")
|
14
|
+
|
15
|
+
# Build up the custom facts if we have any
|
16
|
+
facter = ""
|
17
|
+
if !config.facter.empty?
|
18
|
+
facts = []
|
19
|
+
config.facter.each do |key, value|
|
20
|
+
if env[:vm].config.vm.guest == :windows
|
21
|
+
facts << "$env:FACTER_#{key}='#{value}';"
|
22
|
+
else
|
23
|
+
facts << "FACTER_#{key}='#{value}'"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
facter = "#{facts.join(" ")} "
|
28
|
+
end
|
29
|
+
if env[:vm].config.vm.guest == :windows
|
30
|
+
command = "cd #{manifests_guest_path}; if($?) \{ #{facter} puppet apply #{options} \}"
|
31
|
+
else
|
32
|
+
command = "cd #{manifests_guest_path} && #{facter}puppet apply #{options}"
|
33
|
+
end
|
34
|
+
|
35
|
+
env[:ui].info I18n.t("vagrant.provisioners.puppet.running_puppet",
|
36
|
+
:manifest => @manifest_file)
|
37
|
+
|
38
|
+
env[:vm].channel.sudo(command) do |type, data|
|
39
|
+
env[:ui].info(data.chomp, :prefix => false)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
def verify_binary(binary)
|
43
|
+
if env[:vm].config.vm.guest == :windows
|
44
|
+
command = "command #{binary}"
|
45
|
+
else
|
46
|
+
command = "which #{binary}"
|
47
|
+
end
|
48
|
+
env[:vm].channel.sudo(command,
|
49
|
+
:error_class => PuppetError,
|
50
|
+
:error_key => :not_detected,
|
51
|
+
:binary => binary)
|
52
|
+
end
|
53
|
+
def verify_shared_folders(folders)
|
54
|
+
folders.each do |folder|
|
55
|
+
@logger.debug("Checking for shared folder: #{folder}")
|
56
|
+
|
57
|
+
|
58
|
+
if env[:vm].config.vm.guest == :windows
|
59
|
+
if !env[:vm].channel.test("if(-not (test-path #{folder})) \{exit 1\} ")
|
60
|
+
raise PuppetError, :missing_shared_folders
|
61
|
+
end
|
62
|
+
else
|
63
|
+
if !env[:vm].channel.test("test -d #{folder}")
|
64
|
+
raise PuppetError, :missing_shared_folders
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'tempfile'
|
2
|
+
|
3
|
+
module Vagrant
|
4
|
+
module Provisioners
|
5
|
+
class Winrm < Base
|
6
|
+
class Config < Vagrant::Config::Base
|
7
|
+
attr_accessor :inline
|
8
|
+
|
9
|
+
def validate(env, errors)
|
10
|
+
# Validate that the parameters are properly set
|
11
|
+
if !inline
|
12
|
+
errors.add("No inline set")
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.config_class
|
18
|
+
Config
|
19
|
+
end
|
20
|
+
|
21
|
+
def provision!
|
22
|
+
env[:vm].channel.sudo(config.inline) do |type, data|
|
23
|
+
if [:stderr, :stdout].include?(type)
|
24
|
+
# Output the data with the proper color based on the stream.
|
25
|
+
color = type == :stdout ? :green : :red
|
26
|
+
|
27
|
+
# Note: Be sure to chomp the data to avoid the newlines that the
|
28
|
+
# Chef outputs.
|
29
|
+
env[:ui].info(data.chomp, :color => color, :prefix => false)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
function which {
|
2
|
+
$command = [Array](Get-Command $args[0] -errorAction continue)
|
3
|
+
write-host $command[0].Definition
|
4
|
+
}
|
5
|
+
|
6
|
+
function test ([Switch] $d, [String] $path) {
|
7
|
+
Resolve-Path $path| Out-Null;
|
8
|
+
}
|
9
|
+
|
10
|
+
function chown {
|
11
|
+
exit 0
|
12
|
+
}
|
13
|
+
|
14
|
+
function mkdir ([Switch] $p, [String] $path)
|
15
|
+
{
|
16
|
+
if(Test-Path $path)
|
17
|
+
{
|
18
|
+
exit 0
|
19
|
+
} else {
|
20
|
+
New-Item $p -Type Directory -Force | Out-Null
|
21
|
+
}
|
22
|
+
}
|
23
|
+
|
@@ -0,0 +1,49 @@
|
|
1
|
+
|
2
|
+
function Test-ReparsePoint([string]$path) {
|
3
|
+
$file = Get-Item $path -Force -ea 0
|
4
|
+
return [bool]($file.Attributes -band [IO.FileAttributes]::ReparsePoint)
|
5
|
+
}
|
6
|
+
|
7
|
+
|
8
|
+
$MountPoint = [System.IO.Path]::GetFullPath("<%= options[:mount_point] %>")
|
9
|
+
$ShareName = "<%= options[:name] %>"
|
10
|
+
|
11
|
+
# https://github.com/BIAINC/vagrant-windows/issues/4
|
12
|
+
# Not sure why this works, but it does.
|
13
|
+
|
14
|
+
& net use $ShareName 2>&1 | Out-Null
|
15
|
+
|
16
|
+
Write-Debug "Attempting to mount $ShareName to $MountPoint"
|
17
|
+
if( (Test-Path "$MountPoint") -and (Test-ReparsePoint "$MountPoint") )
|
18
|
+
{
|
19
|
+
Write-Debug "Junction already exists, so I will delete it"
|
20
|
+
# Powershell refuses to delete junctions, oh well use cmd
|
21
|
+
cmd /c rd "$MountPoint"
|
22
|
+
|
23
|
+
if ( $LASTEXITCODE -ne 0 )
|
24
|
+
{
|
25
|
+
Write-Error "Failed to delete symbolic link at $MountPoint"
|
26
|
+
exit 1
|
27
|
+
}
|
28
|
+
|
29
|
+
}
|
30
|
+
elseif(Test-Path $MountPoint)
|
31
|
+
{
|
32
|
+
Write-Debug "Mount point already exists and is not a symbolic link"
|
33
|
+
exit 1
|
34
|
+
}
|
35
|
+
|
36
|
+
$BaseDirectory = [System.IO.Path]::GetDirectoryName($MountPoint)
|
37
|
+
|
38
|
+
if (-not (Test-Path $BaseDirectory))
|
39
|
+
{
|
40
|
+
Write-Debug "Creating parent directory for mount point $BaseDirectory"
|
41
|
+
New-Item $BaseDirectory -Type Directory -Force | Out-Null
|
42
|
+
}
|
43
|
+
|
44
|
+
cmd /c mklink /D "$MountPoint" "\\vboxsrv\$ShareName" | out-null
|
45
|
+
|
46
|
+
if ( $LASTEXITCODE -ne 0 )
|
47
|
+
{
|
48
|
+
exit 1
|
49
|
+
}
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'log4r'
|
2
|
+
#require 'em-winrm'
|
3
|
+
|
4
|
+
module Vagrant
|
5
|
+
# Manages WINRM access to a specific environment. Allows an environment to
|
6
|
+
# run commands, upload files, and check if a host is up.
|
7
|
+
class WinRM
|
8
|
+
include Util::SafeExec
|
9
|
+
|
10
|
+
def initialize(vm)
|
11
|
+
@vm = vm
|
12
|
+
@logger = Log4r::Logger.new("vagrant::winrm")
|
13
|
+
end
|
14
|
+
|
15
|
+
# Returns a hash of information necessary for accessing this
|
16
|
+
# virtual machine via WINRM.
|
17
|
+
#
|
18
|
+
# @return [Hash]
|
19
|
+
def info
|
20
|
+
results = {
|
21
|
+
:host => @vm.config.winrm.host,
|
22
|
+
:port => @vm.config.winrm.port || @vm.driver.ssh_port(@vm.config.winrm.guest_port),
|
23
|
+
:username => @vm.config.winrm.username
|
24
|
+
}
|
25
|
+
|
26
|
+
# This can happen if no port is set and for some reason Vagrant
|
27
|
+
# can't detect an SSH port.
|
28
|
+
raise Errors::WinRMPortNotDetected if !results[:port]
|
29
|
+
|
30
|
+
# Return the results
|
31
|
+
return results
|
32
|
+
end
|
33
|
+
|
34
|
+
# Checks if this environment's machine is up (i.e. responding to WINRM).
|
35
|
+
#
|
36
|
+
# @return [Boolean]
|
37
|
+
def up?
|
38
|
+
# We have to determine the port outside of the block since it uses
|
39
|
+
# API calls which can only be used from the main thread in JRuby on
|
40
|
+
# Windows
|
41
|
+
ssh_port = port
|
42
|
+
|
43
|
+
require 'timeout'
|
44
|
+
Timeout.timeout(@env.config.ssh.timeout) do
|
45
|
+
execute 'hostname'
|
46
|
+
end
|
47
|
+
|
48
|
+
true
|
49
|
+
rescue Timeout::Error, Errno::ECONNREFUSED
|
50
|
+
return false
|
51
|
+
end
|
52
|
+
|
53
|
+
# Returns the port which is either given in the options hash or taken from
|
54
|
+
# the config by finding it in the forwarded ports hash based on the
|
55
|
+
# `config.ssh.forwarded_port_key`.
|
56
|
+
def port(opts={})
|
57
|
+
# Check if port was specified in options hash
|
58
|
+
return opts[:port] if opts[:port]
|
59
|
+
|
60
|
+
# Check if a port was specified in the config
|
61
|
+
return @env.config.winrm.port if @env.config.winrm.port
|
62
|
+
|
63
|
+
# Check if we have an SSH forwarded port
|
64
|
+
pnum_by_name = nil
|
65
|
+
pnum_by_destination = nil
|
66
|
+
@logger.info("Looking for winrm port: #{opts}")
|
67
|
+
@logger.info("Looking for winrm port: #{env.config.winrm.inspect}")
|
68
|
+
|
69
|
+
env.vm.vm.network_adapters.each do |na|
|
70
|
+
# Look for the port number by destination...
|
71
|
+
pnum_by_destination = na.nat_driver.forwarded_ports.detect do |fp|
|
72
|
+
fp.guestport == env.config.winrm.guest_port
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
return pnum_by_destination.hostport if pnum_by_destination
|
77
|
+
|
78
|
+
# This should NEVER happen.
|
79
|
+
raise Errors::WinRMPortNotDetected
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
data/lib/vagrant_init.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
#Add Windows Guest Defintion
|
2
|
+
require 'vagrant-windows/guest/windows'
|
3
|
+
|
4
|
+
#Add Configuration Items
|
5
|
+
require 'vagrant-windows/config/windows'
|
6
|
+
require 'vagrant-windows/config/winrm'
|
7
|
+
|
8
|
+
# Add WinRM Communication Channel
|
9
|
+
require 'vagrant-windows/communication/winrm'
|
10
|
+
|
11
|
+
# Add Provisioner
|
12
|
+
require 'vagrant-windows/provisioners/puppet'
|
13
|
+
|
14
|
+
#Monkey Patch the VM object to support multiple channels
|
15
|
+
require 'vagrant-windows/monkey_patches/vm'
|
16
|
+
|
17
|
+
#Monkey Patch the driver to support returning a mapping of mac addresses to nics
|
18
|
+
require 'vagrant-windows/monkey_patches/driver'
|
19
|
+
|
20
|
+
require 'vagrant-windows/winrm'
|
21
|
+
|
22
|
+
#Errors are good
|
23
|
+
require 'vagrant-windows/errors'
|
24
|
+
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/vagrant-windows/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["Paul Morton"]
|
6
|
+
gem.email = ["pmorton@biaprotect.com"]
|
7
|
+
gem.description = %q{Windows Guest Support for Vagrant}
|
8
|
+
gem.summary = %q{A small gem that adds windows guest support to vagrant, uses WinRM as the Communication Channel}
|
9
|
+
gem.homepage = ""
|
10
|
+
|
11
|
+
gem.files = `git ls-files`.split($\)
|
12
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
13
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
14
|
+
gem.name = "realityforge-vagrant-windows"
|
15
|
+
gem.require_paths = ["lib"]
|
16
|
+
gem.version = VagrantWindows::VERSION
|
17
|
+
|
18
|
+
gem.add_runtime_dependency "winrm", "~> 1.1.1"
|
19
|
+
gem.add_runtime_dependency 'vagrant', "~> 1.0"
|
20
|
+
gem.add_runtime_dependency 'highline'
|
21
|
+
end
|
metadata
ADDED
@@ -0,0 +1,115 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: realityforge-vagrant-windows
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.4
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Paul Morton
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-04-10 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: winrm
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 1.1.1
|
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: 1.1.1
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: vagrant
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ~>
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '1.0'
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '1.0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: highline
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
description: Windows Guest Support for Vagrant
|
63
|
+
email:
|
64
|
+
- pmorton@biaprotect.com
|
65
|
+
executables: []
|
66
|
+
extensions: []
|
67
|
+
extra_rdoc_files: []
|
68
|
+
files:
|
69
|
+
- .gitignore
|
70
|
+
- .ruby-version
|
71
|
+
- Gemfile
|
72
|
+
- README.md
|
73
|
+
- Rakefile
|
74
|
+
- lib/vagrant-windows/communication/winrm.rb
|
75
|
+
- lib/vagrant-windows/config/windows.rb
|
76
|
+
- lib/vagrant-windows/config/winrm.rb
|
77
|
+
- lib/vagrant-windows/errors.rb
|
78
|
+
- lib/vagrant-windows/guest/windows.rb
|
79
|
+
- lib/vagrant-windows/monkey_patches/driver.rb
|
80
|
+
- lib/vagrant-windows/monkey_patches/vm.rb
|
81
|
+
- lib/vagrant-windows/provisioners/puppet.rb
|
82
|
+
- lib/vagrant-windows/provisioners/winrm.rb
|
83
|
+
- lib/vagrant-windows/scripts/command_alias.ps1
|
84
|
+
- lib/vagrant-windows/scripts/mount_volume.ps1.erb
|
85
|
+
- lib/vagrant-windows/version.rb
|
86
|
+
- lib/vagrant-windows/winrm.rb
|
87
|
+
- lib/vagrant_init.rb
|
88
|
+
- vagrant-windows.gemspec
|
89
|
+
homepage: ''
|
90
|
+
licenses: []
|
91
|
+
post_install_message:
|
92
|
+
rdoc_options: []
|
93
|
+
require_paths:
|
94
|
+
- lib
|
95
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
96
|
+
none: false
|
97
|
+
requirements:
|
98
|
+
- - ! '>='
|
99
|
+
- !ruby/object:Gem::Version
|
100
|
+
version: '0'
|
101
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
102
|
+
none: false
|
103
|
+
requirements:
|
104
|
+
- - ! '>='
|
105
|
+
- !ruby/object:Gem::Version
|
106
|
+
version: '0'
|
107
|
+
requirements: []
|
108
|
+
rubyforge_project:
|
109
|
+
rubygems_version: 1.8.23
|
110
|
+
signing_key:
|
111
|
+
specification_version: 3
|
112
|
+
summary: A small gem that adds windows guest support to vagrant, uses WinRM as the
|
113
|
+
Communication Channel
|
114
|
+
test_files: []
|
115
|
+
has_rdoc:
|