tugboat 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. data/.gitignore +0 -1
  2. data/CHANGELOG.md +30 -0
  3. data/README.md +6 -4
  4. data/Rakefile +6 -0
  5. data/lib/tugboat/cli.rb +30 -7
  6. data/lib/tugboat/config.rb +16 -8
  7. data/lib/tugboat/middleware.rb +22 -10
  8. data/lib/tugboat/middleware/ask_for_credentials.rb +2 -1
  9. data/lib/tugboat/middleware/check_credentials.rb +1 -1
  10. data/lib/tugboat/middleware/confirm_action.rb +9 -5
  11. data/lib/tugboat/middleware/create_droplet.rb +2 -1
  12. data/lib/tugboat/middleware/find_droplet.rb +2 -2
  13. data/lib/tugboat/middleware/inject_configuration.rb +0 -1
  14. data/lib/tugboat/middleware/list_ssh_keys.rb +18 -0
  15. data/lib/tugboat/middleware/snapshot_droplet.rb +4 -1
  16. data/lib/tugboat/middleware/ssh_droplet.rb +8 -0
  17. data/lib/tugboat/version.rb +1 -1
  18. data/spec/cli/authorize_cli_spec.rb +37 -0
  19. data/spec/cli/create_cli_spec.rb +40 -0
  20. data/spec/cli/destroy_cli_spec.rb +93 -0
  21. data/spec/cli/droplets_cli_spec.rb +28 -0
  22. data/spec/cli/halt_cli_spec.rb +70 -0
  23. data/spec/cli/images_cli_spec.rb +53 -0
  24. data/spec/cli/info_cli_spec.rb +93 -0
  25. data/spec/cli/keys_cli_spec.rb +27 -0
  26. data/spec/cli/restart_cli_spec.rb +70 -0
  27. data/spec/cli/snapshot_cli_spec.rb +75 -0
  28. data/spec/cli/version_cli_spec.rb +20 -0
  29. data/spec/config_spec.rb +8 -15
  30. data/spec/fixtures/create_droplet.json +0 -0
  31. data/spec/fixtures/show_droplet.json +13 -0
  32. data/spec/fixtures/show_droplets.json +35 -0
  33. data/spec/fixtures/show_images.json +15 -0
  34. data/spec/fixtures/show_images_global.json +20 -0
  35. data/spec/fixtures/show_keys.json +13 -0
  36. data/spec/middleware/base_spec.rb +15 -0
  37. data/spec/middleware/inject_configuration_spec.rb +19 -0
  38. data/spec/shared/environment.rb +43 -0
  39. data/spec/spec_helper.rb +7 -0
  40. data/tmp/.gitkeep +0 -0
  41. data/tugboat.gemspec +7 -0
  42. metadata +141 -2
