vagrant-digitalocean 0.0.4 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. data/.gitignore +1 -1
  2. data/Gemfile +1 -4
  3. data/LICENSE.txt +2 -1
  4. data/README.md +114 -96
  5. data/Rakefile +8 -10
  6. data/box/digital_ocean.box +0 -0
  7. data/lib/vagrant-digitalocean.rb +12 -4
  8. data/lib/vagrant-digitalocean/actions.rb +159 -0
  9. data/lib/vagrant-digitalocean/actions/check_state.rb +19 -0
  10. data/lib/vagrant-digitalocean/actions/create.rb +36 -21
  11. data/lib/vagrant-digitalocean/actions/destroy.rb +10 -8
  12. data/lib/vagrant-digitalocean/actions/modify_provision_path.rb +15 -17
  13. data/lib/vagrant-digitalocean/actions/power_off.rb +33 -0
  14. data/lib/vagrant-digitalocean/actions/power_on.rb +34 -0
  15. data/lib/vagrant-digitalocean/actions/rebuild.rb +9 -9
  16. data/lib/vagrant-digitalocean/actions/reload.rb +31 -0
  17. data/lib/vagrant-digitalocean/actions/setup_key.rb +56 -0
  18. data/lib/vagrant-digitalocean/actions/setup_provisioner.rb +26 -15
  19. data/lib/vagrant-digitalocean/actions/setup_sudo.rb +19 -12
  20. data/lib/vagrant-digitalocean/actions/setup_user.rb +25 -27
  21. data/lib/vagrant-digitalocean/actions/sync_folders.rb +18 -17
  22. data/lib/vagrant-digitalocean/commands/rebuild.rb +2 -3
  23. data/lib/vagrant-digitalocean/config.rb +30 -33
  24. data/lib/vagrant-digitalocean/errors.rb +0 -2
  25. data/lib/vagrant-digitalocean/helpers/client.rb +13 -15
  26. data/lib/vagrant-digitalocean/plugin.rb +5 -14
  27. data/lib/vagrant-digitalocean/provider.rb +48 -34
  28. data/lib/vagrant-digitalocean/version.rb +1 -1
  29. data/locales/en.yml +32 -40
  30. data/test/Vagrantfile +37 -0
  31. data/test/cookbooks/test/recipes/default.rb +1 -1
  32. data/test/scripts/provision.sh +3 -0
  33. data/test/test.sh +14 -0
  34. metadata +15 -27
  35. data/bin/build.sh +0 -39
  36. data/bin/test_run.sh +0 -15
  37. data/box/Vagrantfile +0 -20
  38. data/box/cookbooks/foo/recipes/default.rb +0 -1
  39. data/lib/vagrant-digitalocean/action.rb +0 -105
  40. data/lib/vagrant-digitalocean/actions/check_ssh_user.rb +0 -46
  41. data/lib/vagrant-digitalocean/actions/is_active.rb +0 -16
  42. data/lib/vagrant-digitalocean/actions/message_is_active.rb +0 -18
  43. data/lib/vagrant-digitalocean/actions/read_state.rb +0 -33
  44. data/lib/vagrant-digitalocean/actions/setup_ssh_key.rb +0 -60
  45. data/lib/vagrant-digitalocean/helpers/file.rb +0 -41
  46. data/lib/vagrant-digitalocean/helpers/translator.rb +0 -20
  47. data/scripts/chef/debian.sh +0 -5
  48. data/scripts/chef/redhat.sh +0 -5
  49. data/scripts/nfs/debian.sh +0 -4
  50. data/scripts/nfs/redhat.sh +0 -11
  51. data/scripts/sudo/redhat.sh +0 -2
  52. data/test/Vagrantfile.centos +0 -19
  53. data/test/Vagrantfile.ubuntu +0 -21
  54. data/test/provision.sh +0 -3
  55. data/test/scripts/setup_user.sh +0 -18
@@ -1,25 +1,32 @@
1
- require "vagrant-digitalocean/helpers/file"
2
-
3
1
  module VagrantPlugins
4
2
  module DigitalOcean
5
3
  module Actions
6
4
  class SetupSudo
7
- include Helpers::File
8
-
9
5
  def initialize(app, env)
