vagrant-digitalocean 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -1,33 +1,47 @@
1
1
  # Vagrant Digital Ocean
2
2
 
3
- `vagrant-digitalocean` is a provider plugin for Vagrant that allows the management of [Digital Ocean](https://www.digitalocean.com/) droplets (instances).
3
+ `vagrant-digitalocean` is a provider plugin for Vagrant that allows the
4
+ management of [Digital Ocean](https://www.digitalocean.com/) droplets
5
+ (instances).
4
6
 
5
- ## SSH Key Support
7
+ ## SSH Authentication
6
8
 
7
- By default the provider will use the *insecure* vagrant key for SSH access. That means anyone with the vagrant key and the IP of your droplet has root on your machine. To ovvrride this behavior, define your SSH key name and public key path within the provider configuration:
9
+ This provider does not support the use of Vagrant's insecure key for SSH
10
+ access. You must specify your own SSH key. The key may be defined within
11
+ the global config section, `config.ssh.private_key_path`, or within the
12
+ provider config section, `provider.ssh_private_key_path`. The provider
13
+ config will take precedence. Additionally, you may provide a name for
14
+ the SSH key using the `ssh_key_name` attribute within the provider config
15
+ section. This is useful for de-conflict SSH keys used by different
16
+ individuals when creating machines on Digital Ocean.
8
17
 
9
18
  ```ruby
10
19
  config.vm.provider :digital_ocean do |provider|
11
20
  provider.ssh_key_name = "My Laptop"
12
- provider.pub_ssh_key_path = "~/.ssh/id_rsa.pub"
21
+ provider.ssh_private_key_path = "~/.ssh/id_rsa.pub"
13
22
 
14
23
  # additional configuration here
15
24
  end
16
25
  ```
17
26
 
18
- The provider will assume the private key path is identical to the public key path without the *.pub* extention. If this is not the case, define the private key path using `config.ssh.private_key_path`. If an SSH key name is not set, it will default to *Vagrant*.
27
+ The provider will assume the public key path is identical to the private
28
+ key path with the *.pub* extention.
19
29
 
20
- ## Status
21
-
22
- As of this writing the provider implementation is geared entirely toward a development workflow. That is, Digital Ocean droplets are meant to be used as a replacement for VirtualBox in a server developers workflow.
30
+ By default, the provider uses the `root` account for SSH access. This is
31
+ required for initial droplet creation and provisioning. You may specify
32
+ an account that may be used for subsequent SSH access and provisioning
33
+ by setting the `ssh_username` attribute within the provider config
34
+ section.
23
35
 
24
36
  ## Supported Guests/Hosts
25
37
 
26
- This project is primarily to support my workflow which currently only involves Ubuntu and CentOS. It's likely that any unix host will work but I've not tested it. Guests require porting of the nfs, chef, and sudo setup scripts.
38
+ The project is currently in alpha state and has been tested on the
39
+ following hosts and guests:
27
40
 
28
41
  Hosts:
29
42
 
30
43
  * Ubuntu 12.04
44
+ * Mac OS X
31
45
 
32
46
  Guests:
33
47
 
@@ -36,7 +50,9 @@ Guests:
36
50
 
37
51
  ## Supported Provisioners
38
52
 
39
- The shell provisioner is supported by default but other provisioners require bootstrapping on the server. Chef is currently the only supported provisioner. Adding support for puppet and others requires adding the install scripts.
53
+ The shell provisioner is supported by default but other provisioners require
54
+ bootstrapping on the server. Chef is currently the only supported provisioner.
55
+ Adding support for puppet and others requires adding the install scripts.
40
56
 
41
57
  ## Installation
42
58
 
@@ -44,13 +60,16 @@ Installation is performed in the prescribed manner for Vagrant 1.1 plugins.
44
60
 
45
61
  vagrant plugin install vagrant-digitalocean
46
62
 
47
- In addition to installing the plugin the default box associated with the provider needs to be installed.
63
+ In addition to installing the plugin the default box associated with the
64
+ provider needs to be installed.
48
65
 
49
66
  vagrant box add digital_ocean https://raw.github.com/johnbender/vagrant-digitalocean/master/box/digital_ocean.box
50
67
 
51
68
  ## Usage
52
69
 
53
- To use the Digital Ocean provider you will need to visit the [API access page](https://www.digitalocean.com/api_access) to retrieve the client identifier and API key associated with your account.
70
+ To use the Digital Ocean provider you will need to visit the
71
+ [API access page](https://www.digitalocean.com/api_access) to retrieve
72
+ the client identifier and API key associated with your account.
54
73
 
55
74
  ### Config
56
75
 
@@ -67,7 +86,8 @@ Vagrant.configure("2") do |config|
67
86
  vm.region = "New York 1"
68
87
  vm.size = "512MB"
69
88
  vm.ssh_key_name = "My Key"
70
- vm.pub_ssh_key_path = "~/.ssh/id_rsa"
89
+ vm.ssh_private_key_path = "~/.ssh/id_rsa"
90
+ vm.ssh_username = "test"
71
91
 
72
92
  # optional config for SSL cert on OSX and others
73
93
  vm.ca_path = "/usr/local/etc/openssl/ca-bundle.crt"
data/bin/build.sh CHANGED
@@ -1,5 +1,11 @@
1
1
  #!/bin/bash
2
2
 
3
+ # uninstall existing versions
4
+ gem uninstall -a vagrant-digitalocean
5
+
6
+ # clean old gem builds
7
+ rm *.gem
8
+
3
9
  # build the gem
4
10
  gem build *.gemspec
5
11
 
data/bin/test_run.sh CHANGED
@@ -1,7 +1,10 @@
1
1
  function run_test_for {
2
2
  cp Vagrantfile.$1 Vagrantfile
3
3
  vagrant up --provider=digital_ocean
4
+ vagrant up
4
5
  vagrant provision
6
+ vagrant rebuild
7
+ vagrant destroy
5
8
  vagrant destroy
6
9
  }
7
10
 
data/box/Vagrantfile CHANGED
@@ -3,7 +3,7 @@
3
3
 
4
4
  Vagrant.configure("2") do |config|
5
5
  config.vm.box = "digital_ocean"
6
- config.vm.synced_folder ".", "/vagrant", :nfs => true
6
+ config.ssh.private_key_path = "../test/test_id_rsa"
7
7
 
8
8
  config.vm.provider :digital_ocean do |vm|
9
9
  vm.client_id = ENV["DO_CLIENT_ID"]
@@ -8,12 +8,5 @@ module VagrantPlugins
8
8
  def self.source_root
9
9
  @source_root ||= Pathname.new(File.expand_path('../../', __FILE__))
10
10
  end
11
-
12
- def self.vagrant_key
13
- file = File.open(Vagrant.source_root + "keys/vagrant.pub")
14
- key = file.read
15
- file.close
16
- key
17
- end
18
11
  end
19
12
  end
@@ -1,15 +1,19 @@
1
1
  require "vagrant-digitalocean/actions/destroy"
2
2
  require "vagrant-digitalocean/actions/read_state"
3
3
  require "vagrant-digitalocean/actions/setup_provisioner"
4
- require "vagrant-digitalocean/actions/setup_nfs"
5
4
  require "vagrant-digitalocean/actions/setup_sudo"
6
- require "vagrant-digitalocean/actions/setup_user"
7
5
  require "vagrant-digitalocean/actions/create"
6
+ require "vagrant-digitalocean/actions/setup_ssh_key"
7
+ require "vagrant-digitalocean/actions/sync_folders"
8
+ require "vagrant-digitalocean/actions/rebuild"
9
+ require "vagrant-digitalocean/actions/is_active"
10
+ require "vagrant-digitalocean/actions/message_is_active"
11
+ require "vagrant-digitalocean/actions/check_ssh_user"
12
+ require "vagrant-digitalocean/actions/modify_provision_path"
8
13
 
9
14
  module VagrantPlugins
10
15
  module DigitalOcean
11
16
  class Action
12
- # Include the built-in callable actions, eg SSHExec
13
17
  include Vagrant::Action::Builtin
14
18
 
15
19
  def action(name)
@@ -19,7 +23,13 @@ module VagrantPlugins
19
23
  def destroy
20
24
  return Vagrant::Action::Builder.new.tap do |builder|
21
25
  builder.use ConfigValidate
22
- builder.use Actions::Destroy
26
+ builder.use Call, Actions::IsActive do |env, b|
27
+ if !env[:is_active]
28
+ b.use Actions::MessageIsActive
29
+ next
30
+ end
31
+ b.use Actions::Destroy
32
+ end
23
33
  end
24
34
  end
25
35
 
@@ -33,43 +43,61 @@ module VagrantPlugins
33
43
  def ssh
34
44
  return Vagrant::Action::Builder.new.tap do |builder|
35
45
  builder.use ConfigValidate
36
- builder.use SSHExec
46
+ builder.use Call, Actions::IsActive do |env, b|
47
+ if !env[:is_active]
48
+ b.use Actions::MessageIsActive
49
+ next
50
+ end
51
+ b.use Actions::CheckSSHUser
52
+ b.use SSHExec
53
+ end
37
54
  end
38
55
  end
39
56
 
40
57
  def provision
41
58
  return Vagrant::Action::Builder.new.tap do |builder|
42
59
  builder.use ConfigValidate
43
-
44
- # sort out sudo for redhat, etc
45
- builder.use Actions::SetupSudo
46
-
47
- # sort out sudo for redhat, etc
48
- builder.use Actions::SetupUser
49
-
50
- # execute provisioners
51
- builder.use Provision
52
-
53
- # setup provisioners, comes after Provision to force nfs folders
54
- builder.use Actions::SetupProvisioner
55
-
56
- # set the host and remote ips for NFS
57
- builder.use Actions::SetupNFS
58
-
59
- # mount the nfs folders which should be all shared folders
60
- builder.use NFS
60
+ builder.use Call, Actions::IsActive do |env, b|
61
+ if !env[:is_active]
62
+ b.use Actions::MessageIsActive
63
+ next
64
+ end
65
+ b.use Actions::CheckSSHUser
66
+ b.use Actions::ModifyProvisionPath
67
+ b.use Provision
68
+ b.use Actions::SetupSudo
69
+ b.use Actions::SetupProvisioner
70
+ b.use Actions::SyncFolders
71
+ end
61
72
  end
62
73
  end
63
74
 
64
75
  def up
65
- # TODO figure out when to exit if the vm is created
66
76
  return Vagrant::Action::Builder.new.tap do |builder|
67
77
  builder.use ConfigValidate
78
+ builder.use Call, Actions::IsActive do |env, b|
79
+ if env[:is_active]
80
+ b.use Actions::MessageIsActive
81
+ next
82
+ end
83
+ b.use Actions::SetupSSHKey
84
+ b.use Actions::Create
85
+ b.use provision
86
+ end
87
+ end
88
+ end
68
89
 
69
- # build the vm if necessary
70
- builder.use Actions::Create
71
-
72
- builder.use provision
90
+ def rebuild
91
+ return Vagrant::Action::Builder.new.tap do |builder|
92
+ builder.use ConfigValidate
93
+ builder.use Call, Actions::IsActive do |env, b|
94
+ if !env[:is_active]
95
+ b.use Actions::MessageIsActive
96
+ next
97
+ end
98
+ b.use Actions::Rebuild
99
+ b.use provision
100
+ end
73
101
  end
74
102
  end
75
103
  end
@@ -0,0 +1,46 @@
1
+ module VagrantPlugins
2
+ module DigitalOcean
3
+ module Actions
4
+ class CheckSSHUser
5
+ include Vagrant::Util::Counter
6
+
7
+ def initialize(app, env)
8
+ @app = app
9
+ @machine = env[:machine]
10
+ @translator = Helpers::Translator.new("actions.check_ssh_user")
11
+ end
12
+
13
+ def call(env)
14
+ # return if the machine is set with the default ssh username
15
+ return @app.call(env) if @machine.ssh_info()[:username] == "root"
16
+
17
+ # check if ssh username account has been provisioned
18
+ begin
19
+ tries = @machine.config.ssh.max_tries
20
+ @machine.config.ssh.max_tries = 0
21
+ @machine.communicate.execute("echo")
22
+ rescue Vagrant::Errors::SSHAuthenticationFailed
23
+ original_username = @machine.ssh_info()[:username]
24
+ @machine.provider_config.ssh_username = "root"
25
+ env[:ui].info @translator.t('fallback', {
26
+ :user => original_username
27
+ })
28
+
29
+ # TODO remove when vagrant chef provisioning defect fixed
30
+ @machine.config.ssh.username = "root"
31
+ end
32
+
33
+ @machine.config.ssh.max_tries = tries
34
+
35
+ @app.call(env)
36
+
37
+ # reset ssh username
38
+ @machine.provider_config.ssh_username = original_username
39
+
40
+ # TODO remove when vagrant chef provisioning defect fixed
41
+ @machine.config.ssh.username = original_username
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -14,28 +14,7 @@ module VagrantPlugins
14
14
  end
15
15
 
16
16
  def call(env)
17
- # if the machine state is created skip
18
- if env[:machine].state.id == :active
19
- env[:ui].info @translator.t("skip")
20
- return @app.call(env)
21
- end
22
-
23
- # TODO check the content of the key to see if it has changed
24
- ssh_key_name = env[:machine].provider_config.ssh_key_name
25
- begin
26
- ssh_key_id = @client
27
- .request("/ssh_keys/")
28
- .find_id(:ssh_keys, :name => ssh_key_name)
29
- rescue Errors::ResultMatchError
30
- env[:ui].info @translator.t("create_key")
31
-
32
- result = @client.request("/ssh_keys/new", {
33
- :name => ssh_key_name,
34
- :ssh_pub_key => pub_ssh_key(env)
35
- })
36
-
37
- ssh_key_id = result["ssh_key"]["id"]
38
- end
17
+ ssh_key_id = env[:ssh_key_id]
39
18
 
40
19
  size_id = @client
41
20
  .request("/sizes")
@@ -60,21 +39,11 @@ module VagrantPlugins
60
39
  :ssh_key_ids => ssh_key_id
61
40
  })
62
41
 
63
- # assign the machine id for reference in other commands
64
- env[:machine].id = result["droplet"]["id"].to_s
65
-
66
42
  env[:ui].info @translator.t("wait_active")
43
+ @client.wait_for_event(result["droplet"]["event_id"])
67
44
 
68
- retryable(:tries => 30, :sleep => 10) do
69
- # If we're interrupted don't worry about waiting
70
- next if env[:interrupted]
71
-
72
- # Wait for the server to be ready
73
- raise "not ready" if env[:machine].state.id != :active
74
- end
75
-
76
- # signal that the machine has just been created, used in ReadState
77
- env[:machine_just_created] = true
45
+ # assign the machine id for reference in other commands
46
+ env[:machine].id = result["droplet"]["id"].to_s
78
47
 
79
48
  @app.call(env)
80
49
  end
@@ -96,19 +65,6 @@ module VagrantPlugins
96
65
  destroy_env[:force_confirm_destroy] = true
97
66
  env[:action_runner].run(Action.new.destroy, destroy_env)
98
67
  end
99
-
100
- private
101
-
102
- def pub_ssh_key(env)
103
- path = env[:machine].provider_config.pub_ssh_key_path
104
- file = File.open(File.expand_path(path))
105
- key = file.read
106
- file.close
107
- key
108
- rescue
109
- env[:ui].info @translator.t("key_not_found")
110
- DigitalOcean.vagrant_key
111
- end
112
68
  end
113
69
  end
114
70
  end
@@ -14,28 +14,13 @@ module VagrantPlugins
14
14
  end
15
15
 
16
16
  def call(env)
17
- # TODO remove the key associated with this machine
18
- if [:active, :new].include?(env[:machine].state.id)
19
- env[:ui].info @translator.t("destroying")
20
- result = @client.request("/droplets/#{env[:machine].id}/destroy")
17
+ # submit destroy droplet request
18
+ env[:ui].info @translator.t("destroying")
19
+ result = @client.request("/droplets/#{env[:machine].id}/destroy")
21
20
 
22
- env[:ui].info @translator.t("wait_off")
23
-
24
- retryable(:tries => 30, :sleep => 10) do
25
- # If we're interrupted don't worry about waiting
26
- next if env[:interrupted]
27
-
28
- # Wait for the server to be ready
29
- raise "not off" if env[:machine].state.id != :off
30
- end
31
- else
32
- env[:ui].info @translator.t("not_active_or_new")
33
- end
34
-
35
- # make sure to remove the export when the machine is destroyed
36
- # private in some hosts and requires a send
37
- env[:ui].info @translator.t("clean_nfs")
38
- env[:host].send(:nfs_cleanup, env[:machine].id.to_s)
21
+ # wait for request to complete
22
+ env[:ui].info @translator.t("wait_off")
23
+ @client.wait_for_event(result["event_id"])
39
24
 
40
25
  @app.call(env)
41
26
  end
@@ -0,0 +1,16 @@
1
+ module VagrantPlugins
2
+ module DigitalOcean
3
+ module Actions
4
+ class IsActive
5
+ def initialize(app, env)
6
+ @app = app
7
+ end
8
+
9
+ def call(env)
10
+ env[:is_active] = env[:machine].state.id == :active
11
+ @app.call(env)
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,18 @@
1
+ module VagrantPlugins
2
+ module DigitalOcean
3
+ module Actions
4
+ class MessageIsActive
5
+ def initialize(app, env)
6
+ @app = app
7
+ @translator = Helpers::Translator.new("actions.message_is_active")
8
+ end
9
+
10
+ def call(env)
11
+ msg = env[:is_active] ? "active" : "not_active"
12
+ env[:ui].info @translator.t(msg)
13
+ @app.call(env)
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,40 @@
1
+ module VagrantPlugins
2
+ module DigitalOcean
3
+ module Actions
4
+ class ModifyProvisionPath
5
+ include Vagrant::Util::Counter
6
+
7
+ def initialize(app, env)
8
+ @app = app
9
+ @machine = env[:machine]
10
+ @translator =
11
+ Helpers::Translator.new("actions.modify_provision_path")
12
+ end
13
+
14
+ def call(env)
15
+ username = @machine.ssh_info()[:username]
16
+ env[:ui].info @translator.t("modify", { :user => username })
17
+
18
+ # modify provisioning paths to enable different users to
19
+ # provision the same machine
20
+ #
21
+ # TODO submit patch to vagrant to set appropriate permissions
22
+ # based on ssh username
23
+ @machine.communicate.execute("mkdir -p /home/#{username}/tmp")
24
+ env[:global_config].vm.provisioners.each do |prov|
25
+ if prov.name == :shell
26
+ prov.config.upload_path =
27
+ prov.config.upload_path.prepend("/home/#{username}")
28
+ else
29
+ counter = get_and_update_counter(:provisioning_path)
30
+ path = "/home/#{username}/tmp/vagrant-chef-#{counter}"
31
+ prov.config.provisioning_path = path
32
+ end
33
+ end
34
+
35
+ @app.call(env)
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,36 @@
1
+ require "vagrant-digitalocean/helpers/client"
2
+
3
+ module VagrantPlugins
4
+ module DigitalOcean
5
+ module Actions
6
+ class Rebuild
7
+ include Helpers::Client
8
+
9
+ def initialize(app, env)
10
+ @app, @env = app, env
11
+ @client = client
12
+ @translator = Helpers::Translator.new("actions.rebuild")
13
+ end
14
+
15
+ def call(env)
16
+ # look up image id
17
+ image_id = @client
18
+ .request("/images", { :filter => "global" })
19
+ .find_id(:images, :name => env[:machine].provider_config.image)
20
+
21
+ # submit rebuild request
22
+ env[:ui].info @translator.t("rebuild")
23
+ result = @client.request("/droplets/#{env[:machine].id}/rebuild", {
24
+ :image_id => image_id
25
+ })
26
+
27
+ # wait for request to complete
28
+ env[:ui].info @translator.t("wait")
29
+ @client.wait_for_event(result["event_id"])
30
+
31
+ @app.call(env)
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,60 @@
1
+ require "vagrant-digitalocean/helpers/client"
2
+
3
+ module VagrantPlugins
4
+ module DigitalOcean
5
+ module Actions
6
+ class SetupSSHKey
7
+ include Helpers::Client
8
+
9
+ def initialize(app, env)
10
+ @app, @env = app, env
11
+ @client = client
12
+ @translator = Helpers::Translator.new("actions.setup_ssh_key")
13
+ end
14
+
15
+ # TODO check the content of the key to see if it has changed
16
+ def call(env)
17
+ ssh_key_name = env[:machine].provider_config.ssh_key_name
18
+
19
+ begin
20
+ # assigns existing ssh key id to env for use by other commands
21
+ env[:ssh_key_id] = @client
22
+ .request("/ssh_keys/")
23
+ .find_id(:ssh_keys, :name => ssh_key_name)
24
+
25
+ env[:ui].info @translator.t("existing_key", { :name => ssh_key_name })
26
+ rescue Errors::ResultMatchError
27
+ env[:ssh_key_id] = create_ssh_key(ssh_key_name, env)
28
+ end
29
+
30
+ @app.call(env)
31
+ end
32
+
33
+ private
34
+
35
+ def create_ssh_key(name, env)
36
+ # assumes public key exists on the same path as private key with .pub ext
37
+ path = env[:machine].provider_config.ssh_private_key_path
38
+ path = env[:machine].config.ssh.private_key_path if !path
39
+ path = File.expand_path("#{path}.pub", env[:machine].env.root_path)
40
+
41
+ env[:ui].info @translator.t("new_key", { :name => name, :path => path })
42
+ begin
43
+ file = File.open(path)
44
+ key = file.read
45
+ file.close
46
+ rescue
47
+ raise Errors::PublicKeyError,
48
+ :path => path
49
+ end
50
+
51
+ result = @client.request("/ssh_keys/new", {
52
+ :name => name,
53
+ :ssh_pub_key => key
54
+ })
55
+ result["ssh_key"]["id"]
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,56 @@
1
+ require "vagrant/util/subprocess"
2
+
3
+ module VagrantPlugins
4
+ module DigitalOcean
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
+ class SyncFolders
10
+ def initialize(app, env)
11
+ @app, @env = app, env
12
+ @translator = Helpers::Translator.new("actions.sync_folders")
13
+ end
14
+
15
+ def call(env)
16
+ @app.call(env)
17
+
18
+ ssh_info = env[:machine].ssh_info
19
+
20
+ env[:machine].config.vm.synced_folders.each do |id, data|
21
+ hostpath = File.expand_path(data[:hostpath], env[:root_path])
22
+ guestpath = data[:guestpath]
23
+
24
+ # Make sure there is a trailing slash on the host path to
25
+ # avoid creating an additional directory with rsync
26
+ hostpath = "#{hostpath}/" if hostpath !~ /\/$/
27
+
28
+ env[:ui].info @translator.t("rsync_folder",
29
+ :hostpath => hostpath,
30
+ :guestpath => guestpath)
31
+
32
+ # Create the guest path
33
+ env[:machine].communicate.sudo("mkdir -p #{guestpath}")
34
+ env[:machine].communicate.sudo(
35
+ "chown -R #{ssh_info[:username]} #{guestpath}")
36
+
37
+ # Rsync over to the guest path using the SSH info
38
+ command = [
39
+ "rsync", "--verbose", "--archive", "-z",
40
+ "-e", "ssh -p #{ssh_info[:port]} -o StrictHostKeyChecking=no -i '#{ssh_info[:private_key_path]}'",
41
+ hostpath,
42
+ "#{ssh_info[:username]}@#{ssh_info[:host]}:#{guestpath}"]
43
+
44
+ r = Vagrant::Util::Subprocess.execute(*command)
45
+ if r.exit_code != 0
46
+ raise Errors::RsyncError,
47
+ :guestpath => guestpath,
48
+ :hostpath => hostpath,
49
+ :stderr => r.stderr
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,24 @@
1
+ require 'optparse'
2
+
3
+ module VagrantPlugins
4
+ module DigitalOcean
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
+ return if !argv
14
+
15
+ with_target_vms(argv) do |machine|
16
+ machine.action(:rebuild)
17
+ end
18
+
19
+ 0
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -2,34 +2,49 @@ module VagrantPlugins
2
2
  module DigitalOcean
3
3
  class Config < Vagrant.plugin("2", :config)
4
4
  attr_accessor :client_id, :api_key, :image, :region, :size, :ca_path,
5
- :ssh_key_name, :pub_ssh_key_path
5
+ :ssh_key_name, :ssh_private_key_path, :ssh_username
6
6
 
7
7
  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
- @pub_ssh_key_path = UNSET_VALUE
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")
16
19
  end
17
20
 
18
21
  def finalize!
19
- @client_id = ENV["DO_CLIENT_ID"] if @client_id == UNSET_VALUE
20
- @api_key = ENV["DO_API_KEY"] if @api_key == UNSET_VALUE
21
- @image = "Ubuntu 12.04 x32 Server" if @image == UNSET_VALUE
22
- @region = "New York 1" if @region == UNSET_VALUE
23
- @size = "512MB" if @size == UNSET_VALUE
24
- @ca_path = nil if @ca_path == UNSET_VALUE
25
- @ssh_key_name = "Vagrant" if @ssh_key_name == UNSET_VALUE
26
- @pub_ssh_key_path = nil if @pub_ssh_key_path == UNSET_VALUE
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
27
31
  end
28
32
 
29
33
  def validate(machine)
30
34
  errors = []
31
- errors << "Client ID required" if !@client_id
32
- errors << "API Key required" if !@api_key
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
40
+
41
+ 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 })
45
+ elsif !File.file?(File.expand_path("#{key}.pub", machine.env.root_path))
46
+ errors << @translator.t(:public_key_missing, { :key => key })
47
+ end
33
48
 
