vagrant-windows 0.1.2 → 1.0.0

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