vagrant-linode 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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