34
49
  { "Digital Ocean Provider" => errors }
35
50
  end
@@ -26,6 +26,14 @@ module VagrantPlugins
26
26
  class LocalIPError < DigitalOceanError
27
27
  error_key(:local_ip)
28
28
  end
29
+
30
+ class PublicKeyError < DigitalOceanError
31
+ error_key(:public_key)
32
+ end
33
+
34
+ class RsyncError < DigitalOceanError
35
+ error_key(:rsync)
36
+ end
29
37
  end
30
38
  end
31
39
  end
@@ -7,17 +7,20 @@ module VagrantPlugins
7
7
  module Helpers
8
8
  module Client
9
9
  def client
10
- @client ||= ApiClient.new(@env[:machine].provider_config)
10
+ @client ||= ApiClient.new(@env)
11
11
  end
12
12
  end
13
13
 
14
14
  class ApiClient
15
- def initialize(config)
16
- @config = config
15
+ include Vagrant::Util::Retryable
16
+
17
+ def initialize(env)
18
+ @env = env
19
+ @config = env[:machine].provider_config
17
20
  @client = Faraday.new({
18
21
  :url => "https://api.digitalocean.com/",
19
22
  :ssl => {
20
- :ca_file => config.ca_path
23
+ :ca_file => @config.ca_path
21
24
  }
22
25
  })
