vagrant-linode 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. checksums.yaml +15 -0
  2. data/.gitignore +19 -0
  3. data/CHANGELOG.md +4 -0
  4. data/Gemfile +11 -0
  5. data/LICENSE.txt +23 -0
  6. data/README.md +201 -0
  7. data/Rakefile +22 -0
  8. data/box/README.md +13 -0
  9. data/box/linode.box +0 -0
  10. data/box/metadata.json +3 -0
  11. data/lib/vagrant-linode/actions/check_state.rb +19 -0
  12. data/lib/vagrant-linode/actions/create.rb +191 -0
  13. data/lib/vagrant-linode/actions/destroy.rb +30 -0
  14. data/lib/vagrant-linode/actions/modify_provision_path.rb +38 -0
  15. data/lib/vagrant-linode/actions/power_off.rb +32 -0
  16. data/lib/vagrant-linode/actions/power_on.rb +32 -0
  17. data/lib/vagrant-linode/actions/rebuild.rb +53 -0
  18. data/lib/vagrant-linode/actions/reload.rb +28 -0
  19. data/lib/vagrant-linode/actions/setup_key.rb +52 -0
  20. data/lib/vagrant-linode/actions/setup_sudo.rb +41 -0
  21. data/lib/vagrant-linode/actions/setup_user.rb +62 -0
  22. data/lib/vagrant-linode/actions/sync_folders.rb +80 -0
  23. data/lib/vagrant-linode/actions.rb +160 -0
  24. data/lib/vagrant-linode/commands/rebuild.rb +23 -0
  25. data/lib/vagrant-linode/config.rb +65 -0
  26. data/lib/vagrant-linode/errors.rb +49 -0
  27. data/lib/vagrant-linode/helpers/client.rb +114 -0
  28. data/lib/vagrant-linode/helpers/result.rb +38 -0
  29. data/lib/vagrant-linode/plugin.rb +26 -0
  30. data/lib/vagrant-linode/provider.rb +117 -0
  31. data/lib/vagrant-linode/version.rb +5 -0
  32. data/lib/vagrant-linode.rb +20 -0
  33. data/locales/en.yml +96 -0
  34. data/test/Vagrantfile +60 -0
  35. data/test/cookbooks/test/recipes/default.rb +1 -0
  36. data/test/scripts/provision.sh +3 -0
  37. data/test/test.sh +14 -0
  38. data/test/test_id_rsa +27 -0
  39. data/test/test_id_rsa.pub +1 -0
  40. data/vagrant-linode.gemspec +21 -0
  41. metadata +132 -0
