vagrant-windows 0.1.2 → 1.0.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.
@@ -1,45 +1,52 @@
1
- module Vagrant
1
+ module VagrantWindows
2
2
  module Guest
3
3
  # A general Vagrant system implementation for "windows".
4
4
  #
5
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")
6
+ class Windows < Vagrant.plugin("2", :guest)
7
+
8
+ attr_reader :machine
9
+
10
+ def initialize(machine)
11
+ super(machine)
12
+ @machine = machine
13
+ @logger = Log4r::Logger.new("vagrant_windows::guest::windows")
11
14
  end
12
15
 
13
16
  def change_host_name(name)
17
+ @logger.info("change host name to: #{name}")
14
18
  #### on windows, renaming a computer seems to require a reboot
15
- vm.channel.execute("wmic computersystem where name=\"%COMPUTERNAME%\" call rename name=\"#{name}\"")
19
+ @machine.communicate.execute(
20
+ "wmic computersystem where name=\"%COMPUTERNAME%\" call rename name=\"#{name}\"",
21
+ :shell => :cmd)
16
22
  end
17
23
 
18
24
  # TODO: I am sure that ciphering windows versions will be important at some point
19
25
  def distro_dispatch
26
+ @logger.info("distro_dispatch: windows")
20
27
  :windows
21
28
  end
22
29
 
23
30
  def halt
24
- @vm.channel.execute("shutdown /s /t 1 /c \"Vagrant Halt\" /f /d p:4:1")
31
+ @machine.communicate.execute("shutdown /s /t 1 /c \"Vagrant Halt\" /f /d p:4:1")
25
32
 
26
33
  # Wait until the VM's state is actually powered off. If this doesn't
27
34
  # occur within a reasonable amount of time (15 seconds by default),
28
35
  # then simply return and allow Vagrant to kill the machine.
29
36
  count = 0
30
- while @vm.state != :poweroff
37
+ while @machine.state != :poweroff
31
38
  count += 1
32
39
 
33
- return if count >= @vm.config.windows.halt_timeout
34
- sleep @vm.config.windows.halt_check_interval
40
+ return if count >= @machine.config.windows.halt_timeout
41
+ sleep @machine.config.windows.halt_check_interval
35
42
  end
36
43
  end
37
44
 
38
45
  def mount_shared_folder(name, guestpath, options)
39
- mount_script = TemplateRenderer.render(File.expand_path("#{File.dirname(__FILE__)}/../scripts/mount_volume.ps1"),
40
- :options => {:mount_point => guestpath, :name => name})
41
-
42
- @vm.channel.execute(mount_script,{:shell => :powershell})
46
+ @logger.info("mount_shared_folder: #{name}")
47
+ mount_script = VagrantWindows.load_script_template("mount_volume.ps1",
48
+ :options => {:mount_point => guestpath, :name => name})
49
+ @machine.communicate.execute(mount_script, {:shell => :powershell})
43
50
  end
44
51
 
45
52
  def mount_nfs(ip, folders)
@@ -51,54 +58,52 @@ module Vagrant
51
58
  # real_guestpath = expanded_guest_path(opts[:guestpath])
52
59
 
53
60
  # Do the actual creating and mounting
54
- # @vm.channel.sudo("mkdir -p #{real_guestpath}")
55
- # @vm.channel.sudo("mount -o vers=#{opts[:nfs_version]} #{ip}:'#{opts[:hostpath]}' #{real_guestpath}",
61
+ # @machine.communicate.sudo("mkdir -p #{real_guestpath}")
62
+ # @machine.communicate.sudo("mount -o vers=#{opts[:nfs_version]} #{ip}:'#{opts[:hostpath]}' #{real_guestpath}",
56
63
  # :error_class => LinuxError,
57
64
  # :error_key => :mount_nfs_fail)
58
65
  #end
59
66
  end
60
67
 
61
68
  def configure_networks(networks)