23
26
  end
@@ -64,6 +67,20 @@ module VagrantPlugins
64
67
 
65
68
  Result.new(body)
66
69
  end
70
+
71
+ def wait_for_event(id)
72
+ retryable(:tries => 30, :sleep => 10) do
73
+ # stop waiting if interrupted
74
+ next if @env[:interrupted]
75
+
76
+
77
+ # check event status
78
+ result = self.request("/events/#{id}")
79
+
80
+ yield result if block_given?
81
+ raise "not ready" if result["event"]["action_status"] != "done"
82
+ end
83
+ end
67
84
  end
68
85
  end
69
86
  end
@@ -12,7 +12,7 @@ module VagrantPlugins
12
12
 
13
13
  def t(keys, opts = {})
14
14
  value = I18n.t("#{@@plugin_namespace}.#{@namespace}.#{keys}", opts)
15
- opts[:progress] == false ? value : value + " ..."
15
+ opts[:progress] == false ? value : value + "..."
16
16
  end
17
17
  end
18
18
  end
@@ -25,6 +25,11 @@ module VagrantPlugins
25
25
 
26
26
  Provider
27
27
  end
28
+
29
+ command(:rebuild) do
30
+ require_relative "commands/rebuild"
31
+ Commands::Rebuild
32
+ end
28
33
  end