10
- @app, @env = app, env
11
- @translator = Helpers::Translator.new("actions.setup_sudo")
6
+ @app = app
7
+ @machine = env[:machine]
8
+ @logger = Log4r::Logger.new('vagrant::digitalocean::setup_sudo')
12
9
  end
13
10
 
14
11
  def call(env)
15
- env[:ui].info @translator.t("exec")
16
- env[:machine].communicate.execute(fix_sudo(env[:machine].guest))
12
+ # override ssh username to root temporarily
13
+ user = @machine.config.ssh.username
14
+ @machine.config.ssh.username = 'root'
17
15
 
18
- @app.call(env)
19
- end
16
+ case @machine.guest.to_s
17
+ when /RedHat/
18
+ env[:ui].info I18n.t('vagrant_digital_ocean.info.modifying_sudo')
20
19
 
21
- def fix_sudo(guest)
22
- read_script("sudo", guest, false)
20
+ # disable tty requirement for sudo
21
+ @machine.communicate.execute(<<-'BASH')
22
+ sed -i'.bk' -e 's/\(Defaults\s\+requiretty\)/# \1/' /etc/sudoers
23
+ BASH
24
+ end
25
+
26
+ # reset ssh username
27
+ @machine.config.ssh.username = user
28
+
29
+ @app.call(env)
23
30
  end
24
31
  end
25
32
  end
@@ -3,56 +3,54 @@ module VagrantPlugins
3
3
  module Actions
4
4
  class SetupUser
5
5
  def initialize(app, env)
6
- @app, @env = app, env
7
- @translator = Helpers::Translator.new("actions.setup_user")
6
+ @app = app
7
+ @machine = env[:machine]
8
+ @logger = Log4r::Logger.new('vagrant::digitalocean::setup_user')
8
9
  end
9
10
 
10
11
  def call(env)