62
- ### HACK!!!!!
63
- Nori.advanced_typecasting = false
64
- if driver_mac_address = @vm.driver.read_mac_addresses
65
- driver_mac_address = driver_mac_address.invert
69
+ @logger.info("configure_networks: #{networks.inspect}")
70
+
71
+ # The VBox driver 4.0 and 4.1 implement read_mac_addresses, but 4.2 does not?
72
+ begin
73
+ driver_mac_address = @machine.provider.driver.read_mac_addresses.invert
74
+ rescue NoMethodError
75
+ driver_mac_address = {}
76
+ driver_mac_address[@machine.provider.driver.read_mac_address] = "macaddress1"
66
77
  end
67
78
 
68
79
  vm_interface_map = {}
69
- @vm.channel.session.wql("SELECT * FROM Win32_NetworkAdapter WHERE NetConnectionStatus=2")[:win32_network_adapter].each do |nic|
80
+
81
+ # NetConnectionStatus=2 -- connected
82
+ wql = "SELECT * FROM Win32_NetworkAdapter WHERE NetConnectionStatus=2"
83
+ @machine.communicate.session.wql(wql)[:win32_network_adapter].each do |nic|
70
84
  naked_mac = nic[:mac_address].gsub(':','')
71
85
  if driver_mac_address[naked_mac]
72
- vm_interface_map[driver_mac_address[naked_mac]] = { :name => nic[:net_connection_id], :mac_address => naked_mac, :index => nic[:interface_index] }
86
+ vm_interface_map[driver_mac_address[naked_mac]] =
87
+ { :name => nic[:net_connection_id], :mac_address => naked_mac, :index => nic[:interface_index] }
73
88
  end
74
89
  end
90
+
75
91
  networks.each do |network|
92
+ netsh = "netsh interface ip set address \"#{vm_interface_map[network[:interface]+1][:name]}\" "
76
93
  if network[:type].to_sym == :static
77
- vm.channel.execute("netsh interface ip set address \"#{vm_interface_map[network[:interface]+1][:name]}\" static #{network[:ip]} #{network[:netmask]}")
94
+ netsh = "#{netsh} static #{network[:ip]} #{network[:netmask]}"
78
95
  elsif network[:type].to_sym == :dhcp
79
- vm.channel.execute("netsh interface ip set address \"#{vm_interface_map[network[:interface]+1][:name]}\" dhcp")
96
+ netsh = "#{netsh} dhcp"
97
+ else
98
+ raise WindowsError, "#{network[:type]} network type is not supported, try static or dhcp"
80
99
  end
100
+ @machine.communicate.execute(netsh)
81
101
  end
82
102
 
83
103
  #netsh interface ip set address name="Local Area Connection" static 192.168.0.100 255.255.255.0 192.168.0.1 1
84
104
 
85
105
  end
86
106
 
87
-
88
- def windows_path(path)
89
- p = ''
90
- if path =~ /^\//
91
- p << 'C:\\'
92
- end
93
- p << path
94
- p.gsub! /\//, "\\"
95
- p.gsub /\\\\{0,}/, "\\"
96
- end
97
-
98
-
99
-
100
107
  end
101
108
  end
102
109
  end
