├── .ruby-version ├── example_box └── metadata.json ├── .travis.yml ├── vsphere_screenshot.png ├── .gitignore ├── lib ├── vSphere │ ├── version.rb │ ├── errors.rb │ ├── action │ │ ├── is_running.rb │ │ ├── is_created.rb │ │ ├── is_suspended.rb │ │ ├── message_not_created.rb │ │ ├── message_not_running.rb │ │ ├── message_not_suspended.rb │ │ ├── message_already_created.rb │ │ ├── close_vsphere.rb │ │ ├── snapshot_list.rb │ │ ├── power_on.rb │ │ ├── resume.rb │ │ ├── suspend.rb │ │ ├── snapshot_restore.rb │ │ ├── connect_vsphere.rb │ │ ├── get_state.rb │ │ ├── snapshot_save.rb │ │ ├── destroy.rb │ │ ├── snapshot_delete.rb │ │ ├── power_off.rb │ │ ├── get_ssh_info.rb │ │ ├── wait_for_ip_address.rb │ │ └── clone.rb │ ├── cap │ │ ├── public_address.rb │ │ └── snapshot_list.rb │ ├── provider.rb │ ├── plugin.rb │ ├── config.rb │ ├── util │ │ ├── vim_helpers.rb │ │ └── vm_helpers.rb │ └── action.rb └── vagrant-vsphere.rb ├── .bumpversion.cfg ├── Gemfile ├── Rakefile ├── spec ├── is_created_spec.rb ├── destroy_spec.rb ├── connect_vsphere_spec.rb ├── power_off_spec.rb ├── get_state_spec.rb ├── clone_spec.rb ├── get_ssh_info_spec.rb ├── spec_helper.rb └── action_spec.rb ├── .rubocop.yml ├── .github └── workflows │ └── main.yml ├── vSphere.gemspec ├── LICENSE ├── CODE_OF_CONDUCT.md ├── .rubocop_todo.yml ├── locales └── en.yml ├── DEVELOPMENT.md ├── README.md └── CHANGELOG.md /.ruby-version: -------------------------------------------------------------------------------- 1 | 2.6.5 2 | -------------------------------------------------------------------------------- /example_box/metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "provider": "vsphere" 3 | } -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | cache: bundler 3 | 4 | sudo: false 5 | -------------------------------------------------------------------------------- /vsphere_screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nsidc/vagrant-vsphere/HEAD/vsphere_screenshot.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | .vagrant/* 3 | Vagrantfile 4 | pkg/* 5 | *.box 6 | Gemfile.lock 7 | /vendor/ 8 | /synced_folders 9 | -------------------------------------------------------------------------------- /lib/vSphere/version.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module VSphere 3 | VERSION = '1.13.5' 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /lib/vSphere/errors.rb: -------------------------------------------------------------------------------- 1 | require 'vagrant' 2 | 3 | module VagrantPlugins 4 | module VSphere 5 | module Errors 6 | class VSphereError < Vagrant::Errors::VagrantError 7 | error_namespace('vsphere.errors') 8 | end 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /.bumpversion.cfg: -------------------------------------------------------------------------------- 1 | [bumpversion] 2 | current_version = 1.13.5 3 | tag = true 4 | commit = true 5 | 6 | [bumpversion:file:lib/vSphere/version.rb] 7 | 8 | [bumpversion:file:README.md] 9 | parse = version: (?P\d+)\.(?P\d+)\.(?P\d+) 10 | serialize = version: {major}.{minor}.{patch} 11 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'http://rubygems.org' 2 | 3 | group :development do 4 | # We depend on Vagrant for development, but we don't add it as a 5 | # gem dependency because we expect to be installed within the 6 | # Vagrant environment itself using `vagrant plugin`. 7 | gem 'vagrant', github: 'mitchellh/vagrant', ref: 'v2.2.6' 8 | end 9 | 10 | group :plugins do 11 | gemspec 12 | end 13 | -------------------------------------------------------------------------------- /lib/vSphere/action/is_running.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module VSphere 3 | module Action 4 | class IsRunning 5 | def initialize(app, _env) 6 | @app = app 7 | end 8 | 9 | def call(env) 10 | env[:result] = env[:machine].state.id == :running 11 | @app.call env 12 | end 13 | end 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /lib/vSphere/action/is_created.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module VSphere 3 | module Action 4 | class IsCreated 5 | def initialize(app, _env) 6 | @app = app 7 | end 8 | 9 | def call(env) 10 | env[:result] = env[:machine].state.id != :not_created 11 | @app.call env 12 | end 13 | end 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /lib/vSphere/action/is_suspended.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module VSphere 3 | module Action 4 | class IsSuspended 5 | def initialize(app, _env) 6 | @app = app 7 | end 8 | 9 | def call(env) 10 | env[:result] = env[:machine].state.id == :suspended 11 | @app.call env 12 | end 13 | end 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /lib/vSphere/cap/public_address.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module VSphere 3 | module Cap 4 | module PublicAddress 5 | def self.public_address(machine) 6 | return nil if machine.state.id != :running 7 | 8 | ssh_info = machine.ssh_info 9 | return nil unless ssh_info 10 | ssh_info[:host] 11 | end 12 | end 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/vSphere/action/message_not_created.rb: -------------------------------------------------------------------------------- 1 | require 'i18n' 2 | 3 | module VagrantPlugins 4 | module VSphere 5 | module Action 6 | class MessageNotCreated 7 | def initialize(app, _env) 8 | @app = app 9 | end 10 | 11 | def call(env) 12 | env[:ui].info I18n.t('vsphere.vm_not_created') 13 | @app.call(env) 14 | end 15 | end 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/vSphere/action/message_not_running.rb: -------------------------------------------------------------------------------- 1 | require 'i18n' 2 | 3 | module VagrantPlugins 4 | module VSphere 5 | module Action 6 | class MessageNotRunning 7 | def initialize(app, _env) 8 | @app = app 9 | end 10 | 11 | def call(env) 12 | env[:ui].info I18n.t('vsphere.vm_not_running') 13 | @app.call(env) 14 | end 15 | end 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/vSphere/action/message_not_suspended.rb: -------------------------------------------------------------------------------- 1 | require 'i18n' 2 | 3 | module VagrantPlugins 4 | module VSphere 5 | module Action 6 | class MessageNotSuspended 7 | def initialize(app, _env) 8 | @app = app 9 | end 10 | 11 | def call(env) 12 | env[:ui].info I18n.t('vsphere.vm_not_suspended') 13 | @app.call(env) 14 | end 15 | end 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/vSphere/action/message_already_created.rb: -------------------------------------------------------------------------------- 1 | require 'i18n' 2 | 3 | module VagrantPlugins 4 | module VSphere 5 | module Action 6 | class MessageAlreadyCreated 7 | def initialize(app, _env) 8 | @app = app 9 | end 10 | 11 | def call(env) 12 | env[:ui].info I18n.t('vsphere.vm_already_created') 13 | @app.call(env) 14 | end 15 | end 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/vSphere/cap/snapshot_list.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module VSphere 3 | module Cap 4 | module SnapshotList 5 | # Returns a list of the snapshots that are taken on this machine. 6 | # 7 | # @return [Array] Snapshot Name 8 | def self.snapshot_list(machine) 9 | env = machine.action(:snapshot_list, lock: false) 10 | env[:machine_snapshot_list] 11 | end 12 | end 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'bundler/setup' 3 | require 'rspec/core/rake_task' 4 | require 'rubocop/rake_task' 5 | 6 | # Immediately sync all stdout so that tools like buildbot can 7 | # immediately load in the output. 8 | $stdout.sync = true 9 | $stderr.sync = true 10 | 11 | # Change to the directory of this file. 12 | Dir.chdir(File.expand_path('../', __FILE__)) 13 | 14 | Bundler::GemHelper.install_tasks 15 | 16 | RSpec::Core::RakeTask.new 17 | 18 | RuboCop::RakeTask.new 19 | 20 | task default: %w(rubocop spec) 21 | -------------------------------------------------------------------------------- /lib/vagrant-vsphere.rb: -------------------------------------------------------------------------------- 1 | require 'pathname' 2 | 3 | require 'vSphere/plugin' 4 | 5 | module VagrantPlugins 6 | module VSphere 7 | lib_path = Pathname.new(File.expand_path('../vSphere', __FILE__)) 8 | autoload :Action, lib_path.join('action') 9 | autoload :Errors, lib_path.join('errors') 10 | 11 | # This returns the path to the source of this plugin. 12 | # 13 | # @return [Pathname] 14 | def self.source_root 15 | @source_root ||= Pathname.new(File.expand_path('../../', __FILE__)) 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/vSphere/action/close_vsphere.rb: -------------------------------------------------------------------------------- 1 | require 'rbvmomi' 2 | 3 | module VagrantPlugins 4 | module VSphere 5 | module Action 6 | class CloseVSphere 7 | def initialize(app, _env) 8 | @app = app 9 | end 10 | 11 | def call(env) 12 | env[:vSphere_connection].close if env && env[:vSphere_connection] 13 | @app.call env 14 | rescue Errors::VSphereError 15 | raise 16 | rescue StandardError => e 17 | raise Errors::VSphereError.new, e.message 18 | end 19 | end 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /lib/vSphere/action/snapshot_list.rb: -------------------------------------------------------------------------------- 1 | require 'vSphere/util/vim_helpers' 2 | require 'vSphere/util/vm_helpers' 3 | 4 | module VagrantPlugins 5 | module VSphere 6 | module Action 7 | class SnapshotList 8 | include Util::VimHelpers 9 | include Util::VmHelpers 10 | 11 | def initialize(app, _env) 12 | @app = app 13 | end 14 | 15 | def call(env) 16 | vm = get_vm_by_uuid(env[:vSphere_connection], env[:machine]) 17 | 18 | env[:machine_snapshot_list] = enumerate_snapshots(vm).map(&:name) 19 | 20 | @app.call env 21 | end 22 | end 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/vSphere/action/power_on.rb: -------------------------------------------------------------------------------- 1 | require 'rbvmomi' 2 | require 'i18n' 3 | require 'vSphere/util/vim_helpers' 4 | require 'vSphere/util/vm_helpers' 5 | 6 | module VagrantPlugins 7 | module VSphere 8 | module Action 9 | class PowerOn 10 | include Util::VimHelpers 11 | include Util::VmHelpers 12 | 13 | def initialize(app, _env) 14 | @app = app 15 | end 16 | 17 | def call(env) 18 | vm = get_vm_by_uuid env[:vSphere_connection], env[:machine] 19 | 20 | env[:ui].info I18n.t('vsphere.power_on_vm') 21 | power_on_vm(vm) 22 | 23 | @app.call env 24 | end 25 | end 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /lib/vSphere/action/resume.rb: -------------------------------------------------------------------------------- 1 | require 'rbvmomi' 2 | require 'i18n' 3 | require 'vSphere/util/vim_helpers' 4 | require 'vSphere/util/vm_helpers' 5 | 6 | module VagrantPlugins 7 | module VSphere 8 | module Action 9 | class Resume 10 | include Util::VimHelpers 11 | include Util::VmHelpers 12 | 13 | def initialize(app, _env) 14 | @app = app 15 | end 16 | 17 | def call(env) 18 | vm = get_vm_by_uuid env[:vSphere_connection], env[:machine] 19 | 20 | if suspended?(vm) 21 | env[:ui].info I18n.t('vsphere.resume_vm') 22 | resume_vm(vm) 23 | end 24 | 25 | @app.call env 26 | end 27 | end 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /spec/is_created_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'vSphere/action/is_created' 3 | 4 | describe VagrantPlugins::VSphere::Action::IsCreated do 5 | before :each do 6 | @env[:vSphere_connection] = @vim 7 | end 8 | 9 | it 'should set result to false if the VM does not exist' do 10 | @env[:machine].state.stub(:id).and_return(:running) 11 | 12 | call 13 | 14 | expect(@env[:result]).to be true 15 | end 16 | 17 | it 'should set result to false if the VM does not exist' do 18 | @env[:machine].state.stub(:id).and_return(:not_created) 19 | 20 | call 21 | 22 | expect(@env[:result]).to be false 23 | end 24 | 25 | it 'should call the next item in the middleware stack' do 26 | call 27 | expect(@app).to have_received :call 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | inherit_from: .rubocop_todo.yml 2 | 3 | Style/FileName: 4 | Exclude: 5 | - 'lib/vagrant-vsphere.rb' 6 | 7 | Lint/RescueException: 8 | Exclude: 9 | # the logic in the method `find_clustercompute_or_compute_resource` does not 10 | # work when rescuing StandardError, so exclude 11 | # lib/vSphere/util/vim_helpers.rb to continue rescuing Exception in that 12 | # method 13 | - 'lib/vSphere/util/vim_helpers.rb' 14 | 15 | Metrics/AbcSize: 16 | Enabled: false 17 | 18 | Metrics/ClassLength: 19 | Enabled: false 20 | 21 | Metrics/CyclomaticComplexity: 22 | Enabled: false 23 | 24 | Metrics/LineLength: 25 | Enabled: false 26 | 27 | Metrics/MethodLength: 28 | Enabled: false 29 | 30 | Metrics/ModuleLength: 31 | Enabled: false 32 | 33 | Metrics/PerceivedComplexity: 34 | Enabled: false 35 | -------------------------------------------------------------------------------- /spec/destroy_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe VagrantPlugins::VSphere::Action::Destroy do 4 | before :each do 5 | @env[:vSphere_connection] = @vim 6 | end 7 | 8 | it 'should set the machine id to nil' do 9 | @env[:machine].stub(:id).and_return(MISSING_UUID) 10 | 11 | call 12 | 13 | expect(@env[:machine]).to have_received(:id=).with(nil) 14 | end 15 | 16 | it 'should not create a Destroy task if VM is not found' do 17 | @env[:machine].stub(:id).and_return(MISSING_UUID) 18 | 19 | call 20 | 21 | expect(@vm).not_to have_received :Destroy_Task 22 | end 23 | 24 | it 'should create a VM Destroy task if the VM exists' do 25 | @env[:machine].stub(:id).and_return(EXISTING_UUID) 26 | 27 | call 28 | 29 | expect(@vm).to have_received :Destroy_Task 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/vSphere/action/suspend.rb: -------------------------------------------------------------------------------- 1 | require 'rbvmomi' 2 | require 'i18n' 3 | require 'vSphere/util/vim_helpers' 4 | require 'vSphere/util/vm_helpers' 5 | 6 | module VagrantPlugins 7 | module VSphere 8 | module Action 9 | class Suspend 10 | include Util::VimHelpers 11 | include Util::VmHelpers 12 | 13 | def initialize(app, _env) 14 | @app = app 15 | end 16 | 17 | def call(env) 18 | vm = get_vm_by_uuid env[:vSphere_connection], env[:machine] 19 | 20 | # Suspending is a no-op if we can't find the VM or it is already off 21 | # or suspended 22 | unless vm.nil? || suspended?(vm) || powered_off?(vm) 23 | env[:ui].info I18n.t('vsphere.suspend_vm') 24 | suspend_vm(vm) 25 | end 26 | 27 | @app.call env 28 | end 29 | end 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /spec/connect_vsphere_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe VagrantPlugins::VSphere::Action::ConnectVSphere do 4 | before :each do 5 | described_class.new(@app, @env).call(@env) 6 | end 7 | 8 | it 'should connect to vSphere' do 9 | expect(VIM).to have_received(:connect).with( 10 | host: @env[:machine].provider_config.host, 11 | user: @env[:machine].provider_config.user, 12 | password: @env[:machine].provider_config.password, 13 | insecure: @env[:machine].provider_config.insecure, 14 | proxyHost: @env[:machine].provider_config.proxy_host, 15 | proxyPort: @env[:machine].provider_config.proxy_port 16 | ) 17 | end 18 | 19 | it 'should add the vSphere connection to the environment' do 20 | expect(@env[:vSphere_connection]).to be @vim 21 | end 22 | 23 | it 'should call the next item in the middleware stack' do 24 | expect(@app).to have_received :call 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/vSphere/action/snapshot_restore.rb: -------------------------------------------------------------------------------- 1 | require 'vSphere/util/vim_helpers' 2 | require 'vSphere/util/vm_helpers' 3 | 4 | module VagrantPlugins 5 | module VSphere 6 | module Action 7 | class SnapshotRestore 8 | include Util::VimHelpers 9 | include Util::VmHelpers 10 | 11 | def initialize(app, _env) 12 | @app = app 13 | end 14 | 15 | def call(env) 16 | vm = get_vm_by_uuid(env[:vSphere_connection], env[:machine]) 17 | 18 | env[:ui].info(I18n.t( 19 | "vagrant.actions.vm.snapshot.restoring", 20 | name: env[:snapshot_name])) 21 | 22 | restore_snapshot(vm, env[:snapshot_name]) do |progress| 23 | env[:ui].clear_line 24 | env[:ui].report_progress(progress, 100, false) 25 | end 26 | 27 | env[:ui].clear_line 28 | 29 | @app.call env 30 | end 31 | end 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: GitHub Issue to JIRA 2 | on: 3 | issues: 4 | types: 5 | - opened 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | name: Jira issue 11 | steps: 12 | - name: Login 13 | uses: atlassian/gajira-login@v3 14 | env: 15 | JIRA_BASE_URL: ${{ secrets.JIRA_BASE_URL }} 16 | JIRA_USER_EMAIL: ${{ secrets.JIRA_USER_EMAIL }} 17 | JIRA_API_TOKEN: ${{ secrets.JIRA_API_TOKEN }} 18 | 19 | - name: Create 20 | id: create 21 | uses: atlassian/gajira-create@v3 22 | with: 23 | project: VGTVSPHERE 24 | issuetype: Story 25 | # summary: "GitHub Issue: ${{ github.event_name }}" 26 | summary: "GitHub Issue: ${{ github.event.issue.title }}" 27 | description: "${{ github.even.bodyText }}.\n\nCreated by ${{ github.actor }}" 28 | 29 | - name: Log created issue 30 | run: echo "Issue ${{ steps.create.outputs.issue }} was created" 31 | -------------------------------------------------------------------------------- /lib/vSphere/action/connect_vsphere.rb: -------------------------------------------------------------------------------- 1 | require 'rbvmomi' 2 | 3 | module VagrantPlugins 4 | module VSphere 5 | module Action 6 | class ConnectVSphere 7 | def initialize(app, _env) 8 | @app = app 9 | end 10 | 11 | def call(env) 12 | config = env[:machine].provider_config 13 | 14 | begin 15 | env[:vSphere_connection] = RbVmomi::VIM.connect host: config.host, 16 | user: config.user, password: config.password, 17 | insecure: config.insecure, proxyHost: config.proxy_host, 18 | proxyPort: config.proxy_port 19 | @app.call env 20 | rescue Errors::VSphereError 21 | raise 22 | rescue StandardError => e 23 | raise Errors::VSphereError.new, e.message 24 | end 25 | end 26 | end 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /lib/vSphere/action/get_state.rb: -------------------------------------------------------------------------------- 1 | require 'rbvmomi' 2 | require 'vSphere/util/vim_helpers' 3 | require 'vSphere/util/vm_helpers' 4 | 5 | module VagrantPlugins 6 | module VSphere 7 | module Action 8 | class GetState 9 | include Util::VimHelpers 10 | include Util::VmHelpers 11 | 12 | def initialize(app, _env) 13 | @app = app 14 | end 15 | 16 | def call(env) 17 | env[:machine_state_id] = get_state(env[:vSphere_connection], env[:machine]) 18 | 19 | @app.call env 20 | end 21 | 22 | private 23 | 24 | def get_state(connection, machine) 25 | return :not_created if machine.id.nil? 26 | 27 | vm = get_vm_by_uuid connection, machine 28 | 29 | return :not_created if vm.nil? 30 | 31 | if powered_on?(vm) 32 | :running 33 | elsif suspended?(vm) 34 | :suspended 35 | else 36 | :poweroff 37 | end 38 | end 39 | end 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /lib/vSphere/action/snapshot_save.rb: -------------------------------------------------------------------------------- 1 | require 'vSphere/util/vim_helpers' 2 | require 'vSphere/util/vm_helpers' 3 | 4 | module VagrantPlugins 5 | module VSphere 6 | module Action 7 | class SnapshotSave 8 | include Util::VimHelpers 9 | include Util::VmHelpers 10 | 11 | def initialize(app, _env) 12 | @app = app 13 | end 14 | 15 | def call(env) 16 | vm = get_vm_by_uuid(env[:vSphere_connection], env[:machine]) 17 | 18 | env[:ui].info(I18n.t( 19 | "vagrant.actions.vm.snapshot.saving", 20 | name: env[:snapshot_name])) 21 | 22 | create_snapshot(vm, env[:snapshot_name]) do |progress| 23 | env[:ui].clear_line 24 | env[:ui].report_progress(progress, 100, false) 25 | end 26 | 27 | env[:ui].clear_line 28 | 29 | env[:ui].success(I18n.t( 30 | "vagrant.actions.vm.snapshot.saved", 31 | name: env[:snapshot_name])) 32 | @app.call env 33 | end 34 | end 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /lib/vSphere/action/destroy.rb: -------------------------------------------------------------------------------- 1 | require 'rbvmomi' 2 | require 'i18n' 3 | require 'vSphere/util/vim_helpers' 4 | 5 | module VagrantPlugins 6 | module VSphere 7 | module Action 8 | class Destroy 9 | include Util::VimHelpers 10 | 11 | def initialize(app, _env) 12 | @app = app 13 | end 14 | 15 | def call(env) 16 | destroy_vm env 17 | env[:machine].id = nil 18 | 19 | @app.call env 20 | end 21 | 22 | private 23 | 24 | def destroy_vm(env) 25 | return if env[:machine].state.id == :not_created 26 | vm = get_vm_by_uuid env[:vSphere_connection], env[:machine] 27 | return if vm.nil? 28 | 29 | begin 30 | env[:ui].info I18n.t('vsphere.destroy_vm') 31 | vm.Destroy_Task.wait_for_completion 32 | rescue Errors::VSphereError 33 | raise 34 | rescue StandardError => e 35 | raise Errors::VSphereError.new, e.message 36 | end 37 | end 38 | end 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /lib/vSphere/action/snapshot_delete.rb: -------------------------------------------------------------------------------- 1 | require 'vSphere/util/vim_helpers' 2 | require 'vSphere/util/vm_helpers' 3 | 4 | module VagrantPlugins 5 | module VSphere 6 | module Action 7 | class SnapshotDelete 8 | include Util::VimHelpers 9 | include Util::VmHelpers 10 | 11 | def initialize(app, _env) 12 | @app = app 13 | end 14 | 15 | def call(env) 16 | vm = get_vm_by_uuid(env[:vSphere_connection], env[:machine]) 17 | 18 | env[:ui].info(I18n.t( 19 | "vagrant.actions.vm.snapshot.deleting", 20 | name: env[:snapshot_name])) 21 | 22 | delete_snapshot(vm, env[:snapshot_name]) do |progress| 23 | env[:ui].clear_line 24 | env[:ui].report_progress(progress, 100, false) 25 | end 26 | 27 | env[:ui].clear_line 28 | 29 | env[:ui].info(I18n.t( 30 | "vagrant.actions.vm.snapshot.deleted", 31 | name: env[:snapshot_name])) 32 | 33 | @app.call env 34 | end 35 | end 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /lib/vSphere/provider.rb: -------------------------------------------------------------------------------- 1 | require 'vagrant' 2 | 3 | module VagrantPlugins 4 | module VSphere 5 | class Provider < Vagrant.plugin('2', :provider) 6 | def initialize(machine) 7 | @machine = machine 8 | end 9 | 10 | def action(name) 11 | action_method = "action_#{name}" 12 | return Action.send(action_method) if Action.respond_to?(action_method) 13 | nil 14 | end 15 | 16 | def ssh_info 17 | env = @machine.action('get_ssh_info', lock: false) 18 | env[:machine_ssh_info] 19 | end 20 | 21 | def state 22 | env = @machine.action('get_state', lock: false) 23 | 24 | state_id = env[:machine_state_id] 25 | 26 | short = "vagrant_vsphere.states.short_#{state_id}" 27 | long = "vagrant_vsphere.states.long_#{state_id}" 28 | 29 | # Return the MachineState object 30 | Vagrant::MachineState.new(state_id, short, long) 31 | end 32 | 33 | def to_s 34 | id = @machine.id.nil? ? 'new' : @machine.id 35 | "vSphere (#{id})" 36 | end 37 | end 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /spec/power_off_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe VagrantPlugins::VSphere::Action::PowerOff do 4 | before :each do 5 | @env[:vSphere_connection] = @vim 6 | end 7 | 8 | it 'should power off the VM if it is powered on' do 9 | @machine.stub(:id).and_return(EXISTING_UUID) 10 | @machine.state.stub(:id).and_return(VagrantPlugins::VSphere::Util::VmState::POWERED_ON) 11 | 12 | call 13 | 14 | expect(@vm).to have_received :PowerOffVM_Task 15 | end 16 | 17 | it 'should not power off the VM if is powered off' do 18 | @machine.stub(:id).and_return(EXISTING_UUID) 19 | @vm.runtime.stub(:powerState).and_return(VagrantPlugins::VSphere::Util::VmState::POWERED_OFF) 20 | 21 | call 22 | 23 | expect(@vm).not_to have_received :PowerOffVM_Task 24 | end 25 | 26 | it 'should power on and off the VM if is suspended' do 27 | @machine.stub(:id).and_return(EXISTING_UUID) 28 | @vm.runtime.stub(:powerState).and_return(VagrantPlugins::VSphere::Util::VmState::SUSPENDED) 29 | 30 | call 31 | 32 | expect(@vm).to have_received :PowerOnVM_Task 33 | expect(@vm).to have_received :PowerOffVM_Task 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /vSphere.gemspec: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.unshift File.expand_path('../lib', __FILE__) 2 | require 'vSphere/version' 3 | 4 | Gem::Specification.new do |s| 5 | s.name = 'vagrant-vsphere' 6 | s.version = VagrantPlugins::VSphere::VERSION 7 | s.authors = ['Andrew Grauch'] 8 | s.email = ['andrew.grauch@nsidc.org'] 9 | s.homepage = '' 10 | s.license = 'MIT' 11 | s.summary = 'VMWare vSphere provider' 12 | s.description = 'Enables Vagrant to manage machines with VMWare vSphere.' 13 | 14 | # pin nokogiri to 1.10.10 to get around 1.11.0 requiring ruby >=2.5 15 | s.add_dependency 'nokogiri', '1.10.10' 16 | 17 | s.add_dependency 'rbvmomi', '>=1.11.5', '<2.0.0' 18 | 19 | s.add_dependency 'i18n', '>=0.6.4' 20 | 21 | s.add_development_dependency 'rake', '11.1.2' # pinned to accommodate rubocop 0.32.1 22 | s.add_development_dependency 'rspec-core' 23 | s.add_development_dependency 'rspec-expectations' 24 | s.add_development_dependency 'rspec-mocks' 25 | s.add_development_dependency 'rubocop', '~> 0.32.1' 26 | 27 | s.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR) 28 | s.executables = s.files.grep(/^bin\//) { |f| File.basename(f) } 29 | s.test_files = s.files.grep(/^(test|spec|features)\//) 30 | s.require_path = 'lib' 31 | end 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013-2022 Regents of the University of Colorado 2 | 3 | This software was developed by the National Snow and Ice Data Center with funding from multiple sources. 4 | 5 | MIT License 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining 8 | a copy of this software and associated documentation files (the 9 | "Software"), to deal in the Software without restriction, including 10 | without limitation the rights to use, copy, modify, merge, publish, 11 | distribute, sublicense, and/or sell copies of the Software, and to 12 | permit persons to whom the Software is furnished to do so, subject to 13 | the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be 16 | included in all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 19 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 20 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 21 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 22 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 24 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 25 | -------------------------------------------------------------------------------- /lib/vSphere/action/power_off.rb: -------------------------------------------------------------------------------- 1 | require 'rbvmomi' 2 | require 'i18n' 3 | require 'vSphere/util/vim_helpers' 4 | require 'vSphere/util/vm_helpers' 5 | 6 | module VagrantPlugins 7 | module VSphere 8 | module Action 9 | class PowerOff 10 | include Util::VimHelpers 11 | include Util::VmHelpers 12 | 13 | def initialize(app, _env) 14 | @app = app 15 | end 16 | 17 | def call(env) 18 | vm = get_vm_by_uuid env[:vSphere_connection], env[:machine] 19 | 20 | # If the vm is suspended, we need to turn it on so that we can turn it off. 21 | # This may seem counterintuitive, but the vsphere API documentation states 22 | # that the Power Off task for a VM will fail if the state is not poweredOn 23 | # see: https://www.vmware.com/support/developer/vc-sdk/visdk41pubs/ApiReference/vim.VirtualMachine.html#powerOff 24 | if suspended?(vm) 25 | env[:ui].info I18n.t('vsphere.power_on_vm') 26 | power_on_vm(vm) 27 | end 28 | 29 | # Powering off is a no-op if we can't find the VM or if it is already off 30 | unless vm.nil? || powered_off?(vm) 31 | env[:ui].info I18n.t('vsphere.power_off_vm') 32 | power_off_vm(vm) 33 | end 34 | 35 | @app.call env 36 | end 37 | end 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | ## 1. Our Commitment 4 | 5 | We are dedicated to fostering a respectful environment for everyone contributing to this project. We expect all participants to treat each other with respect, professionalism, and kindness. 6 | 7 | ## 2. Expected Behavior 8 | 9 | - Be respectful and considerate of others. 10 | - Engage in constructive discussions and offer helpful feedback. 11 | - Gracefully accept constructive criticism. 12 | 13 | ## 3. Unacceptable Behavior 14 | 15 | The following behaviors will not be tolerated: 16 | 17 | - Harassment, discrimination, or intimidation of any kind. 18 | - Offensive, abusive, or derogatory language and actions. 19 | - Personal attacks or insults. 20 | - Trolling or disruptive conduct. 21 | - Sharing inappropriate content. 22 | 23 | ## 4. Reporting Violations 24 | If you experience or witness any behavior that violates this Code of Conduct, please report it by contacting the project maintainers. All reports will be reviewed confidentially. 25 | 26 | ## 5. Enforcement 27 | Violations of this Code of Conduct may result in actions such as warnings, temporary bans, or permanent exclusion from participation at the discretion of the maintainers. 28 | 29 | ## Contact Info 30 | Email: 31 | Organization: National Snow and Ice Data Center¹ 32 | Website: 33 | Date last modified: 01-22-2025 34 | 35 | ¹Work performed under NASA contract 80GSFC23CA035. 36 | -------------------------------------------------------------------------------- /lib/vSphere/action/get_ssh_info.rb: -------------------------------------------------------------------------------- 1 | require 'rbvmomi' 2 | require 'vSphere/util/vim_helpers' 3 | 4 | module VagrantPlugins 5 | module VSphere 6 | module Action 7 | class GetSshInfo 8 | include Util::VimHelpers 9 | 10 | def initialize(app, _env) 11 | @app = app 12 | end 13 | 14 | def call(env) 15 | env[:machine_ssh_info] = get_ssh_info(env[:vSphere_connection], env[:machine]) 16 | @app.call env 17 | end 18 | 19 | private 20 | 21 | def filter_guest_nic(vm, machine) 22 | return vm.guest.ipAddress unless machine.provider_config.real_nic_ip 23 | 24 | interfaces = vm.guest.net.select { |g| g.deviceConfigId > 0 } 25 | ip_addresses = interfaces.map { |i| i.ipConfig.ipAddress.select { |a| a.state == 'preferred' } }.flatten 26 | 27 | return (vm.guest.ipAddress || nil) if ip_addresses.empty? 28 | 29 | fail Errors::VSphereError.new, :'multiple_interface_with_real_nic_ip_set' if ip_addresses.size > 1 30 | ip_addresses.first.ipAddress 31 | end 32 | 33 | def get_ssh_info(connection, machine) 34 | return nil if machine.id.nil? 35 | 36 | vm = get_vm_by_uuid connection, machine 37 | return nil if vm.nil? 38 | ip_address = filter_guest_nic(vm, machine) 39 | return nil if ip_address.nil? || ip_address.empty? 40 | { 41 | host: ip_address, 42 | port: 22 43 | } 44 | end 45 | end 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /lib/vSphere/plugin.rb: -------------------------------------------------------------------------------- 1 | begin 2 | require 'vagrant' 3 | rescue LoadError 4 | raise 'The Vagrant vSphere 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.5' 10 | fail 'The Vagrant vSphere plugin is only compatible with Vagrant 1.5+' 11 | end 12 | 13 | module VagrantPlugins 14 | module VSphere 15 | class Plugin < Vagrant.plugin('2') 16 | name 'vsphere' 17 | description 'Allows Vagrant to manage machines with VMWare vSphere' 18 | 19 | config(:vsphere, :provider) do 20 | require_relative 'config' 21 | Config 22 | end 23 | 24 | provider(:vsphere, parallel: true) do 25 | # TODO: add logging 26 | setup_i18n 27 | 28 | # Return the provider 29 | require_relative 'provider' 30 | Provider 31 | end 32 | 33 | provider_capability('vsphere', 'public_address') do 34 | require_relative 'cap/public_address' 35 | Cap::PublicAddress 36 | end 37 | 38 | # TODO: Remove the if guard when Vagrant 1.8.0 is the minimum version. 39 | # rubocop:disable IndentationWidth 40 | if Gem::Version.new(Vagrant::VERSION) >= Gem::Version.new('1.8.0') 41 | provider_capability('vsphere', 'snapshot_list') do 42 | require_relative 'cap/snapshot_list' 43 | Cap::SnapshotList 44 | end 45 | end 46 | # rubocop:enable IndentationWidth 47 | 48 | def self.setup_i18n 49 | I18n.load_path << File.expand_path('locales/en.yml', VSphere.source_root) 50 | I18n.reload! 51 | end 52 | end 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /spec/get_state_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'vSphere/util/vim_helpers' 3 | 4 | describe VagrantPlugins::VSphere::Action::GetState do 5 | before :each do 6 | @env[:vSphere_connection] = @vim 7 | end 8 | 9 | it 'should set state id to not created if machine ID is not set' do 10 | call 11 | 12 | expect(@env[:machine_state_id]).to be :not_created 13 | end 14 | 15 | it 'should set state id to not created if VM is not found' do 16 | @env[:machine].stub(:id).and_return(MISSING_UUID) 17 | 18 | call 19 | 20 | expect(@env[:machine_state_id]).to be :not_created 21 | end 22 | 23 | it 'should set state id to running if machine is powered on' do 24 | @env[:machine].stub(:id).and_return(EXISTING_UUID) 25 | @vm.runtime.stub(:powerState).and_return(VagrantPlugins::VSphere::Util::VmState::POWERED_ON) 26 | 27 | call 28 | 29 | expect(@env[:machine_state_id]).to be :running 30 | end 31 | 32 | it 'should set state id to powered off if machine is powered off' do 33 | @env[:machine].stub(:id).and_return(EXISTING_UUID) 34 | @vm.runtime.stub(:powerState).and_return(VagrantPlugins::VSphere::Util::VmState::POWERED_OFF) 35 | 36 | call 37 | 38 | expect(@env[:machine_state_id]).to be :poweroff 39 | end 40 | 41 | it 'should set state id to suspended if machine is suspended' do 42 | @env[:machine].stub(:id).and_return(EXISTING_UUID) 43 | @vm.runtime.stub(:powerState).and_return(VagrantPlugins::VSphere::Util::VmState::SUSPENDED) 44 | 45 | call 46 | 47 | expect(@env[:machine_state_id]).to be :suspended 48 | end 49 | 50 | it 'should call the next item in the middleware stack' do 51 | call 52 | 53 | expect(@app).to have_received :call 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /lib/vSphere/action/wait_for_ip_address.rb: -------------------------------------------------------------------------------- 1 | require 'ipaddr' 2 | require 'timeout' 3 | 4 | module VagrantPlugins 5 | module VSphere 6 | module Action 7 | class WaitForIPAddress 8 | def initialize(app, _env) 9 | @app = app 10 | @logger = Log4r::Logger.new('vagrant::vsphere::wait_for_ip_addr') 11 | end 12 | 13 | def call(env) 14 | timeout = env[:machine].provider_config.ip_address_timeout 15 | 16 | env[:ui].output('Waiting for the machine to report its IP address...') 17 | env[:ui].detail("Timeout: #{timeout} seconds") 18 | 19 | guest_ip = nil 20 | Timeout.timeout(timeout) do 21 | loop do 22 | # If a ctrl-c came through, break out 23 | return if env[:interrupted] 24 | 25 | guest_ip = nil 26 | 27 | if env[:machine].state.id == :running 28 | ssh_info = env[:machine].ssh_info 29 | guest_ip = ssh_info[:host] unless ssh_info.nil? 30 | end 31 | 32 | if guest_ip 33 | begin 34 | IPAddr.new(guest_ip) 35 | break 36 | rescue IPAddr::InvalidAddressError 37 | # Ignore, continue looking. 38 | @logger.warn("Invalid IP address returned: #{guest_ip}") 39 | end 40 | end 41 | 42 | sleep 1 43 | end 44 | end 45 | 46 | # If we were interrupted then return now 47 | return if env[:interrupted] 48 | 49 | env[:ui].detail("IP: #{guest_ip}") 50 | 51 | @app.call(env) 52 | rescue Timeout::Error 53 | raise Errors::VSphereError, :wait_for_ip_address_timeout 54 | end 55 | end 56 | end 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /.rubocop_todo.yml: -------------------------------------------------------------------------------- 1 | # This configuration was generated by `rubocop --auto-gen-config` 2 | # on 2016-03-08 12:12:34 -0700 using RuboCop version 0.32.1. 3 | # The point is for the user to remove these configuration records 4 | # one by one as the offenses are removed from the code base. 5 | # Note that changes in the inspected code, or installation of new 6 | # versions of RuboCop, may require this file to be generated again. 7 | 8 | # Offense count: 1 9 | Lint/NonLocalExitFromIterator: 10 | Enabled: false 11 | 12 | # Offense count: 24 13 | Style/Documentation: 14 | Enabled: false 15 | 16 | # Offense count: 3 17 | # Cop supports --auto-correct. 18 | # Configuration parameters: EnforcedStyle, SupportedStyles. 19 | Style/EmptyLinesAroundBlockBody: 20 | Enabled: false 21 | 22 | # Offense count: 1 23 | # Cop supports --auto-correct. 24 | Style/EmptyLiteral: 25 | Enabled: false 26 | 27 | # Offense count: 1 28 | # Cop supports --auto-correct. 29 | # Configuration parameters: EnforcedStyle, SupportedStyles. 30 | Style/FirstParameterIndentation: 31 | Enabled: false 32 | 33 | # Offense count: 6 34 | # Cop supports --auto-correct. 35 | # Configuration parameters: EnforcedStyle, SupportedStyles, UseHashRocketsWithSymbolValues. 36 | Style/HashSyntax: 37 | Enabled: false 38 | 39 | # Offense count: 2 40 | # Cop supports --auto-correct. 41 | # Configuration parameters: EnforcedStyle, SupportedStyles, AllowInnerSlashes. 42 | Style/RegexpLiteral: 43 | Enabled: false 44 | 45 | # Offense count: 3 46 | # Cop supports --auto-correct. 47 | # Configuration parameters: MultiSpaceAllowedForOperators. 48 | Style/SpaceAroundOperators: 49 | Enabled: false 50 | 51 | # Offense count: 1 52 | # Cop supports --auto-correct. 53 | # Configuration parameters: EnforcedStyle, SupportedStyles. 54 | Style/StringLiterals: 55 | Enabled: false 56 | 57 | # Offense count: 4 58 | # Cop supports --auto-correct. 59 | Style/SymbolLiteral: 60 | Enabled: false 61 | -------------------------------------------------------------------------------- /lib/vSphere/config.rb: -------------------------------------------------------------------------------- 1 | require 'vagrant' 2 | 3 | module VagrantPlugins 4 | module VSphere 5 | class Config < Vagrant.plugin('2', :config) 6 | attr_accessor :ip_address_timeout # Time to wait for an IP address when booting, in seconds @return [Integer] 7 | attr_accessor :host 8 | attr_accessor :insecure 9 | attr_accessor :user 10 | attr_accessor :password 11 | attr_accessor :data_center_name 12 | attr_accessor :compute_resource_name 13 | attr_accessor :resource_pool_name 14 | attr_accessor :clone_from_vm 15 | attr_accessor :template_name 16 | attr_accessor :name 17 | attr_accessor :vm_base_path 18 | attr_accessor :customization_spec_name 19 | attr_accessor :data_store_name 20 | attr_accessor :linked_clone 21 | attr_accessor :proxy_host 22 | attr_accessor :proxy_port 23 | attr_accessor :vlan 24 | attr_accessor :addressType 25 | attr_accessor :mac 26 | attr_accessor :memory_mb 27 | attr_accessor :cpu_count 28 | attr_accessor :cpu_reservation 29 | attr_accessor :mem_reservation 30 | attr_accessor :extra_config 31 | attr_accessor :real_nic_ip 32 | attr_accessor :notes 33 | attr_accessor :wait_for_sysprep 34 | 35 | attr_reader :custom_attributes 36 | 37 | def initialize 38 | @ip_address_timeout = UNSET_VALUE 39 | @wait_for_sysprep = UNSET_VALUE 40 | @custom_attributes = {} 41 | @extra_config = {} 42 | end 43 | 44 | def finalize! 45 | @ip_address_timeout = 240 if @ip_address_timeout == UNSET_VALUE 46 | @wait_for_sysprep = false if @wait_for_sysprep == UNSET_VALUE 47 | end 48 | 49 | def custom_attribute(key, value) 50 | @custom_attributes[key.to_sym] = value 51 | end 52 | 53 | def validate(machine) 54 | errors = _detected_errors 55 | 56 | if password == :ask || password.nil? 57 | self.password = machine.ui.ask('vSphere Password (will be hidden): ', echo: false) 58 | end 59 | 60 | # TODO: add blank? 61 | errors << I18n.t('vsphere.config.host') if host.nil? 62 | errors << I18n.t('vsphere.config.user') if user.nil? 63 | errors << I18n.t('vsphere.config.password') if password.nil? 64 | errors << I18n.t('vsphere.config.template') if template_name.nil? 65 | 66 | # Only required if we're cloning from an actual template 67 | errors << I18n.t('vsphere.config.compute_resource') if compute_resource_name.nil? && !clone_from_vm 68 | 69 | { 'vSphere Provider' => errors } 70 | end 71 | end 72 | end 73 | end 74 | -------------------------------------------------------------------------------- /locales/en.yml: -------------------------------------------------------------------------------- 1 | en: 2 | vsphere: 3 | creating_cloned_vm: |- 4 | Calling vSphere CloneVM with the following settings: 5 | creating_cloned_vm_sdrs: |- 6 | Calling vSphere ApplyStorageDrsRecommendation with the following settings: 7 | requesting_sdrs_recommendation: |- 8 | Calling vSphere RecommendDatastores with StoragePlacementSpec of the following settings: 9 | vm_clone_success: |- 10 | New virtual machine successfully cloned 11 | destroy_vm: |- 12 | Calling vSphere Destroy 13 | power_off_vm: |- 14 | Calling vSphere PowerOff 15 | power_on_vm: |- 16 | Calling vSphere PowerOn 17 | resume_vm: |- 18 | Calling vSphere PowerOn 19 | suspend_vm: |- 20 | Calling vSphere Suspend 21 | vm_already_created: |- 22 | The VM is already created 23 | vm_not_created: |- 24 | The VM has not been created 25 | vm_not_running: |- 26 | The VM is not running 27 | vm_not_suspended: |- 28 | The VM is not suspended 29 | wait_sysprep: |- 30 | Waiting for sysprep 31 | 32 | errors: 33 | missing_template: |- 34 | Configured template/source VM could not be found 35 | invalid_base_path: |- 36 | Could not find base path for target VM, check 'vm_base_path' configuration value 37 | missing_datacenter: |- 38 | Configured data center not found 39 | missing_compute_resource: |- 40 | Configured compute resource not found 41 | missing_resource_pool: |- 42 | Configured resource pool not found 43 | null_configuration_spec_manager: |- 44 | Configuration spec manager not configured in the ServiceInstance 45 | missing_configuration_spec: |- 46 | Configured configuration spec not found 47 | missing_datastore: |- 48 | Configured data store not found 49 | too_many_private_networks: |- 50 | There a more private networks configured than can be assigned to the customization spec 51 | missing_vlan: |- 52 | Configured vlan not found 53 | missing_network_card: |- 54 | Cannot find network card to customize 55 | invalid_configuration_linked_clone_with_sdrs: |- 56 | Cannot use Linked Clone with Storage DRS 57 | multiple_interface_with_real_nic_ip_set: |- 58 | real_nic_ip filtering set with multiple valid VM interfaces available 59 | sysprep_timeout: |- 60 | Customization of VM not succeeded within timeout. 61 | wait_for_ip_address_timeout: |- 62 | Timeout while waiting for ip address 63 | 64 | config: 65 | host: |- 66 | Configuration must specify a vSphere host 67 | user: |- 68 | Configuration must specify a vSphere user 69 | password: |- 70 | Configuration must specify a vSphere password 71 | name: |- 72 | Configuration must specify a VM name 73 | template: |- 74 | Configuration must specify a template name 75 | compute_resource: |- 76 | Configuration must specify a compute resource name 77 | resource_pool: |- 78 | Configuration must specify a resource pool name 79 | -------------------------------------------------------------------------------- /DEVELOPMENT.md: -------------------------------------------------------------------------------- 1 | ### Contributing 2 | 3 | 1. Fork it 4 | 2. Create your feature branch (`git checkout -b my-new-feature`) 5 | 3. Commit your RuboCop-compliant and test-passing changes (`git commit -am 'Add 6 | some feature'`) 7 | 4. Push to the branch (`git push origin my-new-feature`) 8 | 5. Create new Pull Request 9 | 10 | ### Versioning 11 | 12 | This plugin follows the principles of 13 | [Semantic Versioning 2.0.0](http://semver.org/). 14 | 15 | ### Unit Tests 16 | 17 | Please run the unit tests to verify your changes. To do this simply run `rake`. 18 | If you want a quick merge, write a spec that fails before your changes are 19 | applied and that passes after. 20 | 21 | If you don't have rake installed, first install [bundler](http://bundler.io/) 22 | and run `bundle install`. Then you can run `bundle exec rake`, even if rake is 23 | still not installed to your `PATH`. 24 | 25 | ### RuboCop 26 | 27 | Please make changes [RuboCop](https://github.com/bbatsov/rubocop)-compliant. 28 | 29 | Changes that eliminate rules from 30 | [`.rubocop_todo.yml`](https://github.com/nsidc/vagrant-vsphere/blob/master/.rubocop_todo.yml) 31 | are welcome. 32 | 33 | ### Travis-CI 34 | 35 | [Travis](https://travis-ci.org/nsidc/vagrant-vsphere) will automatically run 36 | RuboCop and the unit tests when you create a new pull request. If there are any 37 | failures, a notification will appear on the pull request. To update your pull 38 | request, simply create a new commit on the branch that fixes the failures, and 39 | push to the branch. 40 | 41 | ### Development Without Building the Plugin 42 | 43 | To test your changes when developing the plugin, you have two main 44 | options. First, you can build and install the plugin from source every time you 45 | make a change: 46 | 47 | 1. Make changes 48 | 2. `rake build` 49 | 3. `vagrant plugin install ./pkg/vagrant-vsphere-$VERSION.gem` 50 | 4. `vagrant up --provider=vsphere` 51 | 52 | Second, you can use Bundler and the Vagrant gem to execute vagrant commands, 53 | saving time as you never have to wait for the plugin to build and install: 54 | 55 | 1. Make changes 56 | 2. `bundle exec vagrant up --provider=vsphere` 57 | 58 | This method uses the version of Vagrant specified in 59 | [`Gemfile`](https://github.com/nsidc/vagrant-vsphere/blob/master/Gemfile). It 60 | will also cause Bundler and Vagrant to output warnings every time you run 61 | `bundle exec vagrant`, because `Gemfile` lists **vagrant-vsphere** twice (once 62 | with `gemspec` and another time in the `group :plugins` block), and Vagrant 63 | prefers to be run from the official installer rather than through the gem. 64 | 65 | Despite those warning messages, this is the 66 | [officially recommended](https://docs.vagrantup.com/v2/plugins/development-basics.html) 67 | method for Vagrant plugin development. 68 | 69 | ### Releasing 70 | 71 | 1) Ensure [travis-ci](https://travis-ci.org/github/nsidc/vagrant-vsphere/) build is passing 72 | 2) Ensure `CHANGELOG.md` is up-to-date with the changes to release 73 | 3) Update version in the code, according to [semver](https://semver.org/) 74 | * [bumpversion](https://github.com/peritus/bumpversion) can be used; if not, 75 | the version needs to be manually updated in `.bumpversion.cfg`, 76 | `README.md`, and `lib/vSphere/version.rb` (e.g., as in 77 | [`11eced2`](https://github.com/nsidc/vagrant-vsphere/commit/11eced2)) 78 | 4) `bundle exec rake build` 79 | * builds the plugin to `pkg/vagrant-vsphere-$VERSION.gem` 80 | * install to your system vagrant for further testing with `vagrant plugin 81 | install ./pkg/vagrant-vsphere-$VERSION.gem` 82 | 5) `bundle exec rake release` 83 | * creates the version tag and pushes it to GitHub 84 | * pushes the built gem to 85 | [RubyGems.org](https://rubygems.org/gems/vagrant-vsphere/) 86 | 6) Update the [Releases page](https://github.com/nsidc/vagrant-vsphere/releases) 87 | * the release name should match the version tag (e.g., `v1.2.3`) 88 | * the release description can be the same as the `CHANGELOG.md` entry 89 | * upload the `.gem` from RubyGems.org as an attached binary for the release 90 | -------------------------------------------------------------------------------- /lib/vSphere/util/vim_helpers.rb: -------------------------------------------------------------------------------- 1 | require 'rbvmomi' 2 | 3 | module VagrantPlugins 4 | module VSphere 5 | module Util 6 | module VimHelpers 7 | def get_datacenter(connection, machine) 8 | connection.serviceInstance.find_datacenter(machine.provider_config.data_center_name) || fail(Errors::VSphereError, :missing_datacenter) 9 | end 10 | 11 | def get_vm_by_uuid(connection, machine) 12 | get_datacenter(connection, machine).vmFolder.findByUuid machine.id 13 | end 14 | 15 | def get_resource_pool(datacenter, machine) 16 | rp = get_compute_resource(datacenter, machine) 17 | 18 | resource_pool_name = machine.provider_config.resource_pool_name || '' 19 | 20 | entity_array = resource_pool_name.split('/') 21 | entity_array.each do |entity_array_item| 22 | next if entity_array_item.empty? 23 | if rp.is_a? RbVmomi::VIM::Folder 24 | rp = rp.childEntity.find { |f| f.name == entity_array_item } || fail(Errors::VSphereError, :missing_resource_pool) 25 | elsif rp.is_a? RbVmomi::VIM::ClusterComputeResource 26 | rp = rp.resourcePool.resourcePool.find { |f| f.name == entity_array_item } || fail(Errors::VSphereError, :missing_resource_pool) 27 | elsif rp.is_a? RbVmomi::VIM::ResourcePool 28 | rp = rp.resourcePool.find { |f| f.name == entity_array_item } || fail(Errors::VSphereError, :missing_resource_pool) 29 | elsif rp.is_a? RbVmomi::VIM::ComputeResource 30 | rp = rp.resourcePool.find(resource_pool_name) || fail(Errors::VSphereError, :missing_resource_pool) 31 | else 32 | fail Errors::VSphereError, :missing_resource_pool 33 | end 34 | end 35 | rp = rp.resourcePool if !rp.is_a?(RbVmomi::VIM::ResourcePool) && rp.respond_to?(:resourcePool) 36 | rp 37 | end 38 | 39 | def get_compute_resource(datacenter, machine) 40 | cr = find_clustercompute_or_compute_resource(datacenter, machine.provider_config.compute_resource_name) 41 | fail Errors::VSphereError, :missing_compute_resource if cr.nil? 42 | cr 43 | end 44 | 45 | def find_clustercompute_or_compute_resource(datacenter, path) 46 | if path.is_a? String 47 | es = path.split('/').reject(&:empty?) 48 | elsif path.is_a? Enumerable 49 | es = path 50 | else 51 | fail "unexpected path class #{path.class}" 52 | end 53 | return datacenter.hostFolder if es.empty? 54 | final = es.pop 55 | 56 | p = es.inject(datacenter.hostFolder) do |f, e| 57 | f.find(e, RbVmomi::VIM::Folder) || return 58 | end 59 | 60 | begin 61 | if (x = p.find(final, RbVmomi::VIM::ComputeResource)) 62 | x 63 | elsif (x = p.find(final, RbVmomi::VIM::ClusterComputeResource)) 64 | x 65 | end 66 | rescue Exception 67 | # When looking for the ClusterComputeResource there seems to be some parser error in RbVmomi Folder.find, try this instead 68 | x = p.childEntity.find { |x2| x2.name == final } 69 | if x.is_a?(RbVmomi::VIM::ClusterComputeResource) || x.is_a?(RbVmomi::VIM::ComputeResource) 70 | x 71 | else 72 | puts 'ex unknown type ' + x.to_json 73 | nil 74 | end 75 | end 76 | end 77 | 78 | def get_customization_spec_info_by_name(connection, machine) 79 | name = machine.provider_config.customization_spec_name 80 | return if name.nil? || name.empty? 81 | 82 | manager = connection.serviceContent.customizationSpecManager 83 | fail Errors::VSphereError, :null_configuration_spec_manager if manager.nil? 84 | 85 | spec = manager.GetCustomizationSpec(name: name) 86 | fail Errors::VSphereError, :missing_configuration_spec if spec.nil? 87 | 88 | spec 89 | end 90 | 91 | def get_datastore(datacenter, machine) 92 | name = machine.provider_config.data_store_name 93 | return if name.nil? || name.empty? 94 | 95 | # find_datastore uses folder datastore that only lists Datastore and not StoragePod, if not found also try datastoreFolder which contains StoragePod(s) 96 | datacenter.find_datastore(name) || datacenter.datastoreFolder.traverse(name) || fail(Errors::VSphereError, :missing_datastore) 97 | end 98 | 99 | def get_network_by_name(dc, name) 100 | dc.network.find { |f| f.name == name } || fail(Errors::VSphereError, :missing_vlan) 101 | end 102 | end 103 | end 104 | end 105 | end 106 | -------------------------------------------------------------------------------- /spec/clone_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | CUSTOM_VM_FOLDER = 'custom_vm_folder' 4 | 5 | describe VagrantPlugins::VSphere::Action::Clone do 6 | before :each do 7 | @env[:vSphere_connection] = @vim 8 | end 9 | 10 | it "should create a CloneVM task with template's parent" do 11 | call 12 | expect(@template).to have_received(:CloneVM_Task).with( 13 | folder: @data_center, 14 | name: NAME, 15 | spec: { location: { pool: @child_resource_pool }, 16 | config: RbVmomi::VIM.VirtualMachineConfigSpec } 17 | ) 18 | end 19 | 20 | it 'should create a CloneVM task with custom folder when given vm base path' do 21 | custom_base_folder = double(CUSTOM_VM_FOLDER, 22 | pretty_path: "#{@data_center.pretty_path}/#{CUSTOM_VM_FOLDER}") 23 | @machine.provider_config.stub(:vm_base_path).and_return(CUSTOM_VM_FOLDER) 24 | @data_center.vmFolder.stub(:traverse).with(CUSTOM_VM_FOLDER, RbVmomi::VIM::Folder, true).and_return(custom_base_folder) 25 | call 26 | expect(@template).to have_received(:CloneVM_Task).with( 27 | folder: custom_base_folder, 28 | name: NAME, 29 | spec: { location: { pool: @child_resource_pool }, 30 | config: RbVmomi::VIM.VirtualMachineConfigSpec } 31 | ) 32 | end 33 | 34 | it 'should set the machine id to be the new UUID' do 35 | call 36 | expect(@machine).to have_received(:id=).with(NEW_UUID) 37 | end 38 | 39 | it 'should call the next item in the middleware stack' do 40 | call 41 | expect(@app).to have_received :call 42 | end 43 | 44 | it 'should create a CloneVM spec with configured vlan' do 45 | @machine.provider_config.stub(:vlan).and_return('vlan') 46 | network = double('network', name: 'vlan') 47 | network.stub(:config).and_raise(StandardError) 48 | @data_center.stub(:network).and_return([network]) 49 | call 50 | 51 | expected_config = RbVmomi::VIM.VirtualMachineConfigSpec(deviceChange: Array.new) 52 | expected_dev_spec = RbVmomi::VIM.VirtualDeviceConfigSpec(device: @device, operation: 'edit') 53 | expected_config[:deviceChange].push expected_dev_spec 54 | 55 | expect(@template).to have_received(:CloneVM_Task).with( 56 | folder: @data_center, 57 | name: NAME, 58 | spec: { location: { pool: @child_resource_pool }, 59 | config: expected_config 60 | } 61 | ) 62 | end 63 | 64 | it 'should create a CloneVM spec with configured memory_mb' do 65 | @machine.provider_config.stub(:memory_mb).and_return(2048) 66 | call 67 | expect(@template).to have_received(:CloneVM_Task).with( 68 | folder: @data_center, 69 | name: NAME, 70 | spec: { location: { pool: @child_resource_pool }, 71 | config: RbVmomi::VIM.VirtualMachineConfigSpec(memoryMB: 2048) } 72 | ) 73 | end 74 | 75 | it 'should create a CloneVM spec with configured number of cpus' do 76 | @machine.provider_config.stub(:cpu_count).and_return(4) 77 | call 78 | expect(@template).to have_received(:CloneVM_Task).with( 79 | folder: @data_center, 80 | name: NAME, 81 | spec: { location: { pool: @child_resource_pool }, 82 | config: RbVmomi::VIM.VirtualMachineConfigSpec(numCPUs: 4) } 83 | ) 84 | end 85 | 86 | it 'should set static IP when given config spec' do 87 | @machine.provider_config.stub(:customization_spec_name).and_return('spec') 88 | call 89 | expect(@ip).to have_received(:ipAddress=).with('0.0.0.0') 90 | end 91 | 92 | it 'should use root resource pool when cloning from template and no resource pool specified' do 93 | @machine.provider_config.stub(:resource_pool_name).and_return(nil) 94 | call 95 | expect(@template).to have_received(:CloneVM_Task).with( 96 | folder: @data_center, 97 | name: NAME, 98 | spec: { location: { pool: @root_resource_pool }, 99 | config: RbVmomi::VIM.VirtualMachineConfigSpec } 100 | ) 101 | end 102 | 103 | it 'should set extraConfig if specified' do 104 | @machine.provider_config.stub(:extra_config).and_return( 105 | 'guestinfo.hostname' => 'somehost.testvm') 106 | expected_config = RbVmomi::VIM.VirtualMachineConfigSpec(extraConfig: [ 107 | { 'key' => 'guestinfo.hostname', 'value' => 'somehost.testvm' } 108 | ]) 109 | 110 | call 111 | expect(@template).to have_received(:CloneVM_Task).with( 112 | folder: @data_center, 113 | name: NAME, 114 | spec: { location: { pool: @child_resource_pool }, 115 | config: expected_config } 116 | ) 117 | end 118 | 119 | it 'should set custom notes when they are specified' do 120 | @machine.provider_config.stub(:notes).and_return('custom_notes') 121 | call 122 | expect(@template).to have_received(:CloneVM_Task).with( 123 | folder: @data_center, 124 | name: NAME, 125 | spec: { location: { pool: @child_resource_pool }, 126 | config: RbVmomi::VIM.VirtualMachineConfigSpec(annotation: 'custom_notes') } 127 | ) 128 | end 129 | end 130 | -------------------------------------------------------------------------------- /spec/get_ssh_info_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe VagrantPlugins::VSphere::Action::GetSshInfo do 4 | before :each do 5 | @env[:vSphere_connection] = @vim 6 | end 7 | 8 | it 'should set the ssh info to nil if machine ID is not set' do 9 | call 10 | 11 | expect(@env.key?(:machine_ssh_info)).to be true 12 | expect(@env[:machine_ssh_info]).to be nil 13 | end 14 | 15 | it 'should set the ssh info to nil for a VM that does not exist' do 16 | @env[:machine].stub(:id).and_return(MISSING_UUID) 17 | 18 | call 19 | 20 | expect(@env.key?(:machine_ssh_info)).to be true 21 | expect(@env[:machine_ssh_info]).to be nil 22 | end 23 | 24 | it 'should set the ssh info host to the IP an existing VM' do 25 | @env[:machine].stub(:id).and_return(EXISTING_UUID) 26 | call 27 | 28 | expect(@env[:machine_ssh_info][:host]).to be IP_ADDRESS 29 | end 30 | 31 | context 'when acting on a VM with a single network adapter' do 32 | before do 33 | allow(@vm.guest).to receive(:ipAddress) { '127.0.0.2' } 34 | @env[:machine].stub(:id).and_return(EXISTING_UUID) 35 | end 36 | 37 | it 'should return the correct ip address' do 38 | call 39 | expect(@env[:machine_ssh_info][:host]).to eq '127.0.0.2' 40 | end 41 | end 42 | 43 | context 'when acting on a VM with multiple network adapters' do 44 | before do 45 | @env[:machine].stub(:id).and_return(EXISTING_UUID) 46 | allow(@vm.guest).to receive(:net) { 47 | [double('GuestNicInfo', 48 | ipAddress: ['bad address', '127.0.0.1'], 49 | deviceConfigId: 4000, 50 | ipConfig: double('NetIpConfigInfo', 51 | ipAddress: [double('NetIpConfigInfoIpAddress', 52 | ipAddress: 'bad address', state: 'unknown'), 53 | double('NetIpConfigInfoIpAddress', 54 | ipAddress: '127.0.0.1', state: 'preferred')] 55 | ) 56 | ), 57 | double('GuestNicInfo', 58 | ipAddress: ['bad address', '255.255.255.255'], 59 | deviceConfigId: -1, 60 | ipConfig: double('NetIpConfigInfo', 61 | ipAddress: [double('NetIpConfigInfoIpAddress', 62 | ipAddress: 'bad address', state: 'unknown'), 63 | double('NetIpConfigInfoIpAddress', 64 | ipAddress: '255.255.255.255', state: 'preferred')] 65 | ) 66 | ) 67 | ] 68 | } 69 | end 70 | 71 | context 'when the real_nic_ip option is false' do 72 | it 'sets the ssh info the original adapter' do 73 | call 74 | expect(@env[:machine_ssh_info][:host]).to eq IP_ADDRESS 75 | end 76 | end 77 | 78 | context 'when the real_nic_ip option is true' do 79 | before do 80 | @env[:machine].provider_config.stub(:real_nic_ip).and_return(true) 81 | end 82 | context 'when there are mutiple valid adapters' do 83 | before do 84 | allow(@vm.guest).to receive(:net) { 85 | [double('GuestNicInfo', 86 | ipAddress: ['bad address', '127.0.0.1'], 87 | deviceConfigId: 4000, 88 | ipConfig: double('NetIpConfigInfo', 89 | ipAddress: [double('NetIpConfigInfoIpAddress', 90 | ipAddress: 'bad address', state: 'unknown'), 91 | double('NetIpConfigInfoIpAddress', 92 | ipAddress: '127.0.0.2', state: 'preferred')] 93 | ) 94 | ), 95 | double('GuestNicInfo', 96 | ipAddress: ['bad address', '255.255.255.255'], 97 | deviceConfigId: 2000, 98 | ipConfig: double('NetIpConfigInfo', 99 | ipAddress: [double('NetIpConfigInfoIpAddress', 100 | ipAddress: 'bad address', state: 'unknown'), 101 | double('NetIpConfigInfoIpAddress', 102 | ipAddress: '255.255.255.255', state: 'preferred')] 103 | ) 104 | ) 105 | ] 106 | } 107 | 108 | end 109 | it 'should raise an error' do 110 | expect { call }.to raise_error(VagrantPlugins::VSphere::Errors::VSphereError) 111 | end 112 | end 113 | 114 | it 'sets the ssh info host to the correct adapter' do 115 | call 116 | expect(@env[:machine_ssh_info][:host]).to eq IP_ADDRESS 117 | end 118 | 119 | context 'when the VM networking is uninitialized' do 120 | before do 121 | allow(@vm.guest).to receive(:net) { [] } 122 | allow(@vm.guest).to receive(:ipAddress) { '123.234.156.78' } 123 | end 124 | it 'should set the ssh info to the guest ipAddress and port 22 if no valid adapters are present' do 125 | call 126 | expect(@env[:machine_ssh_info]).to eq(host: '123.234.156.78', port: 22) 127 | end 128 | end 129 | end 130 | end 131 | end 132 | -------------------------------------------------------------------------------- /lib/vSphere/util/vm_helpers.rb: -------------------------------------------------------------------------------- 1 | require 'rbvmomi' 2 | 3 | module VagrantPlugins 4 | module VSphere 5 | module Util 6 | module VmState 7 | POWERED_ON = 'poweredOn' 8 | POWERED_OFF = 'poweredOff' 9 | SUSPENDED = 'suspended' 10 | end 11 | 12 | module VmHelpers 13 | def power_on_vm(vm) 14 | vm.PowerOnVM_Task.wait_for_completion 15 | end 16 | 17 | def power_off_vm(vm) 18 | vm.PowerOffVM_Task.wait_for_completion 19 | end 20 | 21 | # https://www.vmware.com/support/developer/converter-sdk/conv61_apireference/vim.VirtualMachine.html#powerOn 22 | def resume_vm(vm) 23 | vm.PowerOnVM_Task.wait_for_completion 24 | end 25 | 26 | def suspend_vm(vm) 27 | vm.SuspendVM_Task.wait_for_completion 28 | end 29 | 30 | def get_vm_state(vm) 31 | vm.runtime.powerState 32 | end 33 | 34 | def powered_on?(vm) 35 | get_vm_state(vm).eql?(VmState::POWERED_ON) 36 | end 37 | 38 | def powered_off?(vm) 39 | get_vm_state(vm).eql?(VmState::POWERED_OFF) 40 | end 41 | 42 | def suspended?(vm) 43 | get_vm_state(vm).eql?(VmState::SUSPENDED) 44 | end 45 | 46 | # Enumerate VM snapshot tree 47 | # 48 | # This method returns an enumerator that performs a depth-first walk 49 | # of the VM snapshot grap and yields each VirtualMachineSnapshotTree 50 | # node. 51 | # 52 | # @param vm [RbVmomi::VIM::VirtualMachine] 53 | # 54 | # @return [Enumerator] 55 | def enumerate_snapshots(vm) 56 | snapshot_info = vm.snapshot 57 | 58 | if snapshot_info.nil? 59 | snapshot_root = [] 60 | else 61 | snapshot_root = snapshot_info.rootSnapshotList 62 | end 63 | 64 | recursor = lambda do |snapshot_list| 65 | Enumerator.new do |yielder| 66 | snapshot_list.each do |s| 67 | # Yield the current VirtualMachineSnapshotTree object 68 | yielder.yield s 69 | 70 | # Recurse into child VirtualMachineSnapshotTree objects 71 | children = recursor.call(s.childSnapshotList) 72 | loop do 73 | yielder.yield children.next 74 | end 75 | end 76 | end 77 | end 78 | 79 | recursor.call(snapshot_root) 80 | end 81 | 82 | # Create a named snapshot on a given VM 83 | # 84 | # This method creates a named snapshot on the given VM. This method 85 | # blocks until the snapshot creation task is complete. An optional 86 | # block can be passed which is used to report progress. 87 | # 88 | # @param vm [RbVmomi::VIM::VirtualMachine] 89 | # @param name [String] 90 | # @yield [Integer] Percentage complete as an integer. Called multiple 91 | # times. 92 | # 93 | # @return [void] 94 | def create_snapshot(vm, name) 95 | task = vm.CreateSnapshot_Task( 96 | name: name, 97 | memory: false, 98 | quiesce: false) 99 | 100 | if block_given? 101 | task.wait_for_progress do |progress| 102 | yield progress unless progress.nil? 103 | end 104 | else 105 | task.wait_for_completion 106 | end 107 | end 108 | 109 | # Delete a named snapshot on a given VM 110 | # 111 | # This method deletes a named snapshot on the given VM. This method 112 | # blocks until the snapshot deletion task is complete. An optional 113 | # block can be passed which is used to report progress. 114 | # 115 | # @param vm [RbVmomi::VIM::VirtualMachine] 116 | # @param name [String] 117 | # @yield [Integer] Percentage complete as an integer. Called multiple 118 | # times. 119 | # 120 | # @return [void] 121 | def delete_snapshot(vm, name) 122 | snapshot = enumerate_snapshots(vm).find { |s| s.name == name } 123 | 124 | # No snapshot matching "name" 125 | return nil if snapshot.nil? 126 | 127 | task = snapshot.snapshot.RemoveSnapshot_Task(removeChildren: false) 128 | 129 | if block_given? 130 | task.wait_for_progress do |progress| 131 | yield progress unless progress.nil? 132 | end 133 | else 134 | task.wait_for_completion 135 | end 136 | end 137 | 138 | # Restore a VM to a named snapshot 139 | # 140 | # This method restores a VM to the named snapshot state. This method 141 | # blocks until the restoration task is complete. An optional block can 142 | # be passed which is used to report progress. 143 | # 144 | # @param vm [RbVmomi::VIM::VirtualMachine] 145 | # @param name [String] 146 | # @yield [Integer] Percentage complete as an integer. Called multiple 147 | # times. 148 | # 149 | # @return [void] 150 | def restore_snapshot(vm, name) 151 | snapshot = enumerate_snapshots(vm).find { |s| s.name == name } 152 | 153 | # No snapshot matching "name" 154 | return nil if snapshot.nil? 155 | 156 | task = snapshot.snapshot.RevertToSnapshot_Task(suppressPowerOn: true) 157 | 158 | if block_given? 159 | task.wait_for_progress do |progress| 160 | yield progress unless progress.nil? 161 | end 162 | else 163 | task.wait_for_completion 164 | end 165 | end 166 | end 167 | end 168 | end 169 | end 170 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'rbvmomi' 2 | require 'pathname' 3 | require 'vSphere/errors' 4 | require 'vSphere/action' 5 | require 'vSphere/action/connect_vsphere' 6 | require 'vSphere/action/close_vsphere' 7 | require 'vSphere/action/is_created' 8 | require 'vSphere/action/get_state' 9 | require 'vSphere/action/get_ssh_info' 10 | require 'vSphere/action/clone' 11 | require 'vSphere/action/message_already_created' 12 | require 'vSphere/action/message_not_created' 13 | require 'vSphere/action/destroy' 14 | require 'vSphere/action/power_off' 15 | require 'vagrant-vsphere' 16 | 17 | VIM = RbVmomi::VIM 18 | 19 | EXISTING_UUID = 'existing_uuid' 20 | MISSING_UUID = 'missing_uuid' 21 | NEW_UUID = 'new_uuid' 22 | TEMPLATE = 'template' 23 | NAME = 'vm' 24 | IP_ADDRESS = '127.0.0.1' 25 | 26 | RSpec.configure do |config| 27 | VagrantPlugins::VSphere::Plugin.setup_i18n 28 | 29 | # removes deprecation warnings. 30 | # http://stackoverflow.com/questions/20275510/how-to-avoid-deprecation-warning-for-stub-chain-in-rspec-3-0/20296359#20296359 31 | config.mock_with :rspec do |c| 32 | c.syntax = [:should, :expect] 33 | end 34 | 35 | config.before(:each) do 36 | def call 37 | described_class.new(@app, @env).call(@env) 38 | end 39 | 40 | provider_config = double( 41 | host: 'testhost.com', 42 | user: 'testuser', 43 | password: 'testpassword', 44 | data_center_name: nil, 45 | compute_resource_name: 'testcomputeresource', 46 | resource_pool_name: 'testresourcepool', 47 | vm_base_path: nil, 48 | template_name: TEMPLATE, 49 | name: NAME, 50 | insecure: true, 51 | validate: [], 52 | customization_spec_name: nil, 53 | data_store_name: nil, 54 | clone_from_vm: nil, 55 | linked_clone: nil, 56 | proxy_host: nil, 57 | proxy_port: nil, 58 | vlan: nil, 59 | memory_mb: nil, 60 | cpu_count: nil, 61 | mac: nil, 62 | addressType: nil, 63 | cpu_reservation: nil, 64 | mem_reservation: nil, 65 | custom_attributes: {}, 66 | notes: nil, 67 | extra_config: {}, 68 | ip_address_timeout: 1, 69 | real_nic_ip: false, 70 | wait_for_sysprep: false) 71 | vm_config = double( 72 | vm: double('config_vm', 73 | box: nil, 74 | synced_folders: [], 75 | provisioners: [], 76 | hostname: nil, 77 | communicator: nil, 78 | networks: [[:private_network, { ip: '0.0.0.0' }]], 79 | boot_timeout: 1, 80 | graceful_halt_timeout: 0.1, 81 | guest: nil), 82 | validate: [] 83 | ) 84 | @app = double 'app', call: true 85 | @machine = double 'machine', 86 | :provider_config => provider_config, 87 | :config => vm_config, 88 | :state => double('state', id: nil), 89 | :communicate => double('communicator', :wait_for_ready => true, :ready? => true), 90 | :ssh_info => { :host => IP_ADDRESS }, 91 | :data_dir => Pathname.new(''), 92 | :id => nil, 93 | :id= => nil, 94 | :name => nil, 95 | :guest => double('guest', capability: nil) 96 | 97 | @env = { 98 | machine: @machine, 99 | ui: double('ui', info: nil, output: nil, detail: nil) 100 | } 101 | 102 | @vm = double('vm', 103 | runtime: double('runtime', powerState: nil), 104 | guest: double('guest', ipAddress: IP_ADDRESS), 105 | Destroy_Task: double('result', wait_for_completion: nil), 106 | PowerOffVM_Task: double('result', wait_for_completion: nil), 107 | PowerOnVM_Task: double('result', wait_for_completion: nil)) 108 | 109 | vm_folder = double('vm_folder') 110 | vm_folder.stub(:findByUuid).with(EXISTING_UUID).and_return(@vm) 111 | vm_folder.stub(:findByUuid).with(MISSING_UUID).and_return(nil) 112 | vm_folder.stub(:findByUuid).with(nil).and_return(nil) 113 | 114 | @child_resource_pool = double('testresourcepool') 115 | @root_resource_pool = double('pools', find: @child_resource_pool) 116 | 117 | @compute_resource = RbVmomi::VIM::ComputeResource.new(nil, nil) 118 | @compute_resource.stub(:resourcePool).and_return(@root_resource_pool) 119 | 120 | @host_folder = double('hostFolder', childEntity: double('childEntity', find: @compute_resource)) 121 | 122 | @data_center = double('data_center', 123 | vmFolder: vm_folder, 124 | pretty_path: "data_center/#{vm_folder}", 125 | find_compute_resource: @compute_resource, 126 | hostFolder: @host_folder) 127 | 128 | @device = RbVmomi::VIM::VirtualEthernetCard.new 129 | @device.stub(:backing).and_return(RbVmomi::VIM::VirtualEthernetCardNetworkBackingInfo.new) 130 | 131 | @virtual_hardware = double('virtual_hardware', 132 | device: [@device]) 133 | @template_config = double('template_config', 134 | hardware: @virtual_hardware) 135 | 136 | @template = double('template_vm', 137 | parent: @data_center, 138 | pretty_path: "#{@data_center.pretty_path}/template_vm", 139 | CloneVM_Task: double('result', 140 | wait_for_completion: double('new_vm', config: double('config', uuid: NEW_UUID))), 141 | config: @template_config) 142 | 143 | @data_center.stub(:find_vm).with(TEMPLATE).and_return(@template) 144 | 145 | service_instance = double 'service_instance', find_datacenter: @data_center 146 | @ip = double 'ip', :ipAddress= => nil 147 | @customization_spec = double 'customization spec', nicSettingMap: [double('nic setting', adapter: double('adapter', ip: @ip))] 148 | @customization_spec.stub(:clone).and_return(@customization_spec) 149 | customization_spec_manager = double 'customization spec manager', GetCustomizationSpec: double('spec info', spec: @customization_spec) 150 | service_content = double 'service content', customizationSpecManager: customization_spec_manager 151 | @vim = double 'vim', serviceInstance: service_instance, close: true, serviceContent: service_content 152 | 153 | VIM.stub(:connect).and_return(@vim) 154 | VIM.stub(:VirtualMachineRelocateSpec).and_return({}) 155 | VIM.stub(:VirtualMachineCloneSpec) { |location, _powerOn, _template| { location: location[:location] } } 156 | end 157 | end 158 | -------------------------------------------------------------------------------- /spec/action_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'vagrant' 3 | 4 | describe VagrantPlugins::VSphere::Action do 5 | def run(action) 6 | Vagrant::Action::Runner.new.run described_class.send("action_#{action}"), @env 7 | end 8 | 9 | before :each do 10 | @machine.stub(:id).and_return(EXISTING_UUID) 11 | # Vagrant has some pretty buggy multi threading and their conditions 12 | # check can fail if the wait_for_ready method returns right away 13 | @machine.communicate.stub(:wait_for_ready) { sleep(1) } 14 | end 15 | 16 | describe 'up' do 17 | def run_up 18 | run :up 19 | end 20 | 21 | it 'should connect to vSphere' do 22 | VagrantPlugins::VSphere::Action::ConnectVSphere.any_instance.should_receive(:call) 23 | 24 | run_up 25 | end 26 | 27 | it 'should check if the VM exits' do 28 | @machine.state.stub(:id).and_return(:running) 29 | 30 | VagrantPlugins::VSphere::Action::IsCreated.any_instance.should_receive(:call) 31 | 32 | run_up 33 | end 34 | 35 | it 'should create the VM when the VM does already not exist' do 36 | @machine.state.stub(:id).and_return(:not_created) 37 | 38 | VagrantPlugins::VSphere::Action::Clone.any_instance.should_receive(:call) 39 | 40 | run_up 41 | end 42 | 43 | it 'should not create the VM when the VM already exists' do 44 | @machine.state.stub(:id).and_return(:running) 45 | 46 | VagrantPlugins::VSphere::Action::Clone.any_instance.should_not_receive(:call) 47 | 48 | run_up 49 | end 50 | end 51 | 52 | describe 'destroy' do 53 | def run_destroy 54 | run :destroy 55 | end 56 | 57 | it 'should connect to vSphere' do 58 | VagrantPlugins::VSphere::Action::ConnectVSphere.any_instance.should_receive(:call) 59 | 60 | run_destroy 61 | end 62 | 63 | it 'should power off the VM' do 64 | @machine.state.stub(:id).and_return(:running) 65 | VagrantPlugins::VSphere::Action::PowerOff.any_instance.should_receive(:call) 66 | 67 | run_destroy 68 | end 69 | 70 | it 'should destroy the VM' do 71 | VagrantPlugins::VSphere::Action::Destroy.any_instance.should_receive(:call) 72 | 73 | run_destroy 74 | end 75 | end 76 | 77 | describe 'halt' do 78 | after :each do 79 | run :halt 80 | end 81 | 82 | it 'should connect to vSphere' do 83 | VagrantPlugins::VSphere::Action::ConnectVSphere.any_instance.should_receive(:call) 84 | end 85 | 86 | it 'should gracefully power off the VM' do 87 | @machine.state.stub(:id).and_return(:running) 88 | VagrantPlugins::VSphere::Action::GracefulHalt.any_instance.should_receive(:call) 89 | end 90 | end 91 | 92 | describe 'get state' do 93 | def run_get_state 94 | run :get_state 95 | end 96 | 97 | it 'should connect to vSphere' do 98 | VagrantPlugins::VSphere::Action::ConnectVSphere.any_instance.should_receive(:call) 99 | 100 | run_get_state 101 | end 102 | 103 | it 'should get the power state' do 104 | VagrantPlugins::VSphere::Action::GetState.any_instance.should_receive(:call) 105 | 106 | run_get_state 107 | end 108 | 109 | it 'should handle the values in a base vagrant box' do 110 | Vagrant::Action::Builtin::HandleBox.any_instance.should_receive(:call) 111 | 112 | run_get_state 113 | end 114 | end 115 | 116 | describe 'get ssh info' do 117 | def run_get_ssh_info 118 | run :get_ssh_info 119 | end 120 | 121 | it 'should connect to vSphere' do 122 | VagrantPlugins::VSphere::Action::ConnectVSphere.any_instance.should_receive(:call) 123 | 124 | run_get_ssh_info 125 | end 126 | 127 | it 'should get the power state' do 128 | VagrantPlugins::VSphere::Action::GetSshInfo.any_instance.should_receive(:call) 129 | 130 | run_get_ssh_info 131 | end 132 | end 133 | 134 | describe 'reload' do 135 | after :each do 136 | run :reload 137 | end 138 | 139 | it 'should gracefully power off the VM' do 140 | @machine.state.stub(:id).and_return(:running) 141 | 142 | VagrantPlugins::VSphere::Action::GracefulHalt.any_instance.should_receive(:call) 143 | end 144 | 145 | it 'should connect to vSphere' do 146 | VagrantPlugins::VSphere::Action::ConnectVSphere.any_instance.should_receive(:call) 147 | end 148 | 149 | it 'should check if the VM exits' do 150 | VagrantPlugins::VSphere::Action::IsCreated.any_instance.should_receive(:call) 151 | end 152 | 153 | it 'should not create the VM when the VM does already not exist' do 154 | @machine.state.stub(:id).and_return(:not_created) 155 | 156 | VagrantPlugins::VSphere::Action::Clone.any_instance.should_not_receive(:call) 157 | VagrantPlugins::VSphere::Action::MessageNotCreated.any_instance.should_receive(:call) 158 | end 159 | 160 | it 'should not create the VM when the VM already exists' do 161 | @machine.state.stub(:id).and_return(:running) 162 | 163 | VagrantPlugins::VSphere::Action::Clone.any_instance.should_not_receive(:call) 164 | VagrantPlugins::VSphere::Action::MessageAlreadyCreated.any_instance.should_receive(:call) 165 | end 166 | end 167 | 168 | describe 'suspend' do 169 | it 'should connect to vSphere' do 170 | VagrantPlugins::VSphere::Action::ConnectVSphere.any_instance.should_receive(:call) 171 | run :suspend 172 | end 173 | 174 | it 'should check if the VM exits' do 175 | @machine.state.stub(:id).and_return(:running) 176 | VagrantPlugins::VSphere::Action::IsCreated.any_instance.should_receive(:call) 177 | run :suspend 178 | end 179 | 180 | it 'should suspend the VM when the VM is running' do 181 | @machine.state.stub(:id).and_return(:running) 182 | VagrantPlugins::VSphere::Action::Suspend.any_instance.should_receive(:call) 183 | run :suspend 184 | end 185 | 186 | it 'should not suspend the VM when the VM is suspended' do 187 | @machine.state.stub(:id).and_return(:suspended) 188 | VagrantPlugins::VSphere::Action::Suspend.any_instance.should_not_receive(:call) 189 | run :suspend 190 | end 191 | 192 | it 'should not suspend the VM when the VM is off' do 193 | @machine.state.stub(:id).and_return(:poweroff) 194 | VagrantPlugins::VSphere::Action::Suspend.any_instance.should_not_receive(:call) 195 | run :suspend 196 | end 197 | end 198 | 199 | describe 'resume' do 200 | it 'should connect to vSphere' do 201 | VagrantPlugins::VSphere::Action::ConnectVSphere.any_instance.should_receive(:call) 202 | run :resume 203 | end 204 | 205 | it 'should check if the VM exits' do 206 | @machine.state.stub(:id).and_return(:suspended) 207 | VagrantPlugins::VSphere::Action::IsCreated.any_instance.should_receive(:call) 208 | run :resume 209 | end 210 | 211 | it 'should not resume the VM when the VM is running' do 212 | @machine.state.stub(:id).and_return(:running) 213 | VagrantPlugins::VSphere::Action::Resume.any_instance.should_not_receive(:call) 214 | run :resume 215 | end 216 | 217 | it 'should resume the VM when the VM is suspended' do 218 | @machine.state.stub(:id).and_return(:suspended) 219 | VagrantPlugins::VSphere::Action::Resume.any_instance.should_receive(:call) 220 | run :resume 221 | end 222 | 223 | it 'should not resume the VM when the VM is off' do 224 | @machine.state.stub(:id).and_return(:poweroff) 225 | VagrantPlugins::VSphere::Action::Resume.any_instance.should_not_receive(:call) 226 | run :resume 227 | end 228 | end 229 | end 230 | -------------------------------------------------------------------------------- /lib/vSphere/action.rb: -------------------------------------------------------------------------------- 1 | require 'vagrant' 2 | require 'vagrant/action/builder' 3 | 4 | module VagrantPlugins 5 | module VSphere 6 | module Action 7 | include Vagrant::Action::Builtin 8 | 9 | # Vagrant commands 10 | def self.action_destroy 11 | Vagrant::Action::Builder.new.tap do |b| 12 | b.use ConfigValidate 13 | b.use ConnectVSphere 14 | b.use(ProvisionerCleanup, :before) 15 | 16 | b.use Call, IsRunning do |env, b2| 17 | if env[:result] 18 | if env[:force_confirm_destroy] 19 | b2.use PowerOff 20 | next 21 | end 22 | 23 | b2.use Call, GracefulHalt, :poweroff, :running do |env2, b3| 24 | b3.use PowerOff unless env2[:result] 25 | end 26 | end 27 | end 28 | b.use Destroy 29 | end 30 | end 31 | 32 | def self.action_provision 33 | Vagrant::Action::Builder.new.tap do |b| 34 | b.use ConfigValidate 35 | b.use Call, IsCreated do |env, b2| 36 | unless env[:result] 37 | b2.use MessageNotCreated 38 | next 39 | end 40 | 41 | b2.use Call, IsRunning do |env2, b3| 42 | unless env2[:result] 43 | b3.use MessageNotRunning 44 | next 45 | end 46 | 47 | b3.use Provision 48 | b3.use SyncedFolders 49 | end 50 | end 51 | end 52 | end 53 | 54 | def self.action_ssh 55 | Vagrant::Action::Builder.new.tap do |b| 56 | b.use ConfigValidate 57 | b.use Call, IsCreated do |env, b2| 58 | unless env[:result] 59 | b2.use MessageNotCreated 60 | next 61 | end 62 | 63 | b2.use Call, IsRunning do |env2, b3| 64 | unless env2[:result] 65 | b3.use MessageNotRunning 66 | next 67 | end 68 | 69 | b3.use SSHExec 70 | end 71 | end 72 | end 73 | end 74 | 75 | def self.action_ssh_run 76 | Vagrant::Action::Builder.new.tap do |b| 77 | b.use ConfigValidate 78 | b.use Call, IsCreated do |env, b2| 79 | unless env[:result] 80 | b2.use MessageNotCreated 81 | next 82 | end 83 | 84 | b2.use Call, IsRunning do |env2, b3| 85 | unless env2[:result] 86 | b3.use MessageNotRunning 87 | next 88 | end 89 | 90 | b3.use SSHRun 91 | end 92 | end 93 | end 94 | end 95 | 96 | def self.action_up 97 | Vagrant::Action::Builder.new.tap do |b| 98 | b.use HandleBox 99 | b.use ConfigValidate 100 | b.use ConnectVSphere 101 | b.use Call, IsCreated do |env, b2| 102 | if env[:result] 103 | b2.use MessageAlreadyCreated 104 | next 105 | end 106 | 107 | b2.use Clone 108 | end 109 | b.use Call, IsRunning do |env, b2| 110 | b2.use PowerOn unless env[:result] 111 | end 112 | b.use CloseVSphere 113 | b.use WaitForIPAddress 114 | b.use WaitForCommunicator, [:running] 115 | b.use Provision 116 | b.use SyncedFolders 117 | b.use SetHostname 118 | end 119 | end 120 | 121 | def self.action_halt 122 | Vagrant::Action::Builder.new.tap do |b| 123 | b.use ConfigValidate 124 | b.use ConnectVSphere 125 | b.use Call, IsCreated do |env, b2| 126 | unless env[:result] 127 | b2.use MessageNotCreated 128 | next 129 | end 130 | 131 | b2.use Call, IsRunning do |env2, b3| 132 | unless env2[:result] 133 | b3.use MessageNotRunning 134 | next 135 | end 136 | 137 | b3.use Call, GracefulHalt, :poweroff, :running do |env3, b4| 138 | b4.use PowerOff unless env3[:result] 139 | end 140 | end 141 | end 142 | b.use CloseVSphere 143 | end 144 | end 145 | 146 | def self.action_reload 147 | Vagrant::Action::Builder.new.tap do |b| 148 | b.use ConnectVSphere 149 | b.use Call, IsCreated do |env, b2| 150 | unless env[:result] 151 | b2.use MessageNotCreated 152 | next 153 | end 154 | b2.use action_halt 155 | b2.use action_up 156 | end 157 | end 158 | end 159 | 160 | def self.action_resume 161 | Vagrant::Action::Builder.new.tap do |b| 162 | b.use ConfigValidate 163 | b.use ConnectVSphere 164 | b.use Call, IsCreated do |env, b2| 165 | unless env[:result] 166 | b2.use MessageNotCreated 167 | next 168 | end 169 | 170 | b2.use Call, IsSuspended do |env2, b3| 171 | unless env2[:result] 172 | b3.use MessageNotSuspended 173 | next 174 | end 175 | 176 | b3.use Resume 177 | end 178 | end 179 | b.use CloseVSphere 180 | end 181 | end 182 | 183 | def self.action_suspend 184 | Vagrant::Action::Builder.new.tap do |b| 185 | b.use ConfigValidate 186 | b.use ConnectVSphere 187 | b.use Call, IsCreated do |env, b2| 188 | unless env[:result] 189 | b2.use MessageNotCreated 190 | next 191 | end 192 | 193 | b2.use Call, IsRunning do |env2, b3| 194 | unless env2[:result] 195 | b3.use MessageNotRunning 196 | next 197 | end 198 | 199 | b3.use Suspend 200 | end 201 | end 202 | b.use CloseVSphere 203 | end 204 | end 205 | 206 | # vSphere specific actions 207 | def self.action_get_state 208 | Vagrant::Action::Builder.new.tap do |b| 209 | b.use HandleBox 210 | b.use ConfigValidate 211 | b.use ConnectVSphere 212 | b.use GetState 213 | b.use CloseVSphere 214 | end 215 | end 216 | 217 | def self.action_get_ssh_info 218 | Vagrant::Action::Builder.new.tap do |b| 219 | b.use ConfigValidate 220 | b.use ConnectVSphere 221 | b.use GetSshInfo 222 | b.use CloseVSphere 223 | end 224 | end 225 | 226 | # TODO: Remove the if guard when Vagrant 1.8.0 is the minimum version. 227 | # rubocop:disable IndentationWidth 228 | if Gem::Version.new(Vagrant::VERSION) >= Gem::Version.new('1.8.0') 229 | def self.action_snapshot_delete 230 | Vagrant::Action::Builder.new.tap do |b| 231 | b.use ConfigValidate 232 | b.use ConnectVSphere 233 | b.use Call, IsCreated do |env, b2| 234 | if env[:result] 235 | b2.use SnapshotDelete 236 | else 237 | b2.use MessageNotCreated 238 | end 239 | end 240 | b.use CloseVSphere 241 | end 242 | end 243 | 244 | def self.action_snapshot_list 245 | Vagrant::Action::Builder.new.tap do |b| 246 | b.use ConfigValidate 247 | b.use ConnectVSphere 248 | b.use Call, IsCreated do |env, b2| 249 | if env[:result] 250 | b2.use SnapshotList 251 | else 252 | b2.use MessageNotCreated 253 | end 254 | end 255 | b.use CloseVSphere 256 | end 257 | end 258 | 259 | def self.action_snapshot_restore 260 | Vagrant::Action::Builder.new.tap do |b| 261 | b.use ConfigValidate 262 | b.use ConnectVSphere 263 | b.use Call, IsCreated do |env, b2| 264 | unless env[:result] 265 | b2.use MessageNotCreated 266 | next 267 | end 268 | 269 | b2.use SnapshotRestore 270 | b2.use Call, IsEnvSet, :snapshot_delete do |env2, b3| 271 | # Used by vagrant push/pop 272 | b3.use action_snapshot_delete if env2[:result] 273 | end 274 | 275 | b2.use action_up 276 | end 277 | b.use CloseVSphere 278 | end 279 | end 280 | 281 | def self.action_snapshot_save 282 | Vagrant::Action::Builder.new.tap do |b| 283 | b.use ConfigValidate 284 | b.use ConnectVSphere 285 | b.use Call, IsCreated do |env, b2| 286 | if env[:result] 287 | b2.use SnapshotSave 288 | else 289 | b2.use MessageNotCreated 290 | end 291 | end 292 | b.use CloseVSphere 293 | end 294 | end 295 | end # Vagrant > 1.8.0 guard 296 | # rubocop:enable IndentationWidth 297 | 298 | # autoload 299 | action_root = Pathname.new(File.expand_path('../action', __FILE__)) 300 | autoload :Clone, action_root.join('clone') 301 | autoload :CloseVSphere, action_root.join('close_vsphere') 302 | autoload :ConnectVSphere, action_root.join('connect_vsphere') 303 | autoload :Destroy, action_root.join('destroy') 304 | autoload :GetSshInfo, action_root.join('get_ssh_info') 305 | autoload :GetState, action_root.join('get_state') 306 | autoload :IsCreated, action_root.join('is_created') 307 | autoload :IsRunning, action_root.join('is_running') 308 | autoload :IsSuspended, action_root.join('is_suspended') 309 | autoload :MessageAlreadyCreated, action_root.join('message_already_created') 310 | autoload :MessageNotCreated, action_root.join('message_not_created') 311 | autoload :MessageNotRunning, action_root.join('message_not_running') 312 | autoload :MessageNotSuspended, action_root.join('message_not_suspended') 313 | autoload :PowerOff, action_root.join('power_off') 314 | autoload :PowerOn, action_root.join('power_on') 315 | autoload :Resume, action_root.join('resume') 316 | autoload :Suspend, action_root.join('suspend') 317 | autoload :WaitForIPAddress, action_root.join('wait_for_ip_address') 318 | 319 | # TODO: Remove the if guard when Vagrant 1.8.0 is the minimum version. 320 | # rubocop:disable IndentationWidth 321 | if Gem::Version.new(Vagrant::VERSION) >= Gem::Version.new('1.8.0') 322 | autoload :SnapshotDelete, action_root.join('snapshot_delete') 323 | autoload :SnapshotList, action_root.join('snapshot_list') 324 | autoload :SnapshotRestore, action_root.join('snapshot_restore') 325 | autoload :SnapshotSave, action_root.join('snapshot_save') 326 | end 327 | # rubocop:enable IndentationWidth 328 | end 329 | end 330 | end 331 | -------------------------------------------------------------------------------- /lib/vSphere/action/clone.rb: -------------------------------------------------------------------------------- 1 | require 'rbvmomi' 2 | require 'i18n' 3 | require 'vSphere/util/vim_helpers' 4 | 5 | module VagrantPlugins 6 | module VSphere 7 | module Action 8 | class Clone 9 | include Util::VimHelpers 10 | 11 | def initialize(app, _env) 12 | @app = app 13 | end 14 | 15 | def call(env) 16 | machine = env[:machine] 17 | config = machine.provider_config 18 | connection = env[:vSphere_connection] 19 | name = get_name machine, config, env[:root_path] 20 | dc = get_datacenter connection, machine 21 | template = dc.find_vm config.template_name 22 | fail Errors::VSphereError, :'missing_template' if template.nil? 23 | vm_base_folder = get_vm_base_folder dc, template, config 24 | fail Errors::VSphereError, :'invalid_base_path' if vm_base_folder.nil? 25 | 26 | begin 27 | # Storage DRS does not support vSphere linked clones. http://www.vmware.com/files/pdf/techpaper/vsphere-storage-drs-interoperability.pdf 28 | ds = get_datastore dc, machine 29 | fail Errors::VSphereError, :'invalid_configuration_linked_clone_with_sdrs' if config.linked_clone && ds.is_a?(RbVmomi::VIM::StoragePod) 30 | 31 | location = get_location ds, dc, machine, template 32 | spec = RbVmomi::VIM.VirtualMachineCloneSpec location: location, powerOn: true, template: false 33 | spec[:config] = RbVmomi::VIM.VirtualMachineConfigSpec 34 | customization_info = get_customization_spec_info_by_name connection, machine 35 | 36 | spec[:customization] = get_customization_spec(machine, customization_info) unless customization_info.nil? 37 | 38 | env[:ui].info "Setting custom address: #{config.addressType}" unless config.addressType.nil? 39 | add_custom_address_type(template, spec, config.addressType) unless config.addressType.nil? 40 | 41 | env[:ui].info "Setting custom mac: #{config.mac}" unless config.mac.nil? 42 | add_custom_mac(template, spec, config.mac) unless config.mac.nil? 43 | 44 | env[:ui].info "Setting custom vlan: #{config.vlan}" unless config.vlan.nil? 45 | add_custom_vlan(template, dc, spec, config.vlan) unless config.vlan.nil? 46 | 47 | env[:ui].info "Setting custom memory: #{config.memory_mb}" unless config.memory_mb.nil? 48 | add_custom_memory(spec, config.memory_mb) unless config.memory_mb.nil? 49 | 50 | env[:ui].info "Setting custom cpu count: #{config.cpu_count}" unless config.cpu_count.nil? 51 | add_custom_cpu(spec, config.cpu_count) unless config.cpu_count.nil? 52 | 53 | env[:ui].info "Setting custom cpu reservation: #{config.cpu_reservation}" unless config.cpu_reservation.nil? 54 | add_custom_cpu_reservation(spec, config.cpu_reservation) unless config.cpu_reservation.nil? 55 | 56 | env[:ui].info "Setting custom memmory reservation: #{config.mem_reservation}" unless config.mem_reservation.nil? 57 | add_custom_mem_reservation(spec, config.mem_reservation) unless config.mem_reservation.nil? 58 | add_custom_extra_config(spec, config.extra_config) unless config.extra_config.empty? 59 | add_custom_notes(spec, config.notes) unless config.notes.nil? 60 | 61 | if !config.clone_from_vm && ds.is_a?(RbVmomi::VIM::StoragePod) 62 | 63 | storage_mgr = connection.serviceContent.storageResourceManager 64 | pod_spec = RbVmomi::VIM.StorageDrsPodSelectionSpec(storagePod: ds) 65 | # TODO: May want to add option on type? 66 | storage_spec = RbVmomi::VIM.StoragePlacementSpec(type: 'clone', cloneName: name, folder: vm_base_folder, podSelectionSpec: pod_spec, vm: template, cloneSpec: spec) 67 | 68 | env[:ui].info I18n.t('vsphere.requesting_sdrs_recommendation') 69 | env[:ui].info " -- DatastoreCluster: #{ds.name}" 70 | env[:ui].info " -- Template VM: #{template.pretty_path}" 71 | env[:ui].info " -- Target VM: #{vm_base_folder.pretty_path}/#{name}" 72 | 73 | result = storage_mgr.RecommendDatastores(storageSpec: storage_spec) 74 | 75 | recommendation = result.recommendations[0] 76 | key = recommendation.key ||= '' 77 | if key == '' 78 | fail Errors::VSphereError, :missing_datastore_recommendation 79 | end 80 | 81 | env[:ui].info I18n.t('vsphere.creating_cloned_vm_sdrs') 82 | env[:ui].info " -- Storage DRS recommendation: #{recommendation.target.name} #{recommendation.reasonText}" 83 | 84 | apply_sr_result = storage_mgr.ApplyStorageDrsRecommendation_Task(key: [key]).wait_for_completion 85 | new_vm = apply_sr_result.vm 86 | 87 | else 88 | env[:ui].info I18n.t('vsphere.creating_cloned_vm') 89 | env[:ui].info " -- #{config.clone_from_vm ? 'Source' : 'Template'} VM: #{template.pretty_path}" 90 | env[:ui].info " -- Target VM: #{vm_base_folder.pretty_path}/#{name}" 91 | 92 | new_vm = template.CloneVM_Task(folder: vm_base_folder, name: name, spec: spec).wait_for_completion 93 | 94 | config.custom_attributes.each do |k, v| 95 | env[:ui].info "Setting custom attribute: #{k}=#{v}" 96 | new_vm.setCustomValue(key: k, value: v) 97 | end 98 | end 99 | rescue Errors::VSphereError 100 | raise 101 | rescue StandardError => e 102 | raise Errors::VSphereError.new, e.message 103 | end 104 | 105 | # TODO: handle interrupted status in the environment, should the vm be destroyed? 106 | 107 | machine.id = new_vm.config.uuid 108 | 109 | wait_for_sysprep(env, new_vm, connection, 600, 10) if config.wait_for_sysprep && machine.config.vm.guest.eql?(:windows) 110 | 111 | env[:ui].info I18n.t('vsphere.vm_clone_success') 112 | 113 | @app.call env 114 | end 115 | 116 | private 117 | 118 | def wait_for_sysprep(env, vm, vim_connection, timeout, sleep_time) 119 | vem = vim_connection.serviceContent.eventManager 120 | 121 | wait = true 122 | waited_seconds = 0 123 | 124 | env[:ui].info I18n.t('vsphere.wait_sysprep') 125 | while wait 126 | events = query_customization_succeeded(vm, vem) 127 | 128 | if events.size > 0 129 | events.each do |e| 130 | env[:ui].info e.fullFormattedMessage 131 | end 132 | wait = false 133 | elsif waited_seconds >= timeout 134 | fail Errors::VSphereError, :'sysprep_timeout' 135 | else 136 | sleep(sleep_time) 137 | waited_seconds += sleep_time 138 | end 139 | end 140 | end 141 | 142 | def query_customization_succeeded(vm, vem) 143 | vem.QueryEvents(filter: 144 | RbVmomi::VIM::EventFilterSpec(entity: 145 | RbVmomi::VIM::EventFilterSpecByEntity(entity: vm, recursion: 146 | RbVmomi::VIM::EventFilterSpecRecursionOption(:self)), eventTypeId: ['CustomizationSucceeded'])) 147 | end 148 | 149 | def get_customization_spec(machine, spec_info) 150 | customization_spec = spec_info.spec.clone 151 | 152 | # find all the configured private networks 153 | private_networks = machine.config.vm.networks.find_all { |n| n[0].eql? :private_network } 154 | return customization_spec if private_networks.nil? 155 | 156 | # make sure we have enough NIC settings to override with the private network settings 157 | fail Errors::VSphereError, :'too_many_private_networks' if private_networks.length > customization_spec.nicSettingMap.length 158 | 159 | # assign the private network IP to the NIC 160 | private_networks.each_index do |idx| 161 | customization_spec.nicSettingMap[idx].adapter.ip.ipAddress = private_networks[idx][1][:ip] 162 | end 163 | 164 | customization_spec 165 | end 166 | 167 | def get_location(datastore, dc, machine, template) 168 | if machine.provider_config.linked_clone 169 | # The API for linked clones is quite strange. We can't create a linked 170 | # straight from any VM. The disks of the VM for which we can create a 171 | # linked clone need to be read-only and thus VC demands that the VM we 172 | # are cloning from uses delta-disks. Only then it will allow us to 173 | # share the base disk. 174 | # 175 | # Thus, this code first create a delta disk on top of the base disk for 176 | # the to-be-cloned VM, if delta disks aren't used already. 177 | disks = template.config.hardware.device.grep(RbVmomi::VIM::VirtualDisk) 178 | disks.select { |disk| disk.backing.parent.nil? }.each do |disk| 179 | spec = { 180 | deviceChange: [ 181 | { 182 | operation: :remove, 183 | device: disk 184 | }, 185 | { 186 | operation: :add, 187 | fileOperation: :create, 188 | device: disk.dup.tap do |new_disk| 189 | new_disk.backing = new_disk.backing.dup 190 | new_disk.backing.fileName = "[#{disk.backing.datastore.name}]" 191 | new_disk.backing.parent = disk.backing 192 | end 193 | } 194 | ] 195 | } 196 | template.ReconfigVM_Task(spec: spec).wait_for_completion 197 | end 198 | 199 | location = RbVmomi::VIM.VirtualMachineRelocateSpec(diskMoveType: :moveChildMostDiskBacking) 200 | elsif datastore.is_a? RbVmomi::VIM::StoragePod 201 | location = RbVmomi::VIM.VirtualMachineRelocateSpec 202 | else 203 | location = RbVmomi::VIM.VirtualMachineRelocateSpec 204 | 205 | location[:datastore] = datastore unless datastore.nil? 206 | end 207 | location[:pool] = get_resource_pool(dc, machine) unless machine.provider_config.clone_from_vm 208 | location 209 | end 210 | 211 | def get_name(machine, config, root_path) 212 | return config.name unless config.name.nil? 213 | 214 | prefix = "#{root_path.basename}_#{machine.name}" 215 | prefix.gsub!(/[^-a-z0-9_\.]/i, '') 216 | # milliseconds + random number suffix to allow for simultaneous `vagrant up` of the same box in different dirs 217 | prefix + "_#{(Time.now.to_f * 1000.0).to_i}_#{rand(100_000)}" 218 | end 219 | 220 | def get_vm_base_folder(dc, template, config) 221 | if config.vm_base_path.nil? 222 | template.parent 223 | else 224 | dc.vmFolder.traverse(config.vm_base_path, RbVmomi::VIM::Folder, true) 225 | end 226 | end 227 | 228 | def modify_network_card(template, spec) 229 | spec[:config][:deviceChange] ||= [] 230 | @card ||= template.config.hardware.device.grep(RbVmomi::VIM::VirtualEthernetCard).first 231 | 232 | fail Errors::VSphereError, :missing_network_card if @card.nil? 233 | 234 | yield(@card) 235 | 236 | dev_spec = RbVmomi::VIM.VirtualDeviceConfigSpec(device: @card, operation: 'edit') 237 | spec[:config][:deviceChange].push dev_spec 238 | spec[:config][:deviceChange].uniq! 239 | end 240 | 241 | def add_custom_address_type(template, spec, addressType) 242 | spec[:config][:deviceChange] = [] 243 | config = template.config 244 | card = config.hardware.device.grep(RbVmomi::VIM::VirtualEthernetCard).first || fail(Errors::VSphereError, :missing_network_card) 245 | card.addressType = addressType 246 | card_spec = { :deviceChange => [{ :operation => :edit, :device => card }] } 247 | template.ReconfigVM_Task(:spec => card_spec).wait_for_completion 248 | end 249 | 250 | def add_custom_mac(template, spec, mac) 251 | modify_network_card(template, spec) do |card| 252 | card.macAddress = mac 253 | end 254 | end 255 | 256 | def add_custom_vlan(template, dc, spec, vlan) 257 | network = get_network_by_name(dc, vlan) 258 | 259 | modify_network_card(template, spec) do |card| 260 | begin 261 | switch_port = RbVmomi::VIM.DistributedVirtualSwitchPortConnection(switchUuid: network.config.distributedVirtualSwitch.uuid, portgroupKey: network.key) 262 | card.backing = RbVmomi::VIM::VirtualEthernetCardDistributedVirtualPortBackingInfo(port: switch_port) 263 | rescue 264 | # not connected to a distibuted switch? 265 | card.backing = RbVmomi::VIM::VirtualEthernetCardNetworkBackingInfo(network: network, deviceName: network.name) 266 | end 267 | end 268 | end 269 | 270 | def add_custom_memory(spec, memory_mb) 271 | spec[:config][:memoryMB] = Integer(memory_mb) 272 | end 273 | 274 | def add_custom_cpu(spec, cpu_count) 275 | spec[:config][:numCPUs] = Integer(cpu_count) 276 | end 277 | 278 | def add_custom_cpu_reservation(spec, cpu_reservation) 279 | spec[:config][:cpuAllocation] = RbVmomi::VIM.ResourceAllocationInfo(reservation: cpu_reservation) 280 | end 281 | 282 | def add_custom_mem_reservation(spec, mem_reservation) 283 | spec[:config][:memoryAllocation] = RbVmomi::VIM.ResourceAllocationInfo(reservation: mem_reservation) 284 | end 285 | 286 | def add_custom_extra_config(spec, extra_config = {}) 287 | return if extra_config.empty? 288 | 289 | # extraConfig must be an array of hashes with `key` and `value` 290 | # entries. 291 | spec[:config][:extraConfig] = extra_config.map { |k, v| { 'key' => k, 'value' => v } } 292 | end 293 | 294 | def add_custom_notes(spec, notes) 295 | spec[:config][:annotation] = notes 296 | end 297 | end 298 | end 299 | end 300 | end 301 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/nsidc/vagrant-vsphere.svg?branch=master)](https://travis-ci.org/nsidc/vagrant-vsphere) [![Gem Version](https://badge.fury.io/rb/vagrant-vsphere.svg)](http://badge.fury.io/rb/vagrant-vsphere) 2 | 3 | # Vagrant vSphere Provider 4 | 5 | This is a [Vagrant](http://www.vagrantup.com) 1.6.4+ plugin that adds a 6 | [vSphere](http://pubs.vmware.com/vsphere-50/index.jsp?topic=%2Fcom.vmware.wssdk.apiref.doc_50%2Fright-pane.html) 7 | provider to Vagrant, allowing Vagrant to control and provision machines using 8 | VMware. New machines are created from virtual machines or templates which must 9 | be configured prior to using this provider. 10 | 11 | This provider is built on top of the 12 | [RbVmomi](https://github.com/vmware/rbvmomi) Ruby interface to the vSphere API. 13 | 14 | 15 | ## Level of Support 16 | 17 | This repository is not actively supported by NSIDC but we welcome issue submissions and 18 | pull requests in order to foster community contribution. 19 | 20 | See the [LICENSE][license] for details on permissions and warranties. Please contact 21 | nsidc@nsidc.org for more information. 22 | 23 | 24 | ## Requirements 25 | 26 | * Vagrant 1.6.4+ 27 | * VMware with vSphere API 28 | * Ruby 1.9+ 29 | * libxml2, libxml2-dev, libxslt, libxslt-dev 30 | 31 | 32 | ## Current Version 33 | **version: 1.13.5** 34 | 35 | vagrant-vsphere (**version: 1.13.5**) is available from 36 | [RubyGems.org](https://rubygems.org/gems/vagrant-vsphere) 37 | 38 | 39 | ## Installation 40 | 41 | Install using standard Vagrant plugin method: 42 | 43 | ```bash 44 | vagrant plugin install vagrant-vsphere 45 | ``` 46 | 47 | This will install the plugin from RubyGems.org. 48 | 49 | Alternatively, you can clone this repository and build the source with `gem 50 | build vSphere.gemspec`. After the gem is built, run the plugin install command 51 | from the build directory. 52 | 53 | 54 | ### Potential Installation Problems 55 | 56 | The requirements for [Nokogiri](http://nokogiri.org/) must be installed before 57 | the plugin can be installed. See the 58 | [Nokogiri tutorial](http://nokogiri.org/tutorials/installing_nokogiri.html) for 59 | detailed instructions. 60 | 61 | The plugin forces use of Nokogiri ~> 1.5 to prevent conflicts with older 62 | versions of system libraries, specifically zlib. 63 | 64 | 65 | ## Usage 66 | 67 | After installing the plugin, you must create a vSphere box. The example_box 68 | directory contains a metadata.json file that can be used to create a dummy box 69 | with the command: 70 | 71 | ```bash 72 | tar cvzf dummy.box ./metadata.json 73 | ``` 74 | 75 | This can be installed using the standard Vagrant methods or specified in the 76 | Vagrantfile. 77 | 78 | After creating the dummy box, make a Vagrantfile that looks like the following: 79 | 80 | ```ruby 81 | Vagrant.configure("2") do |config| 82 | config.vm.box = 'dummy' 83 | config.vm.box_url = './example_box/dummy.box' 84 | 85 | config.vm.provider :vsphere do |vsphere| 86 | vsphere.host = 'HOST NAME OF YOUR VSPHERE INSTANCE' 87 | vsphere.compute_resource_name = 'YOUR COMPUTE RESOURCE' 88 | vsphere.resource_pool_name = 'YOUR RESOURCE POOL' 89 | vsphere.template_name = '/PATH/TO/YOUR VM TEMPLATE' 90 | vsphere.name = 'NEW VM NAME' 91 | vsphere.user = 'YOUR VMWARE USER' 92 | vsphere.password = 'YOUR VMWARE PASSWORD' 93 | end 94 | end 95 | ``` 96 | 97 | And then run `vagrant up --provider=vsphere`. 98 | 99 | 100 | ### Custom Box 101 | 102 | The bulk of this configuration can be included as part of a custom box. See the 103 | [Vagrant documentation](http://docs.vagrantup.com/v2/boxes.html) and the Vagrant 104 | [AWS provider](https://github.com/mitchellh/vagrant-aws/tree/master/example_box) 105 | for more information and an example. 106 | 107 | 108 | ### Supported Commands 109 | 110 | Currently the only implemented actions are `up`, `halt`, `reload`, `destroy`, 111 | and `ssh`. 112 | 113 | `up` supports provisioning of the new VM with the standard Vagrant provisioners. 114 | 115 | 116 | ## Configuration 117 | 118 | This provider has the following settings, all are required unless noted: 119 | 120 | * `host` - IP or name for the vSphere API 121 | * `insecure` - _Optional_ verify SSL certificate from the host 122 | * `user` - user name for connecting to vSphere 123 | * `password` - password for connecting to vSphere. If no value is given, or the 124 | value is set to `:ask`, the user will be prompted to enter the password on 125 | each invocation. 126 | * `data_center_name` - _Optional_ datacenter containing the computed resource, 127 | the template and where the new VM will be created, if not specified the first 128 | datacenter found will be used 129 | * `compute_resource_name` - _Required if cloning from template_ the name of the 130 | host or cluster containing the resource pool for the new VM 131 | * `resource_pool_name` - the resource pool for the new VM. If not supplied, and 132 | cloning from a template, uses the root resource pool 133 | * `clone_from_vm` - _Optional_ use a virtual machine instead of a template as 134 | the source for the cloning operation 135 | * `template_name` - the VM or VM template to clone (including the full folder path) 136 | * `vm_base_path` - _Optional_ path to folder where new VM should be created, if 137 | not specified template's parent folder will be used 138 | * `name` - _Optional_ name of the new VM. If missing, the name will be auto-generated 139 | * `customization_spec_name` - _Optional_ customization spec for the new VM 140 | * `data_store_name` - _Optional_ the datastore where the VM will be located 141 | * `linked_clone` - _Optional_ link the cloned VM to the parent to share virtual 142 | disks 143 | * `proxy_host` - _Optional_ proxy host name for connecting to vSphere via proxy 144 | * `proxy_port` - _Optional_ proxy port number for connecting to vSphere via 145 | proxy 146 | * `vlan` - _Optional_ vlan to connect the first NIC to 147 | * `memory_mb` - _Optional_ Configure the amount of memory (in MB) for the new VM 148 | * `cpu_count` - _Optional_ Configure the number of CPUs for the new VM 149 | * `mac` - _Optional_ Used to set the mac address of the new VM 150 | * `cpu_reservation` - _Optional_ Configure the CPU time (in MHz) to reserve for this VM 151 | * `mem_reservation` - _Optional_ Configure the memory (in MB) to reserve for this VM 152 | * `addressType` - _Optional_ Configure the address type of the 153 | [vSphere Virtual Ethernet Card](https://www.vmware.com/support/developer/vc-sdk/visdk2xpubs/ReferenceGuide/vim.vm.device.VirtualEthernetCard.html) 154 | * `custom_attribute` - _Optional_ Add a 155 | [custom attribute](https://www.google.com/url?sa=t&rct=j&q=&esrc=s&source=web&cd=1&cad=rja&uact=8&ved=0CB4QFjAAahUKEwiWwbWX59jHAhVBC5IKHa3HAEU&url=http%3A%2F%2Fpubs.vmware.com%2Fvsphere-51%2Ftopic%2Fcom.vmware.vsphere.vcenterhost.doc%2FGUID-25244732-D473-4857-A471-579257B6D95F.html&usg=AFQjCNGTSl4cauFrflUJpBeTBb0Yv7R13g&sig2=a9he6W2qVvBSZ5lCiXnENA) 156 | to the VM upon creation. This method takes a key/value pair, 157 | e.g. `vsphere.custom_attribute('timestamp', Time.now.to_s)`, and may be called 158 | multiple times to set different attributes. 159 | * `extra_config` - _Optional_ A hash of extra configuration values to add to 160 | the VM during creation. These are of the form `{'guestinfo.some.variable' => 'somevalue'}`, 161 | where the key must start with `guestinfo.`. VMs with VWware Tools installed can 162 | retrieve the value of these variables using the `vmtoolsd` command: `vmtoolsd --cmd 'info-get guestinfo.some.variable'`. 163 | * `notes` - _Optional_ Add arbitrary notes to the VM 164 | * `real_nic_ip` - _Optional_ true/false - Enable logic that forces the acquisition of the ssh IP address 165 | for a target VM to be retrieved from the list of vm adapters on the host and filtered for a single legitimate 166 | adapter with a defined interface. An error will be raised if this filter is enabled and multiple valid 167 | adapters exist on a host. 168 | * `ip_address_timeout` - _Optional_ Maximum number of seconds to wait while an 169 | IP address is obtained 170 | * `wait_for_sysprep` - _Optional_ Boolean. Enable waiting for Windows machines to reboot 171 | during the sysprep process 172 | ([#199](https://github.com/nsidc/vagrant-vsphere/pull/199)). Defaults to `false`. 173 | 174 | 175 | ### Cloning from a VM rather than a template 176 | 177 | To clone from an existing VM rather than a template, set `clone_from_vm` to 178 | true. If this value is set, `compute_resource_name` and `resource_pool_name` are 179 | not required. 180 | 181 | 182 | ### Template_Name 183 | 184 | * The template name includes the actual template name and the directory path 185 | containing the template. 186 | * **For example:** if the template is a directory called **vagrant-templates** 187 | and the template is called **ubuntu-lucid-template** the `template_name` 188 | setting would be: 189 | 190 | ``` 191 | vsphere.template_name = "vagrant-templates/ubuntu-lucid-template" 192 | ``` 193 | 194 | ![Vagrant Vsphere Screenshot](https://raw.githubusercontent.com/nsidc/vagrant-vsphere/master/vsphere_screenshot.png) 195 | 196 | 197 | ### VM_Base_Path 198 | 199 | * The new vagrant VM will be created in the same directory as the template it 200 | originated from. 201 | * To create the VM in a directory other than the one where the template was 202 | located, include the **vm_base_path** setting. 203 | * **For example:** if the machines will be stored in a directory called 204 | **vagrant-machines** the `vm_base_path` would be: 205 | 206 | ``` 207 | vsphere.vm_base_path = "vagrant-machines" 208 | ``` 209 | 210 | ![Vagrant Vsphere Screenshot](https://raw.githubusercontent.com/nsidc/vagrant-vsphere/master/vsphere_screenshot.png) 211 | 212 | 213 | ### Setting a static IP address 214 | 215 | To set a static IP, add a private network to your vagrant file: 216 | 217 | ```ruby 218 | config.vm.network 'private_network', ip: '192.168.50.4' 219 | ``` 220 | 221 | The IP address will only be set if a customization spec name is given. The 222 | customization spec must have network adapter settings configured with a static 223 | IP address(just an unused address NOT the address you want the VM to be). The 224 | config.vm.network line will overwrite the ip in the customization spec with the one you set. 225 | For each private network specified, there needs to be a corresponding network adapter in 226 | the customization spec. An error will be thrown if there are more networks than 227 | adapters. 228 | 229 | 230 | ### Auto name generation 231 | 232 | The name for the new VM will be automagically generated from the Vagrant machine 233 | name, the current timestamp and a random number to allow for simultaneous 234 | executions. 235 | 236 | This is useful if running Vagrant from multiple directories or if multiple 237 | machines are defined in the Vagrantfile. 238 | 239 | 240 | ### Setting addressType for network adapter 241 | 242 | This sets the addressType of the network adapter, for example 'Manual' to 243 | be able to set a manual mac address. 244 | This value may depend on the version of vSphere you use. It may be necessary 245 | to set this in combination with the mac field, in order to set a manual 246 | mac address. For valid values for this field see VirtualEthernetCard api 247 | documentation of vSphere. 248 | 249 | ```ruby 250 | vsphere.addressType = 'Manual' 251 | ``` 252 | 253 | 254 | ### Setting the MAC address 255 | 256 | To set a static MAC address, add a `vsphere.mac` to your `Vagrantfile`. 257 | In some cases you must also set `vsphere.addressType` (see above) 258 | to make this work: 259 | 260 | ```ruby 261 | vsphere.mac = '00:50:56:XX:YY:ZZ' 262 | ``` 263 | 264 | Take care to avoid using invalid or duplicate VMware MAC addresses, as this can 265 | easily break networking. 266 | 267 | 268 | ## Troubleshooting 269 | 270 | ### vCenter 271 | ESXi is not supported. Make sure to connect to a vCenter server and not directly to an ESXi host. [ESXi vs vCenter](http://www.mustbegeek.com/difference-between-vsphere-esxi-and-vcenter/) 272 | 273 | 274 | ### Permissions 275 | If you have permission issues: 276 | 277 | 1. give the connecting user read only access to everything, and full permission to a specific data center. Narrow the permissions down after a VM is created. 278 | 2. Be sure the path to the VM is correct. see the "Template_Name" screenshots above for more information. 279 | 280 | 281 | ## Example Usage 282 | 283 | ### FILE: Vagrantfile 284 | 285 | ```ruby 286 | VAGRANT_INSTANCE_NAME = "vagrant-vsphere" 287 | 288 | Vagrant.configure("2") do |config| 289 | config.vm.box = 'vsphere' 290 | config.vm.box_url = 'https://vagrantcloud.com/ssx/boxes/vsphere-dummy/versions/0.0.1/providers/vsphere.box' 291 | 292 | config.vm.hostname = VAGRANT_INSTANCE_NAME 293 | config.vm.define VAGRANT_INSTANCE_NAME do |d| 294 | end 295 | 296 | config.vm.provider :vsphere do |vsphere| 297 | vsphere.host = 'vsphere.local' 298 | vsphere.name = VAGRANT_INSTANCE_NAME 299 | vsphere.compute_resource_name = 'vagrant01.vsphere.local' 300 | vsphere.resource_pool_name = 'vagrant' 301 | vsphere.template_name = 'vagrant-templates/ubuntu14041' 302 | vsphere.vm_base_path = "vagrant-machines" 303 | 304 | vsphere.user = 'vagrant-user@vsphere' 305 | vsphere.password = '***************' 306 | vsphere.insecure = true 307 | 308 | vsphere.custom_attribute('timestamp', Time.now.to_s) 309 | end 310 | end 311 | ``` 312 | 313 | 314 | ### Vagrant Up 315 | 316 | ```bash 317 | vagrant up --provider=vsphere 318 | ``` 319 | 320 | 321 | ### Vagrant SSH 322 | 323 | ```bash 324 | vagrant ssh 325 | ``` 326 | 327 | 328 | ### Vagrant Destroy 329 | 330 | ```bash 331 | vagrant destroy 332 | ``` 333 | 334 | 335 | ## Version History 336 | 337 | See 338 | [`CHANGELOG.md`](https://github.com/nsidc/vagrant-vsphere/blob/master/CHANGELOG.md). 339 | 340 | 341 | ## Contributing 342 | 343 | See 344 | [`DEVELOPMENT.md`](https://github.com/nsidc/vagrant-vsphere/blob/master/DEVELOPMENT.md). 345 | 346 | 347 | ## License 348 | 349 | The Vagrant vSphere Provider is licensed under the MIT license. See 350 | [LICENSE][license]. 351 | 352 | 353 | 354 | ## Code of Conduct 355 | 356 | See [Code of Conduct][code-of-conduct]. 357 | 358 | 359 | ## Credit 360 | 361 | This software was developed by the National Snow and Ice Data Center with 362 | funding from multiple sources. 363 | 364 | 365 | [license]: LICENSE 366 | [code-of-conduct]: CODE_OF_CONDUCT.md 367 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [1.13.5 (2021-01-05)](https://github.com/nsidc/vagrant-vsphere/releases/tag/v1.13.5) 2 | - Pin nokogiri to 1.10.10. This fixes an issue where vagrant-vsphere failed to install due to 3 | nokogiri requiring Ruby >=2.5. This is a workaround until the vagrant-nsidc plugin is updated 4 | to work with newer versions of vagrant that are bundled with newer versions of Ruby 5 | 6 | ## [1.13.4 (2020-03-31)](https://github.com/nsidc/vagrant-vsphere/releases/tag/v1.13.4) 7 | 8 | - Allow newer versions of i18n. 9 | ([jmartin-r7:release-i18n](https://github.com/nsidc/vagrant-vsphere/pull/286)) 10 | - Updated .ruby-version to 2.6.5 11 | - Fix broken tests. 12 | ([wwestenbrink:master](https://github.com/nsidc/vagrant-vsphere/pull/283)) 13 | 14 | ## [1.13.3 (2018-12-06)](https://github.com/nsidc/vagrant-vsphere/releases/tag/v1.13.3) 15 | 16 | - Update i18n dependency to allow v1.1.1. This fixes an issue with 17 | installation with Vagrant 2.2.x 18 | ([jarretlavallee:fix/master/il8n_deps](https://github.com/nsidc/vagrant-vsphere/pull/273)). 19 | 20 | ## [1.13.2 (2017-12-06)](https://github.com/nsidc/vagrant-vsphere/releases/tag/v1.13.2) 21 | 22 | - Update rbvmomi dependency to v1.11.5 and greater (but still less than 23 | 2.0). v1.11.5 fixes the issue introduced in v1.11.4. 24 | 25 | ## [1.13.1 (2017-12-05)](https://github.com/nsidc/vagrant-vsphere/releases/tag/v1.13.1) 26 | 27 | - Update rbvmomi dependency to avoid v1.11.4; something in 28 | [2725089](https://github.com/vmware/rbvmomi/commit/2725089a08312315c4eb85f13296fc159f50b4d1) 29 | broke cloning VMs on NSIDC's vSphere instance. 30 | 31 | 32 | ## [1.13.0 (2017-11-06)](https://github.com/nsidc/vagrant-vsphere/releases/tag/v1.13.0) 33 | 34 | - Add support for the commands 35 | [`suspend`](https://www.vagrantup.com/docs/cli/suspend.html) and 36 | [`resume`](https://www.vagrantup.com/docs/cli/resume.html). 37 | 38 | ## [1.12.1 (2017-03-07)](https://github.com/nsidc/vagrant-vsphere/releases/tag/v1.12.1) 39 | 40 | - If no valid adapters can be found on a host when using the `real_nic_ip` 41 | option, fall back to the value of `vm.guest.ipAddress`. This resolves an 42 | issue where the network changes made by Docker Swarm prevent vagrant-vsphere 43 | from acquiring the VM's IP address. 44 | 45 | ## [1.12.0 (2017-03-07)](https://github.com/nsidc/vagrant-vsphere/releases/tag/v1.12.0) 46 | 47 | - Make `wait_for_sysprep` functionality configurable (see README.md for 48 | details). Defaults to `false` to resolve 49 | #222. ([nsidc:make-sysprep-configurable](https://github.com/nsidc/vagrant-vsphere/pull/235)) 50 | - Fix issue (#231) where finding no adapters while waiting for the IP address 51 | to be ready broke the `up` 52 | process. ([nsidc:fix-filter-ssh](https://github.com/nsidc/vagrant-vsphere/pull/234)) 53 | - Fix installation of vagrant-vsphere under Vagrant 54 | 1.9.2. ([nsidc:fix-install-with-1.9.2](https://github.com/nsidc/vagrant-vsphere/pull/233)) 55 | 56 | ## [1.11.1 (2017-02-27)](https://github.com/nsidc/vagrant-vsphere/releases/tag/v1.11.1) 57 | - Fix 'real_nic_ip' filter logic bug 58 | ([vagrant-vsphere:fix_ssh_ip_selection](https://github.com/nsidc/vagrant-vsphere/pull/229)) 59 | 60 | ## [1.11.0 (2016-11-23)](https://github.com/nsidc/vagrant-vsphere/releases/tag/v1.11.0) 61 | 62 | - Wait for Windows sysprep when cloning a Windows box 63 | ([jcaugust:sysprep_wait](https://github.com/nsidc/vagrant-vsphere/pull/199)). 64 | - Add a configurable timeout period for obtaining the VM's IP address 65 | ([taliesins:wait-for-ip](https://github.com/nsidc/vagrant-vsphere/pull/204)). 66 | 67 | ## [1.10.1 (2016-10-17)](https://github.com/nsidc/vagrant-vsphere/releases/tag/v1.10.1) 68 | 69 | - Update dependency on [rbvmomi](https://github.com/vmware/rbvmomi) to allow 70 | versions greater than `1.8.2`, but still less than `2.0.0`. The previous 71 | version constraint was intended to get at least `1.8.2`, but was also 72 | restricting it to less than `1.9.0`. 73 | 74 | ## [1.10.0 (2016-05-17)](https://github.com/nsidc/vagrant-vsphere/releases/tag/v1.10.0) 75 | 76 | - Add support for `vagrant snapshot` and its subcommands 77 | ([Sharpie:add-snapshot-support](https://github.com/nsidc/vagrant-vsphere/pull/198)). 78 | 79 | ## [1.9.0 (2016-05-17)](https://github.com/nsidc/vagrant-vsphere/releases/tag/v1.9.0) 80 | 81 | - Add real_nic_ip option/logic to support VMs with multiple bridge adapters 82 | ([vagrant-vsphere:invalid_ip_address_fix](https://github.com/nsidc/vagrant-vsphere/pull/193)). 83 | 84 | ## [1.8.1 (2016-04-27)](https://github.com/nsidc/vagrant-vsphere/releases/tag/v1.8.1) 85 | 86 | - Fix error for initial VLAN/virtual switch support 87 | ([adampointer:master](https://github.com/nsidc/vagrant-vsphere/pull/190)). 88 | 89 | 90 | ## [1.8.0 (2016-04-21)](https://github.com/nsidc/vagrant-vsphere/releases/tag/v1.8.0) 91 | 92 | - Allow VLANs on virtual switches to work correctly 93 | ([adampointer:master](https://github.com/nsidc/vagrant-vsphere/pull/188)). 94 | - Make compatible with `i18n` v0.7.0 (resolving 95 | [#163](https://github.com/nsidc/vagrant-vsphere/pull/163)). 96 | - Add support for specifying a full resource pool name 97 | ([davidhrbac:master](https://github.com/nsidc/vagrant-vsphere/pull/152) and 98 | [#189](https://github.com/nsidc/vagrant-vsphere/pull/189)). 99 | 100 | ## [1.7.1 (2016-03-30)](https://github.com/nsidc/vagrant-vsphere/releases/tag/v1.7.1) 101 | 102 | - Allow `vagrant status` and `vagrant ssh` to run without a lock 103 | ([Sharpie:dont-lock-for-ssh](https://github.com/nsidc/vagrant-vsphere/pull/184)). 104 | 105 | ## [1.7.0 (2016-03-14)](https://github.com/nsidc/vagrant-vsphere/releases/tag/v1.7.0) 106 | 107 | - Add support for setting guestinfo variables 108 | ([Sharpie:add-guestinfo-support](https://github.com/nsidc/vagrant-vsphere/pull/174)). 109 | - Run provisioner cleanup when destroying VMs 110 | ([Sharpie:enable-provisioner-cleanup](https://github.com/nsidc/vagrant-vsphere/pull/176)). 111 | - Add the ability to configure the notes on the newly cloned VM 112 | ([rylarson:feature/rylarson-add-notes](https://github.com/nsidc/vagrant-vsphere/pull/178)). 113 | 114 | ## [1.6.0 (2016-01-21)](https://github.com/nsidc/vagrant-vsphere/releases/tag/v1.6.0) 115 | 116 | - Use Vagrant core API instead of waiting for SSH communicator, which should 117 | resolve some WinRM connection issues 118 | ([mkuzmin:wait-winrm](https://github.com/nsidc/vagrant-vsphere/pull/162)). 119 | 120 | ## [1.5.0 (2015-09-08)](https://github.com/nsidc/vagrant-vsphere/releases/tag/v1.5.0) 121 | 122 | - Add support for custom attributes 123 | ([michaeljb:custom-attributes](https://github.com/nsidc/vagrant-vsphere/pull/149)). 124 | 125 | ## [1.4.1 (2015-09-01)](https://github.com/nsidc/vagrant-vsphere/releases/tag/v1.4.1) 126 | 127 | - Update dependency on [rbvmomi](https://github.com/vmware/rbvmomi) to 1.8.2 128 | in order to resolve errors with parallelization 129 | ([#139]((https://github.com/nsidc/vagrant-vsphere/issues/139)), 130 | [edmcman:master](https://github.com/nsidc/vagrant-vsphere/pull/147)). 131 | 132 | ## [1.4.0 (2015-07-29)](https://github.com/nsidc/vagrant-vsphere/releases/tag/v1.4.0) 133 | 134 | - Add ability to configure the address type (originally submitted in 135 | [mreuvers:master](https://github.com/nsidc/vagrant-vsphere/pull/121), but 136 | merged 137 | [nsidc:address-type](https://github.com/nsidc/vagrant-vsphere/pull/142)) 138 | 139 | ## [1.3.0 (2015-07-21)](https://github.com/nsidc/vagrant-vsphere/releases/tag/v1.3.0) 140 | 141 | - Add ability to configure CPU and memory reservations 142 | ([edmcman:resource_limit](https://github.com/nsidc/vagrant-vsphere/pull/137)) 143 | - Bypass "graceful" shut down attempts with `vagrant destroy --force` 144 | 145 | ## [1.2.0 (2015-07-09)](https://github.com/nsidc/vagrant-vsphere/releases/tag/v1.2.0) 146 | 147 | - Add `public_address` provider capability 148 | ([mkuzmin:public-address](https://github.com/nsidc/vagrant-vsphere/pull/130)) 149 | - Documentation update 150 | ([standaloneSA:update-readme](https://github.com/nsidc/vagrant-vsphere/pull/133)) 151 | 152 | ## [1.1.0 (2015-07-09)](https://github.com/nsidc/vagrant-vsphere/releases/tag/v1.1.0) 153 | 154 | - Add explicit support for parallelization 155 | ([xlucas:xlucas-patch-parallelization](https://github.com/nsidc/vagrant-vsphere/pull/126)) 156 | - Documentation updates 157 | ([metrix78:metrix78-patch-1](https://github.com/nsidc/vagrant-vsphere/pull/127) 158 | and 159 | [fabriciocolombo:readme-ipaddress](https://github.com/nsidc/vagrant-vsphere/pull/128)) 160 | 161 | ## 1.0.1 (2015-01-06) 162 | 163 | - Fix "undefined local variable or method datastore" error due to typo in a 164 | variable name 165 | ([#116 mkuzmin:datastore](https://github.com/nsidc/vagrant-vsphere/pull/116)) 166 | - Remove "missing required parameter uuid" error from `vagrant destroy` output 167 | when no machine exists 168 | ([#117 mkuzmin:destroy](https://github.com/nsidc/vagrant-vsphere/pull/117)) 169 | 170 | ## 1.0.0 (2015-01-05) 171 | 172 | - Increase Vagrant requirement to 1.6.4+ 173 | - Update copyright date in LICENSE.txt 174 | 175 | ## 0.19.1 (2014-12-31) 176 | 177 | - Move version history and contributing notes out of `README.md` into separate 178 | files 179 | - Add RuboCop, fail the Travis-CI build if RuboCop or unit tests fail 180 | 181 | ## 0.19.0 (2014-12-31) 182 | 183 | - Add support for ClusterComputeResource and DatastoreCluster 184 | ([#101 GregDomjan:StorageSDRS](https://github.com/nsidc/vagrant-vsphere/pull/101)) 185 | 186 | ## 0.18.0 (2014-12-30) 187 | 188 | - Gracefully power off the VM with `vagrant halt`, and shutdown before 189 | deleting the VM with `vagrant destroy` 190 | ([#104 clintoncwolfe:shutdown-guest-on-halt](https://github.com/nsidc/vagrant-vsphere/pull/104)) 191 | - Add configuration option `mac` to specify a MAC address for the VM 192 | ([#108 dataplayer:master](https://github.com/nsidc/vagrant-vsphere/pull/108)) 193 | 194 | ## 0.17.0 (2014-12-29) 195 | 196 | - Add ability to configure the CPU Count 197 | ([#96 rylarson:add-cpu-configuration](https://github.com/nsidc/vagrant-vsphere/pull/96)) 198 | - Prompt the user to enter a password if none is given, or the configuration 199 | value is set to `:ask` 200 | ([#97 topmedia:password-prompt](https://github.com/nsidc/vagrant-vsphere/pull/97)) 201 | - Add support for `vagrant reload` 202 | ([#105 clintoncwolfe:add-reload-action](https://github.com/nsidc/vagrant-vsphere/pull/105)) 203 | - Fix compatibility with Vagrant 1.7 to use vSphere connection info from a 204 | base box 205 | ([#111 mkuzmin:get-state](https://github.com/nsidc/vagrant-vsphere/pull/111)) 206 | 207 | ## 0.16.0 (2014-10-01) 208 | 209 | - Add ability to configure amount of memory the new cloned VM will have 210 | ([#94 rylarson:add-memory-configuration](https://github.com/nsidc/vagrant-vsphere/pull/94)) 211 | 212 | ## 0.15.0 (2014-09-23) 213 | 214 | - Make `vagrant destroy` work in all vm states 215 | ([#93 rylarson:make-destroy-work-in-all-vm-states](https://github.com/nsidc/vagrant-vsphere/pull/93), 216 | fixes [#77](https://github.com/nsidc/vagrant-vsphere/issues/77)) 217 | - If the VM is powered on, then it is powered off, and destroyed 218 | - If the VM is powered off, it is just destroyed 219 | - If the VM is suspended, it is powered on, then powered off, then 220 | destroyed 221 | 222 | ## 0.14.0 (2014-09-19) 223 | 224 | - Add vlan configuration 225 | ([#91 rylarson:add-vlan-configuration](https://github.com/nsidc/vagrant-vsphere/pull/91)) 226 | - Added a new configuration option `vlan` that lets you specify the vlan 227 | string 228 | - If vlan is set, the clone spec is modified with an edit action to connect 229 | the first NIC on the VM to the configured VLAN 230 | 231 | ## 0.13.1 (2014-09-18) 232 | 233 | - Change Nokogiri major version dependency 234 | ([#90 highsineburgh:SAITRADLab-master](https://github.com/nsidc/vagrant-vsphere/pull/90)) 235 | 236 | ## 0.13.0 (2014-09-03) 237 | 238 | - Find and install box file for multi-provider boxes automatically 239 | ([#86 mkuzmin:install-box](https://github.com/nsidc/vagrant-vsphere/pull/86) 240 | & 241 | [#87 mkuzmin/provider-name](https://github.com/nsidc/vagrant-vsphere/pull/87)) 242 | 243 | ## 0.12.0 (2014-08-16) 244 | 245 | - Use a directory name where `Vagrantfile` is stored as a prefix for VM name 246 | ([#82 mkuzmin:name-prefix](https://github.com/nsidc/vagrant-vsphere/pull/82)) 247 | 248 | ## 0.11.0 (2014-07-17) 249 | 250 | - Create the VM target folder if it doesn't exist 251 | ([#76 marnovdm:feature/create_vm_folder](https://github.com/nsidc/vagrant-vsphere/pull/76)) 252 | 253 | ## 0.10.0 (2014-07-07) 254 | 255 | - New optional parameter to clone into custom folder in vSphere 256 | ([#73 mikola-spb:vm-base-path](https://github.com/nsidc/vagrant-vsphere/pull/73)) 257 | - Follows [semver](http://semver.org/) better, this adds functionality in a 258 | backwards compatible way, so bumps the minor. 0.9.0, should have been a 259 | major version 260 | 261 | ## 0.9.2 (2014-07-07) 262 | 263 | - Instruct Vagrant to set the guest hostname according to `Vagrantfile` 264 | ([#69 ddub:set-hostname](https://github.com/nsidc/vagrant-vsphere/pull/69)) 265 | 266 | ## 0.9.1 (2014-07-07) 267 | 268 | - Reuse folder sync code from Vagrant core 269 | ([#66 mkuzmin:sync-folders](https://github.com/nsidc/vagrant-vsphere/pull/66)) 270 | 271 | ## 0.9.0 (2014-07-07) 272 | 273 | - Increases Vagrant requirements to 1.6.3+ 274 | - Supports differentiating between SSH/WinRM communicator 275 | ([#67 marnovdm:feature/waiting-for-winrm](https://github.com/nsidc/vagrant-vsphere/pull/67)) 276 | 277 | ## 0.8.5 (2014-07-07) 278 | 279 | - Fixed synced folders to work with WinRM communicator 280 | ([#72 10thmagnitude:master](https://github.com/nsidc/vagrant-vsphere/pull/72)) 281 | 282 | ## 0.8.4 (2014-07-07) 283 | 284 | - Use root resource pool when cloning from template 285 | ([#63: matt-richardson:support-resource-pools-on-vsphere-standard-edition](https://github.com/nsidc/vagrant-vsphere/pull/63)) 286 | 287 | ## 0.8.3 (2014-07-03) 288 | 289 | - Fixed "No error message" on rbvmomi method calls 290 | ([#74: mkuzmin:rbvmomi-error-messages](https://github.com/nsidc/vagrant-vsphere/pull/74)) 291 | 292 | ## 0.8.2 (2014-04-23) 293 | 294 | - Fixes no error messages 295 | ([#58 leth:no-error-message](https://github.com/nsidc/vagrant-vsphere/pull/58)) 296 | - Fixes typo ([#57 marnovdm](https://github.com/nsidc/vagrant-vsphere/pull/57)) 297 | - Fixes additional no error messages 298 | 299 | ## 0.8.1 (2014-04-10) 300 | 301 | - Fixes [#47](https://github.com/nsidc/vagrant-vsphere/issues/47) via 302 | [#52 olegz-alertlogic](https://github.com/nsidc/vagrant-vsphere/pull/52) 303 | 304 | ## 0.8.0 (2014-04-08) 305 | 306 | - Adds configuration for connecting via proxy server 307 | ([#40 tkak:feature-proxy-connection](https://github.com/nsidc/vagrant-vsphere/pull/40)) 308 | 309 | ## 0.7.2 (2014-04-08) 310 | 311 | - Includes template in get_location 312 | ([#38 tim95030:issue-27](https://github.com/nsidc/vagrant-vsphere/pull/38)) 313 | - Updates `Gemfile` to fall back to old version of Vagrant if ruby < 2.0.0 is 314 | available 315 | 316 | ## 0.7.1 (2014-03-17) 317 | 318 | - Fixes rsync error reporting 319 | - Updates `locales/en.yaml` 320 | - Restricts RbVmomi dependency 321 | 322 | ## 0.7.0 (2013-12-31) 323 | 324 | - Handle multiple private key paths 325 | - Add auto name generation based on machine name 326 | - Add support for linked clones 327 | 328 | ## 0.6.0 (2013-11-21) 329 | 330 | - Add support for the `vagrant ssh -c` command 331 | 332 | ## 0.5.1 (2013-10-21) 333 | 334 | - Fix rsync on Windows, adapted from 335 | [mitchellh/vagrant-aws#77](https://github.com/mitchellh/vagrant-aws/pull/77) 336 | 337 | ## 0.5.0 (2013-10-17) 338 | 339 | - Allow setting static ip addresses using Vagrant private networks 340 | - Allow cloning from VM or template 341 | 342 | ## 0.4.0 343 | 344 | - Add support for specifying datastore location for new VMs 345 | 346 | ## 0.3.0 347 | 348 | - Lock Nokogiri version at 1.5.10 to prevent library conflicts 349 | - Add support for customization specs 350 | 351 | ## 0.2.0 352 | 353 | - Add halt action 354 | ([#16 catharsis:haltaction](https://github.com/nsidc/vagrant-vsphere/pull/16)) 355 | 356 | ## 0.1.0 357 | 358 | - Add folder syncing with guest OS 359 | - Add provisioning 360 | 361 | ## 0.0.1 362 | 363 | - Initial release 364 | --------------------------------------------------------------------------------