diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e98090c --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +id +.DS_Store +Gemfile.lock +azure.box +Vagrantfile +!example_box/Vagrantfile diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..c7d3156 --- /dev/null +++ b/Gemfile @@ -0,0 +1,19 @@ +#-------------------------------------------------------------------------- +# Copyright (c) Microsoft Open Technologies, Inc. +# All Rights Reserved. Licensed under the MIT License. +#-------------------------------------------------------------------------- + +source 'https://rubygems.org' + +gemspec + +group :development do + # We depend on Vagrant for development, but we don't add it as a + # gem dependency because we expect to be installed within the + # Vagrant environment itself using `vagrant plugin`. + gem "vagrant", :git => "git://github.com/mitchellh/vagrant.git" +end + +group :plugins do + gem 'vagrant-azure', path: '.' +end diff --git a/README.md b/README.md index ceec0a9..a147b84 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,9 @@ -vagrantazure -============ +# Vagrant Azure Provider + +This is a [Vagrant](http://www.vagrantup.com) 1.2+ plugin that adds [Windows Azure](https://www.windowsazure.com) +provider to Vagrant, allowing Vagrant to control and provision machines in +Windows Azure. + +**NOTE:** This plugin requires Vagrant 1.2+, + +## Configuration diff --git a/example_box/Vagrantfile b/example_box/Vagrantfile new file mode 100644 index 0000000..8072f75 --- /dev/null +++ b/example_box/Vagrantfile @@ -0,0 +1,11 @@ +# -*- mode: ruby -*- +# vi: set ft=ruby : + +Vagrant.require_plugin "vagrant-azure" + +Vagrant.configure('2') do |config| + config.vm.provider :azure do |azure| + azure.vm_name = 'cloudinittest' + azure.cloud_service_name = 'cloudinittest' + end +end diff --git a/example_box/metadata.json b/example_box/metadata.json new file mode 100644 index 0000000..99219bb --- /dev/null +++ b/example_box/metadata.json @@ -0,0 +1,3 @@ +{ + "provider": "azure" +} diff --git a/lib/vagrant-azure.rb b/lib/vagrant-azure.rb new file mode 100644 index 0000000..8809bff --- /dev/null +++ b/lib/vagrant-azure.rb @@ -0,0 +1,23 @@ +#-------------------------------------------------------------------------- +# Copyright (c) Microsoft Open Technologies, Inc. +# All Rights Reserved. Licensed under the MIT License. +#-------------------------------------------------------------------------- +require 'pathname' + +require 'vagrant-azure/plugin' + +module VagrantPlugins + module WinAzure + lib_path = Pathname.new(File.expand_path('../vagrant-azure', __FILE__)) + autoload :Action, lib_path.join('action') + autoload :Error, lib_path.join('errors') + + # This returns the path to the source of this plugin. + # + # @return [Pathname] + def self.source_root + @source_root ||= Pathname.new(File.expand_path('../../', __FILE__)) + end + end +end + diff --git a/lib/vagrant-azure/action.rb b/lib/vagrant-azure/action.rb new file mode 100644 index 0000000..66da712 --- /dev/null +++ b/lib/vagrant-azure/action.rb @@ -0,0 +1,189 @@ +#-------------------------------------------------------------------------- +# Copyright (c) Microsoft Open Technologies, Inc. +# All Rights Reserved. Licensed under the MIT License. +#-------------------------------------------------------------------------- +require 'pathname' + +require 'vagrant/action/builder' + +module VagrantPlugins + module WinAzure + module Action + # Include the built-in modules so we can use them as top-level things. + include Vagrant::Action::Builtin + + # This action is called to halt the remote machine. + def self.action_halt + Vagrant::Action::Builder.new.tap do |b| + b.use ConfigValidate + b.use Call, IsCreated do |env, b2| + if !env[:result] + b2.use MessageNotCreated + next + end + + b2.use ConnectAzure + b2.use StopInstance + end + end + end + + # This action is called to terminate the remote machine. + def self.action_destroy + Vagrant::Action::Builder.new.tap do |b| + b.use Call, DestroyConfirm do |env, b2| + if env[:result] + b2.use ConfigValidate + b.use Call, IsCreated do |env2, b3| + if !env2[:result] + b3.use MessageNotCreated + next + end + end + + b2.use ConnectAzure + b2.use TerminateInstance + b2.use ProvisionerCleanup if defined?(ProvisionerCleanup) + else + b2.use MessageWillNotDestroy + end + end + end + end + + # This action is called when `vagrant provision` is called. + def self.action_provision + Vagrant::Action::Builder.new.tap do |b| + b.use ConfigValidate + b.use Call, IsCreated do |env, b2| + if !env[:result] + b2.use MessageNotCreated + next + end + + b2.use Provision + b2.use SyncFolders + end + end + end + + # This action is called to read the SSH info of the machine. The + # resulting state is expected to be put into the `:machine_ssh_info` key. + def self.action_read_ssh_info + Vagrant::Action::Builder.new.tap do |b| + b.use ConfigValidate + b.use ConnectAzure + b.use ReadSSHInfo + end + end + + def self.action_read_state + Vagrant::Action::Builder.new.tap do |b| + b.use ConfigValidate + b.use ConnectAzure + b.use ReadState + end + end + + # This action is called to SSH into the machine + def self.action_ssh + Vagrant::Action::Builder.new.tap do |b| + b.use ConfigValidate + b.use Call, IsCreated do |env, b2| + if !env[:result] + b2.use MessageNotCreated + next + end + + b2.use SSHExec + end + end + end + + def self.action_ssh_run + Vagrant::Action::Builder.new.tap do |b| + b.use ConfigValidate + b.use Call, IsCreated do |env, b2| + if !env[:result] + b2.use MessageNotCreated + next + end + + b2.use SSHRun + end + end + end + + def self.action_prepare_boot + Vagrant::Action::Builder.new.tap do |b| + b.use Provision + b.use SyncFolders + b.use WarnNetworks + end + end + + # This action is called to bring the box up from nothing + def self.action_up + Vagrant::Action::Builder.new.tap do |b| + b.use HandleBoxUrl + b.use ConfigValidate + b.use ConnectAzure + b.use Call, IsCreated do |env1, b1| + if env1[:result] + b1.use Call, IsStopped do |env2, b2| + if env2[:result] + # b2.use action_prepare_boot + b2.use StartInstance # restart this instance + else + b2.use MessageAlreadyCreated + end + end + else + # b1.use action_prepare_boot + b1.use RunInstance # Launch a new instance + end + end + end + end + + def self.action_reload + Vagrant::Action::Builder.new.tap do |b| + b.use ConfigValidate + b.use ConnectAzure + b.use Call, IsCreated do |env, b2| + if !env[:result] + b2.use MessageNotCreated + next + end + + b2.use action_halt + b2.use Call, WaitForState, :StoppedDeallocated, 120 do |env2, b3| + if env2[:result] + b3.use action_up + end + end + end + end + end + + # The autoload farm + action_root = Pathname.new(File.expand_path('../action', __FILE__)) + autoload :ConnectAzure, action_root.join('connect_azure') + autoload :IsCreated, action_root.join('is_created') + autoload :IsStopped, action_root.join('is_stopped') + autoload :MessageAlreadyCreated, action_root.join('message_already_created') + autoload :MessageNotCreated, action_root.join('message_not_created') + autoload :MessageWillNotDestroy, action_root.join('message_will_not_destroy') + # autoload :ReadSSHInfo, action_root.join('read_ssh_info') + autoload :ReadState, action_root.join('read_state') + autoload :RunInstance, action_root.join('run_instance') + autoload :StartInstance, action_root.join('start_instance') + autoload :StopInstance, action_root.join('stop_instance') + # autoload :SyncFolders, action_root.join('sync_folders') + autoload :TerminateInstance, action_root.join('terminate_instance') + # autoload :TimedProvision, action_root.join('timed_provision') + autoload :WaitForState, action_root.join('wait_for_state') + # autoload :WarnNetworks, action_root.join('warn_networks') + end + end +end diff --git a/lib/vagrant-azure/action/connect_azure.rb b/lib/vagrant-azure/action/connect_azure.rb new file mode 100644 index 0000000..dd1e1bd --- /dev/null +++ b/lib/vagrant-azure/action/connect_azure.rb @@ -0,0 +1,41 @@ +#--------------------------------------------------------------------------- +# Copyright (c) Microsoft Open Technologies, Inc. +# All Rights Reserved. Licensed under the MIT License. +#-------------------------------------------------------------------------- +require 'azure' +require 'log4r' + +module VagrantPlugins + module WinAzure + module Action + class ConnectAzure + def initialize(app, env) + @app = app + @logger = Log4r::Logger.new('vagrant_azure::action::connect_aws') + end + + def call (env) + config = env[:machine].provider_config + + env[:ui].warn "Subscription ID: [#{config.subscription_id}]" + env[:ui].warn "Mangement Certificate: [#{config.mgmt_certificate}]" + env[:ui].warn "Mangement Endpoint: [#{config.mgmt_endpoint}]" + env[:ui].warn "Storage Account Name: [#{config.storage_acct_name}]" + env[:ui].warn "Storage Access Key: [#{config.storage_access_key}]" + + Azure.configure do |c| + c.subscription_id = config.subscription_id + c.management_certificate = config.mgmt_certificate + c.management_endpoint = config.mgmt_endpoint + c.storage_account_name = config.storage_acct_name + c.storage_access_key = config.storage_access_key + end + + env[:azure_vm_service] = Azure::VirtualMachineManagementService.new + + @app.call(env) + end + end + end + end +end diff --git a/lib/vagrant-azure/action/is_created.rb b/lib/vagrant-azure/action/is_created.rb new file mode 100644 index 0000000..fdc4c24 --- /dev/null +++ b/lib/vagrant-azure/action/is_created.rb @@ -0,0 +1,24 @@ +#-------------------------------------------------------------------------- +# Copyright (c) Microsoft Open Technologies, Inc. +# All Rights Reserved. Licensed under the MIT License. +#-------------------------------------------------------------------------- + +# This is a dummy implementation. +# TODO call Azure APIs to get the state + +module VagrantPlugins + module WinAzure + module Action + class IsCreated + def initialize(app, env) + @app = app + end + + def call(env) + env[:result] = env[:machine].state.id != :NotCreated + @app.call(env) + end + end + end + end +end diff --git a/lib/vagrant-azure/action/is_stopped.rb b/lib/vagrant-azure/action/is_stopped.rb new file mode 100644 index 0000000..eda811a --- /dev/null +++ b/lib/vagrant-azure/action/is_stopped.rb @@ -0,0 +1,24 @@ +#-------------------------------------------------------------------------- +# Copyright (c) Microsoft Open Technologies, Inc. +# All Rights Reserved. Licensed under the MIT License. +#-------------------------------------------------------------------------- + +# This is a dummy implementation. +# TODO call Azure APIs to get the state + +module VagrantPlugins + module WinAzure + module Action + class IsStopped + def initialize(app, env) + @app = app + end + + def call(env) + env[:result] = env[:machine].state.id == :StoppedDeallocated + @app.call(env) + end + end + end + end +end diff --git a/lib/vagrant-azure/action/message_already_created.rb b/lib/vagrant-azure/action/message_already_created.rb new file mode 100644 index 0000000..97ff760 --- /dev/null +++ b/lib/vagrant-azure/action/message_already_created.rb @@ -0,0 +1,21 @@ +#-------------------------------------------------------------------------- +# Copyright (c) Microsoft Open Technologies, Inc. +# All Rights Reserved. Licensed under the MIT License. +#-------------------------------------------------------------------------- +module VagrantPlugins + module WinAzure + module Action + class MessageAlreadyCreated + def initialize(app, env) + @app = app + end + + def call(env) + env[:ui].info( + I18n.t('vagrant_azure.already_status', :status => 'created') + ) + end + end + end + end +end diff --git a/lib/vagrant-azure/action/message_not_created.rb b/lib/vagrant-azure/action/message_not_created.rb new file mode 100644 index 0000000..be9f15e --- /dev/null +++ b/lib/vagrant-azure/action/message_not_created.rb @@ -0,0 +1,20 @@ +#-------------------------------------------------------------------------- +# Copyright (c) Microsoft Open Technologies, Inc. +# All Rights Reserved. Licensed under the MIT License. +#-------------------------------------------------------------------------- +module VagrantPlugins + module WinAzure + module Action + class MessageNotCreated + def initialize(app, env) + @app = app + end + + def env(env) + env[:ui].info(I18n.t('vagrant_azure.not_created')) + @app.call(env) + end + end + end + end +end diff --git a/lib/vagrant-azure/action/message_will_not_destroy.rb b/lib/vagrant-azure/action/message_will_not_destroy.rb new file mode 100644 index 0000000..4152ca0 --- /dev/null +++ b/lib/vagrant-azure/action/message_will_not_destroy.rb @@ -0,0 +1,21 @@ +#-------------------------------------------------------------------------- +# Copyright (c) Microsoft Open Technologies, Inc. +# All Rights Reserved. Licensed under the MIT License. +#-------------------------------------------------------------------------- +module VagrantPlugins + module WinAzure + module Action + class MessageWillNotDestroy + def initialize(app, env) + @app = app + end + + def call(env) + env[:machine].id =~ /@/ + env[:ui].info(I18n.t('vagrant_azure.will_not_destroy', :name => $`)) + @app.call(env) + end + end + end + end +end diff --git a/lib/vagrant-azure/action/read_state.rb b/lib/vagrant-azure/action/read_state.rb new file mode 100644 index 0000000..3907c42 --- /dev/null +++ b/lib/vagrant-azure/action/read_state.rb @@ -0,0 +1,44 @@ +#-------------------------------------------------------------------------- +# Copyright (c) Microsoft Open Technologies, Inc. +# All Rights Reserved. Licensed under the MIT License. +#-------------------------------------------------------------------------- +require 'log4r' + +module VagrantPlugins + module WinAzure + module Action + class ReadState + def initialize(app, env) + @app = app + @logger = Log4r::Logger.new('vagrant_azure::action::read_state') + end + + def call(env) + env[:machine_state_id] = read_state(env) + + @app.call(env) + end + + def read_state(env) + return :NotCreated if env[:machine].id.nil? + + env[:machine].id =~ /@/ + + env[:ui].info "Attempting to read state for #{$`} in #{$'}" + + vm = env[:azure_vm_service].get_virtual_machine($`, $') + + env[:ui].info "VM Status: #{vm.status.to_sym}" + + if vm.nil? + @logger.info 'Machine cannot be found' + env[:machine].id = nil + return :NotCreated + end + + return vm.status.to_sym + end + end + end + end +end diff --git a/lib/vagrant-azure/action/run_instance.rb b/lib/vagrant-azure/action/run_instance.rb new file mode 100644 index 0000000..ed380f9 --- /dev/null +++ b/lib/vagrant-azure/action/run_instance.rb @@ -0,0 +1,65 @@ +#--------------------------------------------------------------------------- +# Copyright (c) Microsoft Open Technologies, Inc. +# All Rights Reserved. Licensed under the MIT License. +#-------------------------------------------------------------------------- +require 'log4r' +require 'json' +require 'azure' + +require 'vagrant/util/retryable' + +module VagrantPlugins + module WinAzure + module Action + class RunInstance + include Vagrant::Util::Retryable + + def initialize(app, env) + @app = app + @logger = Log4r::Logger.new('vagrant_azure::action::run_instance') + end + + def call(env) + config = env[:machine].provider_config + + params = { + vm_name: config.vm_name, + vm_user: config.vm_user, + password: config.vm_password, + image: config.vm_image, + location: config.vm_location, + affinity_group: config.vm_affinity_group + } + + options = { + storage_account_name: config.storage_acct_name, + cloud_service_name: config.cloud_service_name, + deployment_name: config.deployment_name, + tcp_endpoints: config.tcp_endpoints, + private_key_file: config.ssh_private_key_file, + certificate_file: config.ssh_certificate_file, + ssh_port: config.ssh_port, + vm_size: config.vm_size, + winrm_transport: config.winrm_transport, + availability_set_name: config.availability_set_name + } + + add_role = config.add_role + + env[:ui].info(params.inspect) + env[:ui].info(options.inspect) + env[:ui].info("Add Role? - #{add_role}") + + server = env[:azure_vm_service].create_virtual_machine( + params, options, add_role + ) + + # TODO: Exception/Error Handling + env[:machine].id = "#{server.vm_name}@#{server.cloud_service_name}" + + @app.call(env) + end + end + end + end +end diff --git a/lib/vagrant-azure/action/start_instance.rb b/lib/vagrant-azure/action/start_instance.rb new file mode 100644 index 0000000..95ec542 --- /dev/null +++ b/lib/vagrant-azure/action/start_instance.rb @@ -0,0 +1,35 @@ +#-------------------------------------------------------------------------- +# Copyright (c) Microsoft Open Technologies, Inc. +# All Rights Reserved. Licensed under the MIT License. +#-------------------------------------------------------------------------- +require 'log4r' + +# require 'vagrant/util/retryable' + +# Barebones basic implemenation. This a work in progress in very early stages +module VagrantPlugins + module WinAzure + module Action + # This starts a stopped instance + class StartInstance + # include Vagrant:Util::Retryable + + def initialize(app, env) + @app = app + @logger = Log4r::Logger.new('vagrant_azure:action::start_instance') + end + + def call(env) + env[:machine].id = "#{env[:machine].provider_config.vm_name}@#{env[:machine].provider_config.cloud_service_name}" unless env[:machine].id + env[:machine].id =~ /@/ + + env[:ui].info "Attempting to start '#{$`}' in '#{$'}'" + + env[:azure_vm_service].start_virtual_machine($`, $') + + @app.call(env) + end + end + end + end +end diff --git a/lib/vagrant-azure/action/stop_instance.rb b/lib/vagrant-azure/action/stop_instance.rb new file mode 100644 index 0000000..f45247b --- /dev/null +++ b/lib/vagrant-azure/action/stop_instance.rb @@ -0,0 +1,38 @@ +#-------------------------------------------------------------------------- +# Copyright (c) Microsoft Open Technologies, Inc. +# All Rights Reserved. Licensed under the MIT License. +#-------------------------------------------------------------------------- +require 'log4r' + +# Barebones basic implemenation. This a work in progress in very early stages +module VagrantPlugins + module WinAzure + module Action + class StopInstance + def initialize(app, env) + @app = app + @logger = Log4r::Logger.new('vagrant_azure::action::stop_instance') + end + + def call(env) + if env[:machine].state.id == :StoppedDeallocated + env[:ui].info( + I18n.t('vagrant_azure.already_status', :status => 'stopped.') + ) + else + env[:machine].id =~ /@/ + env[:ui].info( + I18n.t( + 'vagrant_azure.stopping', + :vm_name => $`, + :cloud_service_name => $' + ) + ) + env[:azure_vm_service].shutdown_virtual_machine($`, $') + end + @app.call(env) + end + end + end + end +end diff --git a/lib/vagrant-azure/action/terminate_instance.rb b/lib/vagrant-azure/action/terminate_instance.rb new file mode 100644 index 0000000..1b55478 --- /dev/null +++ b/lib/vagrant-azure/action/terminate_instance.rb @@ -0,0 +1,34 @@ +#-------------------------------------------------------------------------- +# Copyright (c) Microsoft Open Technologies, Inc. +# All Rights Reserved. Licensed under the MIT License. +#-------------------------------------------------------------------------- +require 'log4r' + +module VagrantPlugins + module WinAzure + module Action + class TerminateInstance + def initialize(app, env) + @app = app + @logger = Log4r::Logger.new('vagrant_azure::action::terminate_instance') + end + + def call(env) + env[:machine].id =~ /@/ + + vm = env[:azure_vm_service].get_virtual_machine($`, $') + + if vm.nil? + # machine not found. assuming it was not created or destroyed + env[:ui].info (I18n.t('vagrant_azure.not_created')) + else + env[:azure_vm_service].delete_virtual_machine($`, $') + env[:machine].id = nil + end + + @app.call(env) + end + end + end + end +end diff --git a/lib/vagrant-azure/action/wait_for_state.rb b/lib/vagrant-azure/action/wait_for_state.rb new file mode 100644 index 0000000..1aa9038 --- /dev/null +++ b/lib/vagrant-azure/action/wait_for_state.rb @@ -0,0 +1,45 @@ +#-------------------------------------------------------------------------- +# Copyright (c) Microsoft Open Technologies, Inc. +# All Rights Reserved. Licensed under the MIT License. +#-------------------------------------------------------------------------- +require 'log4r' +require 'timeout' + +module VagrantPlugins + module WinAzure + module Action + class WaitForState + def initialize(app, state, timeout) + @app = app + @state = state + @timeout = timeout + @logger = Log4r::Logger.new("vagrant_azure::action::wait_for_state") + end + + def call(env) + env[:result] = true + + if env[:machine].state.id == @state + @logger.info( + I18n.t('vagrant_azure.already_status', :status => @state) + ) + else + @logger.info("Waiting for machine to reache state #{@state}") + + begin + Timeout.timeout(@timeout) do + until env[:machine].state.id == @state + sleep 5 + end + end + rescue Timeout::Error + env[:result] = false # couldn't reach state in time + end + end + + @app.call(env) + end + end + end + end +end diff --git a/lib/vagrant-azure/config.rb b/lib/vagrant-azure/config.rb new file mode 100644 index 0000000..9b2088a --- /dev/null +++ b/lib/vagrant-azure/config.rb @@ -0,0 +1,131 @@ +#-------------------------------------------------------------------------- +# Copyright (c) Microsoft Open Technologies, Inc. +# All Rights Reserved. Licensed under the MIT License. +#-------------------------------------------------------------------------- +require 'vagrant' + +module VagrantPlugins + module WinAzure + class Config < Vagrant.plugin('2', :config) + attr_accessor :storage_acct_name + attr_accessor :storage_access_key + attr_accessor :mgmt_certificate + attr_accessor :mgmt_endpoint + attr_accessor :subscription_id + + attr_accessor :vm_name + attr_accessor :vm_user + attr_accessor :vm_password + attr_accessor :vm_image + attr_accessor :vm_location + attr_accessor :vm_affinity_group + + attr_accessor :cloud_service_name + attr_accessor :deployment_name + attr_accessor :tcp_endpoints + attr_accessor :ssh_private_key_file + attr_accessor :ssh_certificate_file + attr_accessor :ssh_port + attr_accessor :vm_size + attr_accessor :winrm_transport + attr_accessor :availability_set_name + attr_accessor :add_role + + def initialize + @storage_acct_name = UNSET_VALUE + @storage_access_key = UNSET_VALUE + @mgmt_certificate = UNSET_VALUE + @mgmt_endpoint = UNSET_VALUE + @subscription_id = UNSET_VALUE + + @vm_name = UNSET_VALUE + @vm_user = UNSET_VALUE + @vm_password = UNSET_VALUE + @vm_image = UNSET_VALUE + @vm_location = UNSET_VALUE + @vm_affinity_group = UNSET_VALUE + + @cloud_service_name = UNSET_VALUE + @deployment_name = UNSET_VALUE + @tcp_endpoints = UNSET_VALUE + @ssh_private_key_file = UNSET_VALUE + @ssh_certificate_file = UNSET_VALUE + @ssh_port = UNSET_VALUE + @vm_size = UNSET_VALUE + @winrm_transport = UNSET_VALUE + @availability_set_name = UNSET_VALUE + @add_role = UNSET_VALUE + end + + def finalize! + @storage_acct_name = ENV["AZURE_STORAGE_ACCOUNT"] if \ + @storage_acct_name == UNSET_VALUE + @storage_access_key = ENV["AZURE_STORAGE_ACCESS_KEY"] if \ + @storage_access_key == UNSET_VALUE + @mgmt_certificate = ENV["AZURE_MANAGEMENT_CERTIFICATE"] if \ + @mgmt_certificate == UNSET_VALUE + @mgmt_endpoint = ENV["AZURE_MANAGEMENT_ENDPOINT"] if \ + @mgmt_endpoint == UNSET_VALUE + @subscription_id = ENV["AZURE_SUBSCRIPTION_ID"] if \ + @subscription_id == UNSET_VALUE + + @vm_name = nil if @vm_name == UNSET_VALUE + @vm_user = 'vagrantone' if @vm_user == UNSET_VALUE + @vm_password = nil if @vm_password == UNSET_VALUE + @vm_image = nil if @vm_image == UNSET_VALUE + @vm_location = nil if @vm_location == UNSET_VALUE + @vm_affinity_group = nil if @vm_affinity_group == UNSET_VALUE + + @cloud_service_name = nil if @cloud_service_name == UNSET_VALUE + @deployment_name = nil if @deployment_name == UNSET_VALUE + @tcp_endpoints = nil if @tcp_endpoints == UNSET_VALUE + @ssh_private_key_file = nil if @ssh_private_key_file == UNSET_VALUE + @ssh_certificate_file = nil if @ssh_certificate_file == UNSET_VALUE + @ssh_port = nil if @ssh_port == UNSET_VALUE + @vm_size = nil if @vm_size == UNSET_VALUE + @winrm_transport = nil if @winrm_transport == UNSET_VALUE + @availability_set_name = nil if @availability_set_name == UNSET_VALUE + + @add_role = false if @add_role == UNSET_VALUE + end + + def merge(other) + super.tap do |result| + result.storage_account_name = other.storage_acct_name || \ + self.storage_acct_name + result.storage_access_key = other.storage_access_key || \ + self.storage_access_key + result.mgmt_certificate = other.mgmt_certificate || \ + self.mgmt_certificate + result.mgmt_endpoint = other.mgmt_endpoint || \ + self.mgmt_endpoint + result.subscription_id = other.subscription_id || \ + self.subscription_id + end + end + + def validate(machine) + errors = _detected_errors + + # Azure connection properties related validation. + errors << "vagrant_azure.subscription_id.requried" if \ + @subscription_id.nil? + errors << "vagrant_azure.mgmt_certificate.requried" if \ + @mgmt_certificate.nil? + errors << "vagrant_azaure.mgmt_endpoint.requried" if \ + @mgmt_endpoint.nil? + errors << "vagrant_azure.storage_acct_name.requried" if\ + @storage_acct_name.nil? + errors << "vagrant_azure.storage_access_key.requried" if\ + @storage_access_key.nil? + + # Azure Virtual Machine related validation + errors << "vagrant_azure.vm_name.required" if @vm_name.nil? + errors << "vagrant_azure.cloud_serivce_name.required" if \ + @cloud_service_name.nil? + + { "Windows Azure Provider" => errors } + end + end + end +end diff --git a/lib/vagrant-azure/plugin.rb b/lib/vagrant-azure/plugin.rb new file mode 100644 index 0000000..30a5a14 --- /dev/null +++ b/lib/vagrant-azure/plugin.rb @@ -0,0 +1,79 @@ +#-------------------------------------------------------------------------- +# Copyright (c) Microsoft Open Technologies, Inc. +# All Rights Reserved. Licensed under the MIT License. +#-------------------------------------------------------------------------- +begin + require 'vagrant' +rescue LoadError + raise 'The Vagrant Azure plugin must be run within Vagrant.' +end + +# This is a sanity check to make sure no one is attempting to install this into +# an early Vagrant version. +if Vagrant::VERSION < '1.2.0' + raise 'The Vagrant Azure plugin is only compatible with Vagrant 1.2+' +end + +module VagrantPlugins + module WinAzure + class Plugin < Vagrant.plugin('2') + name 'azure' + description <<-DESC + This plugin installs a provider that allows Vagrant to manage + machines in Windows Azure. + DESC + + config(:azure, :provider) do + require_relative 'config' + Config + end + + provider(:azure, parallel: true) do + # Setup logging and i18n + setup_logging + setup_i18n + + # Return the provider + require_relative 'provider' + Provider + end + + def self.setup_i18n + I18n.load_path << File.expand_path( + 'locales/en.yml', + WinAzure.source_root + ) + + I18n.reload! + end + + def self.setup_logging + require 'log4r' + + level = nil + begin + level = Log4r.const_get(ENV['VAGRANT_LOG'].upcase) + rescue NameError + # This means that the logging constant wasn't found, + # which is fine. We just keep `level` as `nil`. But + # we tell the user. + level = nil + end + + # Some constants, such as "true" resolve to booleans, so the + # above error checking doesn't catch it. This will check to make + # sure that the log level is an integer, as Log4r requires. + level = nil if !level.is_a?(Integer) + + # Set the logging level on all "vagrant" namespaced logs as long as + # we have a valid level + if level + logger = Log4r::Logger.new("vagrant_azure") + logger.outputters = Log4r::Outputter.stderr + logger.level = level + logger = nil + end + end + end + end +end diff --git a/lib/vagrant-azure/provider.rb b/lib/vagrant-azure/provider.rb new file mode 100644 index 0000000..9edb031 --- /dev/null +++ b/lib/vagrant-azure/provider.rb @@ -0,0 +1,51 @@ +#-------------------------------------------------------------------------- +# Copyright (c) Microsoft Open Technologies, Inc. +# All Rights Reserved. Licensed under the MIT License. +#-------------------------------------------------------------------------- +require 'log4r' +require 'vagrant' + +module VagrantPlugins + module WinAzure + class Provider < Vagrant.plugin('2', :provider) + def initialize(machine) + @machine = machine + end + + def action(name) + # Attempt to get the action method from the Action class if it + # exists, otherwise return nil to show that we don't support the + # given action. + action_method = "action_#{name}" + return Action.send(action_method) if Action.respond_to?(action_method) + nil + end + + def ssh_info + # Run a custom action called "read_ssh_info" which does what it + # says and puts the resulting SSH info into the `:machine_ssh_info` + # key in the environment. + env = @machine.action('read_ssh_info') + env[:machine_ssh_info] + end + + def state + # Run a custom action we define called "read_state" which does what it + # says. It puts the state in the `:machine_state_id` key in the env + env = @machine.action('read_state') + state_id = env[:machine_state_id] + + short = "Machine's current state is #{state_id}" + long = "" + + # Return the MachineState object + Vagrant::MachineState.new(state_id, short, long) + end + + def to_s + id = @machine.id.nil? ? 'new' : @machine.id + "Azure (#{id})" + end + end + end +end diff --git a/lib/vagrant-azure/version.rb b/lib/vagrant-azure/version.rb new file mode 100644 index 0000000..6ac7413 --- /dev/null +++ b/lib/vagrant-azure/version.rb @@ -0,0 +1,10 @@ +#-------------------------------------------------------------------------- +# Copyright (c) Microsoft Open Technologies, Inc. +# All Rights Reserved. Licensed under the MIT License. +#-------------------------------------------------------------------------- + +module VagrantPlugins + module WinAzure + VERSION = '0.0.1.dev' + end +end diff --git a/locales/en.yml b/locales/en.yml new file mode 100644 index 0000000..c0867cc --- /dev/null +++ b/locales/en.yml @@ -0,0 +1,10 @@ +en: + vagrant_azure: + not_created: |- + The machine does not exist. Please run `vagrant up` first. + will_not_destroy: |- + The machine '%{name}' will not be destroyed as the user declined confirmation. + already_status: |- + The machine is already %{status}. + stopping: |- + Stopping %{vm_name} in %{cloud_service_name} diff --git a/vagrant-azure.gemspec b/vagrant-azure.gemspec new file mode 100644 index 0000000..fb8baa1 --- /dev/null +++ b/vagrant-azure.gemspec @@ -0,0 +1,24 @@ +# coding: utf-8 +lib = File.expand_path('../lib', __FILE__) +$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) +require 'vagrant-azure/version' + +Gem::Specification.new do |s| + s.name = "vagrant-azure" + s.version = VagrantPlugins::WinAzure::VERSION + s.authors = ["DeeJay"] + s.email = ["dheeraj@nagwani.in"] + s.description = "Enable Vagrant to manage machines in Azure" + s.summary = "Enable Vagrant to manage machines in Azure" + s.homepage = "" + s.license = "MIT" + + s.require_paths = ["lib"] + + s.add_development_dependency "bundler", "~> 1.3" + s.add_development_dependency "rake" + s.add_development_dependency "minitest" + s.add_development_dependency "minitest-reporters" + s.add_development_dependency "mocha" + s.add_development_dependency "azure" +end