103
-
104
- Vagrant.guests.register(:windows) { Vagrant::Guest::Windows }
@@ -0,0 +1,61 @@
1
+ require "#{VagrantWindows::vagrant_root}/plugins/provisioners/chef/provisioner/chef_solo"
2
+
3
+ module VagrantPlugins
4
+ module Chef
5
+ module Provisioner
6
+ class ChefSolo < Base
7
+
8
+ run_chef_solo_on_linux = instance_method(:run_chef_solo)
9
+
10
+ # This patch is needed until Vagrant supports chef on Windows guests
11
+ define_method(:run_chef_solo) do
12
+ is_windows ? run_chef_solo_on_windows() : run_chef_solo_on_linux.bind(self).()
13
+ end
14
+
15
+ def run_chef_solo_on_windows
16
+ command_env = @config.binary_env ? "#{@config.binary_env} " : ""
17
+ command_args = @config.arguments ? " #{@config.arguments}" : ""
18
+ command_solo = "#{command_env}#{chef_binary_path("chef-solo")} "
19
+ command_solo << "-c #{@config.provisioning_path}/solo.rb "
20
+ command_solo << "-j #{@config.provisioning_path}/dna.json "
21
+ command_solo << "#{command_args}"
22
+
23
+ command = VagrantWindows.load_script_template("ps_runas.ps1",
24
+ :options => {
25
+ :user => machine.config.winrm.username,
26
+ :password => @machine.config.winrm.password,
27
+ :cmd => "powershell.exe",
28
+ :arguments => "-Command #{command_solo}"})
29
+
30
+ @config.attempts.times do |attempt|
31
+ if attempt == 0
32
+ @machine.env.ui.info I18n.t("vagrant.provisioners.chef.running_solo")
33
+ else
34
+ @machine.env.ui.info I18n.t("vagrant.provisioners.chef.running_solo_again")
35
+ end
36
+
37
+ exit_status = @machine.communicate.sudo(command, :error_check => false) do |type, data|
38
+ # Output the data with the proper color based on the stream.
39
+ color = type == :stdout ? :green : :red
40
+
41
+ # Note: Be sure to chomp the data to avoid the newlines that the
42
+ # Chef outputs.
43
+ @machine.env.ui.info(data.chomp, :color => color, :prefix => false)
44
+ end
45
+
46
+ # There is no need to run Chef again if it converges
47
+ return if exit_status == 0
48
+ end
49
+
50
+ # If we reached this point then Chef never converged! Error.
51
+ raise ChefError, :no_convergence
52
+ end
53
+
54
+ def is_windows
55
+ @machine.config.vm.guest.eql? :windows
56
+ end
57
+
58
+ end # ChefSolo class
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,25 @@
1
+ module Vagrant
2
+ class Machine
3
+
4
+ ssh_communicate = instance_method(:communicate)
5
+
6
+ # This patch is needed until Vagrant supports a configurable communication channel
7
+ define_method(:communicate) do
8
+ unless @communicator
9
+ if @config.vm.guest.eql? :windows
10
+ @logger.info("guest is #{@config.vm.guest}, using WinRM for communication channel")
11
+ @communicator = ::VagrantWindows::Communication::WinRMCommunicator.new(self)
12
+ else
13
+ @logger.info("guest is #{@config.vm.guest}, using SSH for communication channel")
14
+ @communicator = ssh_communicate.bind(self).()
15
+ end
16
+ end
17
+ @communicator
18
+ end
19
+
20
+ def winrm
21
+ @winrm ||= WinRM.new(self)
22
+ end
23
+
24
+ end
25
+ end
@@ -0,0 +1,100 @@
1
+ require "#{VagrantWindows::vagrant_root}/plugins/provisioners/puppet/provisioner/puppet"
2
+
3
+ module VagrantPlugins
4
+ module Puppet
5
+ module Provisioner
6
+ class Puppet < Vagrant.plugin("2", :provisioner)
7
+
8
+ # This patch is needed until Vagrant supports Puppet on Windows guests
9
+ run_puppet_apply_on_linux = instance_method(:run_puppet_apply)
10
+ configure_on_linux = instance_method(:configure)
11
+
12
+ define_method(:run_puppet_apply) do
13
+ is_windows ? run_puppet_apply_on_windows() : run_puppet_apply_on_linux.bind(self).()
14
+ end
15
+
16
+ define_method(:configure) do |root_config|
17
+ is_windows ? configure_on_windows(root_config) : configure_on_linux.bind(self).(root_config)
18
+ end
19
+
20
+ def run_puppet_apply_on_windows
21
+ options = [config.options].flatten
22
+ module_paths = @module_paths.map { |_, to| to }
23
+ if !@module_paths.empty?
24
+ # Prepend the default module path
25
+ module_paths.unshift("/etc/puppet/modules")
26
+
27
+ # Add the command line switch to add the module path
28
+ options << "--modulepath '#{module_paths.join(';')}'"
29
+ end
30
+
31
+ options << @manifest_file
32
+ options = options.join(" ")
33
+
34
+ # Build up the custom facts if we have any
35
+ facter = ""
36
+ if !config.facter.empty?
37
+ facts = []
38
+ config.facter.each do |key, value|
39
+ facts << "$env:FACTER_#{key}='#{value}';"
40
+ end
41
+
42
+ facter = "#{facts.join(" ")} "
43
+ end
44
+
45
+ command = "cd #{manifests_guest_path}; if($?) \{ #{facter} puppet apply #{options} \}"
46
+
47
+ @machine.env.ui.info I18n.t("vagrant.provisioners.puppet.running_puppet",
48
+ :manifest => @manifest_file)
49
+
50
+ @machine.communicate.sudo(command) do |type, data|
51
+ data.chomp!
52
+ @machine.env.ui.info(data, :prefix => false) if !data.empty?
53
+ end
54
+ end
55
+
56
+ def configure_on_windows(root_config)
57
+ # Calculate the paths we're going to use based on the environment
58
+ root_path = @machine.env.root_path
59
+ @expanded_manifests_path = @config.expanded_manifests_path(root_path)
60
+ @expanded_module_paths = @config.expanded_module_paths(root_path)
61
+ @manifest_file = @config.manifest_file
62
+
63
+ # Setup the module paths
64
+ @module_paths = []
65
+ @expanded_module_paths.each_with_index do |path, i|
66
+ @module_paths << [path, File.join(config.pp_path, "modules-#{i}")]
67
+ end
68
+
69
+ @logger.debug("Syncing folders from puppet configure")
70
+ @logger.debug("manifests_guest_path = #{manifests_guest_path}")
71
+ @logger.debug("expanded_manifests_path = #{@expanded_manifests_path}")
72
+
73
+ # Windows guest volume mounting fails without an "id" specified
74
+ # This hacks around that problem and allows the PS mount script to work
75
+ root_config.vm.synced_folder(
76
+ @expanded_manifests_path, manifests_guest_path,
77
+ :id => "v-manifests-1")
78
+
79
+ # Share the manifests directory with the guest
80
+ #root_config.vm.synced_folder(
81
+ # @expanded_manifests_path, manifests_guest_path)
82
+
83
+ # Share the module paths
84
+ count = 0
85
+ @module_paths.each do |from, to|
86
+ # Sorry for the cryptic key here, but VirtualBox has a strange limit on
87
+ # maximum size for it and its something small (around 10)
88
+ root_config.vm.synced_folder(from, to)
89
+ count += 1
90
+ end
91
+ end
92
+
93
+ def is_windows
94
+ @machine.config.vm.guest.eql? :windows
95
+ end
96
+
97
+ end # Puppet class
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,96 @@
1
+ begin
2
+ require "vagrant"
3
+ rescue LoadError
4
+ raise "The Vagrant Windows plugin must be run within Vagrant."
5
+ end
6
+
7
+ # This is a sanity check to make sure no one is attempting to install
8
+ # this into an early Vagrant version.
9
+ if Vagrant::VERSION < "1.1.0"
10
+ raise "The Vagrant Windows plugin is only compatible with Vagrant 1.1+"
11
+ end
12
+
13
+ # Add vagrant-windows plugin errors
14
+ require "vagrant-windows/errors"
15
+
16
+ # Add Vagrant WinRM communication channel
17
+ require "vagrant-windows/communication/winrmcommunicator"
18
+
19
+ # Monkey Patch the VM object to support multiple channels, i.e. WinRM
20
+ require "vagrant-windows/monkey_patches/machine"
21
+
22
+ # Monkey patch the Puppet provisioner to support PowerShell/Windows
23
+ require "vagrant-windows/monkey_patches/puppet"
24
+
25
+ # Monkey patch the Chef-Solo provisioner to support PowerShell/Windows
26
+ require "vagrant-windows/monkey_patches/chef_solo"
27
+
28
+ # Add our windows specific config object
29
+ require "vagrant-windows/config/windows"
30
+
31
+ # Add our winrm specific config object
32
+ require "vagrant-windows/config/winrm"
33
+
34
+ # Add the new Vagrant Windows guest
35
+ require "vagrant-windows/guest/windows"
36
+
37
+ module VagrantWindows
38
+ class Plugin < Vagrant.plugin("2")
39
+ name "Windows guest"
40
+ description <<-DESC
41
+ This plugin installs a provider that allows Vagrant to manage
42
+ Windows machines as guests.
43
+ DESC
44
+
45
+ guest(:windows) do
46
+ VagrantWindows::Guest::Windows
47
+ end
48
+
49
+ config(:windows) do
50
+ VagrantWindows::Config::Windows
51
+ end
52
+
53
+ config(:winrm) do
54
+ VagrantWindows::Config::WinRM
55
+ end
56
+
57
+ # This initializes the internationalization strings.
58
+ def self.setup_i18n
59
+ I18n.load_path << File.expand_path("locales/en.yml", VagrantWindows.vagrant_windows_root)
60
+ I18n.reload!
61
+ end
62
+
63
+ # This sets up our log level to be whatever VAGRANT_LOG is.
64
+ def self.setup_logging
65
+ require "log4r"
66
+
67
+ level = nil
68
+ begin
69
+ level = Log4r.const_get(ENV["VAGRANT_LOG"].upcase)
70
+ rescue NameError
71
+ # This means that the logging constant wasn't found,
72
+ # which is fine. We just keep `level` as `nil`. But
73
+ # we tell the user.
74
+ level = nil
75
+ end
76
+
77
+ # Some constants, such as "true" resolve to booleans, so the
78
+ # above error checking doesn't catch it. This will check to make
79
+ # sure that the log level is an integer, as Log4r requires.
80
+ level = nil if !level.is_a?(Integer)
81
+
82
+ # Set the logging level on all "vagrant" namespaced
83
+ # logs as long as we have a valid level.
84
+ if level
85
+ logger = Log4r::Logger.new("vagrant_windows")
86
+ logger.outputters = Log4r::Outputter.stderr
87
+ logger.level = level
88
+ logger = nil
89
+ end
90
+ end
91
+
92
+ end
93
+ end
94
+
95
+ VagrantWindows::Plugin.setup_logging()
96
+ VagrantWindows::Plugin.setup_i18n()
@@ -1,10 +1,19 @@
1
1
  function which {
2
- $command = [Array](Get-Command $args[0] -errorAction continue)
3
- write-host $command[0].Definition
2
+ $command = [Array](Get-Command $args[0] -errorAction SilentlyContinue)
3
+ if($null -eq $command)
4
+ {
5
+ exit 1
6
+ }
7
+ write-host $command[0].Definition
8
+ exit 0
4
9
  }
