tugboat 2.0.1 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +17 -3
  3. data/features/{vagrant-adam → tugboat}/config_current_directory.feature +0 -0
  4. data/lib/tugboat/cli.rb +6 -1
  5. data/lib/tugboat/middleware/base.rb +59 -0
  6. data/lib/tugboat/middleware/check_credentials.rb +1 -10
  7. data/lib/tugboat/middleware/find_droplet.rb +23 -18
  8. data/lib/tugboat/middleware/list_droplets.rb +23 -19
  9. data/lib/tugboat/middleware/rebuild_droplet.rb +1 -1
  10. data/lib/tugboat/middleware/snapshot_droplet.rb +1 -1
  11. data/lib/tugboat/middleware/ssh_droplet.rb +15 -2
  12. data/lib/tugboat/middleware/wait_for_state.rb +1 -20
  13. data/lib/tugboat/version.rb +1 -1
  14. data/spec/cli/authorize_cli_spec.rb +2 -2
  15. data/spec/cli/create_cli_spec.rb +10 -10
  16. data/spec/cli/debug_cli_spec.rb +2 -2
  17. data/spec/cli/destroy_cli_spec.rb +22 -6
  18. data/spec/cli/droplets_cli_spec.rb +72 -8
  19. data/spec/cli/env_variable_spec.rb +4 -4
  20. data/spec/cli/halt_cli_spec.rb +24 -4
  21. data/spec/cli/info_cli_spec.rb +55 -10
  22. data/spec/cli/password_reset_cli_spec.rb +20 -4
  23. data/spec/cli/rebuild_cli_spec.rb +59 -19
  24. data/spec/cli/resize_cli_spec.rb +19 -3
  25. data/spec/cli/restart_cli_spec.rb +19 -3
  26. data/spec/cli/snapshot_cli_spec.rb +19 -3
  27. data/spec/cli/ssh_cli_spec.rb +37 -2
  28. data/spec/cli/start_cli_spec.rb +19 -3
  29. data/spec/cli/verify_cli_spec.rb +9 -9
  30. data/spec/cli/wait_cli_spec.rb +19 -3
  31. data/spec/fixtures/401.json +4 -0
  32. data/spec/fixtures/show_droplets.json +0 -4
  33. data/spec/fixtures/show_droplets_paginated_first.json +256 -0
  34. data/spec/fixtures/show_droplets_paginated_last.json +253 -0
  35. data/spec/fixtures/show_droplets_private_ip.json +258 -0
  36. data/spec/middleware/check_credentials_spec.rb +3 -3
  37. data/spec/middleware/ssh_droplet_spec.rb +21 -3
  38. data/tugboat.gemspec +1 -0
  39. metadata +26 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: dff182303d9c37521cf816ceb57e0b5b73c9f60f
4
- data.tar.gz: 98b25bcaa9909fcf9dbdc20a23e3db937e77148f
3
+ metadata.gz: 915e7c6438004c7d7e4f44e862b5b25805dfb3ba
4
+ data.tar.gz: 5ff32fa64a881616643aab6c263046b7ddccdcf5
5
5
  SHA512:
6
- metadata.gz: 84eed42cf4f8fbb5927b667bf05f243c34e070c58b9813150c790687fb1e5ed89958644f3c90b789e25e60f4797545f68f6684d7507239ea6fca76c01c9e2ce8
7
- data.tar.gz: 5ccd3c89a919f5a47bd71792effca44afb6c552a1d64d3a085f012cd6846eed8bedb2e191cc961a4570a8b90c33e6af56bfb1a1f76e887beb402016a8df39aff
6
+ metadata.gz: 94024933ca93615895225b97192bf3e6cbd29f960b8168cee9dd7611398a287e276eb3cec308bfebcd65a95b7e74dc91b6f5a20427f296af692c66ec8cca9726
7
+ data.tar.gz: 8c3ceb32fa4a6ffd82e49875646a7914e2aec85e5a4dd75d50eeca1175ed33b811c4857accccb569e15d9e9e9096f49e39dcb75ea36047986286ba46cdf1d303
@@ -2,7 +2,23 @@
2
2
 