29
34
  end
30
35
  end
@@ -57,19 +57,14 @@ module VagrantPlugins
57
57
 
58
58
  return nil if state["status"] == :not_created
59
59
 
60
- if @machine.config.ssh.private_key_path
61
- private_key_path = @machine.config.ssh.private_key_path
62
- elsif @machine.provider_config.pub_ssh_key_path
63
- private_key_path = @machine.provider_config.pub_ssh_key_path.chomp(".pub")
64
- else
65
- private_key_path = Vagrant.source_root + "keys/vagrant"
66
- end
60
+ # TODO remove when defect in vagrant chef provisioner is fixed
61
+ @machine.config.ssh.username = @machine.provider_config.ssh_username
67
62
 
68
63
  return {
69
64
  :host => state["ip_address"],
70
65
  :port => "22",
71
- :username => "root",
72
- :private_key_path => private_key_path
66
+ :username => @machine.provider_config.ssh_username,
67
+ :private_key_path => @machine.provider_config.ssh_private_key_path
73
68
  }
74
69
  end
75
70
 
@@ -1,5 +1,5 @@
1
1
  module VagrantPlugins
2
- module Digitalocean
3
- VERSION = "0.0.3"
2
+ module DigitalOcean
3
+ VERSION = "0.0.4"
4
4
  end