5
10
 
6
11
  function test ([Switch] $d, [String] $path) {
7
- Resolve-Path $path| Out-Null;
12
+ if(Test-Path $path)
13
+ {
14
+ exit 0
15
+ }
16
+ exit 1
8
17
  }
9
18
 
10
19
  function chown {
@@ -17,7 +26,7 @@ function mkdir ([Switch] $p, [String] $path)
17
26
  {
18
27
  exit 0
19
28
  } else {
20
- New-Item $p -Type Directory -Force | Out-Null
29
+ New-Item $path -Type Directory -Force | Out-Null
21
30
  }
22
31
  }
23
32
 
@@ -0,0 +1,56 @@
1
+ function ps-runas ([String] $user, [String] $password, [String] $cmd, [String] $arguments)
2
+ {
3
+ $secpasswd = ConvertTo-SecureString $password -AsPlainText -Force
4
+ $process = New-Object System.Diagnostics.Process
5
+ $setup = $process.StartInfo
6
+ $setup.FileName = $cmd
7
+ $setup.Arguments = $arguments
8
+ $setup.UserName = $user
9
+ $setup.Password = $secpasswd
10
+ $setup.Verb = "runas"
11
+ $setup.UseShellExecute = $false
12
+ $setup.RedirectStandardError = $true
13
+ $setup.RedirectStandardOutput = $true
14
+ $setup.RedirectStandardInput = $false
15
+
16
+ $errEvent = Register-ObjectEvent -InputObj $process `
17
+ -Event "ErrorDataReceived" `
18
+ -Action `
19
+ {
20
+ param([System.Object] $sender, [System.Diagnostics.DataReceivedEventArgs] $e)
21
+ if ($e.Data)
22
+ {
23
+ Write-Host $e.Data
24
+ }
25
+ else
26
+ {
27
+ New-Event -SourceIdentifier "LastMsgReceived"
28
+ }
29
+ }
30
+
31
+ $outEvent = Register-ObjectEvent -InputObj $process `
32
+ -Event "OutputDataReceived" `
33
+ -Action `
34
+ {
35
+ param([System.Object] $sender, [System.Diagnostics.DataReceivedEventArgs] $e)
36
+ Write-Host $e.Data
37
+ }
38
+
39
+ $exitCode = -1
40
+ if ($process.Start())
41
+ {
42
+ $process.BeginOutputReadLine()
43
+ $process.BeginErrorReadLine()
44
+
45
+ $process.WaitForExit()
46
+ $exitCode = [int]$process.ExitCode
47
+ Wait-Event -SourceIdentifier "LastMsgReceived" -Timeout 60 | Out-Null
48
+
49
+ $process.CancelOutputRead()
50
+ $process.CancelErrorRead()
51
+ $process.Close()
52
+ }
53
+ return $exitCode
54
+ }
55
+
56
+ exit ps-runas "<%= options[:user] %>" "<%= options[:password] %>" "<%= options[:cmd] %>" "<%= options[:arguments] %>"
@@ -1,3 +1,3 @@
1
1
  module VagrantWindows
