vagrant-proxmox 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. checksums.yaml +7 -0
  2. data/lib/sanity_checks.rb +11 -0
  3. data/lib/vagrant-proxmox/action/cleanup_after_destroy.rb +20 -0
  4. data/lib/vagrant-proxmox/action/connect_proxmox.rb +33 -0
  5. data/lib/vagrant-proxmox/action/create_vm.rb +63 -0
  6. data/lib/vagrant-proxmox/action/destroy_vm.rb +29 -0
  7. data/lib/vagrant-proxmox/action/get_node_list.rb +24 -0
  8. data/lib/vagrant-proxmox/action/is_created.rb +20 -0
  9. data/lib/vagrant-proxmox/action/is_stopped.rb +20 -0
  10. data/lib/vagrant-proxmox/action/message_already_running.rb +20 -0
  11. data/lib/vagrant-proxmox/action/message_already_stopped.rb +20 -0
  12. data/lib/vagrant-proxmox/action/message_not_created.rb +20 -0
  13. data/lib/vagrant-proxmox/action/proxmox_action.rb +47 -0
  14. data/lib/vagrant-proxmox/action/read_ssh_info.rb +22 -0
  15. data/lib/vagrant-proxmox/action/read_state.rb +37 -0
  16. data/lib/vagrant-proxmox/action/shutdown_vm.rb +31 -0
  17. data/lib/vagrant-proxmox/action/start_vm.rb +40 -0
  18. data/lib/vagrant-proxmox/action/stop_vm.rb +31 -0
  19. data/lib/vagrant-proxmox/action/sync_folders.rb +52 -0
  20. data/lib/vagrant-proxmox/action.rb +159 -0
  21. data/lib/vagrant-proxmox/config.rb +81 -0
  22. data/lib/vagrant-proxmox/errors.rb +39 -0
  23. data/lib/vagrant-proxmox/plugin.rb +75 -0
  24. data/lib/vagrant-proxmox/provider.rb +51 -0
  25. data/lib/vagrant-proxmox/version.rb +7 -0
  26. data/lib/vagrant-proxmox.rb +22 -0
  27. data/locales/en.yml +66 -0
  28. data/spec/actions/cleanup_after_destroy_action_spec.rb +22 -0
  29. data/spec/actions/connect_proxmox_action_spec.rb +49 -0
  30. data/spec/actions/create_vm_action_spec.rb +167 -0
  31. data/spec/actions/destroy_vm_action_spec.rb +37 -0
  32. data/spec/actions/get_node_list_action_spec.rb +25 -0
  33. data/spec/actions/is_created_action_spec.rb +35 -0
  34. data/spec/actions/is_stopped_action_spec.rb +34 -0
  35. data/spec/actions/message_already_running_action_spec.rb +23 -0
  36. data/spec/actions/message_already_stopped_action_spec.rb +23 -0
  37. data/spec/actions/message_not_created_action_spec.rb +23 -0
  38. data/spec/actions/proxmox_action_shared.rb +74 -0
  39. data/spec/actions/read_ssh_info_action_spec.rb +32 -0
  40. data/spec/actions/read_state_action_spec.rb +54 -0
  41. data/spec/actions/shutdown_vm_action_spec.rb +37 -0
  42. data/spec/actions/start_vm_action_spec.rb +47 -0
  43. data/spec/actions/stop_vm_action_spec.rb +37 -0
  44. data/spec/actions/sync_folders_action_spec.rb +51 -0
  45. data/spec/commands/destroy_command_spec.rb +68 -0
  46. data/spec/commands/halt_command_spec.rb +46 -0
  47. data/spec/commands/provision_command_spec.rb +33 -0
  48. data/spec/commands/ssh_command_spec.rb +31 -0
  49. data/spec/commands/ssh_run_command_spec.rb +32 -0
  50. data/spec/commands/status_command_spec.rb +19 -0
  51. data/spec/commands/up_command_spec.rb +55 -0
  52. data/spec/config_spec.rb +71 -0
  53. data/spec/plugin_spec.rb +22 -0
  54. data/spec/provider_spec.rb +25 -0
  55. data/spec/sanity_checks_spec.rb +19 -0
  56. data/spec/spec_helper.rb +128 -0
  57. metadata +242 -0