data/.gitignore CHANGED
@@ -9,4 +9,3 @@ Gemfile.lock
9
9
  doc/*
10
10
  log/*
11
11
  pkg/*
12
- tmp/*
@@ -0,0 +1,30 @@
1
+ ## 0.0.4 (unreleased)
2
+
3
+ BUG FIXES:
4
+
5
+ - Fix a syntax error caused by the order of arguments on `snapshot`.
6
+ This changes the argument order and is a breaking change [GH-10].
7
+ - Fix an issue with looking up a droplet by it's `--name`. A variable
8
+ was changed, and because it was shadowed passed inspection.
9
+
10
+ IMPROVEMENTS:
11
+
12
+ - Added a warning for snapshotting a droplet in a non-powered off
13
+ state. DigitalOcean currently doesn't return an error from their API.
14
+ - Added a `--confirm` or `-c` to confirmed actions, like destroy. [GH-7]
15
+ - [Ørjan](https://github.com/blom) added a `--version` command to see
16
+ what version of Tugboat you're using.
17
+ - Substantially more test coverage - all of the commands (except `ssh` are
18
+ now unit tested. [GH-15]
19
+
20
+ FEATURES:
21
+
22
+ - Optionally add a list of ssh_key_ids when creating a droplet. These
23
+ SSH keys will automatically be added to your droplet.
24
+ - Show a list of SSH keys on your account with `tugboat keys`
25
+ - [Phil](https://github.com/PhilETaylor) added the ability to specify
26
+ an `--ssh-port` on `tugboat ssh`, as well as set a default in your `.tugboat` [GH-13]
27
+
28
+ ## 0.0.3 (April 15, 2013)
29
+
30
+ Initial release.
data/README.md CHANGED
@@ -1,4 +1,5 @@
1
1
  # Tugboat
2
+ [![Build Status](https://travis-ci.org/pearkes/tugboat.png?branch=master)](https://travis-ci.org/pearkes/tugboat)
2
3
 
3
4
  A command line tool for interacting with your [DigitalOcean](https://www.digitalocean.com/) droplets.
4
5
 
@@ -50,7 +51,8 @@ tugboat handles multiple matches as well:
50
51
 
51
52
  ### SSH into a droplet
52
53
 
53
- You can configure a SSH username and key path in `tugboat authorize`.
54
+ *You can configure an SSH username and key path in `tugboat authorize`,
55
+ or by changing your `~/.tugboat`.*
54
56
 
55
57
  This lets you ssh into a droplet by providing it's name, or a partial
56
58
  match.
@@ -63,7 +65,7 @@ match.
63
65
 
64
66
  ### Create a droplet
65
67
 
66
- $ tugboat create pearkes-www-002 -s 64 -i 2676 -r 2
68
+ $ tugboat create pearkes-www-002 -s 64 -i 2676 -r 2 -k 11251
67
69
  Queueing creation of droplet 'pearkes-www-002'...done
68
70
 
69
71
  ### Info about a droplet
@@ -101,8 +103,8 @@ match.
101
103
 
102
104
  ### Snapshot a droplet
103
105
 
104
- $ tugboat snapshot admin test-admin-snaphot
105
- Queuing snapshot 'test' for 13231512 (pearkes-admin-001)...done
106
+ $ tugboat snapshot test-admin-snaphot admin
107
+ Queuing snapshot 'test-admin-snapshot' for 13231512 (pearkes-admin-001)...done
106
108
 
107
109
  ## Help
108
110
 
data/Rakefile CHANGED
@@ -1 +1,7 @@
1
1
  require "bundler/gem_tasks"
2
+ require 'rspec/core/rake_task'
3
+
4
+
5
+ RSpec::Core::RakeTask.new(:spec)
6
+
7
+ task :default => :spec
@@ -9,6 +9,8 @@ module Tugboat
9
9
 
10
10
  !check_unknown_options
11
11
 
12
+ map "--version" => :version, "-v" => :version
13
+
12
14
  desc "help [COMMAND]", "Describe commands or a specific command"
13
15
  def help(meth=nil)
14
16
  super
@@ -58,11 +60,16 @@ module Tugboat
58
60
  :type => :string,
59
61
  :aliases => "-n",
60
62
  :desc => "The exact name of the droplet"
63
+ method_option "ssh_port",
64
+ :type => :string,
65
+ :aliases => "-p",
66
+ :desc => "The custom SSH Port to connect to"
61
67
  def ssh(name=nil)
62
68
  Middleware.sequence_ssh_droplet.call({
63
69
  "user_droplet_id" => options[:id],
64
70
  "user_droplet_name" => options[:name],
65
- "user_droplet_fuzzy_name" => name
71
+ "user_droplet_fuzzy_name" => name,
72
+ "user_droplet_ssh_port" => options[:ssh_port]
66
73
  })
67
74
  end
68
75
 
@@ -82,11 +89,16 @@ module Tugboat
82
89
  :aliases => "-r",
83
90
  :default => 1,
84
91
  :desc => "The region_id of the droplet"
92
+ method_option "keys",
93
+ :type => :string,
94
+ :aliases => "-k",
95
+ :desc => "A comma separated list of SSH key ids to add to the droplet"
85
96
  def create(name)
86
97
  Middleware.sequence_create_droplet.call({
87
98
  "create_droplet_size_id" => options[:size],
88
99
  "create_droplet_image_id" => options[:image],
89
100
  "create_droplet_region_id" => options[:region],
101
+ "create_droplet_ssh_key_ids" => options[:keys],
90
102
  "create_droplet_name" => name
91
103
  })
92
104
  end
@@ -100,10 +112,15 @@ module Tugboat
100
112
  :type => :string,
101
113
  :aliases => "-n",
102
114
  :desc => "The exact name of the droplet"
115
+ method_option "confirm",
116
+ :type => :boolean,
117
+ :aliases => "-c",
118
+ :desc => "Skip confirmation of the action"
103
119
  def destroy(name=nil)
104
120
  Middleware.sequence_destroy_droplet.call({
105
121
  "user_droplet_id" => options[:id],
106
122
  "user_droplet_name" => options[:name],
123
+ "user_confirm_action" => options[:confirm],
107
124
  "user_droplet_fuzzy_name" => name
108
125
  })
109
126
  end
@@ -159,7 +176,7 @@ module Tugboat
159
176
  })
160
177
  end
161
178
 
162
- desc "snapshot FUZZY_NAME [OPTIONS]", "Queue a snapshot of the droplet."
179
+ desc "snapshot SNAPSHOT_NAME FUZZY_NAME [OPTIONS]", "Queue a snapshot of the droplet."
163
180
  method_option "id",
164
181
  :type => :string,
165
182
  :aliases => "-i",
@@ -168,11 +185,7 @@ module Tugboat
168
185
  :type => :string,
169
186
  :aliases => "-n",
170
187
  :desc => "The exact name of the droplet"
171
- method_option "snapshot",
172
- :type => :string,
173
- :aliases => "-s",
174
- :desc => "The name of the snapshot"
175
- def snapshot(name=nil, snapshot_name)
188
+ def snapshot(snapshot_name, name=nil)
176
189
  Middleware.sequence_snapshot_droplet.call({
177
190
  "user_droplet_id" => options[:id],
178
191
  "user_droplet_name" => options[:name],
@@ -180,6 +193,16 @@ module Tugboat
180
193
  "user_snapshot_name" => snapshot_name
181
194
  })
182
195
  end
196
+
197
+ desc "keys", "Show available SSH keys"
198
+ def keys
199
+ Middleware.sequence_ssh_keys.call({})
200
+ end
201
+
202
+ desc "version", "Show version"
203
+ def version
204
+ say "Tugboat #{Tugboat::VERSION}"
205
+ end
183
206
  end
184
207
  end
185
208
 
@@ -11,9 +11,10 @@ module Tugboat
11
11
 
12
12
  FILE_NAME = '.tugboat'
13
13
  DEFAULT_SSH_KEY_PATH = '.ssh/id_rsa'
14
+ DEFAULT_SSH_PORT = '22'
14
15
 
15
16
  def initialize
16
- @path = File.join(File.expand_path("~"), FILE_NAME)
17
+ @path = ENV["TUGBOAT_CONFIG_PATH"] || File.join(File.expand_path("~"), FILE_NAME)
17
18
  @data = self.load_config_file
18
19
  end
19
20
 
@@ -41,11 +42,9 @@ module Tugboat
41
42
  def ssh_user
42
43
  @data['ssh']['ssh_user']
43
44
  end
44
-
45
- # Allow the path to be set.
46
- def path=(path)
47
- @path = path
48
- path
45
+
46
+ def ssh_port
47
+ @data['ssh']['ssh_port']
49
48
  end
50
49
 
51
50
  # Re-runs initialize
@@ -53,8 +52,13 @@ module Tugboat
53
52
  self.send(:initialize)
54
53
  end
55
54
 
55
+ # Re-loads the config
56
+ def reload!
57
+ @data = self.load_config_file
58
+ end
59
+
56
60
  # Writes a config file
57
- def create_config_file(client, api, ssh_key_path, ssh_user)
61
+ def create_config_file(client, api, ssh_key_path, ssh_user, ssh_port)
58
62
  # Default SSH Key path
59
63
  if ssh_key_path.empty?
60
64
  ssh_key_path = File.join(File.expand_path("~"), DEFAULT_SSH_KEY_PATH)
@@ -64,10 +68,14 @@ module Tugboat
64
68
  ssh_user = ENV['USER']
65
69
  end
66
70
 
71
+ if ssh_port.empty?
72
+ ssh_port = DEFAULT_SSH_PORT
73
+ end
74
+
67
75
  require 'yaml'
68
76
  File.open(@path, File::RDWR|File::TRUNC|File::CREAT, 0600) do |file|
69
77
  data = {"authentication" => { "client_key" => client, "api_key" => api },
70
- "ssh" => { "ssh_user" => ssh_user, "ssh_key_path" => ssh_key_path }}
78
+ "ssh" => { "ssh_user" => ssh_user, "ssh_key_path" => ssh_key_path , "ssh_port" => ssh_port}}
71
79
  file.write data.to_yaml
72
80
  end
73
81
  end
@@ -19,8 +19,10 @@ module Tugboat
19
19
  autoload :ConfirmAction, "tugboat/middleware/confirm_action"
20
20
  autoload :SnapshotDroplet, "tugboat/middleware/snapshot_droplet"
21
21
  autoload :ListImages, "tugboat/middleware/list_images"
22
+ autoload :ListSSHKeys, "tugboat/middleware/list_ssh_keys"
22
23
 
23
- # This takes the user through the authorization flow
24
+ # Start the authorization flow.
25
+ # This writes a ~/.tugboat file, which can be edited manually.
24
26
  def self.sequence_authorize
25
27
  ::Middleware::Builder.new do
26
28
  use InjectConfiguration
@@ -32,7 +34,7 @@ module Tugboat
32
34
  end
33
35
  end
34
36
 
35
- # This provides a list of droplets
37
+ # Display a list of droplets
36
38
  def self.sequence_list_droplets
37
39
  ::Middleware::Builder.new do
38
40
  use InjectConfiguration
@@ -42,7 +44,7 @@ module Tugboat
42
44
  end
43
45
  end
44
46
 
45
- # This provides a list of droplets
47
+ # Display a list of images
46
48
  def self.sequence_list_images
47
49
  ::Middleware::Builder.new do
48
50
  use InjectConfiguration
@@ -52,7 +54,7 @@ module Tugboat
52
54
  end
53
55
  end
54
56
 
55
- # This restarts a droplet
57
+ # Restart a droplet
56
58
  def self.sequence_restart_droplet
57
59
  ::Middleware::Builder.new do
58
60
  use InjectConfiguration
@@ -63,7 +65,7 @@ module Tugboat
63
65
  end
64
66
  end
65
67
 
66
- # This halts a droplet
68
+ # Shutdown a droplet
67
69
  def self.sequence_halt_droplet
68
70
  ::Middleware::Builder.new do
69
71
  use InjectConfiguration
@@ -74,7 +76,7 @@ module Tugboat
74
76
  end
75
77
  end
76
78
 
77
- # This shows a droplet
79
+ # Show information about a droplet
78
80
  def self.sequence_info_droplet
79
81
  ::Middleware::Builder.new do
80
82
  use InjectConfiguration
@@ -85,7 +87,7 @@ module Tugboat
85
87
  end
86
88
  end
87
89
 
88
- # SSH Into a droplet
90
+ # SSH into a droplet
89
91
  def self.sequence_ssh_droplet
90
92
  ::Middleware::Builder.new do
91
93
  use InjectConfiguration
@@ -96,7 +98,7 @@ module Tugboat
96
98
  end
97
99
  end
98
100
 
99
- # SSH Into a droplet
101
+ # Create a droplet
100
102
  def self.sequence_create_droplet
101
103
  ::Middleware::Builder.new do
102
104
  use InjectConfiguration
@@ -106,7 +108,7 @@ module Tugboat
106
108
  end
107
109
  end
108
110
 
109
- # SSH Into a droplet
111
+ # Destroy a droplet
110
112
  def self.sequence_destroy_droplet
111
113
  ::Middleware::Builder.new do
112
114
  use InjectConfiguration
@@ -118,7 +120,7 @@ module Tugboat
118
120
  end
119
121
  end
120
122
 
121
- # SSH Into a droplet
123
+ # Snapshot a droplet
122
124
  def self.sequence_snapshot_droplet
123
125
  ::Middleware::Builder.new do
124
126
  use InjectConfiguration
@@ -128,5 +130,15 @@ module Tugboat
128
130
  use SnapshotDroplet
129
131
  end
130
132
  end
133
+
134
+ # Display a list of available SSH keys
135
+ def self.sequence_ssh_keys
136
+ ::Middleware::Builder.new do
137
+ use InjectConfiguration
138
+ use CheckConfiguration
139
+ use InjectClient
140
+ use ListSSHKeys
141
+ end
142
+ end
131
143
  end
132
144
  end
@@ -9,9 +9,10 @@ module Tugboat
9
9
  api_key = ask "Enter your API key:"
10
10
  ssh_key_path = ask "Enter your SSH key path (optional, defaults to ~/.ssh/id_rsa):"
11
11
  ssh_user = ask "Enter your SSH user (optional, defaults to #{ENV['USER']}):"
12
+ ssh_port = ask "Enter your SSH port number (optional, defaults to 22):"
12
13
 
13
14
  # Write the config file.
14
- env['config'].create_config_file(client_key, api_key, ssh_key_path, ssh_user)
15
+ env['config'].create_config_file(client_key, api_key, ssh_key_path, ssh_user, ssh_port)
15
16
 
16
17
  @app.call(env)
17
18
  end
@@ -9,7 +9,7 @@ module Tugboat
9
9
  # work.
10
10
  begin
11
11
  env["ocean"].droplets.list
12
- rescue
12
+ rescue Faraday::Error::ParsingError
13
13
  say "Authentication with DigitalOcean failed. Run `tugboat authorize`", :red
14
14
  return
15
15
  end
@@ -2,12 +2,16 @@ module Tugboat
2
2
  module Middleware
3
3
  class ConfirmAction < Base
4
4
  def call(env)
5
- response = yes? "Warning! Potentially destructive action. Please confirm [y/n]:"
6
5
 
7
- if !response
8
- say "Aborted due to user request.", :red
9
- # Quit
10
- return
6
+ if !env["user_confirm_action"]
7
+ response = yes? "Warning! Potentially destructive action. Please confirm [y/n]:"
8
+
9
+ if !response
10
+ say "Aborted due to user request.", :red
11
+ # Quit
12
+ return
13
+ end
14
+
11
15
  end
12
16
 
13
17
  @app.call(env)
@@ -9,7 +9,8 @@ module Tugboat
9
9
  req = ocean.droplets.create :name => env["create_droplet_name"],
10
10
  :size_id => env["create_droplet_size_id"],
11
11
  :image_id => env["create_droplet_image_id"],
12
- :region_id => env["create_droplet_region_id"]
12
+ :region_id => env["create_droplet_region_id"],
13
+ :ssh_key_ids => env["create_droplet_ssh_key_ids"]
13
14
 
14
15
  if req.status == "ERROR"
15
16
  say req.error_message, :red
@@ -11,7 +11,7 @@ module Tugboat
11
11
  # First, if nothing is provided to us, we should quit and
12
12
  # let the user know.
13
13
  if !user_fuzzy_name && !user_droplet_name && !user_droplet_id
14
- say "Tugboat attempted to find a droplet with no arguments.", :red
14
+ say "Tugboat attempted to find a droplet with no arguments. Try `tugboat help`", :red
15
15
  return
16
16
  end
17
17
 
@@ -42,7 +42,7 @@ module Tugboat
42
42
 
43
43
  # Look for the droplet by an exact name match.
44
44
  ocean.droplets.list.droplets.each do |d|
45
- if droplet.name == user_droplet_name
45
+ if d.name == user_droplet_name
46
46
  env["droplet_id"] = d.id
47
47
  env["droplet_name"] = "(#{d.name})"
48
48
  env["droplet_ip"] = d.ip_address
@@ -4,7 +4,6 @@ module Tugboat
4
4
  class InjectConfiguration < Base
5
5
  def call(env)
6
6
  config = Tugboat::Configuration.instance
7
- config.reset!
8
7
 
9
8
  env["config"] = config
10
9
 
@@ -0,0 +1,18 @@
1
+ module Tugboat
2
+ module Middleware
3
+ class ListSSHKeys < Base
4
+ def call(env)
5
+ ocean = env["ocean"]
6
+ ssh_keys = ocean.ssh_keys.list
7
+
8
+ say "SSH Keys:"
9
+ ssh_keys.ssh_keys.each do |key|
10
+ say "#{key.name} (id: #{key.id})"
11
+ end
12
+
13
+ @app.call(env)
14
+ end
15
+ end
16
+ end
17
+ end
18
+
@@ -3,10 +3,13 @@ module Tugboat
3
3
  class SnapshotDroplet < Base
4
4
  def call(env)
5
5
  ocean = env["ocean"]
6
+ # Right now, the digital ocean API doesn't return an error
7
+ # when your droplet is not powered off and you try to snapshot.
8
+ # This is a temporary measure to let the user know.
9
+ say "Warning: Droplet must be in a powered off state for snapshot to be successful", :yellow
6
10
 
7
11
  say "Queuing snapshot '#{env["user_snapshot_name"]}' for #{env["droplet_id"]} #{env["droplet_name"]}...", nil, false
8
12
 
9
- # Temporary
10
13
  req = ocean.droplets.snapshot env["droplet_id"],
11
14
  :name => env["user_snapshot_name"]
12
15