tugboat 0.2.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.travis.yml +5 -4
  4. data/CHANGELOG.md +325 -102
  5. data/CHANGELOG_old.md +150 -0
  6. data/Gemfile +6 -0
  7. data/README.md +12 -6
  8. data/Rakefile +7 -3
  9. data/features/step_definitions/steps.rb +0 -0
  10. data/features/support/env.rb +9 -0
  11. data/features/vagrant-adam/config_current_directory.feature +27 -0
  12. data/lib/tugboat/cli.rb +31 -3
  13. data/lib/tugboat/config.rb +13 -5
  14. data/lib/tugboat/middleware.rb +8 -0
  15. data/lib/tugboat/middleware/ask_for_credentials.rb +5 -4
  16. data/lib/tugboat/middleware/authentication_middleware.rb +2 -2
  17. data/lib/tugboat/middleware/config.rb +29 -0
  18. data/lib/tugboat/middleware/custom_logger.rb +2 -2
  19. data/lib/tugboat/middleware/find_droplet.rb +4 -0
  20. data/lib/tugboat/middleware/info_droplet.rb +5 -0
  21. data/lib/tugboat/middleware/inject_client.rb +1 -1
  22. data/lib/tugboat/middleware/list_droplets.rb +5 -1
  23. data/lib/tugboat/middleware/list_regions.rb +2 -2
  24. data/lib/tugboat/middleware/ssh_droplet.rb +17 -2
  25. data/lib/tugboat/version.rb +1 -1
  26. data/spec/cli/add_key_spec.rb +9 -9
  27. data/spec/cli/authorize_cli_spec.rb +76 -52
  28. data/spec/cli/create_cli_spec.rb +39 -4
  29. data/spec/cli/debug_cli_spec.rb +44 -0
  30. data/spec/cli/destroy_cli_spec.rb +30 -11
  31. data/spec/cli/destroy_image_cli_spec.rb +11 -11
  32. data/spec/cli/droplets_cli_spec.rb +6 -6
  33. data/spec/cli/halt_cli_spec.rb +9 -9
  34. data/spec/cli/images_cli_spec.rb +6 -6
  35. data/spec/cli/info_cli_spec.rb +42 -7
  36. data/spec/cli/info_image_cli_spec.rb +6 -6
  37. data/spec/cli/keys_cli_spec.rb +1 -1
  38. data/spec/cli/password_reset_cli_spec.rb +8 -8
  39. data/spec/cli/rebuild_cli_spec.rb +49 -49
  40. data/spec/cli/regions_cli_spec.rb +4 -4
  41. data/spec/cli/resize_cli_spec.rb +8 -8
  42. data/spec/cli/restart_cli_spec.rb +8 -8
  43. data/spec/cli/sizes_cli_spec.rb +1 -1
  44. data/spec/cli/snapshot_cli_spec.rb +7 -7
  45. data/spec/cli/ssh_cli_spec.rb +4 -4
  46. data/spec/cli/start_cli_spec.rb +7 -7
  47. data/spec/cli/verify_cli_spec.rb +10 -2
  48. data/spec/cli/wait_cli_spec.rb +6 -6
  49. data/spec/fixtures/500.html +68 -0
  50. data/spec/fixtures/show_droplet.json +1 -0
  51. data/spec/fixtures/show_droplet_fuzzy.json +13 -0
  52. data/spec/fixtures/show_droplet_inactive.json +1 -0
  53. data/spec/fixtures/show_droplets.json +2 -0
  54. data/spec/fixtures/show_droplets_fuzzy.json +35 -0
  55. data/spec/fixtures/show_droplets_inactive.json +2 -0
  56. data/spec/fixtures/show_regions.json +9 -6
  57. data/spec/middleware/base_spec.rb +1 -1
  58. data/spec/middleware/check_credentials_spec.rb +1 -1
  59. data/spec/middleware/inject_client_spec.rb +29 -0
  60. data/spec/middleware/inject_configuration_spec.rb +1 -1
  61. data/spec/middleware/ssh_droplet_spec.rb +30 -7
  62. data/spec/shared/environment.rb +1 -0
  63. data/spec/spec_helper.rb +11 -4
  64. data/tugboat.gemspec +9 -7
  65. metadata +64 -33
@@ -6,10 +6,10 @@ describe Tugboat::CLI do
6
6
  describe "wait" do
7
7
  it "waits for a droplet with a fuzzy name" do
8
8
  stub_request(:get, "https://api.digitalocean.com/droplets?api_key=#{api_key}&client_id=#{client_key}").
