tugboat 0.0.3 → 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +0 -1
- data/CHANGELOG.md +30 -0
- data/README.md +6 -4
- data/Rakefile +6 -0
- data/lib/tugboat/cli.rb +30 -7
- data/lib/tugboat/config.rb +16 -8
- data/lib/tugboat/middleware.rb +22 -10
- data/lib/tugboat/middleware/ask_for_credentials.rb +2 -1
- data/lib/tugboat/middleware/check_credentials.rb +1 -1
- data/lib/tugboat/middleware/confirm_action.rb +9 -5
- data/lib/tugboat/middleware/create_droplet.rb +2 -1
- data/lib/tugboat/middleware/find_droplet.rb +2 -2
- data/lib/tugboat/middleware/inject_configuration.rb +0 -1
- data/lib/tugboat/middleware/list_ssh_keys.rb +18 -0
- data/lib/tugboat/middleware/snapshot_droplet.rb +4 -1
- data/lib/tugboat/middleware/ssh_droplet.rb +8 -0
- data/lib/tugboat/version.rb +1 -1
- data/spec/cli/authorize_cli_spec.rb +37 -0
- data/spec/cli/create_cli_spec.rb +40 -0
- data/spec/cli/destroy_cli_spec.rb +93 -0
- data/spec/cli/droplets_cli_spec.rb +28 -0
- data/spec/cli/halt_cli_spec.rb +70 -0
- data/spec/cli/images_cli_spec.rb +53 -0
- data/spec/cli/info_cli_spec.rb +93 -0
- data/spec/cli/keys_cli_spec.rb +27 -0
- data/spec/cli/restart_cli_spec.rb +70 -0
- data/spec/cli/snapshot_cli_spec.rb +75 -0
- data/spec/cli/version_cli_spec.rb +20 -0
- data/spec/config_spec.rb +8 -15
- data/spec/fixtures/create_droplet.json +0 -0
- data/spec/fixtures/show_droplet.json +13 -0
- data/spec/fixtures/show_droplets.json +35 -0
- data/spec/fixtures/show_images.json +15 -0
- data/spec/fixtures/show_images_global.json +20 -0
- data/spec/fixtures/show_keys.json +13 -0
- data/spec/middleware/base_spec.rb +15 -0
- data/spec/middleware/inject_configuration_spec.rb +19 -0
- data/spec/shared/environment.rb +43 -0
- data/spec/spec_helper.rb +7 -0
- data/tmp/.gitkeep +0 -0
- data/tugboat.gemspec +7 -0
- metadata +141 -2
data/.gitignore
CHANGED
data/CHANGELOG.md
ADDED
@@ -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
|
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
|
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
data/lib/tugboat/cli.rb
CHANGED
@@ -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
|
-
|
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
|
|
data/lib/tugboat/config.rb
CHANGED
@@ -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
|
-
|
46
|
-
|
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
|
data/lib/tugboat/middleware.rb
CHANGED
@@ -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
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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
|
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
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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
|
@@ -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 !
|
8
|
-
|
9
|
-
|
10
|
-
|
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
|
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
|
@@ -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
|
|