@@ -0,0 +1,159 @@
1
+ require 'vagrant/action/builder'
2
+ require 'pathname'
3
+
4
+ module VagrantPlugins
5
+ module Proxmox
6
+ module Action
7
+
8
+ include Vagrant::Action::Builtin
9
+
10
+ def self.action_read_state
11
+ Vagrant::Action::Builder.new.tap do |b|
12
+ b.use ConfigValidate
13
+ b.use ConnectProxmox
14
+ b.use ReadState
15
+ end
16
+ end
17
+
18
+ def self.action_up
19
+ Vagrant::Action::Builder.new.tap do |b|
20
+ b.use ConfigValidate
21
+ b.use ConnectProxmox
22
+ b.use Call, IsCreated do |env1, b1|
23
+ if env1[:result]
24
+ b1.use Call, IsStopped do |env2, b2|
25
+ if env2[:result]
26
+ b2.use Provision
27
+ b2.use StartVm
28
+ b2.use SyncFolders
29
+ else
30
+ b2.use MessageAlreadyRunning
31
+ end
32
+ end
33
+ else
34
+ b1.use GetNodeList
35
+ b1.use Provision
36
+ b1.use CreateVm
37
+ b1.use StartVm
38
+ b1.use SyncFolders
39
+ end
40
+ end
41
+ end
42
+ end
43
+
44
+ def self.action_provision
45
+ Vagrant::Action::Builder.new.tap do |b|
46
+ b.use ConfigValidate
47
+ b.use Call, IsCreated do |env, b2|
48
+ if env[:result]
49
+ b2.use Provision
50
+ b2.use SyncFolders
51
+ else
52
+ b2.use MessageNotCreated
53
+ end
54
+ end
55
+ end
56
+ end
57
+
58
+ def self.action_halt
59
+ Vagrant::Action::Builder.new.tap do |b|
60
+ b.use ConfigValidate
61
+ b.use Call, IsCreated do |env1, b1|
62
+ if env1[:result]
63
+ b1.use Call, IsStopped do |env2, b2|
64
+ if env2[:result]
65
+ b2.use MessageAlreadyStopped
66
+ else
67
+ b2.use ConnectProxmox
68
+ b2.use ShutdownVm
69
+ end
70
+ end
71
+ else
72
+ b1.use MessageNotCreated
73
+ end
74
+ end
75
+ end
76
+ end
77
+
78
+ # This action is called to destroy the remote machine.
79
+ def self.action_destroy
80
+ Vagrant::Action::Builder.new.tap do |b|
81
+ b.use ConfigValidate
82
+ b.use ConnectProxmox
83
+ b.use Call, IsCreated do |env1, b1|
84
+ if env1[:result]
85
+ b1.use Call, ::Vagrant::Action::Builtin::DestroyConfirm do |env2, b2|
86
+ if env2[:result]
87
+ b2.use Call, IsStopped do |env3, b3|
88
+ b3.use ShutdownVm unless env3[:result]
89
+ b3.use DestroyVm
90
+ b3.use ::Vagrant::Action::Builtin::ProvisionerCleanup
91
+ b3.use CleanupAfterDestroy
92
+ end
93
+ else
94
+ b2.use ::VagrantPlugins::ProviderVirtualBox::Action::MessageWillNotDestroy
95
+ end
96
+ end
97
+ else
98
+ b1.use MessageNotCreated
99
+ end
100
+ end
101
+ end
102
+ end
103
+
104
+ def self.action_read_ssh_info
105
+ Vagrant::Action::Builder.new.tap do |b|
106
+ b.use ConfigValidate
107
+ b.use ConnectProxmox
108
+ b.use ReadSSHInfo
109
+ end
110
+ end
111
+
112
+ def self.action_ssh
113
+ Vagrant::Action::Builder.new.tap do |b|
114
+ b.use ConfigValidate
115
+ b.use Call, IsCreated do |env, b2|
116
+ if env[:result]
117
+ b2.use SSHExec
118
+ else
119
+ b2.use MessageNotCreated
120
+ end
121
+ end
122
+ end
123
+ end
124
+
125
+ def self.action_ssh_run
126
+ Vagrant::Action::Builder.new.tap do |b|
127
+ b.use ConfigValidate
128
+ b.use Call, IsCreated do |env, b2|
129
+ if env[:result]
130
+ b2.use SSHRun
131
+ else
132
+ b2.use MessageNotCreated
133
+ end
134
+ end
135
+ end
136
+ end
137
+
138
+ action_root = Pathname.new(File.expand_path '../action', __FILE__)
139
+ autoload :ProxmoxAction, action_root.join('proxmox_action')
140
+ autoload :ConnectProxmox, action_root.join('connect_proxmox')
141
+ autoload :GetNodeList, action_root.join('get_node_list')
142
+ autoload :ReadState, action_root.join('read_state')
143
+ autoload :IsCreated, action_root.join('is_created')
144
+ autoload :IsStopped, action_root.join('is_stopped')
145
+ autoload :MessageAlreadyRunning, action_root.join('message_already_running')
146
+ autoload :MessageAlreadyStopped, action_root.join('message_already_stopped')
147
+ autoload :MessageNotCreated, action_root.join('message_not_created')
148
+ autoload :CreateVm, action_root.join('create_vm')
149
+ autoload :StartVm, action_root.join('start_vm')
150
+ autoload :StopVm, action_root.join('stop_vm')
151
+ autoload :ShutdownVm, action_root.join('shutdown_vm')
152
+ autoload :DestroyVm, action_root.join('destroy_vm')
153
+ autoload :CleanupAfterDestroy, action_root.join('cleanup_after_destroy')
154
+ autoload :ReadSSHInfo, action_root.join('read_ssh_info')
155
+ autoload :SyncFolders, action_root.join('sync_folders')
156
+
157
+ end
158
+ end
159
+ end
@@ -0,0 +1,81 @@
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 user name
11
+ #
12
+ # @return [String]
13
+ attr_accessor :user_name
14
+
15
+ # The Proxmox password
16
+ #
17
+ # @return [String]
18
+ attr_accessor :password
19
+
20
+ # The openvz os template to use for the virtual machines
21
+ #
22
+ # @return [String]
23
+ attr_accessor :os_template
24
+
25
+ # The id range to use for the virtual machines
26
+ #
27
+ # @return [Range]
28
+ attr_accessor :vm_id_range
29
+
30
+ # The prefix for the virtual machine name
31
+ #
32
+ # @return [String]
33
+ attr_accessor :vm_name_prefix
34
+
35
+ # Amount of RAM for the virtual machine in MB
36
+ #
37
+ # @return [Integer]
38
+ attr_accessor :vm_memory
39
+
40
+ # The maximum timeout for a proxmox server task (in seconds)
41
+ #
42
+ # @return [Integer]
43
+ attr_accessor :task_timeout
44
+
45
+ # The interval between two proxmox task status retrievals (in seconds)
46
+ #
47
+ # @return [Integer, Proc]
48
+ attr_accessor :task_status_check_interval
49
+
50
+ def initialize
51
+ @endpoint = UNSET_VALUE
52
+ @user_name = UNSET_VALUE
53
+ @password = UNSET_VALUE
54
+ @os_template = UNSET_VALUE
55
+ @vm_id_range = 900..999
56
+ @vm_name_prefix = 'vagrant_'
57
+ @vm_memory = 512
58
+ @task_timeout = 60
59
+ @task_status_check_interval = 2
60
+ end
61
+
62
+ # This is the hook that is called to finalize the object before it is put into use.
63
+ def finalize!
64
+ @endpoint = nil if @endpoint == UNSET_VALUE
65
+ @user_name = nil if @user_name == UNSET_VALUE
66
+ @password = nil if @password == UNSET_VALUE
67
+ @os_template = nil if @os_template == UNSET_VALUE
68
+ end
69
+
70
+ def validate machine
71
+ errors = []
72
+ errors << I18n.t('vagrant_proxmox.errors.no_endpoint_specified') unless @endpoint
73
+ errors << I18n.t('vagrant_proxmox.errors.no_user_name_specified') unless @user_name
74
+ errors << I18n.t('vagrant_proxmox.errors.no_password_specified') unless @password
75
+ errors << I18n.t('vagrant_proxmox.errors.no_os_template_specified') unless @os_template
76
+ {'Proxmox Provider' => errors}
77
+ end
78
+
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,39 @@
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 VMCreationError < VagrantProxmoxError
30
+ error_key :vm_creation_error
31
+ end
32
+
33
+ class RsyncError < VagrantProxmoxError
34
+ error_key :rsync_error
35
+ end
36
+
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,75 @@
1
+ # Fix wrong header unescaping in RestClient library.
2
+ module RestClient
3
+ class Request
4
+ def make_headers user_headers
5
+ unless @cookies.empty?
6
+ user_headers[:cookie] = @cookies.map { |(key, val)| "#{key.to_s}=#{val}" }.sort.join('; ')
7
+ end
8
+ headers = stringify_headers(default_headers).merge(stringify_headers(user_headers))
9
+ headers.merge!(@payload.headers) if @payload
10
+ headers
11
+ end
12
+ end
13
+ end
14
+
15
+ module VagrantPlugins
16
+ module Proxmox
17
+ class Plugin < Vagrant.plugin ('2')
18
+
19
+ name 'Proxmox'
20
+ description <<-DESC
21
+ This plugin installs a provider that allows Vagrant to manage
22
+ machines on Proxmox.
23
+ DESC
24
+
25
+ config(:proxmox, :provider) do
26
+ require_relative 'config'
27
+ Config
28
+ end
29
+
30
+ provider(:proxmox, parallel: true) do
31
+ # Setup logging and i18n
32
+ setup_logging
33
+ setup_i18n
34
+
35
+ # Return the provider
36
+ require_relative 'provider'
37
+ Provider
38
+ end
39
+
40
+ # This initializes the internationalization strings.
41
+ def self.setup_i18n
42
+ I18n.load_path << File.expand_path('locales/en.yml', Proxmox.source_root)
43
+ I18n.reload!
44
+ end
45
+
46
+ # This sets up our log level to be whatever VAGRANT_LOG is.
47
+ def self.setup_logging
48
+ require 'log4r'
49
+
50
+ level = nil
51
+ begin
52
+ level = Log4r.const_get ENV['VAGRANT_LOG'].upcase
53
+ rescue NameError
54
+ # This means that the logging constant wasn't found,
55
+ # which is fine. We just keep `level` as `nil`. But
56
+ # we tell the user.
57
+ level = nil
58
+ end
59
+
60
+ # Some constants, such as "true" resolve to booleans, so the
61
+ # above error checking doesn't catch it. This will check to make
62
+ # sure that the log level is an integer, as Log4r requires.
63
+ level = nil if !level.is_a?(Integer)
64
+
65
+ # Set the logging level on all "vagrant" namespaced
66
+ # logs as long as we have a valid level.
67
+ if level
68
+ logger = Log4r::Logger.new 'vagrant_proxmox'
69
+ logger.outputters = Log4r::Outputter.stderr
70
+ logger.level = level
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,51 @@
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
@@ -0,0 +1,7 @@
1
+ module VagrantPlugins
2
+ module Proxmox
3
+
4
+ VERSION = '0.0.2'
5
+
6
+ end
7
+ end
@@ -0,0 +1,22 @@
1
+ require 'pathname'
2
+ require 'log4r'
3
+ require 'rest-client'
4
+ require 'retryable'
5
+ require 'active_support/core_ext/object/try'
6
+
7
+ require 'sanity_checks'
8
+ require 'vagrant-proxmox/plugin'
9
+ require 'vagrant-proxmox/errors'
10
+
11
+
12
+ module VagrantPlugins
13
+ module Proxmox
14
+ lib_path = Pathname.new(File.expand_path '../vagrant-proxmox', __FILE__)
15
+ autoload :Action, lib_path.join('action')
16
+ autoload :Errors, lib_path.join('errors')
17
+
18
+ def self.source_root
19
+ @source_root ||= Pathname.new(File.expand_path '../../', __FILE__)
20
+ end
21
+ end
22
+ end
data/locales/en.yml ADDED
@@ -0,0 +1,66 @@
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_os_template_specified: 'No os_template specified.'
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
+ vm_creation_error: |-
44
+ Unable to create the virtual machine!
45
+
46
+ Cause: %{proxmox_exit_status}
47
+ not_created: 'The virtual machine is not created on the server!'
48
+ rsync_folder: |-
49
+ Rsyncing folder: %{hostpath} => %{guestpath}
50
+ shut_down_vm: 'Shutting down the virtual machine...'
51
+ starting_vm: 'Starting the virtual machine...'
52
+ states:
53
+ long_not_created: |-
54
+ The server is not created. Run `vagrant up` to create it.
55
+ long_running: |-
56
+ The server is up and running. Run `vagrant ssh` to access it.
57
+ long_stopped: |-
58
+ The server is stopped.
59
+ short_not_created: |-
60
+ not created
61
+ short_running: |-
62
+ running
63
+ short_stopped: |-
64
+ stopped
65
+ stopping_vm: 'Stopping the virtual machine...'
66
+ waiting_for_ssh_connection: 'Waiting for SSH connection...'
@@ -0,0 +1,22 @@
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 { 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
+ subject.call env
18
+ end.to change{File.exists?(".vagrant/machines/#{env[:machine].name}/proxmox")}.to false
19
+ end
20
+ end
21
+
22
+ end
@@ -0,0 +1,49 @@
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
+
11
+ subject { described_class.new(-> (_) {}, environment) }
12
+
13
+ before { VagrantPlugins::Proxmox::Plugin.setup_i18n }
14
+
15
+ describe '#call' do
16
+
17
+ before do
18
+ allow(RestClient).to receive(:post).and_return({data: {ticket: 'valid_ticket', CSRFPreventionToken: 'valid_token'}}.to_json)
19
+ end
20
+
21
+ it_behaves_like 'a proxmox action call'
22
+
23
+ it 'should call the REST API access/ticket' do
24
+ RestClient.should_receive(:post).with('https://your.proxmox.server/api2/json/access/ticket', {username: 'vagrant', password: 'password'})
25
+ subject.call env
26
+ end
27
+
28
+ it 'should store the access ticket in env[:proxmox_ticket]' do
29
+ subject.call env
30
+ env[:proxmox_ticket].should == 'valid_ticket'
31
+ end
32
+
33
+ it 'should store the access ticket in env[:proxmox_csrf_prevention_token]' do
34
+ subject.call env
35
+ env[:proxmox_csrf_prevention_token].should == 'valid_token'
36
+ end
37
+
38
+ describe 'when the server communication fails' do
39
+ before { RestClient.stub(:post).and_return nil }
40
+ specify do
41
+ expect { subject.call env }.to raise_error Errors::CommunicationError
42
+ end
43
+ end
44
+
45
+ end
46
+
47
+ end
48
+
49
+ end