3
3
  ## [Unreleased](https://github.com/pearkes/tugboat/tree/HEAD)
4
4
 
5
- [Full Changelog](https://github.com/pearkes/tugboat/compare/v2.0.0...HEAD)
5
+ [Full Changelog](https://github.com/pearkes/tugboat/compare/v2.0.1...HEAD)
6
+
7
+ ## [v2.0.1](https://github.com/pearkes/tugboat/tree/v2.0.1) (2015-11-10)
8
+
9
+ [Full Changelog](https://github.com/pearkes/tugboat/compare/v2.0.0...v2.0.1)
10
+
11
+ **Fixed bugs:**
12
+
13
+ - Slugs don't work [\#204](https://github.com/pearkes/tugboat/issues/204)
14
+
15
+ - New Droplets dont have IP address immediatly, so the info command fails [\#198](https://github.com/pearkes/tugboat/issues/198)
16
+
17
+ **Merged pull requests:**
18
+
19
+ - Fixes format for creating droplets [\#207](https://github.com/pearkes/tugboat/pull/207) ([petems](https://github.com/petems))
20
+
21
+ - Fixes issue with new machines having no network [\#203](https://github.com/pearkes/tugboat/pull/203) ([petems](https://github.com/petems))
6
22
 
7
23
  ## [v2.0.0](https://github.com/pearkes/tugboat/tree/v2.0.0) (2015-11-03)
8
24
 
@@ -38,8 +54,6 @@
38
54
 
39
55
  - tugboat doesn't work with team accounts, because they use api v2 [\#168](https://github.com/pearkes/tugboat/issues/168)
40
56
 
41
- - tugboat ssh not using ssh-agent? [\#160](https://github.com/pearkes/tugboat/issues/160)
42
-
43
57
  **Merged pull requests:**
44
58
 
45
59
  - Fixes multiple keys for droplet creation [\#201](https://github.com/pearkes/tugboat/pull/201) ([petems](https://github.com/petems))
@@ -127,6 +127,10 @@ module Tugboat
127
127
  :type => :string,
128
128
  :aliases => [ "-c", "-y" ,],
129
129
  :desc => "Command to run on the droplet"
130
+ method_option "wait",
131
+ :type => :boolean,
132
+ :aliases => '-w',
133
+ :desc => 'Wait for droplet to become active before trying to SSH'
130
134
  def ssh(name=nil)
131
135
  Middleware.sequence_ssh_droplet.call({
132
136
  "tugboat_action" => __method__,
@@ -138,7 +142,8 @@ module Tugboat
138
142
  "user_droplet_use_private_ip" => options[:use_private_ip],
139
143
  "user_droplet_ssh_opts" => options[:ssh_opts],
140
144
  "user_droplet_ssh_command" => options[:ssh_command],
141
- "user_quiet" => options[:quiet]
145
+ "user_droplet_ssh_wait" => options[:wait],
146
+ "user_quiet" => options[:quiet],
142
147
  })
143
148
  end
144
149
 
@@ -22,7 +22,66 @@ module Tugboat
22
22
  @app.call(env)
23
23
  end
24
24
 
25
+ def verify_credentials(ocean, say_success=false)
26
+ begin
27
+ response = ocean.droplet.all({:per_page =>'1', :page =>'1'})
28
+ rescue Faraday::ClientError => e
29
+ say "Authentication with DigitalOcean failed at an early stage"
30
+ say "Error was: #{e}"
31
+ exit 1
32
+ end
33
+
34
+ unless response.success?
35
+ say "Failed to connect to DigitalOcean. Reason given from API: #{response.id} - #{response.message}", :red
36
+ exit 1
37
+ end
38
+
39
+ say "Authentication with DigitalOcean was successful.", :green if say_success
40
+ end
41
+
42
+ def wait_for_state(droplet_id, desired_state,ocean)
43
+ start_time = Time.now
44
+
45
+ response = ocean.droplet.show droplet_id
46
+
47
+ say ".", nil, false
48
+
49
+ if !response.success?
50
+ say "Failed to get status of Droplet: #{response.message}", :red
51
+ exit 1
52
+ end
53
+
54
+ while response.droplet.status != desired_state do
55
+ sleep 2
56
+ response = ocean.droplet.show droplet_id
57
+ say ".", nil, false
58
+ end
59
+
60
+ total_time = (Time.now - start_time).to_i
61
+
62
+ say "done#{CLEAR} (#{total_time}s)", :green
63
+ end
64
+
65
+ # Get all pages of droplets
66
+ def get_droplet_list(ocean)
67
+
68
+ verify_credentials(ocean)
69
+
70
+ page = ocean.droplet.all(per_page: 200, page: 1)
71
+ if not page.paginated?
72
+ return page.droplets
73
+ end
74
+
75
+ Enumerator.new do |enum|
76
+ page.droplets.each { |drop| enum.yield drop }
77
+ for page_num in 2..page.last_page
78
+ page = ocean.droplet.all(per_page: 200, page: page_num)
79
+ page.droplets.each { |drop| enum.yield drop }
80
+ end
81
+ end
82
+ end
25
83
  end
84
+
26
85
  end
27
86
  end
28
87
 
@@ -7,16 +7,7 @@ module Tugboat
7
7
  def call(env)
8
8
  # We use a harmless API call to check if the authentication will
9
9
  # work.
10
- begin
11
- env['barge'].droplet.all.list
12
- rescue Faraday::Error::ClientError => e
13
- say "Authentication with DigitalOcean failed."
14
- say "Error was: #{e}"
15
- say "Try re-running `tugboat authorize`", :red
16
- exit 1
17
- end
18
-
19
- say "Authentication with DigitalOcean was successful.", :green
10
+ verify_credentials(env['barge'], true)
20
11
 
21
12
  @app.call(env)
22
13
  end
@@ -2,6 +2,19 @@ module Tugboat
2
2
  module Middleware
3
3
  # Check if the client has set-up configuration yet.
4
4
  class FindDroplet < Base
5
+ def get_public_ip(networks)
6
+ get_ip_per_network_type networks, "public"
7
+ end
8
+
9
+ def get_private_ip(networks)
10
+ get_ip_per_network_type networks, "private"
11
+ end
12
+
13
+ def get_ip_per_network_type(networks, type)
14
+ found_network = networks.detect { |n| n.type == type }
15
+ found_network.ip_address if found_network
16
+ end
17
+
5
18
  def call(env)
6
19
  ocean = env['barge']
7
20
  user_fuzzy_name = env['user_droplet_fuzzy_name']
@@ -44,8 +57,8 @@ module Tugboat
44
57
 
45
58
  env["droplet_id"] = response.droplet.id
46
59
  env["droplet_name"] = "(#{response.droplet.name})"
47
- env["droplet_ip"] = response.droplet.ip_address
48
- env["droplet_ip_private"] = response.droplet.private_ip_address
60
+ env["droplet_ip"] = get_public_ip response.droplet.networks.v4
61
+ env["droplet_ip_private"] = get_private_ip response.droplet.networks.v4
49
62
  env["droplet_status"] = response.droplet.status
50
63
  end
51
64
 
@@ -57,12 +70,12 @@ module Tugboat
57
70
  end
58
71
 
59
72
  # Look for the droplet by an exact name match.
60
- ocean.droplet.all.droplets.each do |d|
73
+ (get_droplet_list ocean).each do |d|
61
74
  if d.name == user_droplet_name
62
75
  env["droplet_id"] = d.id
63
76
  env["droplet_name"] = "(#{d.name})"
64
- env["droplet_ip"] = d.ip_address
65
- env["droplet_ip_private"] = d.private_ip_address
77
+ env["droplet_ip"] = get_public_ip d.networks.v4
78
+ env["droplet_ip_private"] = get_private_ip d.networks.v4
66
79
  env["droplet_status"] = d.status
67
80
  end
68
81
  end
@@ -86,7 +99,7 @@ module Tugboat
86
99
  found_droplets = []
87
100
  choices = []
88
101
 
89
- ocean.droplet.all.droplets.each_with_index do |d, i|
102
+ (get_droplet_list ocean).each do |d|
90
103
  # Check to see if one of the droplet names have the fuzzy string.
91
104
  if d.name.upcase.include? user_fuzzy_name.upcase
92
105
  found_droplets << d
@@ -104,9 +117,8 @@ module Tugboat
104
117
  env["droplet_ip"] = '' # No Network Yet
105
118
  env["droplet_ip_private"] = '' # No Network Yet
106
119
  else
107
- env["droplet_ip"] = droplet_return.networks.v4.detect { |address| address.type == 'public' }.ip_address
108
- check_private_ip = droplet_return.networks.v4.detect { |address| address.type == 'private' }
109
- env["droplet_ip_private"] = check_private_ip.ip_address if check_private_ip
120
+ env["droplet_ip"] = get_public_ip droplet_return.networks.v4
121
+ env["droplet_ip_private"] = get_private_ip droplet_return.networks.v4
110
122
  end
111
123
  env["droplet_status"] = droplet_return.status
112
124
  elsif found_droplets.length > 1
@@ -121,17 +133,10 @@ module Tugboat
121
133
  end
122
134
  say
123
135
  choice = ask "Please choose a droplet:", :limited_to => choices
124
- for ip_list in found_droplets[choice.to_i].networks.v4 do
125
- if ip_list.type == "private"
126
- private_ip = ip_list.ip_address
127
- elsif ip_list.type == "public"
128
- public_ip = ip_list.ip_address
129
- end
130
- end
131
136
  env["droplet_id"] = found_droplets[choice.to_i].id
132
137
  env["droplet_name"] = found_droplets[choice.to_i].name
133
- env["droplet_ip"] = public_ip
134
- env["droplet_ip_private"] = private_ip
138
+ env["droplet_ip"] = get_public_ip found_droplets[choice.to_i].networks.v4
139
+ env["droplet_ip_private"] = get_private_ip found_droplets[choice.to_i].networks.v4
135
140
  env["droplet_status"] = found_droplets[choice.to_i].status
136
141
  end
137
142
 
@@ -5,28 +5,32 @@ module Tugboat
5
5
  def call(env)
6
6
  ocean = env['barge']
7
7
 
8
- droplet_list = ocean.droplet.all.droplets
8
+ verify_credentials(ocean)
9
9
 
10
- if droplet_list.empty?
10
+ droplet_list = get_droplet_list ocean
11
+
12
+ has_one = false
13
+ droplet_list.each do |droplet|
14
+ has_one = true
15
+
16
+ private_addr = droplet.networks.v4.detect { |address| address.type == 'private' }
17
+ if private_addr
18
+ private_ip = ", private_ip: #{private_addr.ip_address}"
19
+ end
20
+
21
+ if droplet.status == "active"
22
+ status_color = GREEN
23
+ else
24
+ status_color = RED
25
+ end
26
+
27
+ public_addr = droplet.networks.v4.detect { |address| address.type == 'public' }
28
+ say "#{droplet.name} (ip: #{public_addr.ip_address}#{private_ip}, status: #{status_color}#{droplet.status}#{CLEAR}, region: #{droplet.region.slug}, id: #{droplet.id}#{env["include_urls"] ? droplet_id_to_url(droplet.id) : '' })"
29
+ end
30
+
31
+ if not has_one
11
32
  say "You don't appear to have any droplets.", :red
12
33
  say "Try creating one with #{GREEN}\`tugboat create\`#{CLEAR}"
13
- else
14
- droplet_list.each do |droplet|
15
-
16
- if droplet.private_ip_address
17
- private_addr = droplet.networks.v4.detect { |address| address.type == 'private' }
18
- private_ip = ", privateip: #{private_addr.ip_address}"
19
- end
20
-
21
- if droplet.status == "active"
22
- status_color = GREEN
23
- else
24
- status_color = RED
25
- end
26
-
27
- public_addr = droplet.networks.v4.detect { |address| address.type == 'public' }
28
- say "#{droplet.name} (ip: #{public_addr.ip_address}#{private_ip}, status: #{status_color}#{droplet.status}#{CLEAR}, region: #{droplet.region.slug}, id: #{droplet.id}#{env["include_urls"] ? droplet_id_to_url(droplet.id) : '' })"
29
- end
30
34
  end
31
35
 
32
36
  @app.call(env)
@@ -7,7 +7,7 @@ module Tugboat
7
7
  say "Queuing rebuild for droplet #{env["droplet_id"]} #{env["droplet_name"]} with image #{env["image_id"]} #{env["image_name"]}...", nil, false
8
8
 
9
9
  response = ocean.droplet.rebuild env["droplet_id"],
10
- :image_id => env["image_id"]
10
+ :image => env["image_id"]
11
11
 
12
12
  unless response.success?
13
13
  say "Failed to rebuild Droplet: #{response.message}", :red
@@ -17,7 +17,7 @@ module Tugboat
17
17
  say "Failed to snapshot Droplet: #{response.message}", :red
18
18
  exit 1
19
19
  else
20
- say "Snapshot successful!", :red
20
+ say "Snapshot successful!", :green
21
21
  end
22
22
 
23
23
  @app.call(env)
@@ -5,11 +5,17 @@ module Tugboat
5
5
  say "Executing SSH on Droplet #{env["droplet_name"]}..."
6
6
 
7
7
  options = [
8
- "-o", "IdentitiesOnly=yes",
9
8
  "-o", "LogLevel=ERROR",
10
9
  "-o", "StrictHostKeyChecking=no",
11
10
  "-o", "UserKnownHostsFile=/dev/null",
12
- "-i", File.expand_path(env["config"].ssh_key_path.to_s)]
11
+ ]
12
+
13
+ if env["config"].ssh_key_path.nil? || env["config"].ssh_key_path.empty?
14
+ options.push("-o", "IdentitiesOnly=no")
15
+ else
16
+ options.push("-o", "IdentitiesOnly=yes")
17
+ options.push("-i", File.expand_path(env["config"].ssh_key_path.to_s))
18
+ end
13
19
 
14
20
  if env["user_droplet_ssh_port"]
15
21
  options.push("-p", env["user_droplet_ssh_port"].to_s)
@@ -44,6 +50,11 @@ module Tugboat
44
50
 
45
51
  host_string = "#{ssh_user}@#{host_ip}"
46
52
 
53
+ if env['user_droplet_ssh_wait']
54
+ say "Wait flag given, waiting for droplet to become active"
55
+ wait_for_state(env["droplet_id"],'active',env['barge'])
56
+ end
57
+
47
58
  say "Attempting SSH: #{host_string}"
48
59
 
49
60
  options << host_string
@@ -52,6 +63,8 @@ module Tugboat
52
63
  options.push(env["user_droplet_ssh_command"])
53
64
  end
54
65
 
66
+ say "SShing with options: #{options.join(" ")}"
67
+
55
68
  Kernel.exec("ssh", *options)
56
69
 
57
70
  @app.call(env)
@@ -6,26 +6,7 @@ module Tugboat
6
6
 
7
7
  say "Waiting for droplet to become #{env["user_droplet_desired_state"]}.", nil, false
8
8
 
9
- start_time = Time.now
10
-
11
- response = ocean.droplet.show env["droplet_id"]
12
-
13
- say ".", nil, false
14
-
15
- if !response.success?
16
- say "Failed to get status of Droplet: #{response.message}", :red
17
- exit 1
18
- end
19
-
20
- while response.droplet.status != env["user_droplet_desired_state"] do
21
- sleep 2
22
- response = ocean.droplet.show env["droplet_id"]
23
- say ".", nil, false
24
- end
25
-
26
- total_time = (Time.now - start_time).to_i
27
-
28
- say "done#{CLEAR} (#{total_time}s)", :green
9
+ wait_for_state(env["droplet_id"],env["user_droplet_desired_state"],ocean)
29
10
 
30
11
  @app.call(env)
31
12
  end
@@ -1,3 +1,3 @@
1
1
  module Tugboat
2
- VERSION = "2.0.1"
2
+ VERSION = "2.1.0"
3
3
  end
@@ -11,7 +11,7 @@ describe Tugboat::CLI do
11
11
  end
12
12
 
13
13
  it "asks the right questions and checks credentials" do
14
- stub_request(:get, "https://api.digitalocean.com/v2/droplets?per_page=200").
14
+ stub_request(:get, "https://api.digitalocean.com/v2/droplets?page=1&per_page=1").
15
15
  with(:headers => {'Accept'=>'*/*', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Authorization'=>'Bearer foo', 'Content-Type'=>'application/json', 'User-Agent'=>'Faraday v0.9.2'}).
16
16
  to_return(:status => 200, :body => fixture('show_droplets'), :headers => {})
17
17
 
@@ -57,7 +57,7 @@ describe Tugboat::CLI do
57
57
  end
58
58
 
59
59
  it "sets defaults if no input given" do
60
- stub_request(:get, "https://api.digitalocean.com/v2/droplets?per_page=200").
60
+ stub_request(:get, "https://api.digitalocean.com/v2/droplets?page=1&per_page=1").
61
61
  with(:headers => {'Accept'=>'*/*', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Authorization'=>/Bearer/, 'Content-Type'=>'application/json', 'User-Agent'=>'Faraday v0.9.2'}).
62
62
  to_return(:status => 200, :body => fixture('show_droplets'), :headers => {})
63
63
 
@@ -72,8 +72,12 @@ Queueing creation of droplet 'example.com'...Could not find file: /foo/bar/baz.s
72
72
 
73
73
  end
74
74
 
75
- it "doesn't create a droplet when mistyping help command" do
76
- help_text = <<-eos
75
+ context "doesn't create a droplet when mistyping help command" do
76
+
77
+ ['help','--help','-h'].each do |help_attempt|
78
+ it "tugboat create #{help_attempt}" do
79
+
80
+ help_text = <<-eos
77
81
  Usage:
78
82
  rspec create NAME
79
83
 
@@ -91,14 +95,10 @@ Options:
91
95
  Create a droplet.
92
96
  eos
93
97
 
94
- @cli.create('help')
95
- expect($stdout.string).to eq help_text
96
-
97
- @cli.create('--help')
98
- expect($stdout.string).to eq help_text + help_text
99
-
100
- @cli.create('-h')
101
- expect($stdout.string).to eq help_text + help_text + help_text
98
+ @cli.create(help_attempt)
99
+ expect($stdout.string).to eq help_text
100
+ end
101
+ end
102
102
  end
103
103
 
104
104
  it "does not clobber named droplets that contain the word help" do
@@ -12,7 +12,7 @@ describe Tugboat::CLI do
12
12
 
13
13
  it "gives full faraday logs" do
14
14
  pending 'Debug flag not avaliable yet'
15
- stub_request(:get, "https://api.digitalocean.com/v2/droplets?per_page=200").
15
+ stub_request(:get, "https://api.digitalocean.com/v2/droplets?page=1&per_page=200").
16
16
  with(:headers => {'Accept'=>'*/*', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Authorization'=>'Bearer foo', 'Content-Type'=>'application/json', 'User-Agent'=>'Faraday v0.9.2'}).
17
17
  to_return(:status => 200, :body => fixture('show_droplets'), :headers => {})
18
18
  @cli.droplets
@@ -28,7 +28,7 @@ stub_request(:get, "https://api.digitalocean.com/v2/droplets?per_page=200").
28
28
 
29
29
  it "gives full faraday logs with redacted API keys" do
30
30
  pending 'Debug flag not avaliable yet'
31
- stub_request(:get, "https://api.digitalocean.com/v2/droplets?per_page=200").
31
+ stub_request(:get, "https://api.digitalocean.com/v2/droplets?page=1&per_page=200").
32
32
  with(:headers => {'Accept'=>'*/*', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Authorization'=>'Bearer foo', 'Content-Type'=>'application/json', 'User-Agent'=>'Faraday v0.9.2'}).
33
33
  to_return(:status => 200, :body => fixture('show_droplets'), :headers => {})
34
34
  @cli.droplets