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.
- 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
|
+
[](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
|
|