vagrant-azure 1.0.1

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.
Files changed (38) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +12 -0
  3. data/Gemfile +19 -0
  4. data/LICENSE +4 -0
  5. data/README.md +9 -0
  6. data/Rakefile +14 -0
  7. data/lib/vagrant-azure.rb +24 -0
  8. data/lib/vagrant-azure/action.rb +249 -0
  9. data/lib/vagrant-azure/action/connect_azure.rb +41 -0
  10. data/lib/vagrant-azure/action/provision.rb +40 -0
  11. data/lib/vagrant-azure/action/rdp.rb +62 -0
  12. data/lib/vagrant-azure/action/read_ssh_info.rb +51 -0
  13. data/lib/vagrant-azure/action/read_state.rb +46 -0
  14. data/lib/vagrant-azure/action/restart_vm.rb +27 -0
  15. data/lib/vagrant-azure/action/run_instance.rb +111 -0
  16. data/lib/vagrant-azure/action/start_instance.rb +35 -0
  17. data/lib/vagrant-azure/action/stop_instance.rb +38 -0
  18. data/lib/vagrant-azure/action/terminate_instance.rb +34 -0
  19. data/lib/vagrant-azure/action/wait_for_state.rb +49 -0
  20. data/lib/vagrant-azure/command/rdp/command.rb +21 -0
  21. data/lib/vagrant-azure/config.rb +147 -0
  22. data/lib/vagrant-azure/driver.rb +79 -0
  23. data/lib/vagrant-azure/plugin.rb +87 -0
  24. data/lib/vagrant-azure/provider.rb +70 -0
  25. data/lib/vagrant-azure/provisioner/puppet.rb +109 -0
  26. data/lib/vagrant-azure/scripts/check_winrm.ps1 +41 -0
  27. data/lib/vagrant-azure/scripts/export_vm.ps1 +31 -0
  28. data/lib/vagrant-azure/scripts/file_sync.ps1 +145 -0
  29. data/lib/vagrant-azure/scripts/host_info.ps1 +25 -0
  30. data/lib/vagrant-azure/scripts/hyperv_manager.ps1 +36 -0
  31. data/lib/vagrant-azure/scripts/run_in_remote.ps1 +32 -0
  32. data/lib/vagrant-azure/scripts/upload_file.ps1 +95 -0
  33. data/lib/vagrant-azure/scripts/utils/create_session.ps1 +34 -0
  34. data/lib/vagrant-azure/scripts/utils/write_messages.ps1 +18 -0
  35. data/lib/vagrant-azure/version.rb +10 -0
  36. data/locales/en.yml +14 -0
  37. data/vagrant-azure.gemspec +58 -0
  38. metadata +167 -0