@@ -0,0 +1,53 @@
1
+ require 'vagrant-linode/helpers/client'
2
+
3
+ module VagrantPlugins
4
+ module Linode
5
+ module Actions
6
+ class Rebuild
7
+ include Helpers::Client
8
+ include Vagrant::Util::Retryable
9
+
10
+ def initialize(app, env)
11
+ @app = app
12
+ @machine = env[:machine]
13
+ @client = client
14
+ @logger = Log4r::Logger.new('vagrant::linode::rebuild')
15
+ end
16
+
17
+ def call(env)
18
+ # @todo find a convenient way to send provider_config back to the create action, reusing the diskid or configid
19
+ fail 'not implemented'
20
+ # look up image id
21
+ image_id = @client
22
+ .request('/v2/images')
23
+ .find_id(:images, name: @machine.provider_config.image)
24
+
25
+ # submit rebuild request
26
+ result = @client.post("/v2/linodes/#{@machine.id}/actions", type: 'rebuild',
27
+ image: image_id)
28
+
29
+ # wait for request to complete
30
+ env[:ui].info I18n.t('vagrant_linode.info.rebuilding')
31
+ wait_for_event(env, result['jobid'])
32
+
33
+ # refresh linode state with provider
34
+ Provider.linode(@machine, refresh: true)
35
+
36
+ # wait for ssh to be ready
37
+ switch_user = @machine.provider_config.setup?
38
+ user = @machine.config.ssh.username
39
+ @machine.config.ssh.username = 'root' if switch_user
40
+
41
+ retryable(tries: 120, sleep: 10) do
42
+ next if env[:interrupted]
43
+ fail 'not ready' unless @machine.communicate.ready?
44
+ end
45
+
46
+ @machine.config.ssh.username = user
47
+
48
+ @app.call(env)
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,28 @@
1
+ require 'vagrant-linode/helpers/client'
2
+
3
+ module VagrantPlugins
4
+ module Linode
5
+ module Actions
6
+ class Reload
7
+ include Helpers::Client
8
+
9
+ def initialize(app, env)
10
+ @app = app
11
+ @machine = env[:machine]
12
+ @client = client
13
+ @logger = Log4r::Logger.new('vagrant::linode::reload')
14
+ end
15
+
16
+ def call(env)
17
+ # submit reboot linode request
18
+ result = @client.linode.reboot(linodeid: @machine.id)
19
+
20
+ # wait for request to complete
21
+ env[:ui].info I18n.t('vagrant_linode.info.reloading')
22
+ wait_for_event(env, result['jobid'])
23
+ @app.call(env)
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,52 @@
1
+ require 'vagrant-linode/helpers/client'
2
+
3
+ module VagrantPlugins
4
+ module Linode
5
+ module Actions
6
+ class SetupKey
7
+ include Helpers::Client
8
+
9
+ def initialize(app, env)
10
+ @app = app
11
+ @machine = env[:machine]
12
+ @client = client
13
+ @logger = Log4r::Logger.new('vagrant::linode::setup_key')
14
+ end
15
+
16
+ # TODO check the content of the key to see if it has changed
17
+ def call(env)
18
+ ssh_key_name = @machine.provider_config.ssh_key_name
19
+
20
+ begin
21
+ # assigns existing ssh key id to env for use by other commands
22
+ env[:ssh_key_id] = @client
23
+ .request('/v2/account/keys')
24
+ .find_id(:ssh_keys, name: ssh_key_name)
25
+
26
+ env[:ui].info I18n.t('vagrant_linode.info.using_key', name: ssh_key_name)
27
+ rescue Errors::ResultMatchError
28
+ env[:ssh_key_id] = create_ssh_key(ssh_key_name, env)
29
+ end
30
+
31
+ @app.call(env)
32
+ end
33
+
34
+ private
35
+
36
+ def create_ssh_key(name, env)
37
+ # assumes public key exists on the same path as private key with .pub ext
38
+ path = @machine.config.ssh.private_key_path
39
+ path = path[0] if path.is_a?(Array)
40
+ path = File.expand_path(path, @machine.env.root_path)
41
+ pub_key = Linode.public_key(path)
42
+
43
+ env[:ui].info I18n.t('vagrant_linode.info.creating_key', name: name)
44
+
45
+ result = @client.post('/v2/account/keys', name: name,
46
+ public_key: pub_key)
47
+ result['ssh_key']['id']
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,41 @@
1
+ module VagrantPlugins
2
+ module Linode
3
+ module Actions
4
+ class SetupSudo
5
+ def initialize(app, env)
6
+ @app = app
7
+ @machine = env[:machine]
8
+ @logger = Log4r::Logger.new('vagrant::linode::setup_sudo')
9
+ end
10
+
11
+ def call(env)
12
+ # check if setup is enabled
13
+ return @app.call(env) unless @machine.provider_config.setup?
14
+
15
+ # override ssh username to root
16
+ user = @machine.config.ssh.username
17
+ @machine.config.ssh.username = 'root'
18
+
19
+ # check for guest name available in Vagrant 1.2 first
20
+ guest_name = @machine.guest.name if @machine.guest.respond_to?(:name)
21
+ guest_name ||= @machine.guest.to_s.downcase
22
+
23
+ case guest_name
24
+ when /redhat/
25
+ env[:ui].info I18n.t('vagrant_linode.info.modifying_sudo')
26
+
27
+ # disable tty requirement for sudo
28
+ @machine.communicate.execute(<<-'BASH')
29
+ sed -i'.bk' -e 's/\(Defaults\s\+requiretty\)/# \1/' /etc/sudoers
30
+ BASH
31
+ end
32
+
33
+ # reset ssh username
34
+ @machine.config.ssh.username = user
35
+
36
+ @app.call(env)
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,62 @@
1
+ module VagrantPlugins
2
+ module Linode
3
+ module Actions
4
+ class SetupUser
5
+ def initialize(app, env)
6
+ @app = app
7
+ @machine = env[:machine]
8
+ @logger = Log4r::Logger.new('vagrant::linode::setup_user')
9
+ end
10
+
11
+ def call(env)
12
+ # check if setup is enabled
13
+ return @app.call(env) unless @machine.provider_config.setup?
14
+
15
+ # check if a username has been specified
16
+ return @app.call(env) unless @machine.config.ssh.username
17
+
18
+ # override ssh username to root temporarily
19
+ user = @machine.config.ssh.username
20
+ @machine.config.ssh.username = 'root'
21
+
22
+ env[:ui].info I18n.t('vagrant_linode.info.creating_user', user: user)
23
+
24
+ # create user account
25
+ @machine.communicate.execute(<<-BASH)
26
+ if ! (grep ^#{user}: /etc/passwd); then
27
+ useradd -m -s /bin/bash #{user};
28
+ fi
29
+ BASH
30
+
31
+ # grant user sudo access with no password requirement
32
+ @machine.communicate.execute(<<-BASH)
33
+ if ! (grep #{user} /etc/sudoers); then
34
+ echo "#{user} ALL=(ALL:ALL) NOPASSWD: ALL" >> /etc/sudoers;
35
+ else
36
+ sed -i -e "/#{user}/ s/=.*/=(ALL:ALL) NOPASSWD: ALL/" /etc/sudoers;
37
+ fi
38
+ BASH
39
+
40
+ # create the .ssh directory in the users home
41
+ @machine.communicate.execute("su #{user} -c 'mkdir -p ~/.ssh'")
42
+
43
+ # add the specified key to the authorized keys file
44
+ path = @machine.config.ssh.private_key_path
45
+ path = path[0] if path.is_a?(Array)
46
+ path = File.expand_path(path, @machine.env.root_path)
47
+ pub_key = Linode.public_key(path)
48
+ @machine.communicate.execute(<<-BASH)
49
+ if ! grep '#{pub_key}' /home/#{user}/.ssh/authorized_keys; then
50
+ echo '#{pub_key}' >> /home/#{user}/.ssh/authorized_keys;
51
+ fi
52
+ BASH
53
+
54
+ # reset username
55
+ @machine.config.ssh.username = user
56
+
57
+ @app.call(env)
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,80 @@
1
+ require 'vagrant/util/subprocess'
2
+ require 'vagrant/util/which'
3
+
4
+ module VagrantPlugins
5
+ module Linode
6
+ module Actions
7
+ class SyncFolders
8
+ def initialize(app, env)
9
+ @app = app
10
+ @machine = env[:machine]
11
+ @logger = Log4r::Logger.new('vagrant::linode::sync_folders')
12
+ end
13
+
14
+ def call(env)
15
+ ssh_info = @machine.ssh_info
16
+
17
+ @machine.config.vm.synced_folders.each do |_id, data|
18
+ next if data[:disabled]
19
+
20
+ if @machine.guest.capability?(:rsync_installed)
21
+ installed = @machine.guest.capability(:rsync_installed)
22
+ unless installed
23
+ can_install = @machine.guest.capability?(:rsync_install)
24
+ fail Vagrant::Errors::RSyncNotInstalledInGuest unless can_install
25
+ @machine.ui.info I18n.t('vagrant.rsync_installing')
26
+ @machine.guest.capability(:rsync_install)
27
+ end
28
+ end
29
+
30
+ hostpath = File.expand_path(data[:hostpath], env[:root_path])
31
+ guestpath = data[:guestpath]
32
+
33
+ # make sure there is a trailing slash on the host path to
34
+ # avoid creating an additional directory with rsync
35
+ hostpath = "#{hostpath}/" if hostpath !~ /\/$/
36
+
37
+ # on windows rsync.exe requires cygdrive-style paths
38
+ if Vagrant::Util::Platform.windows?
39
+ hostpath = hostpath.gsub(/^(\w):/) { "/cygdrive/#{Regexp.last_match[1]}" }
40
+ end
41
+
42
+ env[:ui].info I18n.t('vagrant_linode.info.rsyncing', hostpath: hostpath,
43
+ guestpath: guestpath)
44
+
45
+ # create the guest path
46
+ @machine.communicate.sudo("mkdir -p #{guestpath}")
47
+ @machine.communicate.sudo(
48
+ "chown -R #{ssh_info[:username]} #{guestpath}")
49
+
50
+ key = ssh_info[:private_key_path]
51
+ key = key[0] if key.is_a?(Array)
52
+
53
+ # rsync over to the guest path using the ssh info
54
+ command = [
55
+ 'rsync', '--verbose', '--archive', '-z', '--delete',
56
+ '-e', "ssh -p #{ssh_info[:port]} -o StrictHostKeyChecking=no -i '#{key}'",
57
+ hostpath,
58
+ "#{ssh_info[:username]}@#{ssh_info[:host]}:#{guestpath}"]
59
+
60
+ # we need to fix permissions when using rsync.exe on windows, see
61
+ # http://stackoverflow.com/questions/5798807/rsync-permission-denied-created-directories-have-no-permissions
62
+ if Vagrant::Util::Platform.windows?
63
+ command.insert(1, '--chmod', 'ugo=rwX')
64
+ end
65
+
66
+ r = Vagrant::Util::Subprocess.execute(*command)
67
+ if r.exit_code != 0
68
+ fail Errors::RsyncError,
69
+ guestpath: guestpath,
70
+ hostpath: hostpath,
71
+ stderr: r.stderr
72
+ end
73
+ end
74
+
75
+ @app.call(env)
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,160 @@
1
+ require 'vagrant-linode/actions/check_state'
2
+ require 'vagrant-linode/actions/create'
3
+ require 'vagrant-linode/actions/destroy'
4
+ require 'vagrant-linode/actions/power_off'
5
+ require 'vagrant-linode/actions/power_on'
6
+ require 'vagrant-linode/actions/rebuild'
7
+ require 'vagrant-linode/actions/reload'
8
+ require 'vagrant-linode/actions/setup_user'
9
+ require 'vagrant-linode/actions/setup_sudo'
10
+ require 'vagrant-linode/actions/setup_key'
11
+ require 'vagrant-linode/actions/sync_folders'
12
+ require 'vagrant-linode/actions/modify_provision_path'
13
+
14
+ module VagrantPlugins
15
+ module Linode
16
+ module Actions
17
+ include Vagrant::Action::Builtin
18
+
19
+ def self.destroy
20
+ Vagrant::Action::Builder.new.tap do |builder|
21
+ builder.use ConfigValidate
22
+ builder.use Call, CheckState do |env, b|
23
+ case env[:machine_state]
24
+ when :not_created
25
+ env[:ui].info I18n.t('vagrant_linode.info.not_created')
26
+ else
27
+ b.use Call, DestroyConfirm do |env2, b2|
28
+ if env2[:result]
29
+ b2.use Destroy
30
+ b2.use ProvisionerCleanup if defined?(ProvisionerCleanup)
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+
38
+ def self.ssh
39
+ Vagrant::Action::Builder.new.tap do |builder|
40
+ builder.use ConfigValidate
41
+ builder.use Call, CheckState do |env, b|
42
+ case env[:machine_state]
43
+ when :active
44
+ b.use SSHExec
45
+ when :off
46
+ env[:ui].info I18n.t('vagrant_linode.info.off')
47
+ when :not_created
48
+ env[:ui].info I18n.t('vagrant_linode.info.not_created')
49
+ end
50
+ end
51
+ end
52
+ end
53
+
54
+ def self.ssh_run
55
+ Vagrant::Action::Builder.new.tap do |builder|
56
+ builder.use ConfigValidate
57
+ builder.use Call, CheckState do |env, b|
58
+ case env[:machine_state]
59
+ when :active
60
+ b.use SSHRun
61
+ when :off
62
+ env[:ui].info I18n.t('vagrant_linode.info.off')
63
+ when :not_created
64
+ env[:ui].info I18n.t('vagrant_linode.info.not_created')
65
+ end
66
+ end
67
+ end
68
+ end
69
+
70
+ def self.provision
71
+ Vagrant::Action::Builder.new.tap do |builder|
72
+ builder.use ConfigValidate
73
+ builder.use Call, CheckState do |env, b|
74
+ case env[:machine_state]
75
+ when :active
76
+ b.use Provision
77
+ b.use ModifyProvisionPath
78
+ b.use SyncFolders
79
+ when :off
80
+ env[:ui].info I18n.t('vagrant_linode.info.off')
81
+ when :not_created
82
+ env[:ui].info I18n.t('vagrant_linode.info.not_created')
83
+ end
84
+ end
85
+ end
86
+ end
87
+
88
+ def self.up
89
+ Vagrant::Action::Builder.new.tap do |builder|
90
+ builder.use ConfigValidate
91
+ builder.use Call, CheckState do |env, b|
92
+ case env[:machine_state]
93
+ when :active
94
+ env[:ui].info I18n.t('vagrant_linode.info.already_active')
95
+ when :off
96
+ b.use PowerOn
97
+ b.use provision
98
+ when :not_created
99
+ # b.use SetupKey # no access to ssh keys in linode api
100
+ b.use Create
101
+ b.use SetupSudo
102
+ b.use SetupUser
103
+ b.use provision
104
+ end
105
+ end
106
+ end
107
+ end
108
+
109
+ def self.halt
110
+ Vagrant::Action::Builder.new.tap do |builder|
111
+ builder.use ConfigValidate
112
+ builder.use Call, CheckState do |env, b|
113
+ case env[:machine_state]
114
+ when :active
115
+ b.use PowerOff
116
+ when :off
117
+ env[:ui].info I18n.t('vagrant_linode.info.already_off')
118
+ when :not_created
119
+ env[:ui].info I18n.t('vagrant_linode.info.not_created')
120
+ end
121
+ end
122
+ end
123
+ end
124
+
125
+ def self.reload
126
+ Vagrant::Action::Builder.new.tap do |builder|
127
+ builder.use ConfigValidate
128
+ builder.use Call, CheckState do |env, b|
129
+ case env[:machine_state]
130
+ when :active
131
+ b.use Reload
132
+ b.use provision
133
+ when :off
134
+ env[:ui].info I18n.t('vagrant_linode.info.off')
135
+ when :not_created
136
+ env[:ui].info I18n.t('vagrant_linode.info.not_created')
137
+ end
138
+ end
139
+ end
140
+ end
141
+
142
+ def self.rebuild
143
+ Vagrant::Action::Builder.new.tap do |builder|
144
+ builder.use ConfigValidate
145
+ builder.use Call, CheckState do |env, b|
146
+ case env[:machine_state]
147
+ when :active, :off
148
+ b.use Rebuild
149
+ b.use SetupSudo
150
+ b.use SetupUser
151
+ b.use provision
152
+ when :not_created
153
+ env[:ui].info I18n.t('vagrant_linode.info.not_created')
154
+ end
155
+ end
156
+ end
157
+ end
158
+ end
159
+ end
160
+ end
@@ -0,0 +1,23 @@
1
+ require 'optparse'
2
+
3
+ module VagrantPlugins
4
+ module Linode
5
+ module Commands
6
+ class Rebuild < Vagrant.plugin('2', :command)
7
+ def execute
8
+ opts = OptionParser.new do |o|
9
+ o.banner = 'Usage: vagrant rebuild [vm-name]'
10
+ end
11
+
12
+ argv = parse_options(opts)
13
+
14
+ with_target_vms(argv) do |machine|
15
+ machine.action(:rebuild)
16
+ end
17
+
18
+ 0
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,65 @@
1
+ module VagrantPlugins
2
+ module Linode
3
+ class Config < Vagrant.plugin('2', :config)
4
+ attr_accessor :token
5
+ attr_accessor :api_url
6
+ attr_accessor :distribution
7
+ attr_accessor :datacenter
8
+ attr_accessor :plan
9
+ attr_accessor :paymentterm
10
+ attr_accessor :private_networking
11
+ attr_accessor :ca_path
12
+ attr_accessor :ssh_key_name
13
+ attr_accessor :setup
14
+ attr_accessor :xvda_size
15
+ attr_accessor :swap_size
16
+
17
+ alias_method :setup?, :setup
18
+
19
+ def initialize
20
+ @token = UNSET_VALUE
21
+ @api_url = UNSET_VALUE
22
+ @distribution = UNSET_VALUE
23
+ @datacenter = UNSET_VALUE
24
+ @plan = UNSET_VALUE
25
+ @paymentterm = UNSET_VALUE
26
+ @private_networking = UNSET_VALUE
27
+ @ca_path = UNSET_VALUE
28
+ @ssh_key_name = UNSET_VALUE
29
+ @setup = UNSET_VALUE
30
+ @xvda_size = UNSET_VALUE
31
+ @swap_size = UNSET_VALUE
32
+ end
33
+
34
+ def finalize!
35
+ @token = ENV['LINODE_TOKEN'] if @token == UNSET_VALUE
36
+ @api_url = ENV['LINODE_URL'] if @api_url == UNSET_VALUE
37
+ @distribution = 'Ubuntu 14.04 LTS' if @distribution == UNSET_VALUE
38
+ @datacenter = 'dallas' if @datacenter == UNSET_VALUE
39
+ @plan = 'Linode 1024' if @plan == UNSET_VALUE
40
+ @paymentterm = '1' if @paymentterm == UNSET_VALUE
41
+ @private_networking = false if @private_networking == UNSET_VALUE
42
+ @ca_path = nil if @ca_path == UNSET_VALUE
43
+ @ssh_key_name = 'Vagrant' if @ssh_key_name == UNSET_VALUE
44
+ @setup = true if @setup == UNSET_VALUE
45
+ @xvda_size = true if @xvda_size == UNSET_VALUE
46
+ @swap_size = '256' if @swap_size == UNSET_VALUE
47
+ end
48
+
49
+ def validate(machine)
50
+ errors = []
51
+ errors << I18n.t('vagrant_linode.config.token') unless @token
52
+
53
+ key = machine.config.ssh.private_key_path
54
+ key = key[0] if key.is_a?(Array)
55
+ if !key
56
+ errors << I18n.t('vagrant_linode.config.private_key')
57
+ elsif !File.file?(File.expand_path("#{key}.pub", machine.env.root_path))
58
+ errors << I18n.t('vagrant_linode.config.public_key', key: "#{key}.pub")
59
+ end
60
+
61
+ { 'Linode Provider' => errors }
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,49 @@
1
+ module VagrantPlugins
2
+ module Linode
3
+ module Errors
4
+ class LinodeError < Vagrant::Errors::VagrantError
5
+ error_namespace('vagrant_linode.errors')
6
+ end
7
+
8
+ class APIStatusError < LinodeError
9
+ error_key(:api_status)
10
+ end
11
+
12
+ class DiskSize < LinodeError
13
+ error_key(:disk_size)
14
+ end
15
+
16
+ class DistroMatch < LinodeError
17
+ error_key(:distro_match)
18
+ end
19
+
20
+ class JSONError < LinodeError
21
+ error_key(:json)
22
+ end
23
+
24
+ class ResultMatchError < LinodeError
25
+ error_key(:result_match)
26
+ end
27
+
28
+ class CertificateError < LinodeError
29
+ error_key(:certificate)
30
+ end
31
+
32
+ class LocalIPError < LinodeError
33
+ error_key(:local_ip)
34
+ end
35
+
36
+ class PlanID < LinodeError
37
+ error_key(:plan_id)
38
+ end
39
+
40
+ class PublicKeyError < LinodeError
41
+ error_key(:public_key)
42
+ end
43
+
44
+ class RsyncError < LinodeError
45
+ error_key(:rsync)
46
+ end
47
+ end
48
+ end
49
+ end