5
5
  end
data/locales/en.yml CHANGED
@@ -1,6 +1,13 @@
1
1
  en:
2
2
  vagrant_digital_ocean:
3
+ config:
4
+ client_id_required: "Client ID required"
5
+ api_key_required: "API key required"
6
+ private_key_required: "SSH private key path required"
7
+ private_key_missing: "SSH private key is missing: %{key}"
8
+ public_key_missing: "SSH public key is missing: %{key}.pub"
3
9
  errors:
10
+ public_key: "Failed to read public key: %{path}"
4
11
  api_status: |-
5
12
  There was an issue with the request made to the Digital Ocean
6
13
  API at:
@@ -12,6 +19,15 @@ en:
12
19
 
13
20
  Status: %{status}
14
21
  Response: %{response}
22
+
23
+ rsync: |-
24
+ There was an error when attemping to rsync a share folder.
25
+ Please inspect the error message below for more info.
26
+
27
+ Host path: %{hostpath}
28
+ Guest path: %{guestpath}
29
+ Error: %{stderr}
30
+
15
31
  :json: |-
16
32
  There was an issue with the JSON response from the Digital Ocean
17
33
  API at:
@@ -48,26 +64,28 @@ en:
48
64
 
49
65
  actions:
50
66
  create:
51
- skip: "Droplet is active, skipping creation"
52
- create_key: "Adding key client account"
53
67
  create_droplet: "Creating the droplet"