9
- to_return(:status => 200, :body => fixture("show_droplets"))
9
+ to_return(:headers => {'Content-Type' => 'application/json'}, :status => 200, :body => fixture("show_droplets"))
10
10
 
11
11
  stub_request(:get, "https://api.digitalocean.com/droplets/100823?api_key=#{api_key}&client_id=#{client_key}").
12
- to_return(:status => 200, :body => fixture("show_droplet"))
12
+ to_return(:headers => {'Content-Type' => 'application/json'}, :status => 200, :body => fixture("show_droplet"))
13
13
 
14
14
  @cli.options = @cli.options.merge(:state => "active")
15
15
  @cli.wait("foo")
@@ -25,10 +25,10 @@ eos
25
25
 
26
26
  it "waits for a droplet with an id" do
27
27
  stub_request(:get, "https://api.digitalocean.com/droplets/#{droplet_id}?api_key=#{api_key}&client_id=#{client_key}").
28
- to_return(:status => 200, :body => fixture("show_droplet"))
28
+ to_return(:headers => {'Content-Type' => 'application/json'}, :status => 200, :body => fixture("show_droplet"))
29
29
 
30
30
  stub_request(:get, "https://api.digitalocean.com/droplets/100823?api_key=#{api_key}&client_id=#{client_key}").
31
- to_return(:status => 200, :body => fixture("show_droplet"))
31
+ to_return(:headers => {'Content-Type' => 'application/json'}, :status => 200, :body => fixture("show_droplet"))
32
32
 
33
33
  @cli.options = @cli.options.merge(:id => droplet_id, :state => "active")
34
34
  @cli.wait
