vagrant-azure 1.0.1

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