54
68
  wait_active: "Waiting for the droplet to become active (>= 1 min)"
55
- key_not_found: "Public SSH key not provided, using insecure Vagrant key"
69
+ rebuild:
70
+ rebuild: "Rebuilding the droplet"
71
+ wait: "Waiting for the droplet to rebuild (>= 1 min)"
56
72
  destroy:
57
73
  destroying: "Destroying droplet"
58
- not_active_or_new: "Droplet not in the `active` or `new` state"
59
- clean_nfs: "Cleaning up NFS exports, may require sudo password"
60
74
  wait_off: "Waiting for the droplet to be destroyed"
61
75
  setup_sudo:
62
76
  exec: "Making alterations to the sudoers file where necessary"
63
- setup_nfs:
64
- machine_ip: "Droplet IP: %{ip}"
65
- host_ip: "Host IP: %{ip}"
66
- install: "Installing NFS on the droplet"
67
- force_shared_folders: "Forcing shared folders to use NFS where necessary"
68
77
  setup_provisioner:
69
78
  install: "Installing provisioner: %{provisioner} (>= 2 min)"
70
- setup_user:
71
- create: "Creating user '%{user}' and setting password"
72
- sudo: "Enabling sudo for user '%{user}'"
73
- key: "Adding public key to authorized_keys"
79
+ sync_folders:
80
+ rsync_folder: "Rsyncing folder: %{hostpath} => %{guestpath}"
81
+ setup_ssh_key:
82
+ existing_key: "Using existing SSH key: %{name}"
83
+ insecure_key: "Using default insecure vagrant SSH key"
84
+ new_key: "Creating new SSH key: %{name} (%{path})"
85
+ message_is_active:
86
+ active: "Droplet is already active"
87
+ not_active: "Droplet is not active"
88
+ check_ssh_user:
89
+ fallback: "Failed to authenticate with '%{user}', falling back to 'root'"
90
+ modify_provision_path:
91
+ modify: "Modifying provisioning path to '/home/%{user}/tmp'"
@@ -1,5 +1,6 @@
1
1
  Vagrant.configure("2") do |config|