@@ -0,0 +1,79 @@
1
+ #---------------------------------------------------------------------------
2
+ # Copyright (c) Microsoft Open Technologies, Inc.
3
+ # All Rights Reserved. Licensed under the Apache 2.0 License.
4
+ #--------------------------------------------------------------------------
5
+ require 'json'
6
+ require "#{Vagrant::source_root}/plugins/providers/hyperv/driver"
7
+
8
+ module VagrantPlugins
9
+ module WinAzure
10
+ class Driver < VagrantPlugins::HyperV::Driver
11
+ def initialize(machine)
12
+ @id = machine.id
13
+ @machine = machine
14
+ end
15
+
16
+ def ssh_info
17
+ @ssh_info ||= @machine.provider.winrm_info
18
+ @ssh_info[:username] ||= @machine.config.ssh.username
19
+ @ssh_info[:password] ||= @machine.config.ssh.password
20
+ @ssh_info
21
+ end
22
+
23
+ def remote_credentials
24
+ @remote_credentials ||= {
25
+ guest_ip: ssh_info[:host],
26
+ guest_port: ssh_info[:port],
27
+ username: ssh_info[:username],
28
+ password: ssh_info[:password]
29
+ }
30
+ end
31
+
32
+ def run_remote_ps(command, &block)
33
+ options = remote_credentials.merge(command: command)
34
+ script_path = local_script_path('run_in_remote.ps1')
35
+
36
+ ps_options = []
37
+
38
+ options.each do |key, value|
39
+ ps_options << "-#{key}"
40
+ ps_options << "'#{value}'"
41
+ end
42
+
43
+ ps_options << '-ErrorAction' << 'Stop'
44
+ opts = { notify: [:stdout, :stderr, :stdin] }
45
+ Vagrant::Util::PowerShell.execute(
46
+ script_path,
47
+ *ps_options,
48
+ **opts,
49
+ &block
50
+ )
51
+ end
52
+
53
+ def upload(from, to)
54
+ options = {
55
+ host_path: windows_path(from),
56
+ guest_path: windows_path(to)
57
+ }.merge(remote_credentials)
58
+
59
+ script_path = local_script_path('upload_file.ps1')
60
+ execute(script_path, options)
61
+ end
62
+
63
+ protected
64
+
65
+ def local_script_path(path)
66
+ lib_path = Pathname.new(File.expand_path('../scripts', __FILE__))
67
+ windows_path(lib_path.join(path).to_s)
68
+ end
69
+
70
+ def windows_path(path)
71
+ if path
72
+ path = path.gsub('/', "\\")
73
+ path = "c:#{path}" if path =~ /^\\/
74
+ end
75
+ path
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,87 @@
1
+ #--------------------------------------------------------------------------
2
+ # Copyright (c) Microsoft Open Technologies, Inc.
3
+ # All Rights Reserved. Licensed under the Apache 2.0 License.
4
+ #--------------------------------------------------------------------------
5
+ begin
6
+ require 'vagrant'
7
+ rescue LoadError
8
+ raise 'The Vagrant Azure plugin must be run within Vagrant.'
9
+ end
10
+
11
+ # This is a sanity check to make sure no one is attempting to install this into
12
+ # an early Vagrant version.
13
+ if Vagrant::VERSION < '1.2.0'
14
+ raise 'The Vagrant Azure plugin is only compatible with Vagrant 1.2+'
15
+ end
16
+
17
+ module VagrantPlugins
18
+ module WinAzure
19
+ class Plugin < Vagrant.plugin('2')
20
+ name 'azure'
21
+ description <<-DESC
22
+ This plugin installs a provider that allows Vagrant to manage
23
+ machines in Windows Azure.
24
+ DESC
25
+
26
+ config(:azure, :provider) do
27
+ require_relative 'config'
28
+ Config
29
+ end
30
+
31
+ provider(:azure, parallel: true) do
32
+ # Setup logging and i18n
33
+ setup_logging
34
+ setup_i18n
35
+
36
+ # Return the provider
37
+ require_relative 'provider'
38
+ Provider
39
+ end
40
+
41
+ command 'rdp' do
42
+ require_relative 'command/rdp/command'
43
+ Command
44
+ end
45
+
46
+ def self.setup_i18n
47
+ I18n.load_path << File.expand_path(
48
+ 'locales/en.yml',
49
+ WinAzure.source_root
50
+ )
51
+ I18n.load_path << File.expand_path(
52
+ 'templates/locales/providers_hyperv.yml',
53
+ Vagrant.source_root
54
+ )
55
+ I18n.reload!
56
+ end
57
+
58
+ def self.setup_logging
59
+ require 'log4r'
60
+
61
+ level = nil
62
+ begin
63
+ level = Log4r.const_get(ENV['VAGRANT_LOG'].upcase)
64
+ rescue NameError
65
+ # This means that the logging constant wasn't found,
66
+ # which is fine. We just keep `level` as `nil`. But
67
+ # we tell the user.
68
+ level = nil
69
+ end
70
+
71
+ # Some constants, such as "true" resolve to booleans, so the
72
+ # above error checking doesn't catch it. This will check to make
73
+ # sure that the log level is an integer, as Log4r requires.
74
+ level = nil if !level.is_a?(Integer)
75
+
76
+ # Set the logging level on all "vagrant" namespaced logs as long as
77
+ # we have a valid level
78
+ if level
79
+ logger = Log4r::Logger.new("vagrant_azure")
80
+ logger.outputters = Log4r::Outputter.stderr
81
+ logger.level = level
82
+ logger = nil
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,70 @@
1
+ #--------------------------------------------------------------------------
2
+ # Copyright (c) Microsoft Open Technologies, Inc.
3
+ # All Rights Reserved. Licensed under the Apache 2.0 License.
4
+ #--------------------------------------------------------------------------
5
+ require 'log4r'
6
+ require 'vagrant'
7
+
8
+ module VagrantPlugins
9
+ module WinAzure
10
+ class Provider < Vagrant.plugin('2', :provider)
11
+ attr_reader :driver
12
+
13
+ def initialize(machine)
14
+ @machine = machine
15
+
16
+ # Load the driver
17
+ machine_id_changed
18
+ end
19
+
20
+ def action(name)
21
+ # Attempt to get the action method from the Action class if it
22
+ # exists, otherwise return nil to show that we don't support the
23
+ # given action.
24
+ action_method = "action_#{name}"
25
+ return Action.send(action_method) if Action.respond_to?(action_method)
26
+ nil
27
+ end
28
+
29
+ def machine_id_changed
30
+ @driver = Driver.new(@machine)
31
+ end
32
+
33
+ def ssh_info
34
+ # Run a custom action called "read_ssh_info" which does what it
35
+ # says and puts the resulting SSH info into the `:machine_ssh_info`
36
+ # key in the environment.
37
+ env = @machine.action('read_ssh_info')
38
+ env[:machine_ssh_info]
39
+ end
40
+
41
+ def rdp_info
42
+ env = @machine.action('read_rdp_info')
43
+ env[:machine_ssh_info]
44
+ end
45
+
46
+ def winrm_info
47
+ env = @machine.action('read_winrm_info')
48
+ env[:machine_ssh_info]
49
+ end
50
+
51
+ def state
52
+ # Run a custom action we define called "read_state" which does what it
53
+ # says. It puts the state in the `:machine_state_id` key in the env
54
+ env = @machine.action('read_state')
55
+ state_id = env[:machine_state_id]
56
+
57
+ short = "Machine's current state is #{state_id}"
58
+ long = ""
59
+
60
+ # Return the MachineState object
61
+ Vagrant::MachineState.new(state_id, short, long)
62
+ end
63
+
64
+ def to_s
65
+ id = @machine.id.nil? ? 'new' : @machine.id
66
+ "Azure (#{id})"
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,109 @@
1
+ #---------------------------------------------------------------------------
2
+ # Copyright (c) Microsoft Open Technologies, Inc.
3
+ # All Rights Reserved. Licensed under the Apache 2.0 License.
4
+ #---------------------------------------------------------------------------
5
+ require 'fileutils'
6
+ require 'tempfile'
7
+
8
+ module VagrantPlugins
9
+ module WinAzure
10
+ module Provisioner
11
+ class Puppet
12
+ attr_reader :provisioner
13
+
14
+ def initialize(env)
15
+ @env = env
16
+ @provisioner = env[:provisioner]
17
+ end
18
+
19
+ def provision_for_windows
20
+ options = [config.options].flatten
21
+ @module_paths = provisioner.instance_variable_get('@module_paths')
22
+ @hiera_config_path = provisioner.instance_variable_get(
23
+ '@hiera_config_path'
24
+ )
25
+ @manifest_file = provisioner.instance_variable_get('@manifest_file')
26
+
27
+ # Copy the manifests directory to the guest
28
+ if config.manifests_path[0].to_sym == :host
29
+ @env[:machine].provider.driver.upload(
30
+ File.expand_path(
31
+ config.manifests_path[1], @env[:machine].env.root_path
32
+ ),
33
+ provisioner.manifests_guest_path
34
+ )
35
+ end
36
+
37
+ # Copy the module paths to the guest
38
+ @module_paths.each do |from, to|
39
+ @env[:machine].provider.driver.upload(from.to_s, to)
40
+ end
41
+
42
+ module_paths = @module_paths.map { |_, to| to }
43
+ unless module_paths.empty?
44
+ # Prepend the default module path
45
+ module_paths.unshift('/ProgramData/PuppetLabs/puppet/etc/modules')
46
+
47
+ # Add the command line switch to add the module path
48
+ options << "--modulepath \"#{module_paths.join(':')}\""
49
+ end
50
+
51
+ if @hiera_config_path
52
+ options << "--hiera_config=#{@hiera_config_path}"
53
+
54
+ # Upload Hiera configuration if we have it
55
+ local_hiera_path = File.expand_path(
56
+ config.hiera_config_path,
57
+ @env[:machine].env.root_path
58
+ )
59
+
60
+ @env[:machine].provider.driver.upload(
61
+ local_hiera_path,
62
+ @hiera_config_path
63
+ )
64
+ end
65
+
66
+ options << "--manifestdir #{provisioner.manifests_guest_path}"
67
+ options << "--detailed-exitcodes"
68
+ options << @manifest_file
69
+ options = options.join(' ')
70
+
71
+ # Build up the custome facts if we have any
72
+ facter = ''
73
+ unless config.facter.empty?
74
+ facts = []
75
+ config.facter.each do |key, value|
76
+ facts << "FACTER_#{key}='#{value}'"
77
+ end
78
+
79
+ facter = "#{facts.join(' ')}"
80
+ end
81
+
82
+ command = "#{facter}puppet apply #{options}"
83
+
84
+ if config.working_directory
85
+ command = "cd #{config.working_directory} && #{command}"
86
+ end
87
+
88
+ @env[:ui].info I18n.t(
89
+ 'vagrant_azure.provisioners.puppet.running_puppet',
90
+ manifest: config.manifest_file
91
+ )
92
+ @env[:ui].info 'Executing puppet script in Windows Azure VM'
93
+ @env[:machine].provider.driver.run_remote_ps(command) do |type, data|
94
+ # Output the data with the proper color based on the stream.
95
+ if (type == :stdout || type == :stderr)
96
+ @env[:ui].detail data
97
+ end
98
+ end
99
+ end
100
+
101
+ protected
102
+
103
+ def config
104
+ provisioner.config
105
+ end
106
+ end
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,41 @@
1
+ #-------------------------------------------------------------------------
2
+ # Copyright (c) Microsoft Open Technologies, Inc.
3
+ # All Rights Reserved. Licensed under the Apache 2.0 License.
4
+ #--------------------------------------------------------------------------
5
+ param (
6
+ [string]$guest_ip = $(throw "-guest_ip is required."),
7
+ [string]$username = $(throw "-guest_username is required."),
8
+ [string]$password = $(throw "-guest_password is required.")
9
+ )
10
+
11
+ # Include the following modules
12
+ $presentDir = Split-Path -parent $PSCommandPath
13
+ . ([System.IO.Path]::Combine($presentDir, "utils\write_messages.ps1"))
14
+ . ([System.IO.Path]::Combine($presentDir, "utils\create_session.ps1"))
15
+
16
+ try {
17
+ $response = Create-Remote-Session $guest_ip $username $password
18
+ if (!$response["session"] -and $response["error"]) {
19
+ Write-Host $response["error"]
20
+ return
21
+ }
22
+ function Remote-Execute() {
23
+ $winrm_state = ""
24
+ get-service winrm | ForEach-Object {
25
+ $winrm_state = $_.status
26
+ }
27
+ return "$winrm_state"
28
+ }
29
+ $result = Invoke-Command -Session $response["session"] -ScriptBlock ${function:Remote-Execute} -ErrorAction "stop"
30
+ $resultHash = @{
31
+ message = "$result"
32
+ }
33
+ Write-Output-Message $resultHash
34
+ } catch {
35
+ $errortHash = @{
36
+ type = "PowerShellError"
37
+ error ="Failed to copy file $_"
38
+ }
39
+ Write-Error-Message $errortHash
40
+ return
41
+ }
@@ -0,0 +1,31 @@
1
+ #-------------------------------------------------------------------------
2
+ # Copyright (c) Microsoft Open Technologies, Inc.
3
+ # All Rights Reserved. Licensed under the Apache 2.0 License.
4
+ #--------------------------------------------------------------------------
5
+
6
+ param (
7
+ [string]$vm_id = $(throw "-vm_id is required."),
8
+ [string]$path = $(throw "-path is required.")
9
+ )
10
+
11
+ # Include the following modules
12
+ $presentDir = Split-Path -parent $PSCommandPath
13
+ . ([System.IO.Path]::Combine($presentDir, "utils\write_messages.ps1"))
14
+
15
+
16
+ # Export the Virtual Machine
17
+ try {
18
+ $vm = Get-Vm -Id $vm_id
19
+ $vm | Export-VM -Path $path -ErrorAction "stop"
20
+ $name = $vm.name
21
+ $resultHash = @{
22
+ name = "$name"
23
+ }
24
+ Write-Output-Message $resultHash
25
+ } catch {
26
+ $errortHash = @{
27
+ type = "PowerShellError"
28
+ error = "Failed to export a VM $_"
29
+ }
30
+ Write-Error-Message $errortHash
31
+ }
@@ -0,0 +1,145 @@
1
+ #-------------------------------------------------------------------------
2
+ # Copyright (c) Microsoft Open Technologies, Inc.
3
+ # All Rights Reserved. Licensed under the Apache 2.0 License.
4
+ #--------------------------------------------------------------------------
5
+
6
+ param (
7
+ [string]$vm_id = $(throw "-vm_id is required."),
8
+ [string]$guest_ip = $(throw "-guest_ip is required."),
9
+ [string]$username = $(throw "-guest_username is required."),
10
+ [string]$password = $(throw "-guest_password is required."),
11
+ [string]$host_path = $(throw "-host_path is required."),
12
+ [string]$guest_path = $(throw "-guest_path is required.")
13
+ )
14
+
15
+ # Include the following modules
16
+ $presentDir = Split-Path -parent $PSCommandPath
17
+ $modules = @()
18
+ $modules += $presentDir + "\utils\write_messages.ps1"
19
+ $modules += $presentDir + "\utils\create_session.ps1"
20
+ forEach ($module in $modules) { . $module }
21
+
22
+ function Get-file-hash($source_path, $delimiter) {
23
+ $source_files = @()
24
+ (Get-ChildItem $source_path -rec -force | ForEach-Object -Process {
25
+ Get-FileHash -Path $_.FullName -Algorithm MD5 } ) |
26
+ ForEach-Object -Process {
27
+ $source_files += $_.Path.Replace($source_path, "") + $delimiter + $_.Hash
28
+ }
29
+ $source_files
30
+ }
31
+
32
+ function Get-remote-file-hash($source_path, $delimiter, $session) {
33
+ Invoke-Command -Session $session -ScriptBlock ${function:Get-file-hash} -ArgumentList $source_path, $delimiter
34
+ # TODO:
35
+ # Check if remote PS Scripting errors
36
+ }
37
+
38
+ function Sync-Remote-Machine($machine, $remove_files, $copy_files, $host_path, $guest_path) {
39
+ ForEach ($item in $copy_files) {
40
+ $from = $host_path + $item
41
+ $to = $guest_path + $item
42
+ # Copy VM can also take a VM object
43
+ Copy-VMFile -VM $machine -SourcePath $from -DestinationPath $to -CreateFullPath -FileSource Host -Force
44
+ }
45
+ }
46
+
47
+ function Create-Remote-Folders($empty_source_folders, $guest_path) {
48
+
49
+ ForEach ($item in $empty_source_folders) {
50
+ $new_name = $guest_path + $item
51
+ New-Item "$new_name" -type directory -Force
52
+ }
53
+ }
54
+
55
+ function Create-Guest-Folder($guest_path) {
56
+ try {
57
+ if (Test-Path $guest_path) {
58
+ $junction = Get-Item $guest_path
59
+ $junction.Delete()
60
+ }
61
+ }
62
+ # Catch any [IOException]
63
+ catch {
64
+ Remove-Item "$guest_path" -Force -Recurse
65
+ }
66
+ New-Item "$guest_path" -type directory -Force
67
+ }
68
+
69
+ function Get-Empty-folders-From-Source($host_path) {
70
+ Get-ChildItem $host_path -recurse |
71
+ Where-Object {$_.PSIsContainer -eq $True} |
72
+ Where-Object {$_.GetFiles().Count -eq 0} |
73
+ Select-Object FullName | ForEach-Object -Process {
74
+ $empty_source_folders += ($_.FullName.Replace($host_path, ""))
75
+ }
76
+ }
77
+
78
+ $delimiter = " || "
79
+
80
+ $machine = Get-VM -Id $vm_id
81
+
82
+ # FIXME: PowerShell guys please fix this.
83
+ # The below script checks for all VMIntegrationService which are not enabled
84
+ # and will enable this.
85
+ # When when all the services are enabled this throws an error.
86
+ # Enable VMIntegrationService to true
87
+ try {
88
+ Get-VM -Id $vm_id | Get-VMIntegrationService -Name "Guest Service Interface" | Enable-VMIntegrationService -Passthru
89
+ }
90
+ catch { }
91
+
92
+
93
+ $response = Create-Remote-Session $guest_ip $username $password
94
+ if (!$response["session"] -and $response["error"]) {
95
+ $errortHash = @{
96
+ type = "PowerShellError"
97
+ error = $response["error"]
98
+ }
99
+ Write-Error-Message $errortHash
100
+ return
101
+ }
102
+
103
+ $session = $response["session"]
104
+ # Create the guest folder if not exist
105
+ $result = Invoke-Command -Session $session -ScriptBlock ${function:Create-Guest-Folder} -ArgumentList $guest_path
106
+
107
+
108
+ $source_files = Get-file-hash $host_path $delimiter
109
+ $destination_files = Get-remote-file-hash $guest_path $delimiter $session
110
+
111
+ if (!$destination_files) {
112
+ $destination_files = @()
113
+ }
114
+ if (!$source_files) {
115
+ $source_files = @()
116
+ }
117
+
118
+ # Compare source and destination files
119
+ $remove_files = @()
120
+ $copy_files = @()
121
+
122
+
123
+ Compare-Object -ReferenceObject $source_files -DifferenceObject $destination_files | ForEach-Object {
124
+ if ($_.SideIndicator -eq '=>') {
125
+ $remove_files += $_.InputObject.Split($delimiter)[0]
126
+ } else {
127
+ $copy_files += $_.InputObject.Split($delimiter)[0]
128
+ }
129
+ }
130
+
131
+ # Update the files to remote machine
132
+ Sync-Remote-Machine $machine $remove_files $copy_files $host_path $guest_path
133
+
134
+ # Create any empty folders which missed to sync to remote machine
135
+ $empty_source_folders = @()
136
+ $directories = Get-Empty-folders-From-Source $host_path
137
+
138
+ $result = Invoke-Command -Session $session -ScriptBlock ${function:Create-Remote-Folders} -ArgumentList $empty_source_folders, $guest_path
139
+ # Always remove the connection after Use
140
+ Remove-PSSession -Id $session.Id
141
+
142
+ $resultHash = @{
143
+ message = "OK"
144
+ }
145
+ Write-Output-Message $resultHash