├── .gitignore ├── .idea ├── .name ├── .rakeTasks ├── compiler.xml ├── copyright │ └── profiles_settings.xml ├── encodings.xml ├── misc.xml ├── modules.xml └── vcs.xml ├── .rspec ├── .ruby-gemset ├── .ruby-version ├── Gemfile ├── Gemfile.lock ├── Guardfile ├── LICENSE ├── README.md ├── Rakefile ├── cucumber.yml ├── dummy_box ├── Cucumber_Vagrantfile ├── Vagrantfile ├── Vagrantfile_qemu ├── dummy.box ├── metadata.json └── provision.sh ├── features ├── destroy_command.feature ├── halt_command.feature ├── provision_command.feature ├── replace_iso_file.feature ├── replace_template_file.feature ├── ssh_command.feature ├── ssh_run_command.feature ├── status_command.feature ├── step_definitions │ └── general_steps.rb ├── support │ ├── env.rb │ ├── file_helper.rb │ ├── machine_helper.rb │ ├── template_helper.rb │ ├── time_helper.rb │ ├── vagrant_process_mock.rb │ ├── vagrant_ui_mock.rb │ └── vagrant_util_ssh_mock.rb ├── template_file.feature ├── up_command.feature ├── upload_iso_file.feature └── upload_template_file.feature ├── lib ├── required_parameters.rb ├── sanity_checks.rb ├── vagrant-proxmox.rb └── vagrant-proxmox │ ├── action.rb │ ├── action │ ├── adjust_forwarded_port_params.rb │ ├── cleanup_after_destroy.rb │ ├── clone_vm.rb │ ├── config_clone.rb │ ├── connect_proxmox.rb │ ├── create_vm.rb │ ├── destroy_vm.rb │ ├── get_node_list.rb │ ├── is_created.rb │ ├── is_stopped.rb │ ├── message_already_running.rb │ ├── message_already_stopped.rb │ ├── message_file_not_found.rb │ ├── message_not_created.rb │ ├── message_not_running.rb │ ├── message_upload_server_error.rb │ ├── proxmox_action.rb │ ├── read_ssh_info.rb │ ├── read_state.rb │ ├── select_node.rb │ ├── shutdown_vm.rb │ ├── start_vm.rb │ ├── stop_vm.rb │ ├── sync_folders.rb │ ├── upload_iso_file.rb │ └── upload_template_file.rb │ ├── config.rb │ ├── errors.rb │ ├── plugin.rb │ ├── provider.rb │ ├── proxmox │ ├── connection.rb │ └── errors.rb │ └── version.rb ├── local_test.rb ├── locales └── en.yml ├── spec ├── actions │ ├── cleanup_after_destroy_action_spec.rb │ ├── connect_proxmox_action_spec.rb │ ├── create_vm_action_spec.rb │ ├── destroy_vm_action_spec.rb │ ├── get_node_list_action_spec.rb │ ├── is_created_action_spec.rb │ ├── is_stopped_action_spec.rb │ ├── message_already_running_action_spec.rb │ ├── message_already_stopped_action_spec.rb │ ├── message_file_not_found_spec.rb │ ├── message_not_created_action_spec.rb │ ├── message_not_running_action_spec.rb │ ├── message_upload_server_error_spec.rb │ ├── proxmox_action_shared.rb │ ├── proxmox_action_spec.rb │ ├── read_ssh_info_action_spec.rb │ ├── read_state_action_spec.rb │ ├── select_node_spec.rb │ ├── shutdown_vm_action_spec.rb │ ├── start_vm_action_spec.rb │ ├── stop_vm_action_spec.rb │ ├── sync_folders_action_spec.rb │ ├── upload_iso_file_action_spec.rb │ └── upload_template_file_action_spec.rb ├── commands │ ├── destroy_command_spec.rb │ ├── halt_command_spec.rb │ ├── provision_command_spec.rb │ ├── ssh_command_spec.rb │ ├── ssh_run_command_spec.rb │ ├── status_command_spec.rb │ └── up_command_spec.rb ├── config_spec.rb ├── plugin_spec.rb ├── provider_spec.rb ├── proxmox │ ├── connection_spec.rb │ └── rest_call_shared.rb ├── sanity_checks_spec.rb ├── spec_helper.rb └── spec_helpers │ ├── common_helpers.rb │ └── time_helpers.rb ├── vagrant-proxmox.gemspec └── vagrant-proxmox.iml /.gitignore: -------------------------------------------------------------------------------- 1 | # If you find yourself ignoring temporary files generated by your text editor 2 | # or operating system, you probably want to add a global ignore instead: 3 | # git config --global core.excludesfile ~/.gitignore_global 4 | 5 | # Ignore bundler config and gem cache 6 | /.bundle 7 | /bin 8 | 9 | /Vagrantfile 10 | /Vagrantfile_qemu 11 | 12 | # Ignore all logfiles and tempfiles. 13 | /.vagrant 14 | /tmp 15 | /.tmp 16 | /.build 17 | /pkg 18 | *.gem 19 | *~ 20 | 21 | # Ignore generated coverage reports 22 | /coverage 23 | 24 | # IntelliJ 25 | .iws 26 | .idea/workspace.xml 27 | .idea/tasks.xml 28 | .idea/gradle.xml 29 | .idea/dataSources.* 30 | atlassian-ide-plugin.xml 31 | -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | vagrant-proxmox -------------------------------------------------------------------------------- /.idea/.rakeTasks: -------------------------------------------------------------------------------- 1 | 2 | 8 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 23 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 1.8 21 | 22 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | --backtrace 3 | -------------------------------------------------------------------------------- /.ruby-gemset: -------------------------------------------------------------------------------- 1 | vagrant-proxmox 2 | -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | ruby-2.2.1 2 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gemspec 4 | 5 | group :development do 6 | #We depend on Vagrant for development, but we don't add it as a 7 | #gem dependency because we expect to be installed within the 8 | #Vagrant environment itself using `vagrant plugin`. 9 | gem "vagrant", '1.4.3', 10 | :git => 'https://github.com/mitchellh/vagrant.git', 11 | :ref => 'v1.4.3' 12 | end 13 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GIT 2 | remote: https://github.com/mitchellh/vagrant.git 3 | revision: 4f0eb9504cc786d5a57a43814427e8eb35407a4c 4 | ref: v1.4.3 5 | specs: 6 | vagrant (1.4.3) 7 | childprocess (~> 0.3.7) 8 | erubis (~> 2.7.0) 9 | i18n (~> 0.6.0) 10 | log4r (~> 1.1.9) 11 | net-scp (~> 1.1.0) 12 | net-ssh (>= 2.6.6, < 2.8.0) 13 | 14 | PATH 15 | remote: . 16 | specs: 17 | vagrant-proxmox (0.0.9) 18 | activesupport (~> 4.0.0) 19 | rest-client (~> 1.6.7) 20 | retryable (~> 1.3.3) 21 | 22 | GEM 23 | remote: https://rubygems.org/ 24 | specs: 25 | activesupport (4.0.13) 26 | i18n (~> 0.6, >= 0.6.9) 27 | minitest (~> 4.2) 28 | multi_json (~> 1.3) 29 | thread_safe (~> 0.1) 30 | tzinfo (~> 0.3.37) 31 | addressable (2.4.0) 32 | awesome_print (1.2.0) 33 | builder (3.2.2) 34 | childprocess (0.3.9) 35 | ffi (~> 1.0, >= 1.0.11) 36 | coderay (1.1.1) 37 | crack (0.4.3) 38 | safe_yaml (~> 1.0.0) 39 | cucumber (1.3.20) 40 | builder (>= 2.1.2) 41 | diff-lcs (>= 1.1.3) 42 | gherkin (~> 2.12) 43 | multi_json (>= 1.7.5, < 2.0) 44 | multi_test (>= 0.1.2) 45 | diff-lcs (1.2.5) 46 | docile (1.1.5) 47 | erubis (2.7.0) 48 | ffi (1.9.10) 49 | formatador (0.2.5) 50 | geminabox (0.11.1) 51 | builder 52 | httpclient (>= 2.2.7) 53 | nesty 54 | sinatra (>= 1.2.7) 55 | gherkin (2.12.2) 56 | multi_json (~> 1.3) 57 | guard (2.13.0) 58 | formatador (>= 0.2.4) 59 | listen (>= 2.7, <= 4.0) 60 | lumberjack (~> 1.0) 61 | nenv (~> 0.1) 62 | notiffany (~> 0.0) 63 | pry (>= 0.9.12) 64 | shellany (~> 0.0) 65 | thor (>= 0.18.1) 66 | guard-rspec (4.2.10) 67 | guard (~> 2.1) 68 | rspec (>= 2.14, < 4.0) 69 | httpclient (2.7.1) 70 | i18n (0.6.11) 71 | libnotify (0.8.4) 72 | ffi (>= 1.0.11) 73 | listen (3.0.6) 74 | rb-fsevent (>= 0.9.3) 75 | rb-inotify (>= 0.9.7) 76 | log4r (1.1.10) 77 | lumberjack (1.0.10) 78 | method_source (0.8.2) 79 | mime-types (1.25.1) 80 | minitest (4.7.5) 81 | multi_json (1.11.2) 82 | multi_test (0.1.2) 83 | nenv (0.3.0) 84 | nesty (1.0.2) 85 | net-scp (1.1.2) 86 | net-ssh (>= 2.6.5) 87 | net-ssh (2.7.0) 88 | notiffany (0.0.8) 89 | nenv (~> 0.1) 90 | shellany (~> 0.0) 91 | pry (0.10.3) 92 | coderay (~> 1.1.0) 93 | method_source (~> 0.8.1) 94 | slop (~> 3.4) 95 | rack (1.6.4) 96 | rack-protection (1.5.3) 97 | rack 98 | rake (10.5.0) 99 | rb-fsevent (0.9.7) 100 | rb-inotify (0.9.7) 101 | ffi (>= 0.5.0) 102 | rest-client (1.6.9) 103 | mime-types (~> 1.16) 104 | retryable (1.3.6) 105 | rspec (3.0.0) 106 | rspec-core (~> 3.0.0) 107 | rspec-expectations (~> 3.0.0) 108 | rspec-mocks (~> 3.0.0) 109 | rspec-core (3.0.4) 110 | rspec-support (~> 3.0.0) 111 | rspec-expectations (3.0.4) 112 | diff-lcs (>= 1.2.0, < 2.0) 113 | rspec-support (~> 3.0.0) 114 | rspec-mocks (3.0.4) 115 | rspec-support (~> 3.0.0) 116 | rspec-support (3.0.4) 117 | safe_yaml (1.0.4) 118 | shellany (0.0.1) 119 | simplecov (0.9.2) 120 | docile (~> 1.1.0) 121 | multi_json (~> 1.0) 122 | simplecov-html (~> 0.9.0) 123 | simplecov-html (0.9.0) 124 | simplecov-rcov (0.2.3) 125 | simplecov (>= 0.4.1) 126 | sinatra (1.4.7) 127 | rack (~> 1.5) 128 | rack-protection (~> 1.4) 129 | tilt (>= 1.3, < 3) 130 | slop (3.6.0) 131 | thor (0.19.1) 132 | thread_safe (0.3.5) 133 | tilt (2.0.2) 134 | timecop (0.7.4) 135 | tzinfo (0.3.47) 136 | webmock (1.18.0) 137 | addressable (>= 2.3.6) 138 | crack (>= 0.3.2) 139 | 140 | PLATFORMS 141 | ruby 142 | 143 | DEPENDENCIES 144 | awesome_print (~> 1.2.0) 145 | cucumber (~> 1.3.15) 146 | geminabox (~> 0.11.1) 147 | guard-rspec (= 4.2.10) 148 | libnotify (~> 0.8.3) 149 | rake (= 10.5.0) 150 | rspec (~> 3.0.0) 151 | simplecov (~> 0.9.0) 152 | simplecov-rcov (~> 0.2.3) 153 | timecop (~> 0.7.1) 154 | vagrant (= 1.4.3)! 155 | vagrant-proxmox! 156 | webmock (~> 1.18.0) 157 | 158 | BUNDLED WITH 159 | 1.11.2 160 | -------------------------------------------------------------------------------- /Guardfile: -------------------------------------------------------------------------------- 1 | # A sample Guardfile 2 | # More info at https://github.com/guard/guard#readme 3 | 4 | guard :rspec do 5 | watch(%r{^spec/.+_spec\.rb$}) 6 | watch('spec/spec_helper.rb') { "spec" } 7 | watch(%r{^lib/vagrant-proxmox/action/(.+)\.rb$}) { |m| "spec/actions/#{m[1]}_action_spec.rb" } 8 | watch(%r{^lib/vagrant-proxmox/proxmox/(.+)\.rb$}) { |m| "spec/proxmox/#{m[1]}_spec.rb" } 9 | end 10 | 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Vagrant Proxmox Provider 2 | 3 | This is a [Vagrant](http://www.vagrantup.com) plugin that adds a 4 | [Proxmox](http://proxmox.com/) provider to Vagrant, allowing Vagrant to manage 5 | and provision Proxmox virtual machines. 6 | 7 | ## Features 8 | 9 | * Create/Destroy OpenVZ containers from specified templates 10 | * Start/Shutdown OpenVZ containers 11 | * Create/Destroy Qemu containers from specified templates or iso file 12 | * Start/Shutdown Qemu containers 13 | * SSH into virtual machine 14 | * Provision the virtual machine 15 | * Synced folder support via rsync 16 | 17 | ## Limitations 18 | 19 | * For OpenVZ containers you need a Vagrant compatible OpenVZ template 20 | * For OpenVZ containers only routed network mode is currently supported 21 | * For KVM machines the ISO file needs to be a Vagrant compatible live system or automatic installation 22 | * For KVM machines the Qemu template has to be on the selected_node 23 | 24 | ## Requirements 25 | 26 | * Vagrant 1.5+ 27 | * Ruby 2+ 28 | 29 | ## Installation 30 | 31 | Install using standard Vagrant plugin method: 32 | 33 | ``` 34 | $ vagrant plugin install vagrant-proxmox 35 | ``` 36 | 37 | This will install the plugin from [RubGems.org](http://rubygems.org/). 38 | 39 | ## Usage 40 | 41 | First install the provided dummy vagrant box: 42 | 43 | ``` 44 | $ vagrant box add dummy dummy_box/dummy.box 45 | ``` 46 | 47 | Then for an openvz container create a Vagrantfile that looks like the following (note that you might have to add "@pam" to your username if you're getting a "401 Unauthorized" error): 48 | 49 | ``` 50 | Vagrant.configure('2') do |config| 51 | 52 | config.vm.provider :proxmox do |proxmox| 53 | proxmox.endpoint = 'https://your.proxmox.server:8006/api2/json' 54 | proxmox.user_name = 'proxmox_username@pam' 55 | proxmox.password = 'proxmox_password' 56 | proxmox.vm_id_range = 900..910 57 | proxmox.vm_name_prefix = 'vagrant_' 58 | proxmox.openvz_os_template = 'local:vztmpl/vagrant-proxmox-ubuntu-12.tar.gz' 59 | proxmox.vm_type = :openvz 60 | proxmox.vm_memory = 256 61 | end 62 | 63 | config.vm.define :box, primary: true do |box| 64 | box.vm.box = 'dummy' 65 | box.vm.network :public_network, ip: '192.168.0.1' 66 | end 67 | 68 | end 69 | ``` 70 | 71 | If you want KVM the Vagrantfile could look as follows: 72 | 73 | ``` 74 | Vagrant.configure('2') do |config| 75 | 76 | config.vm.provider :proxmox do |proxmox| 77 | proxmox.endpoint = 'https://proxmox.example.com/api2/json' 78 | proxmox.user_name = 'vagrant' 79 | proxmox.password = 'password' 80 | proxmox.vm_id_range = 900..910 81 | proxmox.vm_type = :qemu 82 | proxmox.vm_name_prefix = 'vagrant_' 83 | proxmox.qemu_os = :l26 84 | proxmox.qemu_disk_size = '30G' 85 | proxmox.qemu_storage = 'local' 86 | proxmox.qemu_iso_file = '/home/user/system.iso' 87 | proxmox.vm_name_prefix = 'vagrant_test_' 88 | proxmox.qemu_cores = 1 89 | proxmox.qemu_sockets = 1 90 | proxmox.qemu_nic_model = 'virtio' 91 | proxmox.qemu_bridge = 'vmbr0' 92 | proxmox.vm_memory = 512 93 | end 94 | 95 | config.vm.define :box, primary: true do |box| 96 | box.vm.box = 'dummy' 97 | box.vm.network :public_network, ip: '192.168.0.1', macaddress: 'ff:aa:cc:dd:bb:ee' 98 | end 99 | 100 | end 101 | ``` 102 | 103 | For the meaning of the various options, refer to the `Options` section below. 104 | 105 | You need an OpenVZ template or KVM ISO that contains a vagrant user supplied with the default Vagrant SSH keys. 106 | You can download an example Ubuntu based template [here](https://www.dropbox.com/s/vuzywdosxhjjsag/vagrant-proxmox-ubuntu-12.tar.gz). 107 | 108 | Finally run `vagrant up --provider=proxmox` to create and start the new OpenVZ container. 109 | 110 | ## Options 111 | 112 | * `endpoint` URL of the JSON API endpoint of your Proxmox installation 113 | * `user_name` The name of the Proxmox user that Vagrant should use 114 | * `password` The password of the above user 115 | * `vm_id_range` The possible range of machine ids. The smallest free one is chosen for a new machine 116 | * `vm_name_prefix` An optional string that is prepended before the vm name 117 | * `vm_type` The virtual machine type, e.g. :openvz or :qemu 118 | * `openvz_os_template` The name of the template from which the OpenVZ container should be created 119 | * `openvz_template_file` The openvz os template file to upload and use for the virtual machine (can be specified instead of `openvz_os_template`) 120 | * `replace_openvz_template_file` Set to true if the openvz os template file should be replaced on the server (default: false) 121 | * `vm_memory` The container's main memory size 122 | * `task_timeout` How long to wait for completion of a Proxmox API command (in seconds) 123 | * `task_status_check_interval` Interval in seconds between checking for completion of a Proxmox API command 124 | * `ssh_timeout` The maximum timeout for a ssh connection to a virtual machine (in seconds) 125 | * `ssh_status_check_interval` The interval between two ssh reachability status retrievals (in seconds) 126 | * `imgcopy_timeout` The maximum timeout for a proxmox server task in case it's an upload (in seconds) 127 | * `qemu_os` The qemu virtual machine operating system, e.g. :l26 128 | * `qemu_iso` The qemu iso file to use for the virtual machine 129 | * `qemu_iso_file` The qemu iso file to upload and use for the virtual machine (can be specified instead of `qemu_iso`) 130 | * `replace_qemu_iso_file` Set to true if the iso file should be replaced on the server (default: false) 131 | * `replace_template` Set to true if the iso file should be replaced on the server (default: false) 132 | * `qemu_template` The name of a qemu template which is used to create a clone (can be specified instead of `qemu_iso[_file]`) 133 | * `qemu_disk_size` The qemu disk size to use for the virtual machine, e.g. '30G' 134 | * `qemu_storage` The storage pool to use, i.e. the value of the `storage` key of the hash returned by `pvesh get /nodes/{node}/storage`, e.g. 'raid', 'local', 'cephstore' 135 | * `qemu_cores` The number of cores per socket available to the VM 136 | * `qemu_sockets` The number of CPU sockets available to the VM 137 | * `qemu_nic_model` which model of network interface card to use, default 'e1000' 138 | * `qemu_bridge` connect automatically to this bridge, default 'vmbr0' 139 | * `selected_node` If specified, only this specific node is used to create machines 140 | 141 | ## Build the plugin 142 | 143 | Build the plugin gem with 144 | 145 | ``` 146 | $ rake build 147 | ``` 148 | 149 | Optionally run the rspec tests with 150 | 151 | 152 | ``` 153 | $ rake spec 154 | ``` 155 | 156 | ## About us 157 | 158 | [TELCAT MULTICOM GmbH](http://www.telcat.com) is a Germany-wide system house for innovative solutions and 159 | services in the areas of information, communication and security technology. 160 | 161 | We develop IP-based telecommunication systems ([TELCAT-UC](http://www.telcat.de/TELCAT-R-UC.304.0.html)) and 162 | use Vagrant and Proxmox to automatically deploy and test the builds in our Jenkins jobs. 163 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'rake' 2 | require 'rake/clean' 3 | require 'rubygems' 4 | require 'bundler/setup' 5 | require 'rubygems/gem_runner' 6 | require 'rspec/core/rake_task' 7 | require 'geminabox_client' 8 | require 'yaml' 9 | 10 | gemspec = eval(File.read 'vagrant-proxmox.gemspec') 11 | 12 | desc 'Build the project' 13 | task :build do 14 | Gem::GemRunner.new.run ['build', "#{gemspec.name}.gemspec"] 15 | end 16 | 17 | RSpec::Core::RakeTask.new 18 | 19 | desc 'Run RSpec code examples with coverage' 20 | RSpec::Core::RakeTask.new('spec_coverage') do |_| 21 | ENV['RUN_WITH_COVERAGE'] = 'true' 22 | end 23 | 24 | task :release_local do 25 | rake_config = YAML::load(File.read("#{ENV['HOME']}/.rake/rake.yml")) rescue {} 26 | GeminaboxClient.new(rake_config['geminabox']['url']).push "#{gemspec.name}-#{gemspec.version}.gem", overwrite: true 27 | puts "Gem #{gemspec.name} pushed to #{rake_config['geminabox']['url']}" 28 | end 29 | 30 | task :release_rubygems do 31 | `gem push "#{gemspec.name}-#{gemspec.version}.gem"` 32 | end 33 | 34 | task release_all: [:release_local, :release_rubygems] 35 | 36 | namespace :jenkins do 37 | task job: [:build, :spec_coverage, :release_all] 38 | end 39 | 40 | namespace :test do 41 | 42 | desc 'Run all tests (enable coverage with COVERAGE=y)' 43 | task :all do 44 | Rake::Task['test:rspec'].invoke 45 | Rake::Task['test:cucumber'].invoke 46 | end 47 | 48 | desc 'Run all rspec tests (enable coverage with COVERAGE=y)' 49 | task :rspec do 50 | require 'rspec/core/rake_task' 51 | RSpec::Core::RakeTask.new(:_specs) do |task| 52 | task.verbose = false 53 | end 54 | Rake::Task['_specs'].invoke 55 | end 56 | 57 | desc 'Run all cucumber tests (enable coverage with COVERAGE=y)' 58 | task :cucumber do 59 | require 'cucumber/rake/task' 60 | 61 | Cucumber::Rake::Task.new(:_features) do |task| 62 | task.cucumber_opts = '--quiet --format progress --require features features' 63 | end 64 | Rake::Task['_features'].invoke 65 | end 66 | 67 | end 68 | -------------------------------------------------------------------------------- /cucumber.yml: -------------------------------------------------------------------------------- 1 | default: --format progress --tags ~@pending 2 | pending: --format progress --tags @pending --dry-run -------------------------------------------------------------------------------- /dummy_box/Cucumber_Vagrantfile: -------------------------------------------------------------------------------- 1 | Vagrant.configure('2') do |config| 2 | config.vm.provider :proxmox do |proxmox| 3 | proxmox.endpoint = 'https://proxmox.example.com/api2/json' 4 | proxmox.user_name = 'vagrant' 5 | proxmox.password = 'password' 6 | proxmox.vm_type = :openvz 7 | proxmox.openvz_os_template = 'local:vztmpl/template.tar.gz' 8 | proxmox.vm_id_range = 900..910 9 | proxmox.vm_name_prefix = 'vagrant_test_' 10 | proxmox.vm_memory = 256 11 | proxmox.task_timeout = 30 12 | proxmox.task_status_check_interval = 1 13 | end 14 | 15 | config.vm.define :machine, primary: true do |machine| 16 | machine.vm.box = 'b681e2bc-617b-4b35-94fa-edc92e1071b8' 17 | machine.vm.network :public_network, ip: '172.16.100.1' 18 | machine.vm.provision 'shell', path: 'dummy_box/provision.sh' 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /dummy_box/Vagrantfile: -------------------------------------------------------------------------------- 1 | Vagrant.configure('2') do |config| 2 | config.vm.provider :proxmox do |proxmox| 3 | proxmox.endpoint = 'https://proxmox.example.com/api2/json' 4 | proxmox.user_name = 'vagrant' 5 | proxmox.password = 'password' 6 | proxmox.vm_type = :openvz 7 | proxmox.openvz_os_template = 'local:vztmpl/template.tar.gz' 8 | proxmox.vm_id_range = 900..910 9 | proxmox.vm_name_prefix = 'vagrant_test_' 10 | proxmox.vm_memory = 256 11 | end 12 | 13 | config.vm.define :machine, primary: true do |machine| 14 | machine.vm.box = 'b681e2bc-617b-4b35-94fa-edc92e1071b8' 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /dummy_box/Vagrantfile_qemu: -------------------------------------------------------------------------------- 1 | Vagrant.configure('2') do |config| 2 | config.vm.provider :proxmox do |proxmox| 3 | proxmox.endpoint = 'https://proxmox.example.com/api2/json' 4 | proxmox.user_name = 'vagrant' 5 | proxmox.password = 'password' 6 | proxmox.vm_type = :qemu 7 | proxmox.qemu_os = :l26 8 | proxmox.qemu_iso_file = './tmp/isofile.iso' 9 | proxmox.qemu_disk_size = '30G' 10 | proxmox.vm_id_range = 900..910 11 | proxmox.vm_name_prefix = 'vagrant_test_' 12 | proxmox.vm_memory = 256 13 | end 14 | 15 | config.vm.define :machine, primary: true do |machine| 16 | machine.vm.box = 'b681e2bc-617b-4b35-94fa-edc92e1071b8' 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /dummy_box/dummy.box: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/telcat/vagrant-proxmox/59dd4b55ff1ecf59bd1eda5bb65e8f871124b904/dummy_box/dummy.box -------------------------------------------------------------------------------- /dummy_box/metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "provider": "proxmox" 3 | } 4 | -------------------------------------------------------------------------------- /dummy_box/provision.sh: -------------------------------------------------------------------------------- 1 | #!bin/sh 2 | -------------------------------------------------------------------------------- /features/destroy_command.feature: -------------------------------------------------------------------------------- 1 | Feature: VM Destruction 2 | As a system administrator I want to destroy a virtual machine. 3 | 4 | Scenario: A running virtual machine exists on the proxmox server and the user confirms the destruction 5 | Given a proxmox virtual machine exists 6 | And it is running 7 | And I run "vagrant destroy" and answer the confirmation with "Y" 8 | Then the machine should not exist any longer 9 | And I should see "Shutting down the virtual machine..." 10 | And I should see "Destroying the virtual machine..." 11 | 12 | Scenario: A running virtual machine exists on the proxmox server and the user does not confirm the destruction 13 | Given a proxmox virtual machine exists 14 | And it is running 15 | When I run "vagrant destroy" and answer the confirmation with "n" 16 | Then the machine should still exist 17 | And it is still running 18 | 19 | Scenario: A stopped virtual machine exists on the proxmox server and the user confirms the destruction 20 | Given a proxmox virtual machine exists 21 | And it is stopped 22 | When I run "vagrant destroy" and answer the confirmation with "Y" 23 | Then the machine should not exist any longer 24 | And I should see "Destroying the virtual machine..." 25 | 26 | Scenario: A stopped virtual machine exists on the proxmox server and the user does not confirm the destruction 27 | Given a proxmox virtual machine exists 28 | And it is stopped 29 | When I run "vagrant destroy" and answer the confirmation with "n" 30 | Then the machine should still exist 31 | 32 | Scenario: The virtual machine does not exist on the proxmox server 33 | Given no proxmox virtual machine exists 34 | When I run "vagrant destroy" 35 | Then I should see "The virtual machine is not created on the server!" 36 | -------------------------------------------------------------------------------- /features/halt_command.feature: -------------------------------------------------------------------------------- 1 | Feature: VM Shutdown 2 | As a system administrator I want to shut a virtual machine down (halt). 3 | 4 | Scenario: A running virtual machine exists on the proxmox server 5 | Given a proxmox virtual machine exists 6 | And it is running 7 | When I run "vagrant halt" 8 | Then the machine is no longer running 9 | And I should see "Shutting down the virtual machine..." 10 | 11 | Scenario: A stopped virtual machine exists on the proxmox server 12 | Given a proxmox virtual machine exists 13 | And it is stopped 14 | When I run "vagrant halt" 15 | Then I should see "The virtual machine is already stopped" 16 | 17 | Scenario: The virtual machine does not exist on the proxmox server 18 | Given no proxmox virtual machine exists 19 | When I run "vagrant halt" 20 | Then I should see "The virtual machine is not created on the server!" -------------------------------------------------------------------------------- /features/provision_command.feature: -------------------------------------------------------------------------------- 1 | Feature: VM Provisioning 2 | As a system administrator I want to provision a virtual machine on the proxmox server so that its 3 | deployment state matches its configuraton. 4 | 5 | Scenario: The virtual machine exists on the proxmox server 6 | Given a proxmox virtual machine exists 7 | And it is running 8 | When I run "vagrant provision" 9 | Then Vagrant provisions the virtual machine 10 | And the local project folder is synchronized with the virtual machine 11 | 12 | Scenario: A stopped virtual machine exists on the proxmox server 13 | Given a proxmox virtual machine exists 14 | And it is stopped 15 | When I run "vagrant provision" 16 | Then I should see "VM must be running to execute this command." 17 | 18 | Scenario: The virtual machine does not exist on the proxmox server 19 | Given no proxmox virtual machine exists 20 | When I run "vagrant provision" 21 | Then I should see "The virtual machine is not created on the server" 22 | -------------------------------------------------------------------------------- /features/replace_iso_file.feature: -------------------------------------------------------------------------------- 1 | Feature: Use new iso file 2 | As a system administrator I want to choose an iso file to be uploaded to the proxmox server in order to 3 | use its contents for the virtual machine. It should replace the already existing file of the same name. 4 | 5 | Background: 6 | Given a Vagrantfile with these provider settings: 7 | """ 8 | Vagrant.configure('2') do |config| 9 | config.vm.provider :proxmox do |proxmox| 10 | proxmox.endpoint = 'https://proxmox.example.com/api2/json' 11 | proxmox.user_name = 'vagrant' 12 | proxmox.password = 'password' 13 | proxmox.vm_type = :qemu 14 | proxmox.qemu_os = :l26 15 | proxmox.qemu_iso_file = './tmp/justanisofile.iso' 16 | proxmox.replace_qemu_iso_file = true 17 | proxmox.qemu_disk_size = '30G' 18 | end 19 | config.vm.define :machine, primary: true do |machine| 20 | machine.vm.box = 'b681e2bc-617b-4b35-94fa-edc92e1071b8' 21 | machine.vm.network :public_network, ip: '172.16.100.1', macaddress: 'aa:bb:cc:dd:ee:ff' 22 | end 23 | end 24 | """ 25 | 26 | Scenario: An iso file is specified in the Vagrantfile and does not exist on the proxmox server 27 | Given An iso file "./tmp/justanisofile.iso" exists locally 28 | When I run "vagrant up --provider=proxmox --no-provision" 29 | Then The iso file "./tmp/justanisofile.iso" is uploaded into the local storage of the vm node 30 | And the new virtual machine using the iso "local:iso/justanisofile.iso" is created 31 | 32 | Scenario: An iso file is specified in the Vagrantfile and already exists on the proxmox server 33 | Given An iso file "./tmp/justanisofile.iso" exists locally 34 | And the iso file "justanisofile.iso" already exists in the proxmox storage 35 | When I run "vagrant up --provider=proxmox --no-provision" 36 | Then The iso file "justanisofile.iso" is deleted from proxmox 37 | And The iso file "./tmp/justanisofile.iso" is uploaded into the local storage of the vm node 38 | And the new virtual machine using the iso "local:iso/justanisofile.iso" is created 39 | 40 | Scenario: An iso file is specified in the Vagrantfile but does not exist locally 41 | When I run "vagrant up --provider=proxmox --no-provision" 42 | Then I should see "File for upload not found" 43 | 44 | Scenario: An iso file is specified in the Vagrantfile and an error occurs during upload 45 | Given An iso file "./tmp/justanisofile.iso" exists locally 46 | But during upload an error will occur 47 | When I run "vagrant up --provider=proxmox --no-provision" 48 | Then I should see "Error during upload" 49 | -------------------------------------------------------------------------------- /features/replace_template_file.feature: -------------------------------------------------------------------------------- 1 | Feature: Use new template file 2 | As a system administrator I want to choose a template file to be uploaded to the proxmox server in order to 3 | generate the filesystem of the virtual machine. 4 | 5 | Background: 6 | Given a Vagrantfile with these provider settings: 7 | """ 8 | Vagrant.configure('2') do |config| 9 | config.vm.provider :proxmox do |proxmox| 10 | proxmox.endpoint = 'https://proxmox.example.com/api2/json' 11 | proxmox.user_name = 'vagrant' 12 | proxmox.password = 'password' 13 | proxmox.vm_type = :openvz 14 | proxmox.openvz_template_file = './tmp/mytemplate.tar.gz' 15 | proxmox.replace_openvz_template_file = true 16 | end 17 | config.vm.define :machine, primary: true do |machine| 18 | machine.vm.box = 'b681e2bc-617b-4b35-94fa-edc92e1071b8' 19 | machine.vm.network :public_network, ip: '172.16.100.1' 20 | end 21 | end 22 | """ 23 | 24 | Scenario: A template is specified in the Vagrantfile and does not exist on the proxmox server 25 | Given A templatefile "./tmp/mytemplate.tar.gz" exists locally 26 | When I run "vagrant up --provider=proxmox --no-provision" 27 | Then The template file "./tmp/mytemplate.tar.gz" is uploaded into the local storage of the vm node 28 | And the new virtual machine using the template "local:vztmpl/mytemplate.tar.gz" is created 29 | 30 | Scenario: A template is specified in the Vagrantfile and already exists on the proxmox server 31 | Given A templatefile "./tmp/mytemplate.tar.gz" exists locally 32 | And the template file "mytemplate.tar.gz" already exists in the proxmox storage 33 | When I run "vagrant up --provider=proxmox --no-provision" 34 | Then The template file "mytemplate.tar.gz" is deleted from proxmox 35 | And the new virtual machine using the template "local:vztmpl/mytemplate.tar.gz" is created 36 | 37 | Scenario: A template is specified in the Vagrantfile but does not exist locally 38 | When I run "vagrant up --provider=proxmox --no-provision" 39 | Then I should see "File for upload not found" 40 | 41 | Scenario: A template is specified in the Vagrantfile and an error occurs during upload 42 | Given A templatefile "./tmp/mytemplate.tar.gz" exists locally 43 | But during upload an error will occur 44 | When I run "vagrant up --provider=proxmox --no-provision" 45 | Then I should see "Error during upload" -------------------------------------------------------------------------------- /features/ssh_command.feature: -------------------------------------------------------------------------------- 1 | Feature: VM ssh 2 | As a system administrator I want use ssh with the virtual machine 3 | 4 | Scenario: A running virtual machine exists on the proxmox server 5 | Given a proxmox virtual machine exists 6 | And it is running 7 | When I run "vagrant ssh" 8 | Then an ssh shell should be provided 9 | 10 | Scenario: A stopped virtual machine exists on the proxmox server 11 | Given a proxmox virtual machine exists 12 | And it is stopped 13 | When I run "vagrant ssh" 14 | Then I should see "VM must be running to execute this command." 15 | 16 | Scenario: The virtual machine does not exist on the proxmox server 17 | Given no proxmox virtual machine exists 18 | When I run "vagrant ssh" 19 | Then I should see "The virtual machine is not created on the server!" -------------------------------------------------------------------------------- /features/ssh_run_command.feature: -------------------------------------------------------------------------------- 1 | Feature: VM ssh 2 | As a system administrator I want use execute remote command with ssh on the virtual machine 3 | 4 | Scenario: A running virtual machine exists on the proxmox server 5 | Given a proxmox virtual machine exists 6 | And it is running 7 | When I run "vagrant ssh --command foo" 8 | Then the "foo" command is executed using ssh 9 | 10 | Scenario: A stopped virtual machine exists on the proxmox server 11 | Given a proxmox virtual machine exists 12 | And it is stopped 13 | When I run "vagrant ssh --command foo" 14 | Then I should see "VM must be running to execute this command." 15 | 16 | Scenario: The virtual machine does not exist on the proxmox server 17 | Given no proxmox virtual machine exists 18 | When I run "vagrant ssh --command foo" 19 | Then I should see "The virtual machine is not created on the server!" 20 | -------------------------------------------------------------------------------- /features/status_command.feature: -------------------------------------------------------------------------------- 1 | Feature: VM Status 2 | As a system administrator I want check the status of a virtual machine. 3 | 4 | Scenario: A running virtual machine exists on the proxmox server 5 | Given a proxmox virtual machine exists 6 | And it is running 7 | When I run "vagrant status" 8 | Then I should see "running" 9 | 10 | Scenario: A stopped virtual machine exists on the proxmox server 11 | Given a proxmox virtual machine exists 12 | And it is stopped 13 | When I run "vagrant status" 14 | Then I should see "stopped" 15 | 16 | Scenario: The virtual machine does not exist on the proxmox server 17 | Given no proxmox virtual machine exists 18 | When I run "vagrant status" 19 | Then I should see "not created" 20 | -------------------------------------------------------------------------------- /features/step_definitions/general_steps.rb: -------------------------------------------------------------------------------- 1 | Given(/^a proxmox virtual machine exists$/) do 2 | up_machine 3 | end 4 | 5 | Given(/^no proxmox virtual machine exists$/) do 6 | up_machine 7 | stub_request(:get, proxmox_api_url('/nodes/node1/openvz/900/status/current')). 8 | to_return(status: 500) 9 | end 10 | 11 | When(/^I run "vagrant (\w+)(?:\s)?([^"]*)?"$/) do |command, params| 12 | 13 | execute_vagrant_command command.to_sym, *(params.split) 14 | end 15 | 16 | When(/^I run "vagrant (\w+)(?:\s)?([^"]*)?" and answer the confirmation with "(\w+)"$/) do |command, params, answer| 17 | stub_ui_input answer 18 | execute_vagrant_command command.to_sym, *(params.split) 19 | end 20 | 21 | And(/^it is running$/) do 22 | stub_request(:get, proxmox_api_url('/nodes/node1/openvz/900/status/current')). 23 | to_return(body: {data: {status: 'running'}}.to_json) 24 | end 25 | 26 | And(/^it is stopped$/) do 27 | stub_request(:get, proxmox_api_url('/nodes/node1/openvz/900/status/current')). 28 | to_return(body: {data: {status: 'stopped'}}.to_json) 29 | end 30 | 31 | Then(/^I should see "([^"]*)"$/) do |text| 32 | expect_vagrant_ui_message /#{text}/ 33 | end 34 | 35 | Then(/^the machine should not exist any longer$/) do 36 | assert_requested :delete, proxmox_api_url('/nodes/node1/openvz/900') 37 | end 38 | 39 | Then(/^the machine should still exist$/) do 40 | assert_not_requested :delete, proxmox_api_url('/nodes/node1/openvz/900') 41 | end 42 | 43 | And(/^it is still running$/) do 44 | assert_not_requested :post, proxmox_api_url('/nodes/node1/openvz/900/status/shutdown') 45 | end 46 | 47 | Then(/^the machine is no longer running$/) do 48 | assert_requested :post, proxmox_api_url('/nodes/node1/openvz/900/status/shutdown') 49 | end 50 | 51 | Then(/^the machine is now running$/) do 52 | assert_requested :post, proxmox_api_url('/nodes/node1/openvz/900/status/start') 53 | end 54 | 55 | Then(/^Vagrant provisions the virtual machine$/) do 56 | expect_remote_vagrant_call /\/tmp\/vagrant-shell/ 57 | end 58 | 59 | Then(/^the local project folder is synchronized with the virtual machine$/) do 60 | expect_local_vagrant_call /rsync .+ #{Dir.pwd}\/ vagrant@172.16.100.1:\/vagrant/ 61 | end 62 | 63 | Then(/^an ssh shell should be provided$/) do 64 | expect_vagrant_ssh_command "" 65 | end 66 | 67 | Then(/^the "([^"]*)" command is executed using ssh$/) do |command| 68 | expect_vagrant_ssh_command /#{command}/ 69 | end 70 | 71 | Given(/^a Vagrantfile with these provider settings:$/) do |settings| 72 | prepare_and_stub_custom_environment settings 73 | end 74 | 75 | Then(/^the new virtual machine using the template "([^"]*)" is created$/) do |template| 76 | assert_requested :post, proxmox_api_url('/nodes/node1/openvz'), body: /#{CGI.escape(template)}/ 77 | end 78 | 79 | Then(/^The template file "([^"]*)" is uploaded into the local storage of the vm node$/) do |filename| 80 | assert_requested :post, proxmox_api_url('/nodes/node1/storage/local/upload') 81 | end 82 | 83 | Then(/^The template file "([^"]*)" is not uploaded$/) do |filename| 84 | assert_not_requested :post, proxmox_api_url('/nodes/node1/storage/local/upload') 85 | end 86 | 87 | Given(/^the template file "([^"]*)" already exists in the proxmox storage$/) do |template| 88 | remove_request_stub @storage_content_request_stub 89 | @storage_content_request_stub = stub_request(:get, proxmox_api_url('/nodes/node1/storage/local/content')). 90 | to_return(body: {data: [{volid: "local:vztmpl/#{template}"}]}.to_json) 91 | end 92 | 93 | Given(/^A templatefile "([^"]*)" exists locally$/) do |filename| 94 | touch_tempfile filename 95 | end 96 | 97 | But(/^during upload an error will occur$/) do 98 | stub_request(:post, proxmox_api_url('/nodes/node1/storage/local/upload')). 99 | to_return status: 500 100 | end 101 | 102 | Then(/^(\d+) seconds should have passed$/) do |interval| 103 | expect(Time).to have_elapsed interval.to_i.seconds 104 | end 105 | 106 | And(/^it won't response to ssh once it's started$/) do 107 | CommunicatorMock.ssh_enabled = false 108 | end 109 | 110 | Given(/^An iso file "([^"]*)" exists locally$/) do |filename| 111 | touch_tempfile filename 112 | end 113 | 114 | Then(/^The iso file "([^"]*)" is uploaded into the local storage of the vm node$/) do |_| 115 | assert_requested :post, proxmox_api_url('/nodes/node1/storage/local/upload') 116 | end 117 | 118 | And(/^the new virtual machine using the iso "([^"]*)" is created$/) do |iso| 119 | assert_requested :post, proxmox_api_url('/nodes/node1/qemu'), body: /#{CGI.escape(iso)}/ 120 | end 121 | 122 | And(/^the iso file "([^"]*)" already exists in the proxmox storage$/) do |iso| 123 | remove_request_stub @storage_content_request_stub 124 | @storage_content_request_stub = stub_request(:get, proxmox_api_url('/nodes/node1/storage/local/content')). 125 | to_return(body: {data: [{volid: "local:iso/#{iso}"}]}.to_json) 126 | end 127 | 128 | Then(/^The iso file "([^"]*)" is not uploaded$/) do |_| 129 | assert_not_requested :post, proxmox_api_url('/nodes/node1/storage/local/upload') 130 | end 131 | 132 | Then(/^The iso file "([^"]*)" is deleted from proxmox$/) do |iso| 133 | assert_requested :delete, proxmox_api_url("/nodes/node1/storage/local/content/iso/#{iso}") 134 | end 135 | 136 | Then(/^The template file "([^"]*)" is deleted from proxmox$/) do |template| 137 | assert_requested :delete, proxmox_api_url("/nodes/node1/storage/local/content/vztmpl/#{template}") 138 | end -------------------------------------------------------------------------------- /features/support/env.rb: -------------------------------------------------------------------------------- 1 | require 'vagrant-proxmox' 2 | require 'cucumber/rspec/doubles' 3 | require 'rspec/matchers' 4 | require 'active_support/core_ext/string' 5 | require 'webmock/cucumber' 6 | require_relative 'vagrant_process_mock' 7 | require_relative 'machine_helper' 8 | require_relative 'template_helper' 9 | require_relative 'file_helper' 10 | 11 | ENV['PATH'] = "#{File.expand_path(File.dirname(__FILE__) + '/../../bin')}#{File::PATH_SEPARATOR}#{ENV['PATH']}" 12 | LIB_DIR = File.join(File.expand_path(File.dirname(__FILE__)), '..', '..', 'lib') 13 | 14 | add_dummy_box 15 | 16 | at_exit do 17 | remove_dummy_box 18 | end 19 | 20 | Before do 21 | @ui = VagrantUIMock.new 22 | @original_rubylib = ENV['RUBYLIB'] 23 | ENV['RUBYLIB'] = LIB_DIR + File::PATH_SEPARATOR + ENV['RUBYLIB'].to_s 24 | FileUtils.rm_r '.vagrant', force: true 25 | VagrantSSHMock.reset! self 26 | clear_tempfile_list 27 | CommunicatorMock.ssh_enabled = true 28 | end 29 | 30 | After do 31 | ENV['RUBYLIB'] = @original_rubylib 32 | remove_all_tempfiles 33 | end 34 | -------------------------------------------------------------------------------- /features/support/file_helper.rb: -------------------------------------------------------------------------------- 1 | def touch_tempfile filename 2 | FileUtils.mkdir_p File.dirname filename 3 | FileUtils.touch (filename) 4 | @step_tempfiles << filename 5 | end 6 | 7 | def remove_all_tempfiles 8 | @step_tempfiles.each do |tempfile| 9 | File.unlink tempfile 10 | end 11 | end 12 | 13 | def clear_tempfile_list 14 | @step_tempfiles = [] 15 | end -------------------------------------------------------------------------------- /features/support/machine_helper.rb: -------------------------------------------------------------------------------- 1 | def stub_machine_initialization 2 | stub_remote_vagrant_call /mkdir/ 3 | stub_remote_vagrant_call /chown/ 4 | stub_local_vagrant_call /rsync/ 5 | stub_remote_vagrant_call /chmod/ 6 | stub_request(:post, proxmox_api_url('/access/ticket')). 7 | to_return body: {data: {ticket: 'ticket', CSRFPreventionToken: 'token'}}.to_json 8 | stub_request(:get, proxmox_api_url('/nodes')). 9 | to_return body: {data: [{node: 'node1'}]}.to_json 10 | stub_request(:get, proxmox_api_url('/cluster/resources?type=vm')). 11 | to_return body: {data: [{node: 'node1', id: 'openvz/900'}]}.to_json 12 | stub_request(:post, proxmox_api_url('/nodes/node1/openvz')). 13 | to_return body: {data: 'UPID:node1:A:B:C:D:task_type:user@host:'}.to_json 14 | stub_request(:get, proxmox_api_url('/nodes/node1/tasks/UPID:node1:A:B:C:D:task_type:user@host:/status')). 15 | to_return body: {data: {exitstatus: 'OK'}}.to_json 16 | stub_request(:post, proxmox_api_url('/nodes/node1/openvz/900/status/start')). 17 | to_return body: {data: 'UPID:node1:A:B:C:D:task_type:user@host:'}.to_json 18 | stub_request(:get, proxmox_api_url('/nodes/node1/openvz/900/status/current')). 19 | to_return(body: {data: {status: 'running'}}.to_json) 20 | @storage_content_request_stub = stub_request(:get, proxmox_api_url('/nodes/node1/storage/local/content')). 21 | to_return body: {data: [{node: 'node1', id: 'openvz/900'}]}.to_json 22 | stub_request(:post, proxmox_api_url('/nodes/node1/storage/local/upload')). 23 | to_return body: {data: 'UPID:node1:A:B:C:D:task_type:user@host:'}.to_json 24 | stub_request(:delete, proxmox_api_url('/nodes/node1/storage/local/content/iso/justanisofile.iso')). 25 | to_return do |request| 26 | remove_request_stub @storage_content_request_stub 27 | @storage_content_request_stub = stub_request(:get, proxmox_api_url('/nodes/node1/storage/local/content')). 28 | to_return(body: {data: []}.to_json) 29 | {body: {data: nil}.to_json} 30 | end 31 | stub_request(:delete, proxmox_api_url('/nodes/node1/storage/local/content/vztmpl/mytemplate.tar.gz')). 32 | to_return do |request| 33 | remove_request_stub @storage_content_request_stub 34 | @storage_content_request_stub = stub_request(:get, proxmox_api_url('/nodes/node1/storage/local/content')). 35 | to_return(body: {data: []}.to_json) 36 | {body: {data: nil}.to_json} 37 | end 38 | end 39 | 40 | def stub_default_calls 41 | @ui.reset! 42 | WebMock.reset! 43 | VagrantProcessMock.reset_history! 44 | stub_request(:post, proxmox_api_url('/access/ticket')). 45 | to_return body: {data: {ticket: 'ticket', CSRFPreventionToken: 'token'}}.to_json 46 | stub_request(:get, proxmox_api_url('/nodes')). 47 | to_return body: {data: [{node: 'node1'}]}.to_json 48 | stub_request(:get, proxmox_api_url('/cluster/resources?type=vm')). 49 | to_return body: {data: [{node: 'node1', id: 'openvz/900'}]}.to_json 50 | stub_request(:post, proxmox_api_url('/nodes/node1/openvz')). 51 | to_return body: {data: 'UPID:node1:A:B:C:D:task_type:user@host:'}.to_json 52 | stub_request(:get, proxmox_api_url('/nodes/node1/tasks/UPID:node1:A:B:C:D:task_type:user@host:/status')). 53 | to_return body: {data: {exitstatus: 'OK'}}.to_json 54 | stub_request(:post, proxmox_api_url('/nodes/node1/openvz/900/status/shutdown')). 55 | to_return body: {data: 'UPID:node1:A:B:C:D:task_type:user@host:'}.to_json 56 | stub_request(:delete, proxmox_api_url('/nodes/node1/openvz/900')). 57 | to_return body: {data: 'UPID:node1:A:B:C:D:task_type:user@host:'}.to_json 58 | stub_request(:post, proxmox_api_url('/nodes/node1/openvz/900/status/start')). 59 | to_return body: {data: 'UPID:node1:A:B:C:D:task_type:user@host:'}.to_json 60 | stub_request(:get, proxmox_api_url('/nodes/node1/storage/local/content')). 61 | to_return body: {data: []}.to_json 62 | stub_request(:post, proxmox_api_url('/nodes/node1/storage/local/upload')). 63 | to_return body: {data: 'UPID:node1:A:B:C:D:task_type:user@host:'}.to_json 64 | stub_request(:delete, proxmox_api_url('/nodes/node1/storage/local/content/iso/justanisofile.iso')). 65 | to_return do |request| 66 | remove_request_stub @storage_content_request_stub 67 | @storage_content_request_stub = stub_request(:get, proxmox_api_url('/nodes/node1/storage/local/content')). 68 | to_return(body: {data: []}.to_json) 69 | {body: {data: nil}.to_json} 70 | end 71 | stub_request(:delete, proxmox_api_url('/nodes/node1/storage/local/content/vztmpl/mytemplate.tar.gz')). 72 | to_return do |request| 73 | remove_request_stub @storage_content_request_stub 74 | @storage_content_request_stub = stub_request(:get, proxmox_api_url('/nodes/node1/storage/local/content')). 75 | to_return(body: {data: []}.to_json) 76 | {body: {data: nil}.to_json} 77 | end 78 | stub_request(:get, proxmox_api_url('/nodes/node1/network/vmbr0')). 79 | to_return body: {data: {}}.to_json 80 | end 81 | 82 | def up_machine 83 | stub_local_vagrant_call 'ps -o comm= 1' 84 | @environment = Vagrant::Environment.new vagrantfile_name: 'dummy_box/Cucumber_Vagrantfile' 85 | @environment.instance_variable_set :@ui, @ui 86 | stub_machine_initialization 87 | execute_vagrant_command :up, '--provider=proxmox', '--no-provision' 88 | stub_default_calls 89 | end 90 | 91 | def proxmox_api_url path 92 | "https://proxmox.example.com/api2/json#{path}" 93 | end 94 | 95 | def execute_vagrant_command command, *params 96 | begin 97 | Vagrant.plugin('2').manager.commands[command].new(params, @environment).execute 98 | rescue => e 99 | @ui.error e.to_s 100 | end 101 | end 102 | 103 | def add_dummy_box 104 | begin 105 | VagrantProcessMock.enabled = false 106 | Vagrant::Environment.new.boxes.add 'dummy_box/dummy.box', 'b681e2bc-617b-4b35-94fa-edc92e1071b8', :proxmox 107 | VagrantProcessMock.enabled = true 108 | rescue Vagrant::Errors::BoxAlreadyExists 109 | end 110 | end 111 | 112 | def remove_dummy_box 113 | stub_local_vagrant_call 'ps -o comm= 1' 114 | @environment = Vagrant::Environment.new vagrantfile_name: 'dummy_box/Cucumber_Vagrantfile' 115 | execute_vagrant_command :box, 'remove', 'b681e2bc-617b-4b35-94fa-edc92e1071b8' 116 | end 117 | -------------------------------------------------------------------------------- /features/support/template_helper.rb: -------------------------------------------------------------------------------- 1 | require 'erb' 2 | 3 | def create_vagrantfile content 4 | vagrantfile = Tempfile.new 'vagrantfile' 5 | vagrantfile.write content 6 | vagrantfile.flush 7 | vagrantfile 8 | end 9 | 10 | def prepare_and_stub_custom_environment settings 11 | stub_local_vagrant_call 'ps -o comm= 1' 12 | vagrantfile = create_vagrantfile settings 13 | @environment = Vagrant::Environment.new vagrantfile_name: vagrantfile.path 14 | @environment.instance_variable_set :@ui, @ui 15 | stub_machine_initialization 16 | end 17 | -------------------------------------------------------------------------------- /features/support/time_helper.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../spec/spec_helpers/time_helpers.rb' 2 | 3 | Before '@timecop' do 4 | Timecop.freeze 5 | allow_any_instance_of(VagrantPlugins::Proxmox::Action::ProxmoxAction).to(receive(:sleep)) { |_, duration| Timecop.travel(Time.now + duration) } 6 | end 7 | 8 | After '@timecop' do 9 | Timecop.return 10 | end 11 | -------------------------------------------------------------------------------- /features/support/vagrant_process_mock.rb: -------------------------------------------------------------------------------- 1 | require 'vagrant/util/subprocess' 2 | require 'ostruct' 3 | 4 | module Vagrant 5 | module Util 6 | class Subprocess 7 | def self.execute *command, &block 8 | if VagrantProcessMock.enabled 9 | VagrantProcessMock.check_call command.join(' '), :local 10 | OpenStruct.new exit_code: 0, stdout: '' 11 | else 12 | new(*command).execute(&block) 13 | end 14 | end 15 | end 16 | end 17 | end 18 | 19 | class CommunicatorMock 20 | 21 | @ssh_enabled = true 22 | 23 | class << self 24 | attr_accessor :ssh_enabled 25 | end 26 | 27 | def sudo command, opts=nil, &block 28 | VagrantProcessMock.check_call command, :remote 29 | end 30 | 31 | def upload *_ 32 | end 33 | 34 | def execute command, opts=nil, &block 35 | VagrantProcessMock.check_call command, :remote 36 | end 37 | 38 | def ready? 39 | CommunicatorMock.ssh_enabled 40 | end 41 | end 42 | 43 | module Vagrant 44 | class Machine 45 | def communicate 46 | CommunicatorMock.new 47 | end 48 | end 49 | end 50 | 51 | module VagrantProcessMock 52 | 53 | class << self 54 | 55 | attr_accessor :enabled 56 | attr_accessor :logging 57 | 58 | def initialize 59 | reset! 60 | @enabled = true 61 | @logging = false 62 | end 63 | 64 | def reset! 65 | @stubbed_calls = {local: [], remote: []} 66 | reset_history! 67 | end 68 | 69 | def reset_history! 70 | @calls = {local: [], remote: []} 71 | end 72 | 73 | def check_call call, type 74 | if @logging 75 | puts "Vagrant #{type} call: #{call}" 76 | end 77 | @calls[type] << call 78 | unless @stubbed_calls[type].find { |s| s === call } 79 | raise unstubbed_call_error_message call, type 80 | end 81 | end 82 | 83 | def stub_call call, type 84 | @stubbed_calls[type] << call 85 | end 86 | 87 | def expect_call call, type 88 | unless @calls[type].find { |c| call === c } 89 | raise expected_call_not_called_error_message call, type 90 | end 91 | end 92 | 93 | def expected_call_not_called_error_message call, type 94 | < e 25 | raise VagrantPlugins::Proxmox::Errors::VMCloneError, proxmox_exit_status: e.message 26 | end 27 | 28 | begin 29 | vm_id = connection(env).get_free_vm_id 30 | params = create_params_qemu(config, env, vm_id, template_vm_id) 31 | exit_status = connection(env).clone_vm node: node, vm_type: config.vm_type, params: params 32 | exit_status == 'OK' ? exit_status : raise(VagrantPlugins::Proxmox::Errors::ProxmoxTaskFailed, proxmox_exit_status: exit_status) 33 | rescue StandardError => e 34 | raise VagrantPlugins::Proxmox::Errors::VMCloneError, proxmox_exit_status: e.message 35 | end 36 | 37 | env[:machine].id = "#{node}/#{vm_id}" 38 | 39 | env[:ui].info I18n.t('vagrant_proxmox.done') 40 | next_action env 41 | end 42 | 43 | private 44 | def create_params_qemu(config, env, vm_id, template_vm_id) 45 | # without network, which will added in ConfigClonedVm 46 | {vmid: template_vm_id, 47 | newid: vm_id, 48 | name: env[:machine].config.vm.hostname || env[:machine].name.to_s, 49 | description: "#{config.vm_name_prefix}#{env[:machine].name}"} 50 | end 51 | 52 | end 53 | end 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /lib/vagrant-proxmox/action/config_clone.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module Proxmox 3 | module Action 4 | 5 | # This action modifies the configuration of a cloned vm 6 | # Basically it creates a user network interface with hostfwd for the provisioning 7 | # and an interface for every public or private interface defined in the Vagrantfile 8 | class ConfigClone < ProxmoxAction 9 | 10 | def initialize app, env 11 | @app = app 12 | @logger = Log4r::Logger.new 'vagrant_proxmox::action::config_clone' 13 | @node_ip = nil 14 | @guest_port = nil 15 | end 16 | 17 | def call env 18 | env[:ui].info I18n.t('vagrant_proxmox.configuring_vm') 19 | config = env[:machine].provider_config 20 | node = env[:proxmox_selected_node] 21 | vm_id = nil 22 | 23 | begin 24 | vm_id = env[:machine].id.split("/").last 25 | @node_ip = connection(env).get_node_ip(node, 'vmbr0') if config.vm_type == :qemu 26 | @guest_port = sprintf("22%03d", vm_id.to_i).to_s 27 | rescue StandardError => e 28 | raise VagrantPlugins::Proxmox::Errors::VMConfigError, proxmox_exit_status: e.message 29 | end 30 | 31 | begin 32 | template_config = connection(env).get_vm_config node: node, vm_id: vm_id, vm_type: config.vm_type 33 | params = create_params_qemu(config, env, vm_id, template_config) 34 | exit_status = connection(env).config_clone node: node, vm_type: config.vm_type, params: params 35 | exit_status == 'OK' ? exit_status : raise(VagrantPlugins::Proxmox::Errors::ProxmoxTaskFailed, proxmox_exit_status: exit_status) 36 | rescue StandardError => e 37 | raise VagrantPlugins::Proxmox::Errors::VMConfigError, proxmox_exit_status: e.message 38 | end 39 | 40 | env[:ui].info I18n.t('vagrant_proxmox.done') 41 | next_action env 42 | end 43 | 44 | private 45 | def create_params_qemu(provider_config, env, vm_id, template_config) 46 | vm_config = env[:machine].config.vm 47 | params = { 48 | vmid: vm_id, 49 | description: "#{provider_config.vm_name_prefix}#{env[:machine].name}", 50 | } 51 | # delete existing network interfaces from template 52 | to_delete = template_config.keys.select{|key| key.to_s.match(/^net/) } 53 | params[:delete] = to_delete.join(",") if not to_delete.empty? 54 | # net0 is the provisioning network, derived from forwarded_port 55 | net_num = 0 56 | hostname = vm_config.hostname || env[:machine].name 57 | netdev0 = [ 58 | "type=user", 59 | "id=net0", 60 | "hostname=#{hostname}", 61 | "hostfwd=tcp:#{@node_ip}:#{@guest_port}-:22", # selected_node's primary ip and port (22000 + vm_id) 62 | ] 63 | device0 = [ 64 | "#{provider_config.qemu_nic_model}", 65 | "netdev=net0", 66 | "bus=pci.0", 67 | "addr=0x12", # starting point for network interfaces 68 | "id=net0", 69 | "bootindex=299" 70 | ] 71 | params[:args] = "-netdev " + netdev0.join(",") + " -device " + device0.join(",") 72 | # now add a network device for every public_network or private_network 73 | # ip addresses are ignored here, as we can't configure anything inside the qemu vm. 74 | # at least we can set the predefined mac address and a bridge 75 | net_num += 1 76 | vm_config.networks.each do |type, options| 77 | next if not type.match(/^p.*_network$/) 78 | nic = provider_config.qemu_nic_model 79 | nic += "=#{options[:macaddress]}" if options[:macaddress] 80 | nic += ",bridge=#{options[:bridge]}" if options[:bridge] 81 | net = 'net' + net_num.to_s 82 | params[net] = nic 83 | net_num += 1 84 | end 85 | 86 | # some more individual settings 87 | params[:ide2] = "#{provider_config.qemu_iso},media=cdrom" if provider_config.qemu_iso 88 | params[:sockets] = "#{provider_config.qemu_sockets}".to_i if provider_config.qemu_sockets 89 | params[:cores] = "#{provider_config.qemu_cores}".to_i if provider_config.qemu_cores 90 | params[:balloon] = "#{provider_config.vm_memory}".to_i if provider_config.vm_memory and provider_config.vm_memory < template_config[:balloon] 91 | params[:memory] = "#{provider_config.vm_memory}".to_i if provider_config.vm_memory 92 | params 93 | end 94 | 95 | end 96 | end 97 | end 98 | end 99 | -------------------------------------------------------------------------------- /lib/vagrant-proxmox/action/connect_proxmox.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module Proxmox 3 | module Action 4 | 5 | # This action connects to the Proxmox server and stores the 6 | # connection in env[:proxmox_connection] 7 | class ConnectProxmox < ProxmoxAction 8 | 9 | def initialize app, env 10 | @app = app 11 | @logger = Log4r::Logger.new 'vagrant_proxmox::action::connect_proxmox' 12 | end 13 | 14 | def call env 15 | begin 16 | config = env[:machine].provider_config 17 | connection = Connection.new config.endpoint, 18 | vm_id_range: config.vm_id_range, 19 | task_timeout: config.task_timeout, 20 | task_status_check_interval: config.task_status_check_interval, 21 | imgcopy_timeout: config.imgcopy_timeout 22 | connection.login username: config.user_name, password: config.password 23 | env[:proxmox_connection] = connection 24 | rescue => e 25 | raise Errors::CommunicationError, error_msg: e.message 26 | end 27 | next_action env 28 | end 29 | 30 | end 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /lib/vagrant-proxmox/action/create_vm.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module Proxmox 3 | module Action 4 | 5 | # This action creates a new virtual machine on the Proxmox server and 6 | # stores its node and vm_id env[:machine].id 7 | class CreateVm < ProxmoxAction 8 | 9 | def initialize app, env 10 | @app = app 11 | @logger = Log4r::Logger.new 'vagrant_proxmox::action::create_vm' 12 | end 13 | 14 | def call env 15 | env[:ui].info I18n.t('vagrant_proxmox.creating_vm') 16 | config = env[:machine].provider_config 17 | 18 | node = env[:proxmox_selected_node] 19 | vm_id = nil 20 | 21 | begin 22 | vm_id = connection(env).get_free_vm_id 23 | params = create_params_openvz(config, env, vm_id) if config.vm_type == :openvz 24 | params = create_params_lxc(config, env, vm_id) if config.vm_type == :lxc 25 | params = create_params_qemu(config, env, vm_id) if config.vm_type == :qemu 26 | exit_status = connection(env).create_vm node: node, vm_type: config.vm_type, params: params 27 | exit_status == 'OK' ? exit_status : raise(VagrantPlugins::Proxmox::Errors::ProxmoxTaskFailed, proxmox_exit_status: exit_status) 28 | rescue StandardError => e 29 | raise VagrantPlugins::Proxmox::Errors::VMCreateError, proxmox_exit_status: e.message 30 | end 31 | 32 | env[:machine].id = "#{node}/#{vm_id}" 33 | 34 | env[:ui].info I18n.t('vagrant_proxmox.done') 35 | next_action env 36 | end 37 | 38 | private 39 | def create_params_qemu(config, env, vm_id) 40 | network = "#{config.qemu_nic_model},bridge=#{config.qemu_bridge}" 41 | network = "#{config.qemu_nic_model}=#{get_machine_macaddress(env)},bridge=#{config.qemu_bridge}" if get_machine_macaddress(env) 42 | {vmid: vm_id, 43 | name: env[:machine].config.vm.hostname || env[:machine].name.to_s, 44 | ostype: config.qemu_os, 45 | ide2: "#{config.qemu_iso},media=cdrom", 46 | sata0: "#{config.qemu_storage}:#{config.qemu_disk_size},format=qcow2", 47 | sockets: config.qemu_sockets, 48 | cores: config.qemu_cores, 49 | memory: config.vm_memory, 50 | net0: network, 51 | description: "#{config.vm_name_prefix}#{env[:machine].name}"} 52 | end 53 | 54 | private 55 | def create_params_openvz(config, env, vm_id) 56 | {vmid: vm_id, 57 | ostemplate: config.openvz_os_template, 58 | hostname: env[:machine].config.vm.hostname || env[:machine].name.to_s, 59 | password: 'vagrant', 60 | memory: config.vm_memory, 61 | description: "#{config.vm_name_prefix}#{env[:machine].name}"} 62 | .tap do |params| 63 | params[:ip_address] = get_machine_ip_address(env) if get_machine_ip_address(env) 64 | end 65 | end 66 | 67 | private 68 | def create_params_lxc(config, env, vm_id) 69 | {vmid: vm_id, 70 | ostemplate: config.openvz_os_template, 71 | hostname: env[:machine].config.vm.hostname || env[:machine].name.to_s, 72 | password: 'vagrant', 73 | storage: "#{config.vm_storage}:#{config.vm_disk_size}", 74 | memory: config.vm_memory, 75 | description: "#{config.vm_name_prefix}#{env[:machine].name}"} 76 | .tap do |params| 77 | params[:net0] = "name=#{get_machine_interface_name(env)},ip=#{get_machine_ip_address(env)}/24,gw=#{get_machine_gw_ip(env)},bridge=#{get_machine_bridge_name(env)}" if get_machine_ip_address(env) 78 | end 79 | end 80 | end 81 | end 82 | end 83 | end 84 | -------------------------------------------------------------------------------- /lib/vagrant-proxmox/action/destroy_vm.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module Proxmox 3 | module Action 4 | 5 | # This action destroys the virtual machine env[:machine] 6 | class DestroyVm < ProxmoxAction 7 | 8 | def initialize app, env 9 | @app = app 10 | @logger = Log4r::Logger.new 'vagrant_proxmox::action::destroy_vm' 11 | end 12 | 13 | def call env 14 | env[:ui].info I18n.t('vagrant_proxmox.destroying_vm') 15 | 16 | begin 17 | node, vm_id = env[:machine].id.split '/' 18 | exit_status = connection(env).delete_vm vm_id 19 | exit_status == 'OK' ? exit_status : raise(VagrantPlugins::Proxmox::Errors::ProxmoxTaskFailed, proxmox_exit_status: exit_status) 20 | rescue StandardError => e 21 | raise VagrantPlugins::Proxmox::Errors::VMDestroyError, proxmox_exit_status: e.message 22 | end 23 | 24 | env[:ui].info I18n.t('vagrant_proxmox.done') 25 | 26 | next_action env 27 | end 28 | 29 | end 30 | 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /lib/vagrant-proxmox/action/get_node_list.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module Proxmox 3 | module Action 4 | 5 | # This action gets a list of all the nodes e.g. ['node1', 'node2'] of 6 | # a Proxmox server cluster and stores it under env[:proxmox_nodes] 7 | class GetNodeList < ProxmoxAction 8 | 9 | def initialize app, env 10 | @app = app 11 | end 12 | 13 | def call env 14 | begin 15 | env[:proxmox_nodes] = env[:proxmox_connection].get_node_list 16 | next_action env 17 | rescue => e 18 | raise Errors::CommunicationError, error_msg: e.message 19 | end 20 | end 21 | 22 | end 23 | 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/vagrant-proxmox/action/is_created.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module Proxmox 3 | module Action 4 | 5 | # set env[:result] to :is_created 6 | class IsCreated < ProxmoxAction 7 | 8 | def initialize app, env 9 | @app = app 10 | end 11 | 12 | def call env 13 | env[:result] = env[:machine].state.id != :not_created 14 | next_action env 15 | end 16 | 17 | end 18 | 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /lib/vagrant-proxmox/action/is_stopped.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module Proxmox 3 | module Action 4 | 5 | # set env[:result] to :stopped 6 | class IsStopped < ProxmoxAction 7 | 8 | def initialize app, env 9 | @app = app 10 | end 11 | 12 | def call env 13 | env[:result] = env[:machine].state.id == :stopped 14 | next_action env 15 | end 16 | 17 | end 18 | 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /lib/vagrant-proxmox/action/message_already_running.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module Proxmox 3 | module Action 4 | 5 | class MessageAlreadyRunning < ProxmoxAction 6 | 7 | def initialize app, env 8 | @app = app 9 | end 10 | 11 | def call env 12 | env[:ui].info I18n.t('vagrant_proxmox.already_running') 13 | next_action env 14 | end 15 | 16 | end 17 | 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/vagrant-proxmox/action/message_already_stopped.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module Proxmox 3 | module Action 4 | 5 | class MessageAlreadyStopped < ProxmoxAction 6 | 7 | def initialize app, env 8 | @app = app 9 | end 10 | 11 | def call env 12 | env[:ui].info I18n.t('vagrant_proxmox.already_stopped') 13 | next_action env 14 | end 15 | 16 | end 17 | 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/vagrant-proxmox/action/message_file_not_found.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module Proxmox 3 | module Action 4 | 5 | class MessageFileNotFound < ProxmoxAction 6 | 7 | def initialize app, env 8 | @app = app 9 | end 10 | 11 | def call env 12 | #TODO add file name 13 | env[:ui].info I18n.t('vagrant_proxmox.errors.file_not_found') 14 | next_action env 15 | end 16 | 17 | end 18 | 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /lib/vagrant-proxmox/action/message_not_created.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module Proxmox 3 | module Action 4 | 5 | class MessageNotCreated < ProxmoxAction 6 | 7 | def initialize app, env 8 | @app = app 9 | end 10 | 11 | def call env 12 | env[:ui].info I18n.t('vagrant_proxmox.not_created') 13 | next_action env 14 | end 15 | 16 | end 17 | 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/vagrant-proxmox/action/message_not_running.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module Proxmox 3 | module Action 4 | 5 | class MessageNotRunning < ProxmoxAction 6 | 7 | def initialize app, env 8 | @app = app 9 | end 10 | 11 | def call env 12 | env[:ui].info I18n.t('vagrant_proxmox.errors.vm_not_running') 13 | next_action env 14 | end 15 | 16 | end 17 | 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/vagrant-proxmox/action/message_upload_server_error.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module Proxmox 3 | module Action 4 | 5 | class MessageUploadServerError < ProxmoxAction 6 | 7 | def initialize app, env 8 | @app = app 9 | end 10 | 11 | def call env 12 | env[:ui].info I18n.t('vagrant_proxmox.errors.server_upload_error') 13 | next_action env 14 | end 15 | 16 | end 17 | 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/vagrant-proxmox/action/proxmox_action.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module Proxmox 3 | module Action 4 | 5 | class ProxmoxAction 6 | 7 | protected 8 | def next_action env 9 | @app.call env 10 | end 11 | 12 | protected 13 | def get_machine_ip_address env 14 | config = env[:machine].provider_config 15 | if config.vm_type == :qemu 16 | env[:machine].config.vm.networks.select { |type, _| type == :forwarded_port }.first[1][:host_ip] rescue nil 17 | else 18 | env[:machine].config.vm.networks.select { |type, _| type == :public_network }.first[1][:ip] rescue nil 19 | end 20 | end 21 | 22 | protected 23 | def get_machine_interface_name env 24 | env[:machine].config.vm.networks.select { |type, _| type == :public_network }.first[1][:interface] rescue nil 25 | end 26 | 27 | protected 28 | def get_machine_bridge_name env 29 | env[:machine].config.vm.networks.select { |type, _| type == :public_network }.first[1][:bridge] rescue nil 30 | end 31 | 32 | protected 33 | def get_machine_gw_ip env 34 | env[:machine].config.vm.networks.select { |type, _| type == :public_network }.first[1][:gw] rescue nil 35 | end 36 | 37 | protected 38 | def get_machine_macaddress env 39 | env[:machine].config.vm.networks.select { |type, _| type == :public_network }.first[1][:macaddress] rescue nil 40 | end 41 | 42 | protected 43 | def connection env 44 | env[:proxmox_connection] 45 | end 46 | 47 | end 48 | 49 | end 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /lib/vagrant-proxmox/action/read_ssh_info.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module Proxmox 3 | module Action 4 | 5 | # This action stores the ssh information in env[:machine_ssh_info] 6 | class ReadSSHInfo < ProxmoxAction 7 | 8 | def initialize app, env 9 | @app = app 10 | @logger = Log4r::Logger.new 'vagrant_proxmox::action::read_ssh_info' 11 | end 12 | 13 | def call env 14 | env[:machine_ssh_info] = get_machine_ip_address(env).try do |ip_address| 15 | {host: ip_address, port: env[:machine].config.ssh.guest_port} 16 | end 17 | env[:machine_ssh_info] 18 | next_action env 19 | end 20 | 21 | end 22 | 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/vagrant-proxmox/action/read_state.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module Proxmox 3 | module Action 4 | 5 | # This action reads the state of a Proxmox virtual machine and stores it 6 | # in env[:machine_state_id]. 7 | class ReadState < ProxmoxAction 8 | 9 | def initialize app, env 10 | @app = app 11 | @logger = Log4r::Logger.new 'vagrant_proxmox::action::read_state' 12 | end 13 | 14 | def call env 15 | begin 16 | env[:machine_state_id] = 17 | if env[:machine].id 18 | node, vm_id = env[:machine].id.split '/' 19 | env[:proxmox_connection].get_vm_state vm_id 20 | else 21 | :not_created 22 | end 23 | next_action env 24 | rescue => e 25 | raise Errors::CommunicationError, error_msg: e.message 26 | end 27 | 28 | end 29 | 30 | end 31 | 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /lib/vagrant-proxmox/action/select_node.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module Proxmox 3 | module Action 4 | 5 | # This action reads the state of a Proxmox virtual machine and stores it 6 | # in env[:machine_state_id]. 7 | class SelectNode < ProxmoxAction 8 | 9 | def initialize app, env 10 | @app = app 11 | @logger = Log4r::Logger.new 'vagrant_proxmox::action::select_node' 12 | end 13 | 14 | def call env 15 | if env[:machine].provider_config.selected_node != Config::UNSET_VALUE 16 | if env[:proxmox_nodes].include?(env[:machine].provider_config.selected_node) 17 | env[:proxmox_selected_node] = env[:machine].provider_config.selected_node 18 | else 19 | raise Errors::InvalidNodeError, node: env[:machine].provider_config.selected_node 20 | end 21 | else 22 | env[:proxmox_selected_node] = env[:proxmox_nodes].sample 23 | end 24 | next_action env 25 | end 26 | 27 | end 28 | 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/vagrant-proxmox/action/shutdown_vm.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module Proxmox 3 | module Action 4 | 5 | # This action shuts down the Proxmox virtual machine in env[:machine] 6 | class ShutdownVm < ProxmoxAction 7 | 8 | def initialize app, env 9 | @app = app 10 | @logger = Log4r::Logger.new 'vagrant_proxmox::action::shutdown_vm' 11 | end 12 | 13 | def call env 14 | env[:ui].info I18n.t('vagrant_proxmox.shut_down_vm') 15 | begin 16 | node, vm_id = env[:machine].id.split '/' 17 | exit_status = connection(env).shutdown_vm vm_id 18 | exit_status == 'OK' ? exit_status : raise(VagrantPlugins::Proxmox::Errors::ProxmoxTaskFailed, proxmox_exit_status: exit_status) 19 | rescue StandardError => e 20 | raise VagrantPlugins::Proxmox::Errors::VMShutdownError, proxmox_exit_status: e.message 21 | end 22 | env[:ui].info I18n.t('vagrant_proxmox.done') 23 | 24 | next_action env 25 | end 26 | 27 | end 28 | 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/vagrant-proxmox/action/start_vm.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module Proxmox 3 | module Action 4 | 5 | # This action starts the Proxmox virtual machine in env[:machine] 6 | class StartVm < ProxmoxAction 7 | 8 | def initialize app, env 9 | @app = app 10 | @logger = Log4r::Logger.new 'vagrant_proxmox::action::start_vm' 11 | end 12 | 13 | def call env 14 | env[:ui].info I18n.t('vagrant_proxmox.starting_vm') 15 | begin 16 | node, vm_id = env[:machine].id.split '/' 17 | exit_status = connection(env).start_vm vm_id 18 | exit_status == 'OK' ? exit_status : raise(VagrantPlugins::Proxmox::Errors::ProxmoxTaskFailed, proxmox_exit_status: exit_status) 19 | rescue StandardError => e 20 | raise VagrantPlugins::Proxmox::Errors::VMStartError, proxmox_exit_status: e.message 21 | end 22 | 23 | env[:ui].info I18n.t('vagrant_proxmox.done') 24 | 25 | env[:ui].info I18n.t('vagrant_proxmox.waiting_for_ssh_connection') 26 | 27 | retryException = Class.new StandardError 28 | 29 | begin 30 | retryable(on: retryException, 31 | tries: env[:machine].provider_config.ssh_timeout / env[:machine].provider_config.ssh_status_check_interval + 1, 32 | sleep: env[:machine].provider_config.ssh_status_check_interval) do 33 | raise retryException unless env[:interrupted] || env[:machine].communicate.ready? 34 | end 35 | rescue retryException 36 | raise VagrantPlugins::Proxmox::Errors::SSHError 37 | end 38 | 39 | env[:ui].info I18n.t('vagrant_proxmox.done') 40 | 41 | next_action env 42 | end 43 | 44 | end 45 | 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /lib/vagrant-proxmox/action/stop_vm.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module Proxmox 3 | module Action 4 | 5 | # This action stops the Proxmox virtual machine in env[:machine] 6 | class StopVm < ProxmoxAction 7 | 8 | def initialize app, env 9 | @app = app 10 | @logger = Log4r::Logger.new 'vagrant_proxmox::action::stop_vm' 11 | end 12 | 13 | def call env 14 | begin 15 | node, vm_id = env[:machine].id.split '/' 16 | env[:ui].info I18n.t('vagrant_proxmox.stopping_vm') 17 | exit_status = connection(env).stop_vm vm_id 18 | exit_status == 'OK' ? exit_status : raise(VagrantPlugins::Proxmox::Errors::ProxmoxTaskFailed, proxmox_exit_status: exit_status) 19 | rescue StandardError => e 20 | raise VagrantPlugins::Proxmox::Errors::VMStopError, proxmox_exit_status: e.message 21 | end 22 | 23 | env[:ui].info I18n.t('vagrant_proxmox.done') 24 | 25 | next_action env 26 | end 27 | 28 | end 29 | 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /lib/vagrant-proxmox/action/sync_folders.rb: -------------------------------------------------------------------------------- 1 | require 'vagrant/util/subprocess' 2 | 3 | module VagrantPlugins 4 | module Proxmox 5 | module Action 6 | 7 | # This action uses 'rsync' to sync the folders over to the virtual machine. 8 | class SyncFolders < ProxmoxAction 9 | 10 | def initialize app, env 11 | @app = app 12 | @logger = Log4r::Logger.new 'vagrant_proxmox::action::sync_folders' 13 | end 14 | 15 | def call env 16 | ssh_info = env[:machine].ssh_info 17 | 18 | env[:machine].config.vm.synced_folders.each do |_, data| 19 | hostpath = File.expand_path data[:hostpath], env[:root_path] 20 | guestpath = data[:guestpath] 21 | next if data[:disabled] 22 | 23 | # Make sure there is a trailing slash on the host path to 24 | # avoid creating an additional directory with rsync 25 | hostpath = "#{hostpath}/" if hostpath !~ /\/$/ 26 | 27 | env[:ui].info I18n.t('vagrant_proxmox.rsync_folder', hostpath: hostpath, guestpath: guestpath) 28 | 29 | # Create the guest path 30 | env[:machine].communicate.sudo "mkdir -p '#{guestpath}'" 31 | env[:machine].communicate.sudo "chown #{ssh_info[:username]} '#{guestpath}'" 32 | 33 | # rsync over to the guest path using the SSH info 34 | command = [ 35 | 'rsync', '--verbose', '--archive', '--compress', '--delete', 36 | '-e', "ssh -p #{ssh_info[:port]} -i '#{ssh_info[:private_key_path][0]}' -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null", 37 | hostpath, "#{ssh_info[:username]}@#{ssh_info[:host]}:#{guestpath}"] 38 | 39 | rsync_process = Vagrant::Util::Subprocess.execute *command 40 | if rsync_process.exit_code != 0 41 | raise Errors::RsyncError, guestpath: guestpath, hostpath: hostpath, stderr: rsync_process.stderr 42 | end 43 | end 44 | 45 | next_action env 46 | end 47 | 48 | end 49 | 50 | end 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /lib/vagrant-proxmox/action/upload_iso_file.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module Proxmox 3 | module Action 4 | 5 | # This action uploads a iso file into the local storage a given node 6 | class UploadIsoFile < ProxmoxAction 7 | 8 | def initialize app, env 9 | @app = app 10 | @logger = Log4r::Logger.new 'vagrant_proxmox::action::iso_file_upload' 11 | end 12 | 13 | def call env 14 | env[:result] = :ok 15 | config = env[:machine].provider_config 16 | if config.qemu_iso_file 17 | env[:result] = upload_file env, config.qemu_iso_file, config.replace_qemu_iso_file 18 | end 19 | next_action env 20 | end 21 | 22 | private 23 | def upload_file env, filename, replace 24 | if File.exist? filename 25 | begin 26 | connection(env).upload_file(filename, content_type: 'iso', node: env[:proxmox_selected_node], storage: 'local', replace: replace) 27 | :ok 28 | rescue 29 | :server_upload_error 30 | end 31 | else 32 | :file_not_found 33 | end 34 | end 35 | end 36 | end 37 | end 38 | end -------------------------------------------------------------------------------- /lib/vagrant-proxmox/action/upload_template_file.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module Proxmox 3 | module Action 4 | 5 | # This action uploads a template file into the local storage of a given node 6 | class UploadTemplateFile < ProxmoxAction 7 | 8 | def initialize app, env 9 | @app = app 10 | @logger = Log4r::Logger.new 'vagrant_proxmox::action::template_file_upload' 11 | end 12 | 13 | def call env 14 | env[:result] = :ok 15 | config = env[:machine].provider_config 16 | if config.openvz_template_file 17 | env[:result] = upload_file env, config.openvz_template_file, config.replace_openvz_template_file 18 | end 19 | next_action env 20 | end 21 | 22 | private 23 | def upload_file env, filename, replace 24 | if File.exist? filename 25 | begin 26 | connection(env).upload_file(filename, content_type: 'vztmpl', node: env[:proxmox_selected_node], storage: 'local', replace: replace) 27 | :ok 28 | rescue 29 | :server_upload_error 30 | end 31 | else 32 | :file_not_found 33 | end 34 | end 35 | end 36 | end 37 | end 38 | end -------------------------------------------------------------------------------- /lib/vagrant-proxmox/config.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module Proxmox 3 | class Config < Vagrant.plugin('2', :config) 4 | 5 | # The Proxmox REST API endpoint 6 | # 7 | # @return [String] 8 | attr_accessor :endpoint 9 | 10 | # The Proxmox preferred cluster node 11 | # 12 | # @return [String] 13 | attr_accessor :selected_node 14 | 15 | # The Proxmox user name 16 | # 17 | # @return [String] 18 | attr_accessor :user_name 19 | 20 | # The Proxmox password 21 | # 22 | # @return [String] 23 | attr_accessor :password 24 | 25 | # The virtual machine type, e.g. :openvz or :qemu or :lxc 26 | # 27 | # @return [Symbol] 28 | attr_accessor :vm_type 29 | 30 | # The openvz os template to use for the virtual machine 31 | # 32 | # @return [String] 33 | attr_accessor :openvz_os_template 34 | 35 | # The openvz os template file to upload and use for the virtual machine 36 | # 37 | # @return [String] 38 | attr_accessor :openvz_template_file 39 | 40 | # Should the openvz os template be replaced (deleted before upload)? 41 | # 42 | # @return [Boolean] 43 | attr_accessor :replace_openvz_template_file 44 | 45 | # The id range to use for the virtual machines 46 | # 47 | # @return [Range] 48 | attr_accessor :vm_id_range 49 | 50 | # The prefix for the virtual machine name 51 | # 52 | # @return [String] 53 | attr_accessor :vm_name_prefix 54 | 55 | # Amount of RAM for the virtual machine in MB 56 | # 57 | # @return [Integer] 58 | attr_accessor :vm_memory 59 | 60 | # The vm disk size to use for the virtual machine, e.g. '30G' 61 | # 62 | # @return [String] 63 | attr_accessor :vm_disk_size 64 | 65 | # The vm storage to use for the virtual machine, e.g. 'local', 'raid', 'cephstore' 66 | # defaults to 'raid' for backwards compatability 67 | # 68 | # @return [String] 69 | attr_accessor :vm_storage 70 | 71 | # The maximum timeout for a proxmox server task (in seconds) 72 | # 73 | # @return [Integer] 74 | attr_accessor :task_timeout 75 | 76 | # The interval between two proxmox task status retrievals (in seconds) 77 | # 78 | # @return [Integer, Proc] 79 | attr_accessor :task_status_check_interval 80 | 81 | # The maximum timeout for a ssh connection to a virtual machine (in seconds) 82 | # 83 | # @return [Integer] 84 | attr_accessor :ssh_timeout 85 | 86 | # The interval between two ssh reachability status retrievals (in seconds) 87 | # 88 | # @return [Integer, Proc] 89 | attr_accessor :ssh_status_check_interval 90 | 91 | # The maximum timeout for a proxmox server task if it's an upload (in seconds) 92 | # 93 | # @return [Integer] 94 | attr_accessor :imgcopy_timeout 95 | 96 | # The qemu virtual machine operating system, e.g. :l26 97 | # 98 | # @return [Symbol] 99 | attr_accessor :qemu_os 100 | 101 | # The number of cores per socket 102 | # 103 | # @return [Integer] 104 | attr_accessor :qemu_cores 105 | 106 | # The number of CPU sockets 107 | # 108 | # @return [Integer] 109 | attr_accessor :qemu_sockets 110 | 111 | # The qemu template to clone for the virtual machine 112 | # 113 | # @return [String] 114 | attr_accessor :qemu_template 115 | 116 | # The qemu iso file to use for the virtual machine 117 | # 118 | # @return [String] 119 | attr_accessor :qemu_iso 120 | 121 | # The qemu iso file to upload and use for the virtual machine 122 | # 123 | # @return [String] 124 | attr_accessor :qemu_iso_file 125 | 126 | # Should the qemu iso file replaced (deleted before upload)? 127 | # 128 | # @return [Boolean] 129 | attr_accessor :replace_qemu_iso_file 130 | 131 | # The qemu disk size to use for the virtual machine, e.g. '30G' 132 | # 133 | # @return [String] 134 | attr_accessor :qemu_disk_size 135 | 136 | # The qemu storage to use for the virtual machine, e.g. 'local', 'raid', 'cephstore' 137 | # defaults to 'raid' for backwards compatability 138 | # 139 | # @return [String] 140 | attr_accessor :qemu_storage 141 | 142 | # The qemu network interface card model, e.g. 'e1000', 'virtio' 143 | # defaults to 'e1000' for backwards compatability 144 | # 145 | # @return [String] 146 | attr_accessor :qemu_nic_model 147 | 148 | # The qemu network bridge, e.g. 'vmbr0' 149 | # defaults to 'vmbr0' for backwards compatability 150 | # 151 | # @return [String] 152 | attr_accessor :qemu_bridge 153 | 154 | def initialize 155 | @endpoint = UNSET_VALUE 156 | @selected_node = UNSET_VALUE 157 | @user_name = UNSET_VALUE 158 | @password = UNSET_VALUE 159 | @vm_type = UNSET_VALUE 160 | @openvz_os_template = UNSET_VALUE 161 | @openvz_template_file = UNSET_VALUE 162 | @replace_openvz_template_file = false 163 | @vm_id_range = 900..999 164 | @vm_name_prefix = 'vagrant_' 165 | @vm_memory = 512 166 | @vm_disk_size = '20G' 167 | @vm_storage = 'local' 168 | @task_timeout = 60 169 | @task_status_check_interval = 2 170 | @ssh_timeout = 60 171 | @ssh_status_check_interval = 5 172 | @imgcopy_timeout = 120 173 | @qemu_os = UNSET_VALUE 174 | @qemu_cores = 1 175 | @qemu_sockets = 1 176 | @qemu_template = UNSET_VALUE 177 | @qemu_iso = UNSET_VALUE 178 | @qemu_iso_file = UNSET_VALUE 179 | @replace_qemu_iso_file = false 180 | @qemu_disk_size = UNSET_VALUE 181 | @qemu_storage = 'raid' 182 | @qemu_nic_model = 'e1000' 183 | @qemu_bridge = 'vmbr0' 184 | end 185 | 186 | # This is the hook that is called to finalize the object before it is put into use. 187 | def finalize! 188 | @endpoint = nil if @endpoint == UNSET_VALUE 189 | @selected_node = nil if @endpoint == UNSET_VALUE 190 | @user_name = nil if @user_name == UNSET_VALUE 191 | @password = nil if @password == UNSET_VALUE 192 | @vm_type = nil if @vm_type == UNSET_VALUE 193 | @openvz_template_file = nil if @openvz_template_file == UNSET_VALUE 194 | @openvz_os_template = "local:vztmpl/#{File.basename @openvz_template_file}" if @openvz_template_file 195 | @openvz_os_template = nil if @openvz_os_template == UNSET_VALUE 196 | @qemu_template = nil if @qemu_os == UNSET_VALUE 197 | @qemu_os = nil if @qemu_os == UNSET_VALUE 198 | @qemu_iso_file = nil if @qemu_iso_file == UNSET_VALUE 199 | @qemu_iso = "local:iso/#{File.basename @qemu_iso_file}" if @qemu_iso_file 200 | @qemu_iso = nil if @qemu_iso == UNSET_VALUE 201 | @qemu_disk_size = nil if @qemu_disk_size == UNSET_VALUE 202 | @qemu_disk_size = convert_disk_size_to_gigabyte @qemu_disk_size if @qemu_disk_size 203 | @vm_disk_size = convert_disk_size_to_gigabyte @vm_disk_size if @vm_disk_size 204 | end 205 | 206 | def validate machine 207 | errors = [] 208 | errors << I18n.t('vagrant_proxmox.errors.no_endpoint_specified') unless @endpoint 209 | errors << I18n.t('vagrant_proxmox.errors.no_user_name_specified') unless @user_name 210 | errors << I18n.t('vagrant_proxmox.errors.no_password_specified') unless @password 211 | errors << I18n.t('vagrant_proxmox.errors.no_vm_type_specified') unless @vm_type 212 | if @vm_type == :openvz 213 | errors << I18n.t('vagrant_proxmox.errors.no_openvz_os_template_or_openvz_template_file_specified_for_type_openvz') unless @openvz_os_template || @openvz_template_file 214 | end 215 | if @vm_type == :qemu 216 | if @qemu_template 217 | else 218 | errors << I18n.t('vagrant_proxmox.errors.no_qemu_os_specified_for_vm_type_qemu') unless @qemu_os 219 | errors << I18n.t('vagrant_proxmox.errors.no_qemu_iso_or_qemu_iso_file_specified_for_vm_type_qemu') unless @qemu_iso || @qemu_iso_file 220 | errors << I18n.t('vagrant_proxmox.errors.no_qemu_disk_size_specified_for_vm_type_qemu') unless @qemu_disk_size 221 | end 222 | end 223 | {'Proxmox Provider' => errors} 224 | end 225 | 226 | private 227 | def convert_disk_size_to_gigabyte disk_size 228 | case disk_size[-1] 229 | when 'G' 230 | disk_size[0..-2] 231 | else 232 | disk_size 233 | end 234 | end 235 | end 236 | end 237 | end 238 | -------------------------------------------------------------------------------- /lib/vagrant-proxmox/errors.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module Proxmox 3 | 4 | class ProxmoxTaskNotFinished < Exception 5 | end 6 | 7 | module Errors 8 | 9 | class VagrantProxmoxError < Vagrant::Errors::VagrantError 10 | error_namespace 'vagrant_proxmox.errors' 11 | end 12 | 13 | class ProxmoxTaskFailed < VagrantProxmoxError 14 | error_key :proxmox_task_failed 15 | end 16 | 17 | class CommunicationError < VagrantProxmoxError 18 | error_key :communication_error 19 | end 20 | 21 | class Timeout < VagrantProxmoxError 22 | error_key :timeout 23 | end 24 | 25 | class NoVmIdAvailable < VagrantProxmoxError 26 | error_key :no_vm_id_available 27 | end 28 | 29 | class VMCreateError < VagrantProxmoxError 30 | error_key :vm_create_error 31 | end 32 | 33 | class VMCloneError < VagrantProxmoxError 34 | error_key :vm_clone_error 35 | end 36 | 37 | class NoTemplateAvailable < VagrantProxmoxError 38 | error_key :no_template_available 39 | end 40 | 41 | class VMConfigError < VagrantProxmoxError 42 | error_key :vm_configure_error 43 | end 44 | 45 | class VMDestroyError < VagrantProxmoxError 46 | error_key :vm_destroy_error 47 | end 48 | 49 | class VMStartError < VagrantProxmoxError 50 | error_key :vm_start_error 51 | end 52 | 53 | class VMStopError < VagrantProxmoxError 54 | error_key :vm_stop_error 55 | end 56 | 57 | class VMShutdownError < VagrantProxmoxError 58 | error_key :vm_shutdown_error 59 | end 60 | 61 | class RsyncError < VagrantProxmoxError 62 | error_key :rsync_error 63 | end 64 | 65 | class SSHError < VagrantProxmoxError 66 | error_key :ssh_error 67 | end 68 | 69 | class InvalidNodeError < VagrantProxmoxError 70 | error_key :invalid_node_error 71 | end 72 | 73 | end 74 | end 75 | end 76 | -------------------------------------------------------------------------------- /lib/vagrant-proxmox/plugin.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module Proxmox 3 | class Plugin < Vagrant.plugin ('2') 4 | 5 | name 'Proxmox' 6 | description <<-DESC 7 | This plugin installs a provider that allows Vagrant to manage 8 | machines on Proxmox. 9 | DESC 10 | 11 | config(:proxmox, :provider) do 12 | require_relative 'config' 13 | Config 14 | end 15 | 16 | provider(:proxmox, parallel: true) do 17 | # Setup logging and i18n 18 | setup_logging 19 | setup_i18n 20 | 21 | # Return the provider 22 | require_relative 'provider' 23 | Provider 24 | end 25 | 26 | # This initializes the internationalization strings. 27 | def self.setup_i18n 28 | I18n.load_path << File.expand_path('locales/en.yml', Proxmox.source_root) 29 | I18n.reload! 30 | end 31 | 32 | # This sets up our log level to be whatever VAGRANT_LOG is. 33 | def self.setup_logging 34 | require 'log4r' 35 | 36 | level = nil 37 | begin 38 | level = Log4r.const_get ENV['VAGRANT_LOG'].upcase 39 | rescue NameError 40 | # This means that the logging constant wasn't found, 41 | # which is fine. We just keep `level` as `nil`. But 42 | # we tell the user. 43 | level = nil 44 | end 45 | 46 | # Some constants, such as "true" resolve to booleans, so the 47 | # above error checking doesn't catch it. This will check to make 48 | # sure that the log level is an integer, as Log4r requires. 49 | level = nil if !level.is_a?(Integer) 50 | 51 | # Set the logging level on all "vagrant" namespaced 52 | # logs as long as we have a valid level. 53 | if level 54 | logger = Log4r::Logger.new 'vagrant_proxmox' 55 | logger.outputters = Log4r::Outputter.stderr 56 | logger.level = level 57 | end 58 | end 59 | end 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /lib/vagrant-proxmox/provider.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module Proxmox 3 | 4 | class Provider < Vagrant.plugin('2', :provider) 5 | 6 | def initialize machine 7 | @machine = machine 8 | end 9 | 10 | def action name 11 | # Attempt to get the action method from the Action class if it 12 | # exists, otherwise return nil to show that we don't support the 13 | # given action. 14 | action_method = "action_#{name}" 15 | return Action.send(action_method) if Action.respond_to?(action_method) 16 | nil 17 | end 18 | 19 | def state 20 | # Run a custom action we define called "read_state" which does 21 | # what it says. It puts the state in the `:machine_state_id` 22 | # key in the environment. 23 | env = @machine.action 'read_state' 24 | 25 | state_id = env[:machine_state_id] 26 | 27 | # Get the short and long description 28 | short = I18n.t "vagrant_proxmox.states.short_#{state_id}" 29 | long = I18n.t "vagrant_proxmox.states.long_#{state_id}" 30 | 31 | # Return the MachineState object 32 | Vagrant::MachineState.new state_id, short, long 33 | end 34 | 35 | def ssh_info 36 | # Run a custom action called "read_ssh_info" which does what it 37 | # says and puts the resulting SSH info into the `:machine_ssh_info` 38 | # key in the environment. 39 | env = @machine.action 'read_ssh_info' 40 | env[:machine_ssh_info] 41 | end 42 | 43 | def to_s 44 | id = @machine.id.nil? ? 'new' : @machine.id 45 | "Proxmox (#{id})" 46 | end 47 | 48 | end 49 | 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /lib/vagrant-proxmox/proxmox/connection.rb: -------------------------------------------------------------------------------- 1 | require 'vagrant-proxmox/proxmox/errors' 2 | require 'rest-client' 3 | require 'retryable' 4 | require 'required_parameters' 5 | 6 | # Fix wrong header unescaping in RestClient library. 7 | module RestClient 8 | class Request 9 | def make_headers user_headers 10 | unless @cookies.empty? 11 | user_headers[:cookie] = @cookies.map { |(key, val)| "#{key.to_s}=#{val}" }.sort.join('; ') 12 | end 13 | headers = stringify_headers(default_headers).merge(stringify_headers(user_headers)) 14 | headers.merge!(@payload.headers) if @payload 15 | headers 16 | end 17 | end 18 | end 19 | 20 | module VagrantPlugins 21 | module Proxmox 22 | class Connection 23 | 24 | include RequiredParameters 25 | 26 | attr_reader :api_url 27 | attr_reader :ticket 28 | attr_reader :csrf_token 29 | attr_accessor :vm_id_range 30 | attr_accessor :task_timeout 31 | attr_accessor :task_status_check_interval 32 | attr_accessor :imgcopy_timeout 33 | 34 | def initialize api_url, opts = {} 35 | @api_url = api_url 36 | @vm_id_range = opts[:vm_id_range] || (900..999) 37 | @task_timeout = opts[:task_timeout] || 60 38 | @task_status_check_interval = opts[:task_status_check_interval] || 2 39 | @imgcopy_timeout = opts[:imgcopy_timeout] || 120 40 | end 41 | 42 | def login username: required('username'), password: required('password') 43 | begin 44 | response = post "/access/ticket", username: username, password: password 45 | @ticket = response[:data][:ticket] 46 | @csrf_token = response[:data][:CSRFPreventionToken] 47 | rescue ApiError::ServerError 48 | raise ApiError::InvalidCredentials 49 | rescue => x 50 | raise ApiError::ConnectionError, x.message 51 | end 52 | end 53 | 54 | def get_node_list 55 | nodelist = get '/nodes' 56 | nodelist[:data].map { |n| n[:node] } 57 | end 58 | 59 | def get_vm_state vm_id 60 | vm_info = get_vm_info vm_id 61 | if vm_info 62 | begin 63 | response = get "/nodes/#{vm_info[:node]}/#{vm_info[:type]}/#{vm_id}/status/current" 64 | states = {'running' => :running, 65 | 'stopped' => :stopped} 66 | states[response[:data][:status]] 67 | rescue ApiError::ServerError 68 | :not_created 69 | end 70 | else 71 | :not_created 72 | end 73 | end 74 | 75 | def wait_for_completion task_response: required('task_response'), timeout_message: required('timeout_message') 76 | task_upid = task_response[:data] 77 | timeout = task_timeout 78 | task_type = /UPID:.*?:.*?:.*?:.*?:(.*)?:.*?:.*?:/.match(task_upid)[1] 79 | timeout = imgcopy_timeout if task_type == 'imgcopy' 80 | begin 81 | retryable(on: VagrantPlugins::Proxmox::ProxmoxTaskNotFinished, 82 | tries: timeout / task_status_check_interval + 1, 83 | sleep: task_status_check_interval) do 84 | exit_status = get_task_exitstatus task_upid 85 | exit_status.nil? ? raise(VagrantPlugins::Proxmox::ProxmoxTaskNotFinished) : exit_status 86 | end 87 | rescue VagrantPlugins::Proxmox::ProxmoxTaskNotFinished 88 | raise VagrantPlugins::Proxmox::Errors::Timeout.new timeout_message 89 | end 90 | end 91 | 92 | def delete_vm vm_id 93 | vm_info = get_vm_info vm_id 94 | response = delete "/nodes/#{vm_info[:node]}/#{vm_info[:type]}/#{vm_id}" 95 | wait_for_completion task_response: response, timeout_message: 'vagrant_proxmox.errors.destroy_vm_timeout' 96 | end 97 | 98 | def create_vm node: required('node'), vm_type: required('node'), params: required('params') 99 | response = post "/nodes/#{node}/#{vm_type}", params 100 | wait_for_completion task_response: response, timeout_message: 'vagrant_proxmox.errors.create_vm_timeout' 101 | end 102 | 103 | def clone_vm node: required('node'), vm_type: required('node'), params: required('params') 104 | vm_id = params[:vmid] 105 | params.delete(:vmid) 106 | params.delete(:ostype) 107 | params.delete(:ide2) 108 | params.delete(:sata0) 109 | params.delete(:sockets) 110 | params.delete(:cores) 111 | params.delete(:description) 112 | params.delete(:memory) 113 | params.delete(:net0) 114 | response = post "/nodes/#{node}/#{vm_type}/#{vm_id}/clone", params 115 | wait_for_completion task_response: response, timeout_message: 'vagrant_proxmox.errors.create_vm_timeout' 116 | end 117 | 118 | def config_clone node: required('node'), vm_type: required('node'), params: required('params') 119 | vm_id = params[:vmid] 120 | params.delete(:vmid) 121 | response = post "/nodes/#{node}/#{vm_type}/#{vm_id}/config", params 122 | wait_for_completion task_response: response, timeout_message: 'vagrant_proxmox.errors.create_vm_timeout' 123 | end 124 | 125 | def get_vm_config node: required('node'), vm_id: required('node'), vm_type: required('node') 126 | response = get "/nodes/#{node}/#{vm_type}/#{vm_id}/config" 127 | response = response[:data] 128 | response.empty? ? raise(VagrantPlugins::Proxmox::Errors::VMConfigError) : response 129 | end 130 | 131 | def start_vm vm_id 132 | vm_info = get_vm_info vm_id 133 | response = post "/nodes/#{vm_info[:node]}/#{vm_info[:type]}/#{vm_id}/status/start", nil 134 | wait_for_completion task_response: response, timeout_message: 'vagrant_proxmox.errors.start_vm_timeout' 135 | end 136 | 137 | def stop_vm vm_id 138 | vm_info = get_vm_info vm_id 139 | response = post "/nodes/#{vm_info[:node]}/#{vm_info[:type]}/#{vm_id}/status/stop", nil 140 | wait_for_completion task_response: response, timeout_message: 'vagrant_proxmox.errors.stop_vm_timeout' 141 | end 142 | 143 | def shutdown_vm vm_id 144 | vm_info = get_vm_info vm_id 145 | response = post "/nodes/#{vm_info[:node]}/#{vm_info[:type]}/#{vm_id}/status/shutdown", nil 146 | wait_for_completion task_response: response, timeout_message: 'vagrant_proxmox.errors.shutdown_vm_timeout' 147 | end 148 | 149 | def get_free_vm_id 150 | # to avoid collisions in multi-vm setups 151 | sleep (rand(1..3) + 0.1 * rand(0..9)) 152 | response = get "/cluster/resources?type=vm" 153 | allowed_vm_ids = vm_id_range.to_set 154 | used_vm_ids = response[:data].map { |vm| vm[:vmid] } 155 | free_vm_ids = (allowed_vm_ids - used_vm_ids).sort 156 | free_vm_ids.empty? ? raise(VagrantPlugins::Proxmox::Errors::NoVmIdAvailable) : free_vm_ids.first 157 | end 158 | 159 | def get_qemu_template_id template 160 | response = get "/cluster/resources?type=vm" 161 | found_ids = response[:data].select { |vm| vm[:type] == 'qemu' }.select { |vm| vm[:template] == 1 }.select { |vm| vm[:name] == template }.map { |vm| vm[:vmid] } 162 | found_ids.empty? ? raise(VagrantPlugins::Proxmox::Errors::NoTemplateAvailable) : found_ids.first 163 | end 164 | 165 | def upload_file file, content_type: required('content_type'), node: required('node'), storage: required('storage'), replace: false 166 | delete_file(filename: file, content_type: content_type, node: node, storage: storage) if replace 167 | unless is_file_in_storage? filename: file, node: node, storage: storage 168 | res = post "/nodes/#{node}/storage/#{storage}/upload", content: content_type, 169 | filename: File.new(file, 'rb'), node: node, storage: storage 170 | wait_for_completion task_response: res, timeout_message: 'vagrant_proxmox.errors.upload_timeout' 171 | end 172 | end 173 | 174 | def delete_file filename: required('filename'), content_type: required('content_type'), node: required('node'), storage: required('storage') 175 | delete "/nodes/#{node}/storage/#{storage}/content/#{content_type}/#{File.basename filename}" 176 | end 177 | 178 | def list_storage_files node: required('node'), storage: required('storage') 179 | res = get "/nodes/#{node}/storage/#{storage}/content" 180 | res[:data].map { |e| e[:volid] } 181 | end 182 | 183 | def get_node_ip node, interface 184 | begin 185 | response = get "/nodes/#{node}/network/#{interface}" 186 | response[:data][:address] 187 | rescue ApiError::ServerError 188 | :not_created 189 | end 190 | end 191 | 192 | # This is called every time to retrieve the node and vm_type, hence on large 193 | # installations this could be a huge amount of data. Probably an optimization 194 | # with a buffer for the machine info could be considered. 195 | private 196 | def get_vm_info vm_id 197 | response = get '/cluster/resources?type=vm' 198 | response[:data] 199 | .select { |m| m[:id] =~ /^[a-z]*\/#{vm_id}$/ } 200 | .map { |m| {id: vm_id, type: /^(.*)\/(.*)$/.match(m[:id])[1], node: m[:node]} } 201 | .first 202 | end 203 | 204 | private 205 | def get_task_exitstatus task_upid 206 | node = /UPID:(.*?):/.match(task_upid)[1] 207 | response = get "/nodes/#{node}/tasks/#{task_upid}/status" 208 | response[:data][:exitstatus] 209 | end 210 | 211 | private 212 | def get path 213 | begin 214 | response = RestClient.get "#{api_url}#{path}", {cookies: {PVEAuthCookie: ticket}} 215 | JSON.parse response.to_s, symbolize_names: true 216 | rescue RestClient::NotImplemented 217 | raise ApiError::NotImplemented 218 | rescue RestClient::InternalServerError 219 | raise ApiError::ServerError 220 | rescue RestClient::Unauthorized 221 | raise ApiError::UnauthorizedError 222 | rescue => x 223 | raise ApiError::ConnectionError, x.message 224 | end 225 | end 226 | 227 | private 228 | def delete path, params = {} 229 | begin 230 | response = RestClient.delete "#{api_url}#{path}", headers 231 | JSON.parse response.to_s, symbolize_names: true 232 | rescue RestClient::Unauthorized 233 | raise ApiError::UnauthorizedError 234 | rescue RestClient::NotImplemented 235 | raise ApiError::NotImplemented 236 | rescue RestClient::InternalServerError 237 | raise ApiError::ServerError 238 | rescue => x 239 | raise ApiError::ConnectionError, x.message 240 | end 241 | end 242 | 243 | private 244 | def post path, params = {} 245 | begin 246 | response = RestClient.post "#{api_url}#{path}", params, headers 247 | JSON.parse response.to_s, symbolize_names: true 248 | rescue RestClient::Unauthorized 249 | raise ApiError::UnauthorizedError 250 | rescue RestClient::NotImplemented 251 | raise ApiError::NotImplemented 252 | rescue RestClient::InternalServerError 253 | raise ApiError::ServerError 254 | rescue => x 255 | raise ApiError::ConnectionError, x.message 256 | end 257 | end 258 | 259 | private 260 | def headers 261 | ticket.nil? ? {} : {CSRFPreventionToken: csrf_token, cookies: {PVEAuthCookie: ticket}} 262 | end 263 | 264 | private 265 | def is_file_in_storage? filename: required('filename'), node: required('node'), storage: required('storage') 266 | (list_storage_files node: node, storage: storage).find { |f| f =~ /#{File.basename filename}/ } 267 | end 268 | end 269 | end 270 | end 271 | 272 | -------------------------------------------------------------------------------- /lib/vagrant-proxmox/proxmox/errors.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module Proxmox 3 | 4 | module ApiError 5 | 6 | class InvalidCredentials < StandardError 7 | end 8 | 9 | class ConnectionError < StandardError 10 | end 11 | 12 | class NotImplemented < StandardError 13 | end 14 | 15 | class ServerError < StandardError 16 | end 17 | 18 | class UnauthorizedError < StandardError 19 | end 20 | 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/vagrant-proxmox/version.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module Proxmox 3 | 4 | VERSION = '0.0.10' 5 | 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /local_test.rb: -------------------------------------------------------------------------------- 1 | require 'yaml' 2 | $:<<"./lib/" 3 | require 'rest-client' 4 | # require 'vagrant-proxmox/proxmox/connection' 5 | require 'json' 6 | require 'vagrant-proxmox' 7 | require_relative 'features/support/vagrant_ui_mock.rb' 8 | 9 | 10 | config=YAML.load_file("#{ENV['HOME']}/.rake/rake.yml") 11 | @conn=VagrantPlugins::Proxmox::Connection.new config['proxmox']['endpoint'] 12 | @conn.login username: config['proxmox']['user_name'] , password: config['proxmox']['password'] 13 | 14 | @environment = Vagrant::Environment.new vagrantfile_name: 'Vagrantfile_qemu' 15 | @ui = VagrantUIMock.new 16 | @environment.instance_variable_set :@ui, @ui 17 | 18 | # Vagrant.plugin('2').manager.commands[:up].new(['--provider=proxmox'], @environment).execute -------------------------------------------------------------------------------- /locales/en.yml: -------------------------------------------------------------------------------- 1 | en: 2 | vagrant_proxmox: 3 | already_running: 'The virtual machine is already up and running' 4 | already_stopped: "The virtual machine is already stopped" 5 | creating_vm: 'Creating the virtual machine...' 6 | destroying_vm: 'Destroying the virtual machine...' 7 | done: "Done!" 8 | errors: 9 | create_vm_timeout: |- 10 | Creating the vm on the proxmox server timed out. 11 | communication_error: |- 12 | Unable to communicate with proxmox server: 13 | 14 | %{error_msg} 15 | destroy_vm_timeout: |- 16 | Destroying the vm on the proxmox server timed out. 17 | no_endpoint_specified: 'No endpoint specified.' 18 | no_openvz_os_template_or_openvz_template_file_specified_for_type_openvz: 'No openvz_os_template or openvz_template_file specified for vm_type=:openvz' 19 | no_password_specified: 'No password specified.' 20 | no_user_name_specified: 'No user_name specified.' 21 | no_vm_id_available: |- 22 | Unable to create a new virtual machine on the proxmox server. 23 | None of the configured vm_ids is available. 24 | proxmox_task_failed: |- 25 | A task on the proxmox server failed. 26 | It returned the following exit status: 27 | 28 | %{proxmox_exit_status} 29 | rsync_error: |- 30 | There was an error when attemping to rsync a share folder. 31 | Please inspect the error message below for more info. 32 | 33 | Host path: %{hostpath} 34 | Guest path: %{guestpath} 35 | Error: %{stderr} 36 | shutdown_vm_timeout: |- 37 | Stopping the vm on the proxmox server timed out. 38 | start_vm_timeout: |- 39 | Starting the vm on the proxmox server timed out. 40 | stop_vm_timeout: |- 41 | Stopping the vm on the proxmox server timed out. 42 | timeout: 'Timeout error' 43 | upload_timeout: |- 44 | Uploading a file to the proxmox server timed out. 45 | vm_create_error: |- 46 | Unable to create the virtual machine! 47 | 48 | Cause: %{proxmox_exit_status} 49 | vm_destroy_error: |- 50 | Unable to destroy the virtual machine! 51 | 52 | Cause: %{proxmox_exit_status} 53 | vm_start_error: |- 54 | Unable to start the virtual machine! 55 | 56 | Cause: %{proxmox_exit_status} 57 | vm_stop_error: |- 58 | Unable to stop the virtual machine! 59 | 60 | Cause: %{proxmox_exit_status} 61 | vm_shutdown_error: |- 62 | Unable to shutdown the virtual machine! 63 | 64 | Cause: %{proxmox_exit_status} 65 | vm_not_running: |- 66 | VM must be running to execute this command. Run `vagrant up` 67 | to start the virtual machine. 68 | file_not_found: "File for upload not found" 69 | server_upload_error: "Error during upload" 70 | ssh_error: Unable to establish an ssh connection to the virtual machine... 71 | no_vm_type_specified: "No vm_type specified" 72 | no_qemu_iso_or_qemu_iso_file_specified_for_vm_type_openvz: "No qemu_iso or qemu_iso_file specified for vm_type=:qemu" 73 | no_qemu_iso_or_qemu_iso_file_specified_for_vm_type_qemu: "No qemu_iso or qemu_iso_file specified for vm_type=:qemu" 74 | no_qemu_os_specified_for_vm_type_qemu: "No qemu_os specified for vm_type=:qemu" 75 | no_qemu_disk_size_specified_for_vm_type_qemu: "No qemu_disk_size specified for vm_type=:qemu" 76 | invalid_node_error: 'Invalid node specified: %{node}' 77 | not_created: 'The virtual machine is not created on the server!' 78 | rsync_folder: |- 79 | Rsyncing folder: %{hostpath} => %{guestpath} 80 | shut_down_vm: 'Shutting down the virtual machine...' 81 | starting_vm: 'Starting the virtual machine...' 82 | states: 83 | long_not_created: |- 84 | The server is not created. Run `vagrant up` to create it. 85 | long_running: |- 86 | The server is up and running. Run `vagrant ssh` to access it. 87 | long_stopped: |- 88 | The server is stopped. 89 | short_not_created: |- 90 | not created 91 | short_running: |- 92 | running 93 | short_stopped: |- 94 | stopped 95 | stopping_vm: 'Stopping the virtual machine...' 96 | waiting_for_ssh_connection: 'Waiting for SSH connection...' 97 | -------------------------------------------------------------------------------- /spec/actions/cleanup_after_destroy_action_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'actions/proxmox_action_shared' 3 | 4 | describe VagrantPlugins::Proxmox::Action::CleanupAfterDestroy do 5 | 6 | let(:environment) { Vagrant::Environment.new vagrantfile_name: 'dummy_box/Vagrantfile' } 7 | let(:env) { {machine: environment.machine(environment.primary_machine_name, :proxmox)} } 8 | let(:ui) { double('ui').as_null_object } 9 | 10 | subject(:action) { described_class.new(-> (_) {}, environment) } 11 | 12 | it_behaves_like 'a proxmox action call' 13 | 14 | describe '#call', :need_box do 15 | it 'should delete the directory `.vagrant/[:machine].name`' do 16 | expect do 17 | action.call env 18 | end.to change{File.exists?(".vagrant/machines/#{env[:machine].name}/proxmox")}.to false 19 | end 20 | end 21 | 22 | end 23 | -------------------------------------------------------------------------------- /spec/actions/connect_proxmox_action_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'actions/proxmox_action_shared' 3 | 4 | module VagrantPlugins::Proxmox 5 | 6 | describe Action::ConnectProxmox do 7 | 8 | let(:environment) { Vagrant::Environment.new vagrantfile_name: 'dummy_box/Vagrantfile' } 9 | let(:env) { {machine: environment.machine(environment.primary_machine_name, :proxmox)} } 10 | let(:api_url) { 'http://your.proxmox.machine/api' } 11 | let(:username) { 'user' } 12 | let(:password) { 'password' } 13 | let(:connection) { env[:proxmox_connection] } 14 | 15 | subject(:action) { described_class.new(-> (_) {}, environment) } 16 | 17 | before { VagrantPlugins::Proxmox::Plugin.setup_i18n } 18 | 19 | describe '#call' do 20 | 21 | before do 22 | env[:machine].provider_config.endpoint = api_url 23 | env[:machine].provider_config.user_name = username 24 | env[:machine].provider_config.password = password 25 | env[:machine].provider_config.vm_id_range = 500..555 26 | env[:machine].provider_config.task_timeout = 123 27 | env[:machine].provider_config.task_status_check_interval = 5 28 | env[:machine].provider_config.imgcopy_timeout = 99 29 | allow_any_instance_of(Connection).to receive :login 30 | end 31 | 32 | it_behaves_like 'a proxmox action call' 33 | 34 | it 'should store a connection object in env[:proxmox_connection]' do 35 | action.call env 36 | expect(connection.api_url).to eq(api_url) 37 | end 38 | 39 | describe 'sets the connection configuration parameters' do 40 | before { action.call env } 41 | specify { expect(connection.vm_id_range).to eq(500..555) } 42 | specify { expect(connection.task_timeout).to eq(123) } 43 | specify { expect(connection.task_status_check_interval).to eq(5) } 44 | specify { expect(connection.imgcopy_timeout).to eq(99) } 45 | end 46 | 47 | it 'should call the login function with credentials from configuration' do 48 | expect_any_instance_of(Connection).to receive(:login).with username: username, password: password 49 | action.call env 50 | end 51 | 52 | context 'when the server communication fails' do 53 | 54 | before { allow_any_instance_of(Connection).to receive(:login).and_raise ApiError::InvalidCredentials } 55 | 56 | it 'should raise an error' do 57 | expect { action.call env }.to raise_error Errors::CommunicationError 58 | end 59 | 60 | end 61 | 62 | end 63 | 64 | end 65 | 66 | end 67 | -------------------------------------------------------------------------------- /spec/actions/create_vm_action_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'actions/proxmox_action_shared' 3 | 4 | module VagrantPlugins::Proxmox 5 | 6 | describe Action::CreateVm do 7 | 8 | let(:vagrantfile) { 'dummy_box/Vagrantfile' } 9 | let(:environment) { Vagrant::Environment.new vagrantfile_name: vagrantfile } 10 | let(:connection) { Connection.new 'https://your.proxmox.server/api2/json' } 11 | let(:env) { {machine: environment.machine(environment.primary_machine_name, :proxmox), 12 | proxmox_selected_node: 'localhost', 13 | ui: double('ui').as_null_object, 14 | proxmox_connection: connection} } 15 | let(:app) { double('app').as_null_object } 16 | let(:task_upid) { 'UPID:localhost:0000F6ED:00F8E25F:5268CD3B:vzcreate:100:vagrant@pve:' } 17 | 18 | subject(:action) { described_class.new(-> (_) {}, environment) } 19 | 20 | before do 21 | allow(connection).to receive_messages :get_free_vm_id => 100 22 | allow(connection).to receive_messages :create_vm => 'OK' 23 | end 24 | 25 | describe '#call' do 26 | 27 | it_behaves_like 'a proxmox action call' 28 | 29 | describe 'the call to create a new virtual machine' do 30 | 31 | context 'when the vm_type is :openvz' do 32 | 33 | let(:vagrantfile) { 'dummy_box/Vagrantfile' } 34 | before { allow(env[:machine].provider_config).to receive(:vm_type) { :openvz } } 35 | 36 | context 'with default config' do 37 | specify do 38 | expect(connection).to receive(:create_vm). 39 | with(node: 'localhost', 40 | vm_type: :openvz, 41 | params: {vmid: 100, 42 | hostname: 'machine', 43 | ostemplate: 'local:vztmpl/template.tar.gz', 44 | password: 'vagrant', 45 | memory: 256, 46 | description: 'vagrant_test_machine'}) 47 | action.call env 48 | end 49 | end 50 | 51 | context 'with a specified hostname' do 52 | before { env[:machine].config.vm.hostname = 'hostname' } 53 | specify do 54 | expect(connection).to receive(:create_vm). 55 | with(node: 'localhost', 56 | vm_type: :openvz, 57 | params: {vmid: 100, 58 | hostname: 'hostname', 59 | ostemplate: 'local:vztmpl/template.tar.gz', 60 | password: 'vagrant', 61 | memory: 256, 62 | description: 'vagrant_test_machine'}) 63 | action.call env 64 | end 65 | end 66 | 67 | context 'with a specified ip address' do 68 | before { allow(env[:machine].config.vm).to receive(:networks) { [[:public_network, {ip: '127.0.0.1'}]] } } 69 | specify do 70 | expect(connection).to receive(:create_vm). 71 | with(node: 'localhost', 72 | vm_type: :openvz, 73 | params: {vmid: 100, 74 | hostname: 'machine', 75 | ip_address: '127.0.0.1', 76 | ostemplate: 'local:vztmpl/template.tar.gz', 77 | password: 'vagrant', 78 | memory: 256, 79 | description: 'vagrant_test_machine'}) 80 | action.call env 81 | end 82 | end 83 | end 84 | 85 | context 'when the vm_type is :qemu' do 86 | 87 | let(:vagrantfile) { 'dummy_box/Vagrantfile_qemu' } 88 | before { allow(env[:machine].provider_config).to receive(:vm_type) { :qemu } } 89 | 90 | context 'with default config' do 91 | specify do 92 | expect(connection).to receive(:create_vm). 93 | with(node: 'localhost', 94 | vm_type: :qemu, 95 | params: {vmid: 100, 96 | name: 'machine', 97 | ostype: :l26, 98 | ide2: 'local:iso/isofile.iso,media=cdrom', 99 | sata0: 'raid:30,format=qcow2', 100 | sockets: 1, 101 | cores: 1, 102 | net0: 'e1000,bridge=vmbr0', 103 | memory: 256, 104 | description: 'vagrant_test_machine'}) 105 | action.call env 106 | end 107 | end 108 | 109 | context 'with a specified hostname' do 110 | before { env[:machine].config.vm.hostname = 'hostname' } 111 | specify do 112 | expect(connection).to receive(:create_vm). 113 | with(node: 'localhost', 114 | vm_type: :qemu, 115 | params: {vmid: 100, 116 | name: 'hostname', 117 | ostype: :l26, 118 | ide2: 'local:iso/isofile.iso,media=cdrom', 119 | sata0: 'raid:30,format=qcow2', 120 | sockets: 1, 121 | cores: 1, 122 | net0: 'e1000,bridge=vmbr0', 123 | memory: 256, 124 | description: 'vagrant_test_machine'}) 125 | action.call env 126 | end 127 | end 128 | 129 | context 'with predefined network settings' do 130 | before { allow(env[:machine].config.vm).to receive(:networks) { [[:public_network, {ip: '127.0.0.1', macaddress: 'aa:bb:cc:dd:ee:ff'}]] } } 131 | specify do 132 | expect(connection).to receive(:create_vm). 133 | with(node: 'localhost', 134 | vm_type: :qemu, 135 | params: {vmid: 100, 136 | name: 'machine', 137 | ostype: :l26, 138 | ide2: 'local:iso/isofile.iso,media=cdrom', 139 | sata0: 'raid:30,format=qcow2', 140 | sockets: 1, 141 | cores: 1, 142 | net0: 'e1000=aa:bb:cc:dd:ee:ff,bridge=vmbr0', 143 | memory: 256, 144 | description: 'vagrant_test_machine'}) 145 | action.call env 146 | end 147 | end 148 | end 149 | end 150 | 151 | it 'should print a message to the user interface' do 152 | expect(env[:ui]).to receive(:info).with 'Creating the virtual machine...' 153 | expect(env[:ui]).to receive(:info).with 'Done!' 154 | action.call env 155 | end 156 | 157 | it 'should store the node and vmid in env[:machine].id' do 158 | action.call env 159 | expect(env[:machine].id).to eq('localhost/100') 160 | end 161 | 162 | it 'should get a free vm id from connection' do 163 | expect(connection).to receive(:get_free_vm_id) 164 | action.send :call, env 165 | end 166 | 167 | context 'when the proxmox server responds with an error to the create request' do 168 | 169 | context 'when the proxmox server replies with an internal server error to the delete_vm call' do 170 | it 'should raise a VMCreateError' do 171 | allow(connection).to receive(:create_vm).and_raise ApiError::ServerError 172 | expect { action.send :call, env }.to raise_error Errors::VMCreateError 173 | end 174 | end 175 | 176 | context 'when the proxmox server replies with an internal server error to the task status request' do 177 | it 'should raise a VMCreateError' do 178 | allow(connection).to receive(:create_vm).and_raise ApiError::ServerError 179 | expect { action.send :call, env }.to raise_error Errors::VMCreateError 180 | end 181 | end 182 | 183 | context 'when the proxmox server does not reply the task status request with OK' do 184 | it 'should raise a VMCreateError' do 185 | allow(connection).to receive_messages :create_vm => 'create vm error' 186 | expect { action.send :call, env }.to raise_error Errors::VMCreateError, /create vm error/ 187 | end 188 | end 189 | end 190 | end 191 | end 192 | end 193 | -------------------------------------------------------------------------------- /spec/actions/destroy_vm_action_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'actions/proxmox_action_shared' 3 | 4 | module VagrantPlugins::Proxmox 5 | 6 | describe Action::DestroyVm do 7 | 8 | let(:environment) { Vagrant::Environment.new vagrantfile_name: 'dummy_box/Vagrantfile' } 9 | let(:connection) { Connection.new 'https://proxmox.example.com/api2/json' } 10 | let(:env) { {machine: environment.machine(environment.primary_machine_name, :proxmox), 11 | ui: double('ui').as_null_object, 12 | proxmox_connection: connection} } 13 | 14 | subject(:action) { described_class.new(-> (_) {}, environment) } 15 | 16 | describe '#call' do 17 | 18 | before do 19 | env[:machine].id = 'localhost/100' 20 | allow(connection).to receive_messages :delete_vm => 'OK' 21 | end 22 | 23 | it_behaves_like 'a proxmox action call' 24 | 25 | it 'should call the delete_vm function of connection' do 26 | expect(connection).to receive(:delete_vm).with '100' 27 | action.call env 28 | end 29 | 30 | it 'should print a message to the user interface' do 31 | expect(env[:ui]).to receive(:info).with 'Destroying the virtual machine...' 32 | expect(env[:ui]).to receive(:info).with 'Done!' 33 | action.call env 34 | end 35 | 36 | context 'when the proxmox server responds with an error to the destroy request' do 37 | 38 | context 'when the proxmox server replies with an internal server error to the destroy request' do 39 | it 'should raise a VMDestroyError' do 40 | allow(connection).to receive(:delete_vm).and_raise ApiError::ServerError 41 | expect { action.send :call, env }.to raise_error Errors::VMDestroyError 42 | end 43 | end 44 | 45 | context 'when the proxmox server replies with an internal server error to the task status request' do 46 | it 'should raise a VMDestroyError' do 47 | allow(connection).to receive(:delete_vm).and_raise ApiError::ServerError 48 | expect { action.send :call, env }.to raise_error Errors::VMDestroyError 49 | end 50 | end 51 | 52 | context 'when the proxmox server does not reply the task status request with OK' do 53 | it 'should raise a VMDestroyError' do 54 | allow(connection).to receive_messages :delete_vm => 'destroy vm error' 55 | expect { action.send :call, env }.to raise_error Errors::VMDestroyError, /destroy vm error/ 56 | end 57 | end 58 | 59 | end 60 | 61 | end 62 | 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /spec/actions/get_node_list_action_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'actions/proxmox_action_shared' 3 | 4 | module VagrantPlugins::Proxmox 5 | 6 | describe Action::GetNodeList do 7 | 8 | let(:environment) { Vagrant::Environment.new vagrantfile_name: 'dummy_box/Vagrantfile' } 9 | let(:connection) { Connection.new 'https://proxmox.example.com/api2/json' } 10 | let(:env) { {machine: environment.machine(environment.primary_machine_name, :proxmox), proxmox_connection: connection} } 11 | 12 | subject(:action) { described_class.new(-> (_) {}, environment) } 13 | 14 | describe '#call' do 15 | 16 | describe 'proxmox action call' do 17 | before { allow(connection).to receive_messages get_node_list: [] } 18 | it_behaves_like 'a proxmox action call' 19 | end 20 | 21 | it 'should store the node list in env[:proxmox_nodes]' do 22 | expect(connection).to receive(:get_node_list).and_return ['node1', 'node2'] 23 | action.call env 24 | expect(env[:proxmox_nodes]).to eq(['node1', 'node2']) 25 | end 26 | 27 | context 'when the server communication fails' do 28 | before { allow(connection).to receive(:get_node_list).and_raise ApiError::ConnectionError } 29 | it 'should raise an error' do 30 | expect { action.call env }.to raise_error Errors::CommunicationError 31 | end 32 | end 33 | 34 | end 35 | 36 | end 37 | 38 | end -------------------------------------------------------------------------------- /spec/actions/is_created_action_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'actions/proxmox_action_shared' 3 | 4 | describe VagrantPlugins::Proxmox::Action::IsCreated do 5 | 6 | let(:environment) { Vagrant::Environment.new vagrantfile_name: 'dummy_box/Vagrantfile' } 7 | let(:env) { {machine: environment.machine(environment.primary_machine_name, :proxmox)} } 8 | 9 | subject(:action) { described_class.new(-> (_) {}, environment) } 10 | 11 | describe '#call' do 12 | 13 | before { allow(env[:machine].provider).to receive_messages :state => Vagrant::MachineState.new(nil, nil, nil) } 14 | 15 | it_behaves_like 'a proxmox action call' 16 | 17 | context 'when the machine is stopped' do 18 | before do 19 | allow(env[:machine].provider).to receive_messages :state => Vagrant::MachineState.new(:stopped, '', '') 20 | action.call env 21 | end 22 | specify { expect(env[:result]).to eq(true) } 23 | end 24 | 25 | context 'when the machine is not created' do 26 | before do 27 | allow(env[:machine].provider).to receive_messages :state => Vagrant::MachineState.new(:not_created, '', '') 28 | action.call env 29 | end 30 | specify { expect(env[:result]).to eq(false) } 31 | end 32 | 33 | end 34 | 35 | end 36 | -------------------------------------------------------------------------------- /spec/actions/is_stopped_action_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'actions/proxmox_action_shared' 3 | 4 | describe VagrantPlugins::Proxmox::Action::IsStopped do 5 | 6 | let(:environment) { Vagrant::Environment.new vagrantfile_name: 'dummy_box/Vagrantfile' } 7 | let(:env) { {machine: environment.machine(environment.primary_machine_name, :proxmox)} } 8 | 9 | subject(:action) { described_class.new(-> (_) {}, environment) } 10 | 11 | describe '#call' do 12 | 13 | before { allow(env[:machine].provider).to receive_messages :state => Vagrant::MachineState.new(nil, nil, nil) } 14 | 15 | it_behaves_like 'a proxmox action call' 16 | 17 | context 'when the machine is stopped' do 18 | before do 19 | allow(env[:machine].provider).to receive_messages :state => Vagrant::MachineState.new(:stopped, '', '') 20 | action.call env 21 | end 22 | specify { expect(env[:result]).to eq(true) } 23 | end 24 | 25 | context 'when the machine is running' do 26 | before do 27 | allow(env[:machine].provider).to receive_messages :state => Vagrant::MachineState.new(:running, '', '') 28 | action.call env 29 | end 30 | specify { expect(env[:result]).to eq(false) } 31 | end 32 | end 33 | 34 | end 35 | -------------------------------------------------------------------------------- /spec/actions/message_already_running_action_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'actions/proxmox_action_shared' 3 | 4 | describe VagrantPlugins::Proxmox::Action::MessageAlreadyRunning do 5 | 6 | let(:environment) { Vagrant::Environment.new vagrantfile_name: 'dummy_box/Vagrantfile' } 7 | let(:env) { {ui: double('ui').as_null_object} } 8 | 9 | subject(:action) { described_class.new(-> (_) {}, environment) } 10 | 11 | before { VagrantPlugins::Proxmox::Plugin.setup_i18n } 12 | 13 | describe '#call' do 14 | 15 | it_behaves_like 'a proxmox action call' 16 | 17 | specify do 18 | expect(env[:ui]).to receive(:info).with 'The virtual machine is already up and running' 19 | action.call env 20 | end 21 | end 22 | 23 | end 24 | -------------------------------------------------------------------------------- /spec/actions/message_already_stopped_action_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'actions/proxmox_action_shared' 3 | 4 | describe VagrantPlugins::Proxmox::Action::MessageAlreadyStopped do 5 | 6 | let(:environment) { Vagrant::Environment.new vagrantfile_name: 'dummy_box/Vagrantfile' } 7 | let(:env) { {ui: double('ui').as_null_object} } 8 | 9 | subject(:action) { described_class.new(-> (_) {}, environment) } 10 | 11 | before { VagrantPlugins::Proxmox::Plugin.setup_i18n } 12 | 13 | describe '#call' do 14 | 15 | it_behaves_like 'a proxmox action call' 16 | 17 | specify do 18 | expect(env[:ui]).to receive(:info).with 'The virtual machine is already stopped' 19 | action.call env 20 | end 21 | end 22 | 23 | end 24 | -------------------------------------------------------------------------------- /spec/actions/message_file_not_found_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'actions/proxmox_action_shared' 3 | 4 | describe VagrantPlugins::Proxmox::Action::MessageFileNotFound do 5 | 6 | let(:environment) { Vagrant::Environment.new vagrantfile_name: 'dummy_box/Vagrantfile' } 7 | let(:env) { {ui: double('ui').as_null_object} } 8 | 9 | subject(:action) { described_class.new(-> (_) {}, environment) } 10 | 11 | before { VagrantPlugins::Proxmox::Plugin.setup_i18n } 12 | 13 | describe '#call' do 14 | 15 | it_behaves_like 'a proxmox action call' 16 | 17 | specify do 18 | expect(env[:ui]).to receive(:info).with /File for upload not found/ 19 | action.call env 20 | end 21 | end 22 | 23 | end 24 | -------------------------------------------------------------------------------- /spec/actions/message_not_created_action_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'actions/proxmox_action_shared' 3 | 4 | describe VagrantPlugins::Proxmox::Action::MessageNotCreated do 5 | 6 | let(:environment) { Vagrant::Environment.new vagrantfile_name: 'dummy_box/Vagrantfile' } 7 | let(:env) { {ui: double('ui').as_null_object} } 8 | 9 | subject(:action) { described_class.new(-> (_) {}, environment) } 10 | 11 | before { VagrantPlugins::Proxmox::Plugin.setup_i18n } 12 | 13 | describe '#call' do 14 | 15 | it_behaves_like 'a proxmox action call' 16 | 17 | specify do 18 | expect(env[:ui]).to receive(:info).with 'The virtual machine is not created on the server!' 19 | action.call env 20 | end 21 | end 22 | 23 | end 24 | -------------------------------------------------------------------------------- /spec/actions/message_not_running_action_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'actions/proxmox_action_shared' 3 | 4 | describe VagrantPlugins::Proxmox::Action::MessageNotRunning do 5 | 6 | let(:environment) { Vagrant::Environment.new vagrantfile_name: 'dummy_box/Vagrantfile' } 7 | let(:env) { {ui: double('ui').as_null_object} } 8 | 9 | subject(:action) { described_class.new(-> (_) {}, environment) } 10 | 11 | before { VagrantPlugins::Proxmox::Plugin.setup_i18n } 12 | 13 | describe '#call' do 14 | 15 | it_behaves_like 'a proxmox action call' 16 | 17 | specify do 18 | expect(env[:ui]).to receive(:info).with /VM must be running to execute this command/ 19 | action.call env 20 | end 21 | end 22 | 23 | end 24 | -------------------------------------------------------------------------------- /spec/actions/message_upload_server_error_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'actions/proxmox_action_shared' 3 | 4 | describe VagrantPlugins::Proxmox::Action::MessageUploadServerError do 5 | 6 | let(:environment) { Vagrant::Environment.new vagrantfile_name: 'dummy_box/Vagrantfile' } 7 | let(:env) { {ui: double('ui').as_null_object} } 8 | 9 | subject(:action) { described_class.new(-> (_) {}, environment) } 10 | 11 | before { VagrantPlugins::Proxmox::Plugin.setup_i18n } 12 | 13 | describe '#call' do 14 | 15 | it_behaves_like 'a proxmox action call' 16 | 17 | specify do 18 | expect(env[:ui]).to receive(:info).with /Error during upload/ 19 | action.call env 20 | end 21 | end 22 | 23 | end 24 | -------------------------------------------------------------------------------- /spec/actions/proxmox_action_shared.rb: -------------------------------------------------------------------------------- 1 | shared_examples 'a proxmox action call' do 2 | 3 | describe 'when done' do 4 | it 'should call the next action' do 5 | expect(subject).to receive(:next_action).with env 6 | subject.call env 7 | end 8 | end 9 | 10 | end 11 | -------------------------------------------------------------------------------- /spec/actions/proxmox_action_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | module VagrantPlugins::Proxmox 4 | 5 | describe Action::ProxmoxAction do 6 | 7 | let(:environment) { Vagrant::Environment.new vagrantfile_name: 'dummy_box/Vagrantfile' } 8 | let(:connection) { Connection.new 'https://proxmox.example.com/api2/json' } 9 | let(:env) { {machine: environment.machine(environment.primary_machine_name, :proxmox), 10 | proxmox_connection: connection} } 11 | 12 | let (:action) { subject } 13 | 14 | describe '#next_action' do 15 | 16 | let(:app) { double('app') } 17 | before { action.instance_variable_set(:@app, app) } 18 | 19 | it 'should call @app' do 20 | expect(app).to receive(:call) 21 | action.send(:next_action, env) 22 | end 23 | end 24 | 25 | describe '#get_machine_ip_address' do 26 | 27 | before do 28 | allow(env[:machine].config.vm).to receive_messages networks: [[:private_network, {ip: '4.3.2.1'}], [:public_network, {ip: '1.2.3.4'}]] 29 | end 30 | 31 | it 'should return the first public ip address from the configuration' do 32 | expect(action.send(:get_machine_ip_address, env)).to eq('1.2.3.4') 33 | end 34 | 35 | context 'no network configuration exists' do 36 | 37 | before do 38 | allow(env[:machine].config.vm).to receive_messages networks: nil 39 | end 40 | 41 | it 'should return nil' do 42 | expect(action.send(:get_machine_ip_address, env)).to be_nil 43 | end 44 | 45 | end 46 | 47 | end 48 | 49 | describe '#connection' do 50 | it 'should retrieve the connection from the environment' do 51 | expect(action.send(:connection, env)).to eq(connection) 52 | end 53 | end 54 | 55 | end 56 | 57 | end -------------------------------------------------------------------------------- /spec/actions/read_ssh_info_action_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'actions/proxmox_action_shared' 3 | 4 | describe VagrantPlugins::Proxmox::Action::ReadSSHInfo do 5 | 6 | let(:environment) { Vagrant::Environment.new vagrantfile_name: 'dummy_box/Vagrantfile' } 7 | let(:env) { {machine: environment.machine(environment.primary_machine_name, :proxmox)} } 8 | 9 | subject(:action) { described_class.new(-> (_) {}, environment) } 10 | 11 | describe '#call' do 12 | 13 | it_behaves_like 'a proxmox action call' 14 | 15 | context 'when no ip address is configured' do 16 | it 'should write no ssh info into env[:machine_ssh_info]' do 17 | action.call env 18 | expect(env[:machine_ssh_info]).to eq(nil) 19 | end 20 | end 21 | 22 | context 'when an ip address is configured' do 23 | before { allow(env[:machine].config.vm).to receive(:networks) { [[:public_network, {ip: '127.0.0.1'}]] } } 24 | it 'should write the ssh info into env[:machine_ssh_info]' do 25 | action.call env 26 | expect(env[:machine_ssh_info]).to eq({host: '127.0.0.1', port: 22}) 27 | end 28 | end 29 | 30 | end 31 | 32 | end 33 | -------------------------------------------------------------------------------- /spec/actions/read_state_action_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'actions/proxmox_action_shared' 3 | 4 | module VagrantPlugins::Proxmox 5 | 6 | describe VagrantPlugins::Proxmox::Action::ReadState do 7 | 8 | let(:environment) { Vagrant::Environment.new vagrantfile_name: 'dummy_box/Vagrantfile' } 9 | let(:connection) { Connection.new 'https://proxmox.example.com/api2/json' } 10 | let(:env) { {machine: environment.machine(environment.primary_machine_name, :proxmox), proxmox_connection: connection} } 11 | let(:node) { 'localhost' } 12 | let(:vm_id) { '100' } 13 | let(:machine_id) { "#{node}/#{vm_id}" } 14 | 15 | subject(:action) { described_class.new(-> (_) {}, environment) } 16 | 17 | before do 18 | allow(env[:machine]).to receive(:id) { machine_id } 19 | allow(connection).to receive :get_vm_state 20 | end 21 | 22 | describe '#call' do 23 | 24 | it_behaves_like 'a proxmox action call' 25 | 26 | it 'should store the machine state in env[:machine_state_id]' do 27 | expect(connection).to receive(:get_vm_state).and_return :running 28 | action.call env 29 | expect(env[:machine_state_id]).to eq(:running) 30 | end 31 | 32 | it 'should call get_vm_state with the node and vm_id' do 33 | expect(connection).to receive(:get_vm_state).with vm_id 34 | action.call env 35 | end 36 | 37 | context 'when no machine id is defined' do 38 | let(:machine_id) { nil } 39 | it 'should [:machine_state_id] to :not_created' do 40 | action.call env 41 | expect(env[:machine_state_id]).to eq(:not_created) 42 | end 43 | end 44 | 45 | context 'when the server communication fails' do 46 | before { allow(connection).to receive(:get_vm_state).and_raise ApiError::ConnectionError } 47 | it 'should raise an error' do 48 | expect { action.call env }.to raise_error Errors::CommunicationError 49 | end 50 | end 51 | 52 | end 53 | 54 | end 55 | 56 | end 57 | -------------------------------------------------------------------------------- /spec/actions/select_node_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'actions/proxmox_action_shared' 3 | 4 | module VagrantPlugins::Proxmox 5 | 6 | describe VagrantPlugins::Proxmox::Action::SelectNode do 7 | 8 | let(:environment) { Vagrant::Environment.new vagrantfile_name: 'dummy_box/Vagrantfile' } 9 | let(:connection) { Connection.new 'https://proxmox.example.com/api2/json' } 10 | let(:env) { {machine: environment.machine(environment.primary_machine_name, :proxmox), 11 | proxmox_connection: connection, proxmox_nodes: nodes} } 12 | let(:nodes) { ['node1', 'node2', 'selected_node'] } 13 | 14 | subject(:action) { described_class.new(-> (_) {}, environment) } 15 | 16 | describe '#call' do 17 | 18 | it_behaves_like 'a proxmox action call' 19 | 20 | context "when no selected_node is specified in the configuration" do 21 | it 'randomly selects a node from the list of available nodes' do 22 | expect(nodes).to receive(:sample).and_return 'node2' 23 | action.call env 24 | expect(env[:proxmox_selected_node]).to eq('node2') 25 | end 26 | end 27 | 28 | context "when a specific node is specified in the configuration" do 29 | 30 | context "when this node is included in the nodes list" do 31 | before do 32 | env[:machine].provider_config.selected_node = 'selected_node' 33 | end 34 | 35 | it 'selects the selected_node' do 36 | expect(nodes).not_to receive(:sample) 37 | action.call env 38 | expect(env[:proxmox_selected_node]).to eq('selected_node') 39 | end 40 | end 41 | 42 | context "when this node is not included in the nodes list" do 43 | before do 44 | env[:machine].provider_config.selected_node = 'invalid_node' 45 | end 46 | 47 | it 'should raise an error' do 48 | expect { action.call env }.to raise_error Errors::InvalidNodeError 49 | end 50 | end 51 | end 52 | 53 | end 54 | 55 | end 56 | 57 | end 58 | -------------------------------------------------------------------------------- /spec/actions/shutdown_vm_action_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'actions/proxmox_action_shared' 3 | 4 | module VagrantPlugins::Proxmox 5 | 6 | describe Action::ShutdownVm do 7 | 8 | let(:environment) { Vagrant::Environment.new vagrantfile_name: 'dummy_box/Vagrantfile' } 9 | let(:connection) { Connection.new 'https://proxmox.example.com/api2/json' } 10 | let(:env) { {machine: environment.machine(environment.primary_machine_name, :proxmox), 11 | ui: double('ui').as_null_object, 12 | proxmox_connection: connection} } 13 | 14 | subject(:action) { described_class.new(-> (_) {}, environment) } 15 | 16 | describe '#call' do 17 | 18 | before do 19 | env[:machine].id = 'localhost/100' 20 | allow(connection).to receive_messages :shutdown_vm => 'OK' 21 | end 22 | 23 | it_behaves_like 'a proxmox action call' 24 | 25 | it 'should call the shutdown_vm function of connection' do 26 | expect(connection).to receive(:shutdown_vm).with '100' 27 | action.call env 28 | end 29 | 30 | it 'should print a message to the user interface' do 31 | expect(env[:ui]).to receive(:info).with 'Shutting down the virtual machine...' 32 | expect(env[:ui]).to receive(:info).with 'Done!' 33 | action.call env 34 | end 35 | 36 | context 'when the proxmox server responds with an error to the shutdown request' do 37 | 38 | context 'when the proxmox server replies with an internal server error to the shutdown request' do 39 | it 'should raise a VMShutdownError' do 40 | allow(connection).to receive(:shutdown_vm).and_raise ApiError::ServerError 41 | expect { action.send :call, env }.to raise_error Errors::VMShutdownError 42 | end 43 | end 44 | 45 | context 'when the proxmox server replies with an internal server error to the task status request' do 46 | it 'should raise a VMShutdownError' do 47 | allow(connection).to receive(:shutdown_vm).and_raise ApiError::ServerError 48 | expect { action.send :call, env }.to raise_error Errors::VMShutdownError 49 | end 50 | end 51 | 52 | context 'when the proxmox server does not reply the task status request with OK' do 53 | it 'should raise a VMShutdownError' do 54 | allow(connection).to receive_messages :shutdown_vm => 'shutdown vm error' 55 | expect { action.send :call, env }.to raise_error Errors::VMShutdownError, /shutdown vm error/ 56 | end 57 | end 58 | 59 | end 60 | 61 | end 62 | 63 | end 64 | 65 | end 66 | -------------------------------------------------------------------------------- /spec/actions/start_vm_action_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'actions/proxmox_action_shared' 3 | 4 | module VagrantPlugins::Proxmox 5 | 6 | describe Action::StartVm do 7 | 8 | let(:environment) { Vagrant::Environment.new vagrantfile_name: 'dummy_box/Vagrantfile' } 9 | let(:connection) { Connection.new 'https://proxmox.example.com/api2/json' } 10 | let(:env) { {machine: environment.machine(environment.primary_machine_name, :proxmox), 11 | ui: double('ui').as_null_object, 12 | proxmox_connection: connection} } 13 | let(:ssh_reachable) { true } 14 | let(:ssh_timeout) { 60 } 15 | let(:ssh_status_check_interval) { 5 } 16 | 17 | subject(:action) { described_class.new(-> (_) {}, environment) } 18 | 19 | describe '#call' do 20 | 21 | before do 22 | env[:machine].id = 'localhost/100' 23 | allow(connection).to receive_messages :start_vm => 'OK' 24 | allow(env[:machine].communicate).to receive_messages :ready? => ssh_reachable 25 | end 26 | 27 | it_behaves_like 'a proxmox action call' 28 | 29 | it 'should call the start_vm function of connection' do 30 | expect(connection).to receive(:start_vm).with '100' 31 | action.call env 32 | end 33 | 34 | it 'should print a message to the user interface' do 35 | expect(env[:ui]).to receive(:info).with 'Starting the virtual machine...' 36 | expect(env[:ui]).to receive(:info).with 'Done!' 37 | expect(env[:ui]).to receive(:info).with 'Waiting for SSH connection...' 38 | expect(env[:ui]).to receive(:info).with 'Done!' 39 | action.call env 40 | end 41 | 42 | it 'should periodically call env[:machine].communicate.ready? to check for ssh access' do 43 | expect(env[:machine].communicate).to receive(:ready?).and_return false 44 | expect(subject).to receive(:sleep).with ssh_status_check_interval 45 | expect(env[:machine].communicate).to receive(:ready?).and_return true 46 | action.call env 47 | end 48 | 49 | context 'when the proxmox server responds with an error to the start request' do 50 | 51 | context 'when the proxmox server replies with an internal server error to the start request' do 52 | it 'should raise a VMStartError' do 53 | allow(connection).to receive(:start_vm).and_raise ApiError::ServerError 54 | expect { action.send :call, env }.to raise_error Errors::VMStartError 55 | end 56 | end 57 | 58 | context 'when the proxmox server replies with an internal server error to the task status request' do 59 | it 'should raise a VMStartError' do 60 | allow(connection).to receive(:start_vm).and_raise ApiError::ServerError 61 | expect { action.send :call, env }.to raise_error Errors::VMStartError 62 | end 63 | end 64 | 65 | context 'when the proxmox server does not reply the task status request with OK' do 66 | it 'should raise a VMStartError' do 67 | allow(connection).to receive_messages :start_vm => 'start vm error' 68 | expect { action.send :call, env }.to raise_error Errors::VMStartError, /start vm error/ 69 | end 70 | end 71 | 72 | end 73 | 74 | context 'when no ssh connection can be established after startup' do 75 | 76 | let(:ssh_reachable) { false } 77 | 78 | before do 79 | allow(action).to receive(:sleep) { |duration| Timecop.travel(Time.now + duration) } 80 | Timecop.freeze 81 | end 82 | 83 | after do 84 | Timecop.return 85 | end 86 | 87 | it 'should wait the default timeout' do 88 | begin 89 | action.call env 90 | rescue Errors::SSHError 91 | end 92 | expect(Time).to have_elapsed ssh_timeout.seconds 93 | end 94 | 95 | it 'should raise an ssh error' do 96 | expect { action.send :call, env }.to raise_error Errors::SSHError, /Unable to establish an ssh connection/ 97 | end 98 | end 99 | end 100 | end 101 | end 102 | -------------------------------------------------------------------------------- /spec/actions/stop_vm_action_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'actions/proxmox_action_shared' 3 | 4 | module VagrantPlugins::Proxmox 5 | 6 | describe Action::StopVm do 7 | 8 | let(:environment) { Vagrant::Environment.new vagrantfile_name: 'dummy_box/Vagrantfile' } 9 | let(:connection) { Connection.new 'https://proxmox.example.com/api2/json' } 10 | let(:env) { {machine: environment.machine(environment.primary_machine_name, :proxmox), 11 | ui: double('ui').as_null_object, proxmox_connection: connection} } 12 | 13 | subject(:action) { described_class.new(-> (_) {}, environment) } 14 | 15 | describe '#call' do 16 | 17 | before do 18 | env[:machine].id = 'localhost/100' 19 | allow(connection).to receive_messages :stop_vm => 'OK' 20 | end 21 | 22 | it_behaves_like 'a proxmox action call' 23 | 24 | it 'should call the stop_vm function of connection' do 25 | expect(connection).to receive(:stop_vm).with '100' 26 | action.call env 27 | end 28 | 29 | it 'should print a message to the user interface' do 30 | expect(env[:ui]).to receive(:info).with 'Stopping the virtual machine...' 31 | expect(env[:ui]).to receive(:info).with 'Done!' 32 | action.call env 33 | end 34 | 35 | context 'when the proxmox server responds with an error to the stop request' do 36 | 37 | context 'when the proxmox server replies with an internal server error to the stop request' do 38 | it 'should raise a VMStopError' do 39 | allow(connection).to receive(:stop_vm).and_raise ApiError::ServerError 40 | expect { action.send :call, env }.to raise_error Errors::VMStopError 41 | end 42 | end 43 | 44 | context 'when the proxmox server replies with an internal server error to the task status request' do 45 | it 'should raise a VMStopError' do 46 | allow(connection).to receive(:stop_vm).and_raise ApiError::ServerError 47 | expect { action.send :call, env }.to raise_error Errors::VMStopError 48 | end 49 | end 50 | 51 | context 'when the proxmox server does not reply the task status request with OK' do 52 | it 'should raise a VMStopError' do 53 | allow(connection).to receive_messages :stop_vm => 'stop vm error' 54 | expect { action.send :call, env }.to raise_error Errors::VMStopError, /stop vm error/ 55 | end 56 | end 57 | 58 | end 59 | 60 | end 61 | 62 | end 63 | 64 | end -------------------------------------------------------------------------------- /spec/actions/sync_folders_action_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'actions/proxmox_action_shared' 3 | 4 | module VagrantPlugins::Proxmox 5 | 6 | describe Action::SyncFolders do 7 | 8 | let(:environment) { Vagrant::Environment.new vagrantfile_name: 'dummy_box/Vagrantfile' } 9 | let(:env) { {machine: environment.machine(environment.primary_machine_name, :proxmox), ui: double('ui').as_null_object} } 10 | 11 | subject(:action) { described_class.new(-> (_) {}, environment) } 12 | 13 | describe '#call' do 14 | 15 | before do 16 | allow(env[:machine]).to receive(:ssh_info) { {host: '127.0.0.1', port: 22, username: 'vagrant', private_key_path: ['key']} } 17 | allow(env[:machine].communicate).to receive :sudo 18 | allow(Vagrant::Util::Subprocess).to receive_messages :execute => Vagrant::Util::Subprocess::Result.new(0, nil, nil) 19 | end 20 | 21 | it_behaves_like 'a proxmox action call' 22 | 23 | it 'should print a message to the user interface' do 24 | expect(env[:ui]).to receive(:info).with("Rsyncing folder: #{Dir.pwd}/ => /vagrant") 25 | action.call env 26 | end 27 | 28 | it 'should create a directory on the vm with the predefined ownership' do 29 | expect(env[:machine].communicate).to receive(:sudo).with("mkdir -p '/vagrant'") 30 | expect(env[:machine].communicate).to receive(:sudo).with("chown vagrant '/vagrant'") 31 | action.call env 32 | end 33 | 34 | it 'should rsync the directory with the vm' do 35 | expect(Vagrant::Util::Subprocess).to receive(:execute).with( 36 | 'rsync', '--verbose', '--archive', '--compress', '--delete', 37 | '-e', "ssh -p 22 -i 'key' -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null", 38 | "#{Dir.pwd}/", 'vagrant@127.0.0.1:/vagrant') 39 | action.call env 40 | end 41 | 42 | it 'should raise an error if the rsync fails' do 43 | allow(Vagrant::Util::Subprocess).to receive_messages :execute => Vagrant::Util::Subprocess::Result.new(1, nil, nil) 44 | expect { action.call(env) }.to raise_error Errors::RsyncError 45 | end 46 | 47 | end 48 | 49 | end 50 | 51 | end 52 | -------------------------------------------------------------------------------- /spec/actions/upload_iso_file_action_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'actions/proxmox_action_shared' 3 | 4 | module VagrantPlugins::Proxmox 5 | 6 | describe Action::UploadIsoFile do 7 | 8 | let(:environment) { Vagrant::Environment.new vagrantfile_name: 'dummy_box/Vagrantfile_qemu' } 9 | let(:connection) { Connection.new 'https://your.proxmox.server/api2/json' } 10 | let(:env) { {machine: environment.machine(environment.primary_machine_name, :proxmox), 11 | proxmox_connection: connection, proxmox_selected_node: node} } 12 | let(:iso_file) { 'some_iso.iso' } 13 | let(:iso_file_exists) { true } 14 | let(:replace_iso_file) { false } 15 | let(:node) { 'node1' } 16 | 17 | subject(:action) { described_class.new(-> (_) {}, environment) } 18 | 19 | before do 20 | env[:machine].provider_config.qemu_iso_file = iso_file 21 | env[:machine].provider_config.replace_qemu_iso_file = replace_iso_file 22 | allow(File).to receive(:exist?).with(iso_file).and_return(iso_file_exists) 23 | end 24 | 25 | context 'with a specified iso file' do 26 | 27 | it 'should upload the iso file into the local storage of the selected node' do 28 | expect(connection).to receive(:upload_file).with(iso_file, content_type: 'iso', node: node, storage: 'local', replace: replace_iso_file) 29 | action.call env 30 | end 31 | end 32 | 33 | it 'should return :ok after a successful upload' do 34 | allow(connection).to receive(:upload_file).with(iso_file, content_type: 'iso', node: node, storage: 'local', replace: replace_iso_file) 35 | action.call env 36 | expect(env[:result]).to eq(:ok) 37 | end 38 | 39 | context 'The iso file should be replaced' do 40 | 41 | let(:replace_iso_file) { true } 42 | 43 | it 'should delete the iso file on the server' do 44 | expect(connection).to receive(:delete_file).with(filename: iso_file, content_type: 'iso', node: node, storage: 'local') 45 | action.call env 46 | end 47 | end 48 | 49 | context 'when a server error occurs' do 50 | 51 | before do 52 | allow(connection).to receive(:upload_file).and_raise ApiError::ServerError 53 | end 54 | 55 | it 'should return :server_error' do 56 | action.call env 57 | expect(env[:result]).to eq(:server_upload_error) 58 | end 59 | end 60 | 61 | context 'without a specified iso file' do 62 | 63 | let(:iso_file) { nil } 64 | 65 | it 'does nothing and returns OK' do 66 | expect(connection).not_to receive(:upload_file) 67 | action.call env 68 | expect(env[:result]).to eq(:ok) 69 | end 70 | end 71 | 72 | context 'the specified iso file does not exist' do 73 | 74 | let (:iso_file_exists) { false } 75 | 76 | it 'should return :file_not_found' do 77 | action.call env 78 | expect(env[:result]).to eq(:file_not_found) 79 | end 80 | end 81 | end 82 | end 83 | -------------------------------------------------------------------------------- /spec/actions/upload_template_file_action_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'actions/proxmox_action_shared' 3 | 4 | module VagrantPlugins::Proxmox 5 | 6 | describe Action::UploadTemplateFile do 7 | 8 | let(:environment) { Vagrant::Environment.new vagrantfile_name: 'dummy_box/Vagrantfile' } 9 | let(:connection) { Connection.new 'https://your.proxmox.server/api2/json' } 10 | let(:env) { {machine: environment.machine(environment.primary_machine_name, :proxmox), 11 | proxmox_connection: connection, proxmox_selected_node: node} } 12 | let(:template_file) { 'template.tar.gz' } 13 | let(:template_file_exists) { true } 14 | let(:replace_template_file) { false } 15 | let(:node) { 'node1' } 16 | 17 | subject(:action) { described_class.new(-> (_) {}, environment) } 18 | 19 | before do 20 | env[:machine].provider_config.openvz_template_file = template_file 21 | env[:machine].provider_config.replace_openvz_template_file = replace_template_file 22 | allow(File).to receive(:exist?).with(template_file).and_return(template_file_exists) 23 | end 24 | 25 | context 'with a specified template file' do 26 | 27 | it 'should upload the template file into the local storage of the selected node' do 28 | expect(connection).to receive(:upload_file).with(template_file, content_type: 'vztmpl', node: node, storage: 'local', replace: replace_template_file) 29 | action.call env 30 | end 31 | end 32 | 33 | it 'should return :ok after a successful upload' do 34 | allow(connection).to receive(:upload_file).with(template_file, content_type: 'vztmpl', node: node, storage: 'local', replace: replace_template_file) 35 | action.call env 36 | expect(env[:result]).to eq(:ok) 37 | end 38 | 39 | context 'the template file should be replaced' do 40 | 41 | let(:replace_template_file) { true } 42 | 43 | it 'should delete the template file on the server' do 44 | expect(connection).to receive(:delete_file).with(filename: template_file, content_type: 'vztmpl', node: node, storage: 'local') 45 | action.call env 46 | end 47 | end 48 | 49 | context 'when a server error occurs' do 50 | 51 | before do 52 | allow(connection).to receive(:upload_file).and_raise ApiError::ServerError 53 | end 54 | 55 | it 'should return :server_error' do 56 | action.call env 57 | expect(env[:result]).to eq(:server_upload_error) 58 | end 59 | end 60 | 61 | context 'without a specified template file' do 62 | 63 | let(:template_file) { nil } 64 | 65 | it 'does nothing and returns OK' do 66 | expect(connection).not_to receive(:upload_file) 67 | action.call env 68 | expect(env[:result]).to eq(:ok) 69 | end 70 | end 71 | 72 | context 'the specified template file does not exist' do 73 | 74 | let (:template_file_exists) { false } 75 | 76 | it 'should return :file_not_found' do 77 | action.call env 78 | expect(env[:result]).to eq(:file_not_found) 79 | end 80 | end 81 | end 82 | end 83 | -------------------------------------------------------------------------------- /spec/commands/destroy_command_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | module VagrantPlugins::Proxmox 4 | 5 | describe 'Vagrant Proxmox provider destroy command', :need_box do 6 | 7 | let(:environment) { Vagrant::Environment.new vagrantfile_name: 'dummy_box/Vagrantfile' } 8 | let(:env) { {machine: environment.machine(environment.primary_machine_name, :proxmox)} } 9 | let(:ui) { double('ui').as_null_object } 10 | 11 | context 'the vm is not created on the proxmox server' do 12 | it 'should call the appropriate actions to print a ui message that the vm is not created' do 13 | expect(Action::ConnectProxmox).to be_called 14 | expect(Action::IsCreated).to be_called { |env| env[:result] = false } 15 | expect(Action::MessageNotCreated).to be_called 16 | expect(Action::DestroyVm).to be_omitted 17 | execute_vagrant_command environment, :destroy 18 | end 19 | end 20 | 21 | context 'the vm exists on the proxmox server' do 22 | 23 | context 'the destroy command is not confirmed' do 24 | it 'should call the appropriate actions to print a ui message that the vm will not be destroyed' do 25 | expect(Action::ConnectProxmox).to be_called 26 | expect(Action::IsCreated).to be_called { |env| env[:result] = true } 27 | expect(::Vagrant::Action::Builtin::DestroyConfirm).to be_called { |env| env[:result] = false } 28 | expect(::VagrantPlugins::ProviderVirtualBox::Action::MessageWillNotDestroy).to be_called 29 | expect(Action::DestroyVm).to be_omitted 30 | execute_vagrant_command environment, :destroy 31 | end 32 | end 33 | 34 | context 'the destroy command is confirmed' do 35 | context 'the vm is running' do 36 | it 'should call the appropriate actions to destroy the vm' do 37 | expect(Action::ConnectProxmox).to be_called 38 | expect(Action::IsCreated).to be_called { |env| env[:result] = true } 39 | expect(::Vagrant::Action::Builtin::DestroyConfirm).to be_called { |env| env[:result] = true } 40 | expect(Action::IsStopped).to be_called { |env| env[:result] = false } 41 | expect(Action::ShutdownVm).to be_called 42 | expect(Action::DestroyVm).to be_called 43 | expect(::Vagrant::Action::Builtin::ProvisionerCleanup).to be_called 44 | expect(Action::CleanupAfterDestroy).to be_called 45 | execute_vagrant_command environment, :destroy 46 | end 47 | end 48 | 49 | context 'the vm is stopped' do 50 | it 'should call the appropriate actions to destroy the vm' do 51 | expect(Action::ConnectProxmox).to be_called 52 | expect(Action::IsCreated).to be_called { |env| env[:result] = true } 53 | expect(Action::DestroyConfirm).to be_called { |env| env[:result] = true } 54 | expect(Action::IsStopped).to be_called { |env| env[:result] = true } 55 | expect(Action::DestroyVm).to be_called 56 | expect(::Vagrant::Action::Builtin::ProvisionerCleanup).to be_called 57 | expect(Action::CleanupAfterDestroy).to be_called 58 | expect(Action::ShutdownVm).to be_omitted 59 | execute_vagrant_command environment, :destroy 60 | end 61 | end 62 | end 63 | 64 | end 65 | 66 | end 67 | 68 | end 69 | -------------------------------------------------------------------------------- /spec/commands/halt_command_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | module VagrantPlugins::Proxmox 4 | 5 | describe 'Vagrant Proxmox provider halt command', :need_box do 6 | 7 | let(:environment) { Vagrant::Environment.new vagrantfile_name: 'dummy_box/Vagrantfile' } 8 | let(:ui) { double('ui').as_null_object } 9 | 10 | context 'the vm is not created on the proxmox server' do 11 | it 'should call the appropriate actions and print a ui message that the vm is not created' do 12 | expect(Action::ConfigValidate).to be_called 13 | expect(Action::IsCreated).to be_called { |env| env[:result] = false } 14 | expect(Action::MessageNotCreated).to be_called 15 | expect(Action::ConnectProxmox).to be_omitted 16 | expect(Action::ShutdownVm).to be_omitted 17 | execute_vagrant_command environment, :halt 18 | end 19 | end 20 | 21 | context 'the vm is stopped' do 22 | it 'should not shut down the vm and print a vm message that the vm is already stopped' do 23 | expect(Action::ConfigValidate).to be_called 24 | expect(Action::IsCreated).to be_called { |env| env[:result] = true } 25 | expect(Action::IsStopped).to be_called { |env| env[:result] = true } 26 | expect(Action::MessageAlreadyStopped).to be_called 27 | expect(Action::ConnectProxmox).to be_omitted 28 | expect(Action::ShutdownVm).to be_omitted 29 | execute_vagrant_command environment, :halt 30 | end 31 | end 32 | 33 | context 'the vm is running' do 34 | it 'should call the appropriate actions to shut down the vm' do 35 | expect(Action::ConfigValidate).to be_called 36 | expect(Action::IsCreated).to be_called { |env| env[:result] = true } 37 | expect(Action::IsStopped).to be_called { |env| env[:result] = false } 38 | expect(Action::ConnectProxmox).to be_called 39 | expect(Action::ShutdownVm).to be_called 40 | execute_vagrant_command environment, :halt 41 | end 42 | end 43 | 44 | end 45 | 46 | end 47 | -------------------------------------------------------------------------------- /spec/commands/provision_command_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | module VagrantPlugins::Proxmox 4 | 5 | describe 'Vagrant Proxmox provider provision command', :need_box do 6 | 7 | let(:environment) { Vagrant::Environment.new vagrantfile_name: 'dummy_box/Vagrantfile' } 8 | let(:ui) { double('ui').as_null_object } 9 | 10 | context 'the vm is not created on the proxmox server' do 11 | it 'should call the appropriate actions and print a ui message that the vm is not created' do 12 | expect(Action::ConfigValidate).to be_called 13 | expect(Action::IsCreated).to be_called { |env| env[:result] = false } 14 | expect(Action::MessageNotCreated).to be_called 15 | expect(Action::Provision).to be_omitted 16 | expect(Action::SyncFolders).to be_omitted 17 | execute_vagrant_command environment, :provision 18 | end 19 | end 20 | 21 | context 'the vm exists on the proxmox server' do 22 | it 'should call the appropriate actions and provision the vm' do 23 | expect(Action::ConfigValidate).to be_called 24 | expect(Action::IsCreated).to be_called { |env| env[:result] = true } 25 | expect(Action::IsStopped).to be_called { |env| env[:result] = false } 26 | expect(Action::Provision).to be_called 27 | expect(Action::SyncFolders).to be_called 28 | execute_vagrant_command environment, :provision 29 | end 30 | end 31 | 32 | context 'the vm exists on the proxmox server but is not running' do 33 | 34 | it 'should state that the machine is not currently running' do 35 | expect(Action::IsCreated).to be_called { |env| env[:result] = true } 36 | expect(Action::IsStopped).to be_called { |env| env[:result] = true } 37 | expect(Action::MessageNotRunning).to be_called 38 | expect(Action::Provision).to be_omitted 39 | execute_vagrant_command environment, :provision 40 | end 41 | 42 | end 43 | 44 | end 45 | 46 | end 47 | -------------------------------------------------------------------------------- /spec/commands/ssh_command_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | module VagrantPlugins::Proxmox 4 | 5 | describe 'Vagrant Proxmox provider ssh command', :need_box do 6 | 7 | let(:environment) { Vagrant::Environment.new vagrantfile_name: 'dummy_box/Vagrantfile' } 8 | let(:ui) { double('ui').as_null_object } 9 | 10 | context 'the vm is not created on the proxmox server' do 11 | it 'should call the appropriate actions and print a ui message that the vm is not created' do 12 | expect(Action::IsCreated).to be_called { |env| env[:result] = false } 13 | expect(Action::MessageNotCreated).to be_called 14 | expect(Action::SSHExec).to be_omitted 15 | execute_vagrant_command environment, :ssh 16 | end 17 | end 18 | 19 | context 'the vm exists on the proxmox server' do 20 | 21 | it 'should call the appropriate actions to open a ssh connection' do 22 | expect(Action::IsCreated).to be_called { |env| env[:result] = true } 23 | expect(Action::IsStopped).to be_called { |env| env[:result] = false } 24 | expect(Action::SSHExec).to be_called 25 | execute_vagrant_command environment, :ssh 26 | end 27 | 28 | end 29 | 30 | context 'the vm exists on the proxmox server but is not running' do 31 | 32 | it 'should state that the machine is not currently running' do 33 | expect(Action::IsCreated).to be_called { |env| env[:result] = true } 34 | expect(Action::IsStopped).to be_called { |env| env[:result] = true } 35 | expect(Action::MessageNotRunning).to be_called 36 | expect(Action::SSHExec).to be_omitted 37 | execute_vagrant_command environment, :ssh 38 | end 39 | 40 | end 41 | 42 | end 43 | 44 | end 45 | -------------------------------------------------------------------------------- /spec/commands/ssh_run_command_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | module VagrantPlugins::Proxmox 4 | 5 | describe 'Vagrant Proxmox provider ssh exec command', :need_box do 6 | 7 | let(:environment) { Vagrant::Environment.new vagrantfile_name: 'dummy_box/Vagrantfile' } 8 | let(:env) { {machine: environment.machine(environment.primary_machine_name, :proxmox), ui: double('ui').as_null_object} } 9 | let(:ui) { double('ui').as_null_object } 10 | 11 | context 'the vm is not created on the proxmox server' do 12 | it 'should call the appropriate actions and print a ui message that the vm is not created' do 13 | expect(Action::IsCreated).to be_called { |env| env[:result] = false } 14 | expect(Action::MessageNotCreated).to be_called 15 | expect(Action::SSHRun).to be_omitted 16 | execute_vagrant_command environment, :ssh, '--command', 'foo' 17 | end 18 | end 19 | 20 | context 'the vm exists on the proxmox server' do 21 | before { allow(env[:machine]).to receive(:ssh_info) { {host: '127.0.0.1', port: 22, username: 'vagrant', private_key_path: 'key'} } } 22 | 23 | it 'should call the appropriate actions to execute a command via ssh on the vm' do 24 | expect(Action::IsCreated).to be_called { |env| env[:result] = true } 25 | expect(Action::IsStopped).to be_called { |env| env[:result] = false } 26 | expect(Action::SSHRun).to be_called 27 | execute_vagrant_command environment, :ssh, '--command', 'foo' 28 | end 29 | end 30 | 31 | context 'the vm exists on the proxmox server and is not running' do 32 | before { allow(env[:machine]).to receive(:ssh_info) { {host: '127.0.0.1', port: 22, username: 'vagrant', private_key_path: 'key'} } } 33 | 34 | it 'should call the appropriate actions to execute a command via ssh on the vm' do 35 | expect(Action::IsCreated).to be_called { |env| env[:result] = true } 36 | expect(Action::IsStopped).to be_called { |env| env[:result] = true } 37 | expect(Action::MessageNotRunning).to be_called 38 | expect(Action::SSHRun).to be_omitted 39 | execute_vagrant_command environment, :ssh, '--command', 'foo' 40 | end 41 | end 42 | 43 | end 44 | 45 | end 46 | -------------------------------------------------------------------------------- /spec/commands/status_command_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | module VagrantPlugins::Proxmox 4 | 5 | describe 'Vagrant Proxmox provider status command', :need_box do 6 | 7 | let(:environment) { Vagrant::Environment.new vagrantfile_name: 'dummy_box/Vagrantfile' } 8 | let(:ui) { double('ui').as_null_object } 9 | 10 | specify do 11 | expect(Action::ConnectProxmox).to be_called 12 | expect(Action::ReadState).to be_called { |env| env[:machine_state_id] = :stopped } 13 | expect(ui).to receive(:info).with /stopped \(proxmox\)/, anything 14 | execute_vagrant_command environment, :status 15 | end 16 | 17 | end 18 | 19 | end 20 | -------------------------------------------------------------------------------- /spec/commands/up_command_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | module VagrantPlugins::Proxmox 4 | 5 | describe 'Vagrant Proxmox provider up command' do 6 | 7 | let(:environment) { Vagrant::Environment.new vagrantfile_name: 'dummy_box/Vagrantfile' } 8 | let(:connection) { Connection.new 'https://your.proxmox.server/api2/json' } 9 | let(:env) { {machine: environment.machine(environment.primary_machine_name, :proxmox)} } 10 | let(:ui) { double('ui').as_null_object } 11 | let(:proxmox_vm_type) { :openvz } 12 | let(:qemu_os) { :l26 } 13 | let(:qemu_iso) { 'someiso.iso' } 14 | let(:qemu_disk_size) { '30G' } 15 | 16 | before do 17 | allow(Vagrant::UI::Interface).to receive_messages :new => ui 18 | env[:machine].provider_config.vm_type = proxmox_vm_type 19 | end 20 | 21 | context 'the vm_type is :openvz' do 22 | 23 | context 'the vm is not yet created' do 24 | it 'should call the appropriate actions and print a ui message that the vm will be created' do 25 | expect(Action::ConnectProxmox).to be_called 26 | expect(Action::GetNodeList).to be_called { |env| env[:proxmox_nodes] = ['localhost'] } 27 | expect(Action::SelectNode).to be_called { |env| env[:proxmox_selected_node] = ['localhost'] } 28 | expect(Action::IsCreated).to be_called { |env| env[:result] = false } 29 | expect(Action::UploadTemplateFile).to be_called { |env| env[:result] = :ok } 30 | expect(Action::UploadIsoFile).to be_omitted 31 | expect(Action::CreateVm).to be_called { |env| env[:machine].id = 'localhost/100' } 32 | expect(Action::Provision).to be_called 33 | expect(Action::StartVm).to be_called 34 | expect(Action::SyncFolders).to be_called 35 | execute_vagrant_command environment, :up, '--provider=proxmox' 36 | end 37 | end 38 | 39 | context 'the vm is stopped' do 40 | it 'should call the appropriate actions and print a ui message that the vm will be started' do 41 | expect(Action::ConnectProxmox).to be_called 42 | expect(Action::IsCreated).to be_called { |env| env[:result] = true } 43 | expect(Action::IsStopped).to be_called { |env| env[:result] = true } 44 | expect(Action::Provision).to be_called 45 | expect(Action::StartVm).to be_called 46 | expect(Action::SyncFolders).to be_called 47 | expect(Action::UploadTemplateFile).to be_omitted 48 | expect(Action::UploadIsoFile).to be_omitted 49 | expect(Action::CreateVm).to be_omitted 50 | execute_vagrant_command environment, :up, '--provider=proxmox' 51 | end 52 | end 53 | 54 | context 'the vm is already running' do 55 | it 'should call the appropriate actions and print a ui message that the vm is already running' do 56 | expect(Action::ConnectProxmox).to be_called 57 | expect(Action::IsCreated).to be_called { |env| env[:result] = true } 58 | expect(Action::IsStopped).to be_called { |env| env[:result] = false } 59 | expect(Action::MessageAlreadyRunning).to be_called 60 | expect(Action::Provision).to be_omitted 61 | expect(Action::UploadTemplateFile).to be_omitted 62 | expect(Action::UploadIsoFile).to be_omitted 63 | expect(Action::CreateVm).to be_omitted 64 | expect(Action::StartVm).to be_omitted 65 | execute_vagrant_command environment, :up, '--provider=proxmox' 66 | end 67 | end 68 | 69 | context 'an invalid local template file is specified' do 70 | 71 | it 'should call the appropriate actions and print a ui message that a file was not found' do 72 | expect(Action::ConnectProxmox).to be_called 73 | expect(Action::GetNodeList).to be_called { |env| env[:proxmox_nodes] = ['localhost'] } 74 | expect(Action::IsCreated).to be_called { |env| env[:result] = false } 75 | expect(Action::Provision).to be_called 76 | expect(Action::UploadTemplateFile).to be_called { |env| env[:result] = :file_not_found } 77 | expect(Action::UploadIsoFile).to be_omitted 78 | expect(Action::MessageFileNotFound).to be_called 79 | expect(Action::CreateVm).to be_omitted 80 | expect(Action::StartVm).to be_omitted 81 | expect(Action::SyncFolders).to be_omitted 82 | execute_vagrant_command environment, :up, '--provider=proxmox' 83 | end 84 | end 85 | 86 | context 'a server error occurs' do 87 | 88 | it 'should call the appropriate actions and print a ui message that a server error occured' do 89 | expect(Action::ConnectProxmox).to be_called 90 | expect(Action::GetNodeList).to be_called { |env| env[:proxmox_nodes] = ['localhost'] } 91 | expect(Action::IsCreated).to be_called { |env| env[:result] = false } 92 | expect(Action::Provision).to be_called 93 | expect(Action::UploadTemplateFile).to be_called { |env| env[:result] = :server_upload_error } 94 | expect(Action::UploadIsoFile).to be_omitted 95 | expect(Action::MessageUploadServerError).to be_called 96 | expect(Action::CreateVm).to be_omitted 97 | expect(Action::StartVm).to be_omitted 98 | expect(Action::SyncFolders).to be_omitted 99 | execute_vagrant_command environment, :up, '--provider=proxmox' 100 | end 101 | end 102 | end 103 | 104 | context 'the vm_type is :qemu' do 105 | 106 | let(:proxmox_vm_type) { :qemu } 107 | 108 | before do 109 | env[:machine].provider_config.qemu_os = qemu_os 110 | env[:machine].provider_config.qemu_iso = qemu_iso 111 | env[:machine].provider_config.qemu_disk_size = qemu_disk_size 112 | end 113 | 114 | context 'the vm is not yet created' do 115 | it 'should call the appropriate actions and print a ui message that the vm will be created' do 116 | expect(Action::ConnectProxmox).to be_called 117 | expect(Action::GetNodeList).to be_called { |env| env[:proxmox_nodes] = ['localhost'] } 118 | expect(Action::SelectNode).to be_called { |env| env[:proxmox_selected_node] = ['localhost'] } 119 | expect(Action::IsCreated).to be_called { |env| env[:result] = false } 120 | expect(Action::UploadTemplateFile).to be_omitted 121 | expect(Action::UploadIsoFile).to be_called { |env| env[:result] = :ok } 122 | expect(Action::CreateVm).to be_called { |env| env[:machine].id = 'localhost/100' } 123 | expect(Action::Provision).to be_called 124 | expect(Action::StartVm).to be_called 125 | expect(Action::SyncFolders).to be_called 126 | execute_vagrant_command environment, :up, '--provider=proxmox' 127 | end 128 | end 129 | 130 | context 'the vm is stopped' do 131 | it 'should call the appropriate actions and print a ui message that the vm will be started' do 132 | expect(Action::ConnectProxmox).to be_called 133 | expect(Action::IsCreated).to be_called { |env| env[:result] = true } 134 | expect(Action::IsStopped).to be_called { |env| env[:result] = true } 135 | expect(Action::Provision).to be_called 136 | expect(Action::StartVm).to be_called 137 | expect(Action::SyncFolders).to be_called 138 | expect(Action::UploadTemplateFile).to be_omitted 139 | expect(Action::UploadIsoFile).to be_omitted 140 | expect(Action::CreateVm).to be_omitted 141 | execute_vagrant_command environment, :up, '--provider=proxmox' 142 | end 143 | end 144 | 145 | context 'the vm is already running' do 146 | it 'should call the appropriate actions and print a ui message that the vm is already running' do 147 | expect(Action::ConnectProxmox).to be_called 148 | expect(Action::IsCreated).to be_called { |env| env[:result] = true } 149 | expect(Action::IsStopped).to be_called { |env| env[:result] = false } 150 | expect(Action::MessageAlreadyRunning).to be_called 151 | expect(Action::Provision).to be_omitted 152 | expect(Action::UploadTemplateFile).to be_omitted 153 | expect(Action::UploadIsoFile).to be_omitted 154 | expect(Action::CreateVm).to be_omitted 155 | expect(Action::StartVm).to be_omitted 156 | execute_vagrant_command environment, :up, '--provider=proxmox' 157 | end 158 | end 159 | 160 | context 'an invalid local iso file is specified' do 161 | 162 | it 'should call the appropriate actions and print a ui message that a file was not found' do 163 | expect(Action::ConnectProxmox).to be_called 164 | expect(Action::GetNodeList).to be_called { |env| env[:proxmox_nodes] = ['localhost'] } 165 | expect(Action::IsCreated).to be_called { |env| env[:result] = false } 166 | expect(Action::Provision).to be_called 167 | expect(Action::UploadTemplateFile).to be_omitted 168 | expect(Action::UploadIsoFile).to be_called { |env| env[:result] = :file_not_found } 169 | expect(Action::MessageFileNotFound).to be_called 170 | expect(Action::CreateVm).to be_omitted 171 | expect(Action::StartVm).to be_omitted 172 | expect(Action::SyncFolders).to be_omitted 173 | execute_vagrant_command environment, :up, '--provider=proxmox' 174 | end 175 | end 176 | 177 | context 'a server error occurs' do 178 | 179 | it 'should call the appropriate actions and print a ui message that a server error occured' do 180 | expect(Action::ConnectProxmox).to be_called 181 | expect(Action::GetNodeList).to be_called { |env| env[:proxmox_nodes] = ['localhost'] } 182 | expect(Action::IsCreated).to be_called { |env| env[:result] = false } 183 | expect(Action::Provision).to be_called 184 | expect(Action::UploadTemplateFile).to be_omitted 185 | expect(Action::UploadIsoFile).to be_called { |env| env[:result] = :server_upload_error } 186 | expect(Action::MessageUploadServerError).to be_called 187 | expect(Action::CreateVm).to be_omitted 188 | expect(Action::StartVm).to be_omitted 189 | expect(Action::SyncFolders).to be_omitted 190 | execute_vagrant_command environment, :up, '--provider=proxmox' 191 | end 192 | end 193 | end 194 | end 195 | end 196 | -------------------------------------------------------------------------------- /spec/plugin_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'Vagrant Proxmox plugin' do 4 | 5 | specify { expect(Vagrant).to have_plugin 'Proxmox' } 6 | 7 | describe 'when vagrant log level is set in ENV[VAGRANT_LOG]' do 8 | before { ENV['VAGRANT_LOG'] = 'DEBUG' } 9 | it 'should create a new vagrant proxmox logger ' do 10 | expect(Log4r::Logger).to receive(:new).with('vagrant_proxmox').and_call_original 11 | VagrantPlugins::Proxmox::Plugin.setup_logging 12 | end 13 | end 14 | 15 | end 16 | -------------------------------------------------------------------------------- /spec/provider_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'vagrant-proxmox/provider' 3 | 4 | module VagrantPlugins::Proxmox 5 | 6 | describe Provider do 7 | 8 | let(:machine) { environment.machine(environment.primary_machine_name, :proxmox) } 9 | let(:environment) { Vagrant::Environment.new vagrantfile_name: 'dummy_box/Vagrantfile' } 10 | let(:ui) { double('ui').as_null_object } 11 | 12 | subject { described_class.new(machine) } 13 | 14 | describe '#ssh_info', :need_box do 15 | it 'should call the appropriate actions and return the ssh info' do 16 | expect(Action::ConfigValidate).to be_called 17 | expect(Action::ConnectProxmox).to be_called 18 | expect(Action::GetNodeList).to be_called 19 | expect(Action::SelectNode).to be_called 20 | expect(Action::AdjustForwardedPortParams).to be_called 21 | expect(Action::ReadSSHInfo).to be_called { |env| env[:machine_ssh_info] = 'ssh_info' } 22 | expect(subject.ssh_info).to eq('ssh_info') 23 | end 24 | end 25 | 26 | end 27 | 28 | end 29 | -------------------------------------------------------------------------------- /spec/proxmox/rest_call_shared.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins::Proxmox 2 | 3 | shared_examples 'a rest api call' do |rest_method| 4 | 5 | context 'when an invalid resource is requested' do 6 | before { allow(RestClient).to receive(rest_method).and_raise RestClient::NotImplemented } 7 | it 'should raise a connection error' do 8 | expect do 9 | connection.send rest_method, '/invalid_resource' 10 | end.to raise_error ApiError::NotImplemented 11 | end 12 | end 13 | 14 | context 'when an internal server error occurs' do 15 | before { allow(RestClient).to receive(rest_method).and_raise RestClient::InternalServerError } 16 | it 'should raise a server error' do 17 | expect do 18 | connection.send rest_method, '/invalid_resource' 19 | end.to raise_error ApiError::ServerError 20 | end 21 | end 22 | 23 | context 'when a network error occurs' do 24 | before { allow(RestClient).to receive(rest_method).and_raise SocketError } 25 | it 'should raise a connection error' do 26 | expect do 27 | connection.send rest_method, '/resource' 28 | end.to raise_error ApiError::ConnectionError 29 | end 30 | end 31 | 32 | context 'when the client is not authorized' do 33 | before { allow(RestClient).to receive(rest_method).and_raise RestClient::Unauthorized } 34 | it 'should raise a unauthorized error' do 35 | expect do 36 | connection.send rest_method, "/resource" 37 | end.to raise_error ApiError::UnauthorizedError 38 | end 39 | end 40 | end 41 | end -------------------------------------------------------------------------------- /spec/sanity_checks_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'Vagrant Proxmox sanity checks' do 4 | 5 | describe 'when loaded without vagrant installed' do 6 | before { allow_any_instance_of(Object).to receive(:require) { raise LoadError } } 7 | it 'should raise an error' do 8 | expect { load 'sanity_checks.rb' }.to raise_error 9 | end 10 | end 11 | 12 | describe 'when used with an outdated vagrant version' do 13 | before { stub_const 'Vagrant::VERSION', '1.0.7' } 14 | it 'should raise an error' do 15 | expect { load 'sanity_checks.rb' }.to raise_error 16 | end 17 | end 18 | 19 | end 20 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | if ENV['RUN_WITH_COVERAGE'] 2 | require 'simplecov' 3 | require 'simplecov-rcov' 4 | SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[ 5 | SimpleCov::Formatter::HTMLFormatter, 6 | SimpleCov::Formatter::RcovFormatter, 7 | ] 8 | SimpleCov.start do 9 | add_filter '/dummy_box/Vagrantfile' 10 | add_filter '/spec/' 11 | add_filter '/lib/sanity_checks.rb' 12 | end 13 | end 14 | 15 | require 'vagrant-proxmox' 16 | require 'spec_helpers/common_helpers' 17 | require 'active_support/core_ext/string' 18 | require 'spec_helpers/time_helpers' 19 | 20 | RSpec.configure do |config| 21 | 22 | config.before(:suite) do 23 | add_dummy_box 24 | end 25 | config.after(:suite) do 26 | remove_dummy_box 27 | end 28 | config.after(:each) do 29 | FileUtils.rm_r '.vagrant', force: true 30 | end 31 | 32 | config.before(:each, :need_box) do 33 | up_local_box 34 | end 35 | 36 | end 37 | 38 | if ENV['COVERAGE'] 39 | require 'simplecov' 40 | require 'simplecov-rcov' 41 | SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[ 42 | SimpleCov::Formatter::HTMLFormatter, 43 | SimpleCov::Formatter::RcovFormatter, 44 | ] 45 | SimpleCov.start 46 | end -------------------------------------------------------------------------------- /spec/spec_helpers/common_helpers.rb: -------------------------------------------------------------------------------- 1 | def be_called &action_stub 2 | ActionCallMatcher.new action_stub, environment, self, :called 3 | end 4 | 5 | def be_omitted &action_stub 6 | ActionCallMatcher.new action_stub, environment, self, :omitted 7 | end 8 | 9 | # If you want to expect your actions to be called (and optionally stub their call methods), 10 | # use something like this: 11 | # 12 | # let(:environment) { Vagrant::Environment.new vagrantfile_name: 'dummy_box/Vagrantfile' } 13 | # 14 | # Action::ConnectProxmox.should be_called { |env| env[:proxmox_ticket] = 'ticket' } 15 | # 16 | class ActionCallMatcher < RSpec::Matchers::BuiltIn::BaseMatcher 17 | def initialize action_stub, environment, example_group, kind=:called 18 | super action_stub 19 | @environment = environment 20 | @example_group = example_group 21 | @kind = kind 22 | end 23 | 24 | def match(action_stub, action_class) 25 | @example_group.send(:mock_action, action_class).tap do |action| 26 | case @kind 27 | when :called 28 | @example_group.expect(action).to @example_group.receive(:call).at_least(:once) do |env| 29 | action_stub.call(env) if action_stub 30 | action.instance_variable_get(:@app).call env 31 | end 32 | when :omitted 33 | @example_group.expect(action).not_to @example_group.receive :call 34 | end 35 | end 36 | end 37 | end 38 | 39 | # If you want expectations on the action_class use something like: 40 | # 41 | # let(:environment) { Vagrant::Environment.new vagrantfile_name: 'dummy_box/Vagrantfile' } 42 | # 43 | # mock_action(VagrantPlugins::Proxmox::Action::ConnectProxmox).tap do |action| 44 | # action.should receive(:perform) {|env| env[:proxmox_ticket] = 'ticket' } 45 | # end 46 | # 47 | def mock_action action_class, env = RSpec::Mocks::Double.new('env').as_null_object 48 | action_class.new(nil, env).tap do |action_instance| 49 | action_instance_name = "@#{action_class.name.demodulize}Action".underscore 50 | self.instance_variable_set action_instance_name, action_instance 51 | allow(action_class).to receive(:new) do |app, _| 52 | action_instance.instance_variable_set '@app', app 53 | action_instance 54 | end 55 | end 56 | end 57 | 58 | # If you want to stub your actions (stub their call methods), use something like this: 59 | # 60 | # let(:environment) { Vagrant::Environment.new vagrantfile_name: 'dummy_box/Vagrantfile' } 61 | # 62 | # stub_action(Action::ConnectProxmox) { |env| env[:proxmox_ticket] = 'ticket' } 63 | # 64 | def stub_action action_class 65 | mock_action(action_class).tap do |action| 66 | allow(action).to receive(:call) do |env| 67 | yield env if block_given? 68 | action.instance_variable_get(:@app).call env 69 | end 70 | end 71 | end 72 | 73 | def unstub_action action_class 74 | RSpec::Mocks.space.proxy_for(action_class).reset 75 | end 76 | 77 | def execute_vagrant_command environment, command, *params 78 | Vagrant.plugin('2').manager.commands[command].new(params, environment).execute 79 | end 80 | 81 | def up_local_box 82 | allow(Vagrant::UI::Interface).to receive_messages :new => ui 83 | stub_action(VagrantPlugins::Proxmox::Action::ConnectProxmox) 84 | stub_action(VagrantPlugins::Proxmox::Action::GetNodeList) { |env| env[:proxmox_nodes] = [{node: 'node1'}] } 85 | stub_action(VagrantPlugins::Proxmox::Action::IsCreated) { |env| env[:result] = false } 86 | stub_action(VagrantPlugins::Proxmox::Action::CreateVm) { |env| env[:machine].id = 'node1/100' } 87 | stub_action(VagrantPlugins::Proxmox::Action::StartVm) 88 | stub_action(VagrantPlugins::Proxmox::Action::SyncFolders) 89 | execute_vagrant_command environment, :up, '--provider=proxmox' 90 | unstub_action(VagrantPlugins::Proxmox::Action::ConnectProxmox) 91 | unstub_action(VagrantPlugins::Proxmox::Action::GetNodeList) 92 | unstub_action(VagrantPlugins::Proxmox::Action::IsCreated) 93 | unstub_action(VagrantPlugins::Proxmox::Action::CreateVm) 94 | unstub_action(VagrantPlugins::Proxmox::Action::StartVm) 95 | unstub_action(VagrantPlugins::Proxmox::Action::SyncFolders) 96 | end 97 | 98 | def proxmox_api_url path 99 | "https://proxmox.example.com/api2/json/#{path}" 100 | end 101 | 102 | def add_dummy_box 103 | begin 104 | Vagrant::Environment.new.boxes.add 'dummy_box/dummy.box', 'b681e2bc-617b-4b35-94fa-edc92e1071b8', :proxmox 105 | rescue Vagrant::Errors::BoxAlreadyExists 106 | end 107 | end 108 | 109 | def remove_dummy_box 110 | execute_vagrant_command Vagrant::Environment.new, :box, 'remove', 'b681e2bc-617b-4b35-94fa-edc92e1071b8' 111 | end 112 | -------------------------------------------------------------------------------- /spec/spec_helpers/time_helpers.rb: -------------------------------------------------------------------------------- 1 | require 'timecop' 2 | 3 | class Fixnum 4 | 5 | def self.time_interval_converter unit, factor 6 | define_method(unit) do 7 | TimeInterval.new self * factor, factor, unit 8 | end 9 | end 10 | 11 | time_interval_converter :second, 1 12 | time_interval_converter :seconds, 1 13 | time_interval_converter :minute, 60 14 | time_interval_converter :minutes, 60 15 | time_interval_converter :hour, 3600 16 | time_interval_converter :hours, 3600 17 | time_interval_converter :day, 86400 18 | time_interval_converter :days, 86400 19 | 20 | end 21 | 22 | class TimeInterval 23 | 24 | attr_reader :unit 25 | attr_reader :factor 26 | 27 | def initialize value, factor, unit 28 | @value = value.to_i 29 | @factor = factor 30 | @unit = unit 31 | end 32 | 33 | def clone_with_value value 34 | TimeInterval.new value, @factor, @unit 35 | end 36 | 37 | def inspect 38 | to_s 39 | end 40 | 41 | def to_s 42 | ("%.2f #{@unit}" % (in_units)).sub /\.00/, '' 43 | end 44 | 45 | def in_seconds 46 | @value 47 | end 48 | 49 | def in_units 50 | @value.to_f / @factor 51 | end 52 | 53 | def < other 54 | in_seconds < other.in_seconds 55 | end 56 | 57 | end 58 | 59 | class Timecop 60 | 61 | class << self 62 | 63 | alias :old_freeze :freeze 64 | 65 | def freeze *args, &block 66 | args << Time.local(5555) if args.empty? 67 | @frozen_time = args[0] 68 | old_freeze *args, &block 69 | end 70 | 71 | def frozen_time 72 | @frozen_time 73 | end 74 | 75 | def has_travelled? interval 76 | @frozen_time.to_i + interval.in_seconds == Time.now.to_i 77 | end 78 | 79 | end 80 | end 81 | 82 | RSpec::Matchers.define :have_elapsed do |expected| 83 | match do 84 | Timecop.has_travelled? expected 85 | end 86 | failure_message do 87 | result = expected.clone_with_value Time.now - Timecop.frozen_time 88 | "expected current time to be #{expected} later, but #{result < expected ? 'only' : 'already'} #{result} elapsed" 89 | end 90 | end -------------------------------------------------------------------------------- /vagrant-proxmox.gemspec: -------------------------------------------------------------------------------- 1 | $:.unshift File.expand_path '../lib', __FILE__ 2 | require 'vagrant-proxmox/version' 3 | 4 | Gem::Specification.new do |spec| 5 | spec.name = 'vagrant-proxmox' 6 | spec.version = VagrantPlugins::Proxmox::VERSION 7 | spec.platform = Gem::Platform::RUBY 8 | spec.license = 'MIT' 9 | spec.authors = ['Dirk Grappendorf', 'Tim Völpel', 'Sebastian Bremicker'] 10 | spec.email = ['dirk.grappendorf@telcat.de', 'tim.voelpel@telcat.de', 'sebastian.bremicker@telcat.de'] 11 | spec.homepage = 'https://github.com/telcat/vagrant-proxmox' 12 | spec.summary = 'Enables Vagrant to manage virtual machines on a Proxmox server.' 13 | spec.description = 'Enables Vagrant to manage virtual machines on a Proxmox server.' 14 | spec.required_ruby_version = '>= 2' 15 | 16 | spec.add_runtime_dependency 'rest-client', '~> 1.6.7' 17 | spec.add_runtime_dependency 'retryable', '~> 1.3.3' 18 | spec.add_runtime_dependency 'activesupport', '~> 4.0.0' 19 | spec.add_development_dependency 'rake', '10.5.0' 20 | spec.add_development_dependency 'rspec', '~> 3.0.0' 21 | spec.add_development_dependency 'simplecov', '~> 0.9.0' 22 | spec.add_development_dependency 'simplecov-rcov', '~> 0.2.3' 23 | spec.add_development_dependency 'geminabox', '~> 0.11.1' 24 | spec.add_development_dependency 'guard-rspec', '4.2.10' 25 | spec.add_development_dependency 'libnotify', '~> 0.8.3' 26 | spec.add_development_dependency 'timecop', '~>0.7.1' 27 | spec.add_development_dependency 'cucumber', '~>1.3.15' 28 | spec.add_development_dependency 'webmock', '~> 1.18.0' 29 | spec.add_development_dependency 'awesome_print', '~> 1.2.0' 30 | 31 | spec.files = Dir.glob('lib/**/*.rb') + Dir.glob('locales/**/*.yml') 32 | spec.test_files = Dir.glob 'spec/**/*.rb' 33 | spec.require_paths = %w(lib) 34 | end 35 | -------------------------------------------------------------------------------- /vagrant-proxmox.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | --------------------------------------------------------------------------------