2
2
  config.vm.box = "digital_ocean"
3
+ config.ssh.private_key_path = "test_id_rsa"
3
4
 
4
5
  config.vm.provider :digital_ocean do |vm|
5
6
  vm.client_id = ENV["DO_CLIENT_ID"]
@@ -8,10 +8,11 @@ Vagrant.configure("2") do |config|
8
8
  vm.region = "New York 1"
9
9
  vm.size = "512MB"
10
10
  vm.ssh_key_name = "Test Key"
11
- vm.pub_ssh_key_path = "test_id_rsa.pub"
11
+ vm.ssh_private_key_path = "test_id_rsa"
12
+ vm.ssh_username = "tester"
12
13
  end
13
14
 
14
- config.vm.provision :shell, :path => "provision.sh"
15
+ config.vm.provision :shell, :path => "scripts/setup_user.sh"
15
16
 
16
17
  config.vm.provision :chef_solo do |chef|
17
18
  chef.cookbooks_path = "cookbooks"
@@ -0,0 +1,18 @@
1
+ #!/bin/bash
2
+
3
+ if ! grep tester /etc/passwd; then
4
+ useradd -m -s /bin/bash tester
5
+ fi
6
+
7
+ if ! grep tester /etc/sudoers; then
8
+ echo "tester ALL=(ALL:ALL) NOPASSWD: ALL" >> /etc/sudoers
9
+ fi
10
+
11
+ mkdir -p /home/tester/.ssh
12
+ chown tester:tester /home/tester/.ssh
13
+
14
+ pub_key="ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCbFlHJ++BcXGU9b2K+jF990r16uqkKnWiK2CFS0PFvM9IJs3CoGiIlyc9UD9O4LEyeu5Rw0RdiAp9MvyUPcDUeibw3WlMCFJ53mbioAapMy5tPXmxxJH5KcN2uJKESsH/1hJv0tWfVpHQywVLcf/7HWPjDl3qEFqzwGEN+5V3XqyG+hoA4rLTLDL40G68bL/oC7ere3sz3B16U4NGdgtJZapot5gTFErFZZztql76h25Ch7isE1XAaYg6NY4z1oU8Q9Ud0sY74tDI8TF165LStb3prf1TinwaMbOyuQ1wrNU4aMzekiwazeo6LtHMnfPjweIGP01PwjZ8WkYcRF6tt digital_ocean provider test key"
15
+
16
+ if ! [ -e /home/tester/.ssh/authorized_keys ]; then
17
+ echo "${pub_key}" > /home/tester/.ssh/authorized_keys
18
+ fi
@@ -5,14 +5,13 @@ require 'vagrant-digitalocean/version'
5
5
 
6
6
  Gem::Specification.new do |gem|
7
7
  gem.name = "vagrant-digitalocean"
8
- gem.version = VagrantPlugins::Digitalocean::VERSION
8
+ gem.version = VagrantPlugins::DigitalOcean::VERSION
9
9
  gem.authors = ["John Bender"]