@@ -44,10 +44,10 @@ Waiting for droplet to become active..done\e[0m (0s)
44
44
 
45
45
  it "waits for a droplet with a name" do
46
46
  stub_request(:get, "https://api.digitalocean.com/droplets?api_key=#{api_key}&client_id=#{client_key}").
47
- to_return(:status => 200, :body => fixture("show_droplets"))
47
+ to_return(:headers => {'Content-Type' => 'application/json'}, :status => 200, :body => fixture("show_droplets"))
48
48
 
49
49
  stub_request(:get, "https://api.digitalocean.com/droplets/100823?api_key=#{api_key}&client_id=#{client_key}").
50
- to_return(:status => 200, :body => fixture("show_droplet"))
50
+ to_return(:headers => {'Content-Type' => 'application/json'}, :status => 200, :body => fixture("show_droplet"))
51
51
 
52
52
  @cli.options = @cli.options.merge(:name => droplet_name, :state => "active")
53
53
  @cli.wait
@@ -0,0 +1,68 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>DigitalOcean - Seems we've encountered a problem!</title>
5
+ <style type="text/css">
6
+ body {
7
+ background-color: #27a1e3;
8
+ margin: 0px;
9
+ padding: 0px;
10
+ }
11
+ div.header{
12
+ background-color: #E2F4FE;
13
+ background-image: url('https://assets.digitalocean.com/public/waves.png');
14
+ background-repeat: repeat-x;
15
+ background-position: center bottom;
16
+ padding-top: 150px;
17
+ }
18
+ div.main{
19
+ overflow: auto;
20
+ width: 960px;
21
+ margin: auto;
22
+ margin-top: 50px;
23
+ }
24
+ div.text{
25
+ width: 450px;
26
+ float: left;
27
+ }
28
+ div.text p{
29
+ color: #fff;
30
+ font-size: 18pt;
31
+ font-family: helvetica;
32
+ }
33
+ div.mascot{
34
+ width: 450px;
35
+ float: right;
36
+ }
37
+ div.copyright{
38
+ clear: both;
39
+ }
40
+ div.copyright p{
41
+ color: #fff;
42
+ text-align: center;
43
+ font-size: 10pt;
44
+ font-family: helvetica;
45
+ padding-top: 60px;
46
+ }
47
+ </style>
48
+ </head>
49
+
50
+ <body>
51
+ <div class="header">
52
+ </div>
53
+ <div class="main">
54
+ <div class="text">
55
+ <a href="https://www.digitalocean.com"><img src="https://assets.digitalocean.com/public/logo.png"></a>
56
+ <p>Oh no! It seems as though we've encountered a problem! Please try your request again.</p>
57
+ </div>
58
+ <div class="mascot">
59
+ <img src="https://assets.digitalocean.com/public/mascot.png" />
60
+ </div>
61
+ </div>
62
+ <div class="copyright">
63
+ <p>&copy;2011-2013 DigitalOcean<sup>TM</sup>, Inc. All Rights Reserved.</p>
64
+ </div>
65
+ <div style="display:none">
66
+ ::CLOUDFLARE_ERROR_500S_BOX::
67
+ </div>
68
+ </body>
@@ -6,6 +6,7 @@
6
6
  "image_id": 420,
7
7
  "name": "foo",
8
8
  "ip_address": "33.33.33.10",
9
+ "private_ip_address": "10.20.30.40",
9
10
  "region_id": 1,
10
11
  "size_id": 33,
11
12
  "status": "active"
@@ -0,0 +1,13 @@
1
+ {
2
+ "status": "OK",
3
+ "droplet": {
4
+ "backups_active": null,
5
+ "id": 100823,
6
+ "image_id": 420,
7
+ "name": "test222",
8
+ "ip_address": "33.33.33.10",
9
+ "region_id": 1,
10
+ "size_id": 33,
11
+ "status": "active"
12
+ }
13
+ }
@@ -6,6 +6,7 @@
6
6
  "image_id": 420,
7
7
  "name": "foo",
8
8
  "ip_address": "33.33.33.10",
9
+ "private_ip_address": "10.20.30.40",
9
10
  "region_id": 1,
10
11
  "size_id": 33,
11
12
  "status": "off"
@@ -3,6 +3,7 @@
3
3
  "droplets": [
4
4
  {
5
5
  "ip_address": "33.33.33.10",
6
+ "private_ip_address": "10.20.30.1",
6
7
  "backups_active": null,
7
8
  "id": 100823,
8
9
  "image_id": 420,
@@ -23,6 +24,7 @@
23
24
  },
24
25
  {
25
26
  "ip_address": "33.33.33.10",
27
+ "private_ip_address": "10.20.30.40",
26
28
  "backups_active": null,
27
29
  "id": 100823,
28
30
  "image_id": 420,
@@ -0,0 +1,35 @@
1
+ {
2
+ "status": "OK",
3
+ "droplets": [
4
+ {
5
+ "ip_address": "33.33.33.10",
6
+ "backups_active": null,
7
+ "id": 100823,
8
+ "image_id": 420,
9
+ "name": "test222",
10
+ "region_id": 1,
11
+ "size_id": 33,
12
+ "status": "active"
13
+ },
14
+ {
15
+ "ip_address": "33.33.33.10",
16
+ "backups_active": null,
17
+ "id": 100824,
18
+ "image_id": 420,
19
+ "name": "test223",
20
+ "region_id": 1,
21
+ "size_id": 33,
22
+ "status": "active"
23
+ },
24
+ {
25
+ "ip_address": "33.33.33.10",
26
+ "backups_active": null,
27
+ "id": 100825,
28
+ "image_id": 420,
29
+ "name": "foo",
30
+ "region_id": 1,
31
+ "size_id": 33,
32
+ "status": "active"
33
+ }
34
+ ]
35
+ }
@@ -3,6 +3,7 @@
3
3
  "droplets": [
4
4
  {
5
5
  "ip_address": "33.33.33.10",
6
+ "private_ip_address": "10.20.30.1",
6
7
  "backups_active": null,
7
8
  "id": 100823,
8
9
  "image_id": 420,
@@ -23,6 +24,7 @@
23
24
  },
24
25
  {
25
26
  "ip_address": "33.33.33.10",
27
+ "private_ip_address": "10.20.30.40",
26
28
  "backups_active": null,
27
29
  "id": 100823,
28
30
  "image_id": 420,
@@ -1,17 +1,20 @@
1
1
  {
2
2
  "status": "OK",
3
3
  "regions": [
4
- {
5
- "id": 1,
6
- "name": "Region 1"
7
- },
8
4
  {
9
5
  "id": 2,
10
- "name": "Region 2"
6
+ "name": "Region 2",
7
+ "slug": "reg2"
11
8
  },
12
9
  {
13
10
  "id": 3,
14
- "name": "Region 3"
11
+ "name": "Region 3",
12
+ "slug": "reg3"
13
+ },
14
+ {
15
+ "id": 1,
16
+ "name": "Region 1",
17
+ "slug": "reg1"
15
18
  }
16
19
  ]
17
20
  }
@@ -7,7 +7,7 @@ describe Tugboat::Middleware::Base do
7
7
 
8
8
  describe ".initialize" do
9
9
  it "prints a clear line" do
10
- $stdout.should_receive(:print).with("")
10
+ expect($stdout).to receive(:print).with("")
11
11
  klass.new({})
12
12
  end
13
13
  end
@@ -6,7 +6,7 @@ describe Tugboat::Middleware::CheckCredentials do
6
6
  describe ".call" do
7
7
  it "raises SystemExit with no configuration" do
8
8
  stub_request(:get, "https://api.digitalocean.com/droplets?api_key=#{api_key}&client_id=#{client_key}").
9
- to_return(:status => 200, :body => "<html>You are being redirected...</html>")
9
+ to_return(:headers => {'Content-Type' => 'application/json'}, :status => 200, :body => "<html>You are being redirected...</html>")
10
10
 
11
11
  # Inject the client.
12
12
  env["ocean"] = ocean
@@ -0,0 +1,29 @@
1
+ require 'spec_helper'
2
+
3
+ describe Tugboat::Middleware::InjectClient do
4
+ include_context "spec"
5
+
6
+ let(:tmp_path) { project_path + "/tmp/tugboat" }
7
+
8
+ before :each do
9
+ config = Tugboat::Configuration.instance
10
+ env["config"] = config
11
+ end
12
+
13
+ describe ".call" do
14
+
15
+ it "loads the client into the environment" do
16
+ described_class.new(app).call(env)
17
+
18
+ env["ocean"].should be_a DigitalOcean::API
19
+ end
20
+
21
+ it "creates a client with values from config file" do
22
+ DigitalOcean::API.should_receive(:new).with(hash_including(:client_id=>"foo", :api_key=>"bar"))
23
+
24
+ described_class.new(app).call(env)
25
+ end
26
+
27
+ end
28
+
29
+ end
@@ -8,7 +8,7 @@ describe Tugboat::Middleware::InjectConfiguration do
8
8
  it "loads the configuration into the environment" do
9
9
  described_class.new(app).call(env)
10
10
 
11
- env["config"].should == config
11
+ expect(env["config"]).to eq(config)
12
12
  end
13
13
 
14
14
  end
@@ -4,44 +4,67 @@ describe Tugboat::Middleware::SSHDroplet do
4
4
  include_context "spec"
5
5
 
6
6
  before do
7
- Kernel.stub(:exec)
7
+ allow(Kernel).to receive(:exec)
8
8
  end
9
9
 
10
10
  describe ".call" do
11
11
 
12
12
  it "exec ssh with correct options" do
13
- Kernel.should_receive(:exec).with("ssh",
13
+ expect(Kernel).to receive(:exec).with("ssh",
14
14
  "-o", "IdentitiesOnly=yes",
15
15
  "-o", "LogLevel=ERROR",
16
16
  "-o", "StrictHostKeyChecking=no",
17
17
  "-o", "UserKnownHostsFile=/dev/null",
18
- "-i", ssh_key_path,
18
+ "-i", File.expand_path(ssh_key_path),
19
19
  "-p", ssh_port,
20
- "#{ssh_user}@#{droplet_ip}")
20
+ "#{ssh_user}@#{droplet_ip_private}")
21
21
 
22
22
  env["droplet_ip"] = droplet_ip
23
+ env["droplet_ip_private"] = droplet_ip_private
23
24
  env["config"] = config
24
25
 
25
26
  described_class.new(app).call(env)
26
27
  end
27
28
 
28
29
  it "executes ssh with custom options" do
29
- Kernel.should_receive(:exec).with("ssh",
30
+ expect(Kernel).to receive(:exec).with("ssh",
30
31
  "-o", "IdentitiesOnly=yes",
31
32
  "-o", "LogLevel=ERROR",
32
33
  "-o", "StrictHostKeyChecking=no",
33
34
  "-o", "UserKnownHostsFile=/dev/null",
34
- "-i", ssh_key_path,
35
+ "-i", File.expand_path(ssh_key_path),
35
36
  "-p", ssh_port,
37
+ "-e",
36
38
  "-q",
37
39
  "-X",
38
40
  "#{ssh_user}@#{droplet_ip}",
39
41
  "echo hello")
40
42
 
41
43
  env["droplet_ip"] = droplet_ip
44
+ env["droplet_ip_private"] = droplet_ip_private
42
45
  env["config"] = config
43
46
  env["user_droplet_ssh_command"] = "echo hello"
44
- env["user_droplet_ssh_opts"] = "-q -X"
47
+ env["user_droplet_use_public_ip"] = true
48
+ env["user_droplet_ssh_opts"] = "-e -q -X"
49
+
50
+ described_class.new(app).call(env)
51
+ end
52
+
53
+ it "executes ssh using public ip setting from config" do
54
+ config.data["use_public_ip"] = true
55
+
56
+ expect(Kernel).to receive(:exec).with("ssh",
57
+ "-o", "IdentitiesOnly=yes",
58
+ "-o", "LogLevel=ERROR",
59
+ "-o", "StrictHostKeyChecking=no",
60
+ "-o", "UserKnownHostsFile=/dev/null",
61
+ "-i", File.expand_path(ssh_key_path),
62
+ "-p", ssh_port,
63
+ "#{ssh_user}@#{droplet_ip}")
64
+
65
+ env["droplet_ip"] = droplet_ip
66
+ env["droplet_ip_private"] = droplet_ip_private
67
+ env["config"] = config
45
68
 
46
69
  described_class.new(app).call(env)
47
70
  end
@@ -10,6 +10,7 @@ shared_context "spec" do
10
10
  let(:ssh_key_path) { "~/.ssh/id_rsa2" }
11
11
  let(:droplet_name) { "foo" }
12
12
  let(:droplet_ip) { "33.33.33.10" }
13
+ let(:droplet_ip_private) { "10.20.30.40" }
13
14
  let(:droplet_id) { 1234 }
14
15
  let(:region) { '3' }
15
16
  let(:image) { '345791'}
data/spec/spec_helper.rb CHANGED
@@ -1,5 +1,12 @@
1
+ require 'simplecov'
1
2
  require 'coveralls'
2
- Coveralls.wear! { add_filter '/spec/' }
3
+ SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
4
+ SimpleCov::Formatter::HTMLFormatter,
5
+ Coveralls::SimpleCov::Formatter
6
+ ]
7
+ SimpleCov.start do
8
+ coverage_dir('coverage/')
9
+ end
3
10
 
4
11
  require 'tugboat'
5
12
  require 'webmock/rspec'
@@ -8,7 +15,7 @@ require "shared/environment"
8
15
 
9
16
  RSpec.configure do |config|
10
17
  # Pretty tests
11
- config.color_enabled = true
18
+ config.color = true
12
19
 
13
20
  config.order = :random
14
21
  end
@@ -17,8 +24,8 @@ def project_path
17
24
  File.expand_path("../..", __FILE__)
18
25
  end
19
26
 
20
- def fixture(fixture_name)
21
- File.new(project_path + "/spec/fixtures/#{fixture_name}.json")
27
+ def fixture(fixture_name, format='json')
28
+ File.new(project_path + "/spec/fixtures/#{fixture_name}.#{format}")
22
29
  end
23
30
 
24
31
  ENV["TUGBOAT_CONFIG_PATH"] = project_path + "/tmp/tugboat"
data/tugboat.gemspec CHANGED
@@ -12,20 +12,22 @@ Gem::Specification.new do |gem|
12
12
  gem.summary = %q{A command line tool for interacting with your DigitalOcean droplets.}
13
13
  gem.homepage = "https://github.com/pearkes/tugboat"
14
14
 
15
- gem.files = `git ls-files`.split($/)
16
- gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
- gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
- gem.require_paths = ["lib"]
15
+ gem.files = `git ls-files`.split($/)
16
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
+ gem.require_paths = ["lib"]
19
+ gem.required_ruby_version = ">= 1.9.2"
19
20
 
20
21
  gem.add_dependency "thor", "~> 0.18.1"
21
22
  gem.add_dependency "digital_ocean", "~> 1.0.1"
22
23
  gem.add_dependency "middleware" , "~> 0.1.0"
23
24
 
24
25
  gem.add_development_dependency "rake"
25
- gem.add_development_dependency "rspec-core", "~> 2.13.0"
26
- gem.add_development_dependency "rspec-expectations", "~> 2.13.0"
27
- gem.add_development_dependency "rspec-mocks", "~> 2.13.0"
26
+ gem.add_development_dependency "rspec-core", "~> 2.14.0"
27
+ gem.add_development_dependency "rspec-expectations", "~> 2.14.0"
28
+ gem.add_development_dependency "rspec-mocks", "~> 2.14.0"
28
29
  gem.add_development_dependency "webmock", "~> 1.11.0"
29
30
  gem.add_development_dependency "coveralls", "~> 0.6.7"
31
+ gem.add_development_dependency 'aruba', '~> 0.6.2'
30
32
 
31
33
  end