tugboat 0.0.3 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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