10
10
  gem.email = ["john.m.bender@gmail.com"]
11
11
  gem.description = %q{Enables Vagrant to manage Digital Ocean droplets}
12
12
  gem.summary = gem.description
13
13
 
14
14
  gem.files = `git ls-files`.split($/)
15
- gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
16
15
  gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
17
16
  gem.require_paths = ["lib"]
18
17
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: vagrant-digitalocean
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.0.4
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-03-25 00:00:00.000000000 Z
12
+ date: 2013-04-02 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: faraday
@@ -62,9 +62,7 @@ dependencies:
62
62
  description: Enables Vagrant to manage Digital Ocean droplets
63
63
  email:
64
64
  - john.m.bender@gmail.com
65
- executables:
66
- - build.sh
67
- - test_run.sh
65
+ executables: []
68
66
  extensions: []
69
67
  extra_rdoc_files: []
70
68
  files:
@@ -81,13 +79,20 @@ files:
81
79
  - box/metadata.json
82
80
  - lib/vagrant-digitalocean.rb
83
81
  - lib/vagrant-digitalocean/action.rb
82
+ - lib/vagrant-digitalocean/actions/check_ssh_user.rb
84
83
  - lib/vagrant-digitalocean/actions/create.rb
85
84
  - lib/vagrant-digitalocean/actions/destroy.rb
85
+ - lib/vagrant-digitalocean/actions/is_active.rb
86
+ - lib/vagrant-digitalocean/actions/message_is_active.rb
87
+ - lib/vagrant-digitalocean/actions/modify_provision_path.rb
86
88
  - lib/vagrant-digitalocean/actions/read_state.rb
87
- - lib/vagrant-digitalocean/actions/setup_nfs.rb
89
+ - lib/vagrant-digitalocean/actions/rebuild.rb
88
90
  - lib/vagrant-digitalocean/actions/setup_provisioner.rb
91
+ - lib/vagrant-digitalocean/actions/setup_ssh_key.rb
89
92
  - lib/vagrant-digitalocean/actions/setup_sudo.rb
90
93
  - lib/vagrant-digitalocean/actions/setup_user.rb
94
+ - lib/vagrant-digitalocean/actions/sync_folders.rb
95
+ - lib/vagrant-digitalocean/commands/rebuild.rb
91
96
  - lib/vagrant-digitalocean/config.rb
92
97
  - lib/vagrant-digitalocean/errors.rb
93
98
  - lib/vagrant-digitalocean/helpers/client.rb
@@ -107,6 +112,7 @@ files:
107
112
  - test/Vagrantfile.ubuntu
108
113
  - test/cookbooks/test/recipes/default.rb
109
114
  - test/provision.sh
115
+ - test/scripts/setup_user.sh
110
116
  - test/test_id_rsa
111
117
  - test/test_id_rsa.pub
112
118
  - vagrant-digitalocean.gemspec
@@ -139,5 +145,6 @@ test_files:
139
145
  - test/Vagrantfile.ubuntu
140
146
  - test/cookbooks/test/recipes/default.rb
141
147
  - test/provision.sh
148
+ - test/scripts/setup_user.sh
142
149
  - test/test_id_rsa
143
150
  - test/test_id_rsa.pub
@@ -1,61 +0,0 @@
1
- require "socket"
2
-
3
- module VagrantPlugins
4
- module DigitalOcean
5
- module Actions
6
- class SetupNFS
7
- include Helpers::File
8
-
9
- def initialize(app, env)
10
- @app, @env = app, env
11
- @translator = Helpers::Translator.new("actions.setup_nfs")
12
- end
13
-
14
- def call(env)
15
- # set the nfs machine ip
16
- env[:nfs_machine_ip] = env[:machine].provider.ssh_info[:host]
17
- env[:ui].info @translator.t("machine_ip", :ip => env[:nfs_machine_ip])
18
-
19
- # get the host ip from the local adapters
20
- raise Errors::LocalIPError if !(host_ip = determine_host_ip)
21
-
22
- env[:nfs_host_ip] = host_ip
23
-
24
- env[:ui].info @translator.t("host_ip", :ip => env[:nfs_host_ip])
25
-
26
- # make sure the nfs server is setup
27
- env[:ui].info @translator.t("install")
28
- env[:machine].communicate.execute(nfs_install(env[:machine].guest))
29
-
30
- vm = env[:machine].config.vm
31
-
32
- # force all shard folders to use nfs
33
- env[:ui].warn @translator.t("force_shared_folders")
34
- folders = vm.synced_folders.keys.each do |key|
35
- vm.synced_folders[key][:nfs] = true
36
- end
37
-
38
- @app.call(env)
39
- end
40
-
41
- # TODO not thread safe :(
42
- # TODO google ip seems like a bad idea
43
- def determine_host_ip
44
- # turn off reverse DNS resolution temporarily
45
- orig, Socket.do_not_reverse_lookup = Socket.do_not_reverse_lookup, true
46
-
47
- UDPSocket.open do |s|
48
- s.connect '64.233.187.99', 1
49
- s.addr.last
50
- end
51
- ensure
52
- Socket.do_not_reverse_lookup = orig
53
- end
54
-
55
- def nfs_install(guest)
56
- read_script("nfs", guest)
57
- end
58
- end
59
- end
60
- end
61
- end