2
- VERSION = "0.1.2"
2
+ VERSION = "1.0.0"
3
3
  end
@@ -0,0 +1,33 @@
1
+ require "pathname"
2
+
3
+ module VagrantWindows
4
+
5
+ def self.vagrant_lib_root
6
+ # example match: /Applications/Vagrant/embedded/gems/gems/vagrant-1.1.2/lib
7
+ @vagrant_lib_root ||= $LOAD_PATH.select { |p| p =~ /\/vagrant-[1-9].[0-9].[0-9]\/lib/ }.first
8
+ end
9
+
10
+ def self.vagrant_root
11
+ @vagrant_root ||= Pathname.new(File.expand_path("../", vagrant_lib_root))
12
+ end
13
+
14
+ def self.vagrant_windows_root
15
+ @vagrant_windows_root ||= Pathname.new(File.expand_path("../../", __FILE__))
16
+ end
17
+
18
+ def self.load_script(script_file_name)
19
+ File.read(expand_script_path(script_file_name))
20
+ end
21
+
22
+ def self.load_script_template(script_file_name, options)
23
+ Vagrant::Util::TemplateRenderer.render(expand_script_path(script_file_name), options)
24
+ end
25
+
26
+ def self.expand_script_path(script_file_name)
27
+ File.expand_path("lib/vagrant-windows/scripts/#{script_file_name}", VagrantWindows.vagrant_windows_root)
28
+ end
29
+
30
+
31
+ end
32
+
33
+ require "vagrant-windows/plugin"
data/locales/en.yml ADDED
@@ -0,0 +1,29 @@
1
+ en:
2
+ vagrant_windows:
3
+
4
+ errors:
5
+ winrm_port_not_detected: |-
6
+ Vagrant could not detect the WinRM port.
7
+
8
+ Host port: %{port}
9
+ Guest port: %{guest_port}
10
+ winrm_invalid_shell: |-
11
+ %{shell} is not a supported type of Windows shell.
12
+ winrm_execution_error: |-
13
+ An error occurred executing a remote WinRM command.
14
+
15
+ Shell: %{shell}
16
+ Command: %{command}
17
+ Message: %{message}
18
+ winrm_bad_exit_status: |-
19
+ The remote command returned a bad exit status of %{exit_status}.
20
+
21
+ Shell: %{shell}
22
+ Command: %{command}
23
+ winrm_auth_error: |-
24
+ An authorization error occurred connecting to WinRM.
25
+
26
+ User: %{user}
27
+ Password: %{password}
28
+ Endpoint: %{endpoint}
29
+ Message: %{message}
@@ -16,6 +16,5 @@ Gem::Specification.new do |gem|
16
16
  gem.version = VagrantWindows::VERSION
17
17
 
18
18
  gem.add_runtime_dependency "winrm", "~> 1.1.1"
19
- gem.add_runtime_dependency 'vagrant', "~> 1.0.3"
20
19
  gem.add_runtime_dependency 'highline'
21
20
  end