11
- # create the user, set the password to username, add to sudoers
12
- # NOTE assumes group with username is created with useradd
13
- env[:ui].info @translator.t("create", :user => user)
14
- env[:machine].communicate.execute(<<-BASH)
12
+ # check if a username has been specified
13
+ return @app.call(env) if !@machine.config.ssh.username
14
+
15
+ # override ssh username to root temporarily
16
+ user = @machine.config.ssh.username
17
+ @machine.config.ssh.username = 'root'
18
+
19
+ env[:ui].info I18n.t('vagrant_digital_ocean.info.creating_user', {
20
+ :user => user
21
+ })
22
+
23
+ # create user account
24
+ @machine.communicate.execute(<<-BASH)
15
25
  if ! (grep #{user} /etc/passwd); then
16
26
  useradd -m -s /bin/bash #{user};
17
- echo -e "#{user}\n#{user}" | (passwd #{user});
18
27
  fi
19
28
  BASH
20
29
 
21
- env[:ui].info @translator.t("sudo", :user => user)
22
- env[:machine].communicate.execute(<<-BASH)
30
+ # grant user sudo access with no password requirement
31
+ @machine.communicate.execute(<<-BASH)
23
32
  if ! (grep #{user} /etc/sudoers); then
24
- echo "#{user} ALL=(ALL:ALL) ALL" >> /etc/sudoers;
33
+ echo "#{user} ALL=(ALL:ALL) NOPASSWD: ALL" >> /etc/sudoers;
25
34
  fi
26
35
  BASH
27
36
 
28
37
  # create the .ssh directory in the users home
29
- env[:machine].communicate.execute("su #{user} -c 'mkdir -p ~/.ssh'")
38
+ @machine.communicate.execute("su #{user} -c 'mkdir -p ~/.ssh'")
30
39
 
31
- env[:ui].info @translator.t("key")
32
40
  # add the specified key to the authorized keys file
33
- env[:machine].communicate.execute(<<-BASH)
41
+ private_key_path = @machine.ssh_info()[:private_key_path]
42
+ pub_key = DigitalOcean.public_key(private_key_path)
43
+ @machine.communicate.execute(<<-BASH)
34
44
  if ! grep '#{pub_key}' /home/#{user}/.ssh/authorized_keys; then
35
45
  echo '#{pub_key}' >> /home/#{user}/.ssh/authorized_keys;
36
46
  fi
37
47
  BASH
38
48
 
39
- env[:machine_state] ||= {}
40
- env[:machine_state][:user] = user
49
+ # reset username
50
+ @machine.config.ssh.username = user
41
51
 
42
52
  @app.call(env)
43
53
  end
44
-
45
- private
46
-
47
- # TODO use a config option to allow for alternate users
48
- def user
49
- "vagrant"
50
- end
51
-
52
- # TODO allow for a custom key to specified
53
- def pub_key
54
- @key ||= DigitalOcean.vagrant_key
55
- end
56
54
  end
57
55
  end
58
56
  end
@@ -1,40 +1,39 @@
1
- require "vagrant/util/subprocess"
1
+ require 'vagrant/util/subprocess'
2
2
 
3
3
  module VagrantPlugins
4
4
  module DigitalOcean
5
5
  module Actions
6
- # This middleware uses `rsync` to sync the folders over to the
7
- # Digital Ocean instance. The implementation was lifted from the
8
- # vagrant-aws provider plugin.
9
6
  class SyncFolders
10
7
  def initialize(app, env)
11
- @app, @env = app, env
12
- @translator = Helpers::Translator.new("actions.sync_folders")
8
+ @app = app
9
+ @machine = env[:machine]
10
+ @logger = Log4r::Logger.new('vagrant::digitalocean::sync_folders')
13
11
  end
14
12
 
15
13
  def call(env)
16
- @app.call(env)
14
+ ssh_info = @machine.ssh_info
17
15
 
18
- ssh_info = env[:machine].ssh_info
16
+ @machine.config.vm.synced_folders.each do |id, data|
17
+ next if data[:disabled]
19
18
 
20
- env[:machine].config.vm.synced_folders.each do |id, data|
21
19
  hostpath = File.expand_path(data[:hostpath], env[:root_path])
22
20
  guestpath = data[:guestpath]
23
21
 
24
- # Make sure there is a trailing slash on the host path to
22
+ # make sure there is a trailing slash on the host path to
25
23
  # avoid creating an additional directory with rsync
26
24
  hostpath = "#{hostpath}/" if hostpath !~ /\/$/
27
25
 
28
- env[:ui].info @translator.t("rsync_folder",
29
- :hostpath => hostpath,
30
- :guestpath => guestpath)
26
+ env[:ui].info I18n.t('vagrant_digital_ocean.info.rsyncing', {
27
+ :hostpath => hostpath,
28
+ :guestpath => guestpath
29
+ })
31
30
 
32
- # Create the guest path
33
- env[:machine].communicate.sudo("mkdir -p #{guestpath}")
34
- env[:machine].communicate.sudo(
31
+ # create the guest path
32
+ @machine.communicate.sudo("mkdir -p #{guestpath}")
33
+ @machine.communicate.sudo(
35
34
  "chown -R #{ssh_info[:username]} #{guestpath}")
36
35
 
37
- # Rsync over to the guest path using the SSH info
36
+ # rsync over to the guest path using the ssh info
38
37
  command = [
39
38
  "rsync", "--verbose", "--archive", "-z",
40
39
  "-e", "ssh -p #{ssh_info[:port]} -o StrictHostKeyChecking=no -i '#{ssh_info[:private_key_path]}'",
@@ -49,6 +48,8 @@ module VagrantPlugins
49
48
  :stderr => r.stderr
50
49
  end
51
50
  end
51
+
52
+ @app.call(env)
52
53
  end
53
54
  end
54
55
  end
@@ -3,14 +3,13 @@ require 'optparse'
3
3
  module VagrantPlugins
4
4
  module DigitalOcean
5
5
  module Commands
6
- class Rebuild < Vagrant.plugin("2", :command)
6
+ class Rebuild < Vagrant.plugin('2', :command)
7
7
  def execute
8
8
  opts = OptionParser.new do |o|
9
- o.banner = "Usage: vagrant rebuild [vm-name]"
9
+ o.banner = 'Usage: vagrant rebuild [vm-name]'
10
10
  end
11
11
 
12
12
  argv = parse_options(opts)
13
- return if !argv
14
13
 
15
14
  with_target_vms(argv) do |machine|
16
15
  machine.action(:rebuild)
@@ -1,52 +1,49 @@
1
1
  module VagrantPlugins
2
2
  module DigitalOcean
3
- class Config < Vagrant.plugin("2", :config)
4
- attr_accessor :client_id, :api_key, :image, :region, :size, :ca_path,
5
- :ssh_key_name, :ssh_private_key_path, :ssh_username
3
+ class Config < Vagrant.plugin('2', :config)
4
+ attr_accessor :client_id
5
+ attr_accessor :api_key
6
+ attr_accessor :image
7
+ attr_accessor :region
8
+ attr_accessor :size
9
+ attr_accessor :ca_path
10
+ attr_accessor :ssh_key_name
6
11
 
7
12
  def initialize
8
- @client_id = UNSET_VALUE
9
- @api_key = UNSET_VALUE
10
- @image = UNSET_VALUE
11
- @region = UNSET_VALUE
12
- @size = UNSET_VALUE
13
- @ca_path = UNSET_VALUE
14
- @ssh_key_name = UNSET_VALUE
15
- @ssh_private_key_path = UNSET_VALUE
16
- @ssh_username = UNSET_VALUE
17
-
18
- @translator = Helpers::Translator.new("config")
13
+ @client_id = UNSET_VALUE
14
+ @api_key = UNSET_VALUE
15
+ @image = UNSET_VALUE
16
+ @region = UNSET_VALUE
17
+ @size = UNSET_VALUE
18
+ @ca_path = UNSET_VALUE
19
+ @ssh_key_name = UNSET_VALUE
19
20
  end
20
21
 
21
22
  def finalize!
22
- @client_id = ENV["DO_CLIENT_ID"] if @client_id == UNSET_VALUE
23
- @api_key = ENV["DO_API_KEY"] if @api_key == UNSET_VALUE
24
- @image = "Ubuntu 12.04 x32 Server" if @image == UNSET_VALUE
25
- @region = "New York 1" if @region == UNSET_VALUE
26
- @size = "512MB" if @size == UNSET_VALUE
27
- @ca_path = nil if @ca_path == UNSET_VALUE
28
- @ssh_key_name = "Vagrant" if @ssh_key_name == UNSET_VALUE
29
- @ssh_private_key_path = nil if @ssh_private_key_path == UNSET_VALUE
30
- @ssh_username = "root" if @ssh_username == UNSET_VALUE
23
+ @client_id = ENV['DO_CLIENT_ID'] if @client_id == UNSET_VALUE
24
+ @api_key = ENV['DO_API_KEY'] if @api_key == UNSET_VALUE
25
+ @image = 'Ubuntu 12.04 x64 Server' if @image == UNSET_VALUE
26
+ @region = 'New York 1' if @region == UNSET_VALUE
27
+ @size = '512MB' if @size == UNSET_VALUE
28
+ @ca_path = nil if @ca_path == UNSET_VALUE
29
+ @ssh_key_name = 'Vagrant' if @ssh_key_name == UNSET_VALUE
31
30
  end
32
31
 
33
32
  def validate(machine)
34
33
  errors = []
35
- errors << @translator.t(:client_id_required) if !@client_id
36
- errors << @translator.t(:api_key_required) if !@api_key
37
-
38
- key = @ssh_private_key_path
39
- key = machine.config.ssh.private_key_path if !@ssh_private_key_path
34
+ errors << I18n.t('vagrant_digital_ocean.config.client_id') if !@client_id
35
+ errors << I18n.t('vagrant_digital_ocean.config.api_key') if !@api_key
40
36
 
37
+ key = machine.config.ssh.private_key_path
41
38
  if !key
42
- errors << @translator.t(:private_key_required) if !key
43
- elsif !File.file?(File.expand_path(key, machine.env.root_path))
44
- errors << @translator.t(:private_key_missing, { :key => key })
39
+ errors << I18n.t('vagrant_digital_ocean.config.private_key')
45
40
  elsif !File.file?(File.expand_path("#{key}.pub", machine.env.root_path))
46
- errors << @translator.t(:public_key_missing, { :key => key })
41
+ errors << I18n.t('vagrant_digital_ocean.config.public_key', {
42
+ :key => "#{key}.pub"
43
+ })
47
44
  end
48
45
 
49
- { "Digital Ocean Provider" => errors }
46
+ { 'Digital Ocean Provider' => errors }
50
47
  end
51
48
  end
52
49
  end
@@ -1,5 +1,3 @@
1
- require "vagrant"
2
-
3
1
  module VagrantPlugins
4
2
  module DigitalOcean
5
3
  module Errors
@@ -1,24 +1,23 @@
1
- require "vagrant-digitalocean/helpers/result"
2
- require "faraday"
3
- require "json"
1
+ require 'vagrant-digitalocean/helpers/result'
2
+ require 'faraday'
3
+ require 'json'
4
4
 
5
5
  module VagrantPlugins
6
6
  module DigitalOcean
7
7
  module Helpers
8
8
  module Client
9
9
  def client
10
- @client ||= ApiClient.new(@env)
10
+ @client ||= ApiClient.new(@machine)
11
11
  end
12
12
  end
13
13
 
14
14
  class ApiClient
15
15
  include Vagrant::Util::Retryable
16
16
 
17
- def initialize(env)
18
- @env = env
19
- @config = env[:machine].provider_config
17
+ def initialize(machine)
18
+ @config = machine.provider_config
20
19
  @client = Faraday.new({
21
- :url => "https://api.digitalocean.com/",
20
+ :url => 'https://api.digitalocean.com/',
22
21
  :ssl => {
23
22
  :ca_file => @config.ca_path
24
23
  }
@@ -43,7 +42,7 @@ module VagrantPlugins
43
42
  end
44
43
 
45
44
  # remove the api key in case an error gets dumped to the console
46
- params[:api_key] = "REMOVED"
45
+ params[:api_key] = 'REMOVED'
47
46
 
48
47
  begin
49
48
  body = JSON.parse(result.body)
@@ -56,11 +55,11 @@ module VagrantPlugins
56
55
  })
57
56
  end
58
57
 
59
- if body["status"] != "OK"
58
+ if body['status'] != 'OK'
60
59
  raise(Errors::APIStatusError, {
61
60
  :path => path,
62
61
  :params => params,
63
- :status => body["status"],
62
+ :status => body['status'],
64
63
  :response => body.inspect
65
64
  })
66
65
  end
@@ -68,17 +67,16 @@ module VagrantPlugins
68
67
  Result.new(body)
69
68
  end
70
69
 
71
- def wait_for_event(id)
70
+ def wait_for_event(env, id)
72
71
  retryable(:tries => 30, :sleep => 10) do
73
72
  # stop waiting if interrupted
74
- next if @env[:interrupted]
75
-
73
+ next if env[:interrupted]
76
74
 
77
75
  # check event status
78
76
  result = self.request("/events/#{id}")
79
77
 
80
78
  yield result if block_given?
81
- raise "not ready" if result["event"]["action_status"] != "done"
79
+ raise 'not ready' if result['event']['action_status'] != 'done'
82
80
  end
83
81
  end
84
82
  end
@@ -1,33 +1,24 @@
1
- require "i18n"
2
- require "vagrant-digitalocean/helpers/translator"
3
-
4
1
  module VagrantPlugins
5
2
  module DigitalOcean
6
- class Plugin < Vagrant.plugin("2")
7
- name "DigitalOcean"
3
+ class Plugin < Vagrant.plugin('2')
4
+ name 'DigitalOcean'
8
5
  description <<-DESC
9
6
  This plugin installs a provider that allows Vagrant to manage
10
7
  machines using DigitalOcean's API.
11
8
  DESC
12
9
 
13
10
  config(:digital_ocean, :provider) do
14
- require_relative "config"
11
+ require_relative 'config'
15
12
  Config
16
13
  end
17
14
 
18
15
  provider(:digital_ocean) do
19
- # Return the provider
20
- require_relative "provider"
21
-
22
- I18n.load_path << File.expand_path("locales/en.yml", DigitalOcean.source_root)
23
- I18n.reload!
24
- Helpers::Translator.plugin_namespace = "vagrant_digital_ocean"
25
-
16
+ require_relative 'provider'
26
17
  Provider
27
18
  end
28
19
 
29
20
  command(:rebuild) do
30
- require_relative "commands/rebuild"
21
+ require_relative 'commands/rebuild'
31
22
  Commands::Rebuild
32
23
  end
33
24
  end
@@ -1,24 +1,49 @@
1
- require "vagrant-digitalocean/action"
1
+ require 'vagrant-digitalocean/actions'
2
2
 
3
3
  module VagrantPlugins
4
4
  module DigitalOcean
5
- class Provider < Vagrant.plugin("2", :provider)
6
- # Initialize the provider to represent the given machine.
7
- #
8
- # @param [Vagrant::Machine] machine The machine that this provider
9
- # is responsible for.
5
+ class Provider < Vagrant.plugin('2', :provider)
6
+
7
+ # This class method caches status for all droplets within
8
+ # the Digital Ocean account. A specific droplet's status
9
+ # may be refreshed by passing :refresh => true as an option.
10
+ def self.droplet(machine, opts = {})
11
+ client = Helpers::ApiClient.new(machine)
12
+
13
+ # load status of droplets if it has not been done before
14
+ if !@droplets
15
+ result = client.request('/droplets')
16
+ @droplets = result['droplets']
17
+ end
18
+
19
+ if opts[:refresh] && machine.id
20
+ # refresh the droplet status for the given machine
21
+ @droplets.delete_if { |d| d['id'].to_s == machine.id }
22
+ result = client.request("/droplets/#{machine.id}")
23
+ @droplets << droplet = result['droplet']
24
+ else
25
+ # lookup droplet status for the given machine
26
+ droplet = @droplets.find { |d| d['id'].to_s == machine.id }
27
+ end
28
+
29
+ # if lookup by id failed, check for a droplet with a matching name
30
+ # and set the id to ensure vagrant stores locally
31
+ # TODO allow the user to configure this behavior
32
+ if !droplet
33
+ name = machine.config.vm.hostname || machine.name
34
+ droplet = @droplets.find { |d| d['name'] == name.to_s }
35
+ machine.id = droplet['id'].to_s if droplet
36
+ end
37
+
38
+ droplet ||= {'status' => 'not_created'}
39
+ end
40
+
10
41
  def initialize(machine)
11
42
  @machine = machine
12
- @dispatch = Action.new
13
43
  end
14
44
 
15
- # This should return an action callable for the given name.
16
- #
17
- # @param [Symbol] name Name of the action.
18
- # @return [Object] A callable action sequence object, whether it
19
- # is a proc, object, etc.
20
45
  def action(name)
21
- return @dispatch.action(name) if @dispatch.respond_to?(name)
46
+ return Actions.send(name) if Actions.respond_to?(name)
22
47
  nil
23
48
  end
24
49
 
@@ -49,38 +74,27 @@ module VagrantPlugins
49
74
  # mainly for the reason that there is no easy way to exec into an
50
75
  # `ssh` prompt with a password, whereas we can pass a private key
51
76
  # via commandline.
52
- #
53
- # @return [Hash] SSH information. For the structure of this hash
54
- # read the accompanying documentation for this method.
55
77
  def ssh_info
56
- state = @machine.action("read_state")[:machine_state]
57
-
58
- return nil if state["status"] == :not_created
78
+ droplet = Provider.droplet(@machine)
59
79
 
60
- # TODO remove when defect in vagrant chef provisioner is fixed
61
- @machine.config.ssh.username = @machine.provider_config.ssh_username
80
+ return nil if droplet['status'].to_sym != :active
62
81
 
82
+ # TODO remove config.ssh.username reference when Vagrant 1.2 is released
63
83
  return {
64
- :host => state["ip_address"],
65
- :port => "22",
66
- :username => @machine.provider_config.ssh_username,
67
- :private_key_path => @machine.provider_config.ssh_private_key_path
84
+ :host => droplet['ip_address'],
85
+ :port => '22',
86
+ :username => @machine.config.ssh.username || 'root',
87
+ :private_key_path => nil
68
88
  }
69
89
  end
70
90
 
71
91
  # This should return the state of the machine within this provider.
72
92
  # The state must be an instance of {MachineState}. Please read the
73
93
  # documentation of that class for more information.
74
- #
75
- # @return [MachineState]
76
94
  def state
77
- state_id = @machine.action("read_state")[:machine_state]["status"].to_sym
78
-
79
- # TODO provide an actual description
80
- long = short = state_id.to_s
81
-
82
- # Return the MachineState object
83
- Vagrant::MachineState.new(state_id, short, long)
95
+ state = Provider.droplet(@machine)['status'].to_sym
96
+ long = short = state.to_s
97
+ Vagrant::MachineState.new(state, short, long)
84
98
  end
85
99
  end
86
100
  end