tugboat 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +12 -0
- data/CONTRIBUTING.md +7 -0
- data/Gemfile +4 -0
- data/LICENSE.md +22 -0
- data/README.md +122 -0
- data/Rakefile +1 -0
- data/bin/tugboat +4 -0
- data/lib/tugboat/cli.rb +185 -0
- data/lib/tugboat/config.rb +76 -0
- data/lib/tugboat/middleware/ask_for_credentials.rb +21 -0
- data/lib/tugboat/middleware/base.rb +28 -0
- data/lib/tugboat/middleware/check_configuration.rb +18 -0
- data/lib/tugboat/middleware/check_credentials.rb +24 -0
- data/lib/tugboat/middleware/confirm_action.rb +18 -0
- data/lib/tugboat/middleware/create_droplet.rb +26 -0
- data/lib/tugboat/middleware/destroy_droplet.rb +23 -0
- data/lib/tugboat/middleware/find_droplet.rb +112 -0
- data/lib/tugboat/middleware/halt_droplet.rb +23 -0
- data/lib/tugboat/middleware/info_droplet.rb +37 -0
- data/lib/tugboat/middleware/inject_client.rb +19 -0
- data/lib/tugboat/middleware/inject_configuration.rb +16 -0
- data/lib/tugboat/middleware/list_droplets.rb +24 -0
- data/lib/tugboat/middleware/list_images.rb +29 -0
- data/lib/tugboat/middleware/restart_droplet.rb +23 -0
- data/lib/tugboat/middleware/snapshot_droplet.rb +25 -0
- data/lib/tugboat/middleware/ssh_droplet.rb +26 -0
- data/lib/tugboat/middleware.rb +132 -0
- data/lib/tugboat/version.rb +3 -0
- data/lib/tugboat.rb +6 -0
- data/spec/config_spec.rb +90 -0
- data/spec/spec_helper.rb +11 -0
- data/tugboat.gemspec +24 -0
- metadata +144 -0
data/.gitignore
ADDED
data/CONTRIBUTING.md
ADDED
data/Gemfile
ADDED
data/LICENSE.md
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Jack Pearkes
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,122 @@
|
|
1
|
+
# Tugboat
|
2
|
+
|
3
|
+
A command line tool for interacting with your [DigitalOcean](https://www.digitalocean.com/) droplets.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
gem install tugboat
|
8
|
+
|
9
|
+
## Configuration
|
10
|
+
|
11
|
+
Run the configuration utility, `tugboat authorize`. You can grab your keys
|
12
|
+
[here](https://www.digitalocean.com/api_access).
|
13
|
+
|
14
|
+
$ tugboat authorize
|
15
|
+
Enter your client key: foo
|
16
|
+
Enter your API key: bar
|
17
|
+
Enter your SSH key path (optional, defaults to ~/.ssh/id_rsa):
|
18
|
+
Enter your SSH user (optional, defaults to jack):
|
19
|
+
Authentication with DigitalOcean was successful!
|
20
|
+
|
21
|
+
## Usage
|
22
|
+
|
23
|
+
### Retrieve a list of your droplets
|
24
|
+
|
25
|
+
$ tugboat droplets
|
26
|
+
pearkes-web-001 (ip: 30.30.30.1, status: active, region: 1, id: 13231511)
|
27
|
+
pearkes-admin-001 (ip: 30.30.30.3, status: active, region: 1, id: 13231512)
|
28
|
+
pearkes-api-001 (ip: 30.30.30.5, status: active, region: 1, id: 13231513)
|
29
|
+
|
30
|
+
### Fuzzy name matching
|
31
|
+
|
32
|
+
You can pass a unique fragment of a droplets name for interactions
|
33
|
+
throughout `tugboat`.
|
34
|
+
|
35
|
+
$ tugboat restart admin
|
36
|
+
Droplet fuzzy name provided. Finding droplet ID...done, 13231512 (pearkes-admin-001)
|
37
|
+
Queuing restart for 13231512 (pearkes-admin-001)...done
|
38
|
+
|
39
|
+
tugboat handles multiple matches as well:
|
40
|
+
|
41
|
+
$ tugboat restart pearkes
|
42
|
+
Droplet fuzzy name provided. Finding droplet ID...Multiple droplets found.
|
43
|
+
|
44
|
+
0) pearkes-web-001 (13231511)
|
45
|
+
1) pearkes-admin-001 (13231512)
|
46
|
+
2) pearkes-api-001 (13231513)
|
47
|
+
|
48
|
+
Please choose a droplet: ["0", "1", "2"] 0
|
49
|
+
Queuing restart for 13231511 (pearkes-web-001)...done
|
50
|
+
|
51
|
+
### SSH into a droplet
|
52
|
+
|
53
|
+
You can configure a SSH username and key path in `tugboat authorize`.
|
54
|
+
|
55
|
+
This lets you ssh into a droplet by providing it's name, or a partial
|
56
|
+
match.
|
57
|
+
|
58
|
+
$ tugboat ssh admin
|
59
|
+
Droplet fuzzy name provided. Finding droplet ID...done, 13231512 (pearkes-admin-001)
|
60
|
+
Executing SSH (pearkes-admin-001)...
|
61
|
+
Welcome to Ubuntu 12.10 (GNU/Linux 3.5.0-17-generic x86_64)
|
62
|
+
pearkes@pearkes-admin-001:~#
|
63
|
+
|
64
|
+
### Create a droplet
|
65
|
+
|
66
|
+
$ tugboat create pearkes-www-002 -s 64 -i 2676 -r 2
|
67
|
+
Queueing creation of droplet 'pearkes-www-002'...done
|
68
|
+
|
69
|
+
### Info about a droplet
|
70
|
+
|
71
|
+
$ tugboat info admin
|
72
|
+
Droplet fuzzy name provided. Finding droplet ID...done, 13231512 (pearkes-admin-001)
|
73
|
+
|
74
|
+
Name: pearkes-admin-001
|
75
|
+
ID: 13231512
|
76
|
+
Status: active
|
77
|
+
IP: 30.30.30.3
|
78
|
+
Region ID: 1
|
79
|
+
Image ID: 25489
|
80
|
+
Size ID: 66
|
81
|
+
Backups Active: false
|
82
|
+
|
83
|
+
### Destroy a droplet
|
84
|
+
|
85
|
+
$ tugboat destroy pearkes-www-002
|
86
|
+
Droplet fuzzy name provided. Finding droplet ID...done, 13231515 (pearkes-www-002)
|
87
|
+
Warning! Potentially destructive action. Please confirm [y/n]: y
|
88
|
+
Queuing destroy for 13231515 (pearkes-www-002)...done
|
89
|
+
|
90
|
+
### Restart a droplet
|
91
|
+
|
92
|
+
$ tugboat restart admin
|
93
|
+
Droplet fuzzy name provided. Finding droplet ID...done, 13231512 (pearkes-admin-001)
|
94
|
+
Queuing restart for 13231512 (pearkes-admin-001)...done
|
95
|
+
|
96
|
+
### Shutdown a droplet
|
97
|
+
|
98
|
+
$ tugboat halt admin
|
99
|
+
Droplet fuzzy name provided. Finding droplet ID...done, 13231512 (pearkes-admin-001)
|
100
|
+
Queuing shutdown for 13231512 (pearkes-admin-001)...done
|
101
|
+
|
102
|
+
### Snapshot a droplet
|
103
|
+
|
104
|
+
$ tugboat snapshot admin test-admin-snaphot
|
105
|
+
Queuing snapshot 'test' for 13231512 (pearkes-admin-001)...done
|
106
|
+
|
107
|
+
## Help
|
108
|
+
|
109
|
+
If you're curious about command flags for a specific command, you can
|
110
|
+
ask tugboat about it.
|
111
|
+
|
112
|
+
$ tugboat help restart
|
113
|
+
|
114
|
+
|
115
|
+
For a complete overview of all of the available commands, run:
|
116
|
+
|
117
|
+
$ tugboat help
|
118
|
+
|
119
|
+
## Contributing
|
120
|
+
|
121
|
+
See the [contributing guide](CONTRIBUTING.md).
|
122
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/bin/tugboat
ADDED
data/lib/tugboat/cli.rb
ADDED
@@ -0,0 +1,185 @@
|
|
1
|
+
require 'thor'
|
2
|
+
|
3
|
+
module Tugboat
|
4
|
+
autoload :Middleware, "tugboat/middleware"
|
5
|
+
|
6
|
+
class CLI < Thor
|
7
|
+
include Thor::Actions
|
8
|
+
ENV['THOR_COLUMNS'] = '120'
|
9
|
+
|
10
|
+
!check_unknown_options
|
11
|
+
|
12
|
+
desc "help [COMMAND]", "Describe commands or a specific command"
|
13
|
+
def help(meth=nil)
|
14
|
+
super
|
15
|
+
if !meth
|
16
|
+
say "To learn more or to contribute, please see github.com/pearkes/tugboat"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
desc "authorize", "Authorize a DigitalOcean account with tugboat"
|
21
|
+
long_desc "This takes you through a workflow for adding configuration
|
22
|
+
details to tugboat. First, you are asked for your API and Client keys,
|
23
|
+
which are stored in ~/.tugboat.
|
24
|
+
|
25
|
+
You can retrieve your credentials from digitalocean.com/api_access.
|
26
|
+
|
27
|
+
Optionally, you can configure the default SSH key path and username
|
28
|
+
used for `tugboat ssh`. These default to '~/.ssh/id_rsa' and the
|
29
|
+
$USER environment variable.
|
30
|
+
"
|
31
|
+
def authorize
|
32
|
+
Middleware.sequence_authorize.call({})
|
33
|
+
end
|
34
|
+
|
35
|
+
desc "droplets", "Retrieve a list of your droplets"
|
36
|
+
def droplets
|
37
|
+
Middleware.sequence_list_droplets.call({})
|
38
|
+
end
|
39
|
+
|
40
|
+
desc "images", "Retrieve a list of your images"
|
41
|
+
method_option "global",
|
42
|
+
:type => :boolean,
|
43
|
+
:default => false,
|
44
|
+
:aliases => "-g",
|
45
|
+
:desc => "Show global images"
|
46
|
+
def images
|
47
|
+
Middleware.sequence_list_images.call({
|
48
|
+
"user_show_global_images" => options[:global],
|
49
|
+
})
|
50
|
+
end
|
51
|
+
|
52
|
+
desc "ssh FUZZY_NAME", "SSH into a droplet"
|
53
|
+
method_option "id",
|
54
|
+
:type => :string,
|
55
|
+
:aliases => "-i",
|
56
|
+
:desc => "The ID of the droplet."
|
57
|
+
method_option "name",
|
58
|
+
:type => :string,
|
59
|
+
:aliases => "-n",
|
60
|
+
:desc => "The exact name of the droplet"
|
61
|
+
def ssh(name=nil)
|
62
|
+
Middleware.sequence_ssh_droplet.call({
|
63
|
+
"user_droplet_id" => options[:id],
|
64
|
+
"user_droplet_name" => options[:name],
|
65
|
+
"user_droplet_fuzzy_name" => name
|
66
|
+
})
|
67
|
+
end
|
68
|
+
|
69
|
+
desc "create NAME", "Create a droplet."
|
70
|
+
method_option "size",
|
71
|
+
:type => :numeric,
|
72
|
+
:aliases => "-s",
|
73
|
+
:default => 64,
|
74
|
+
:desc => "The size_id of the droplet"
|
75
|
+
method_option "image",
|
76
|
+
:type => :numeric,
|
77
|
+
:aliases => "-i",
|
78
|
+
:default => 2676,
|
79
|
+
:desc => "The image_id of the droplet"
|
80
|
+
method_option "region",
|
81
|
+
:type => :numeric,
|
82
|
+
:aliases => "-r",
|
83
|
+
:default => 1,
|
84
|
+
:desc => "The region_id of the droplet"
|
85
|
+
def create(name)
|
86
|
+
Middleware.sequence_create_droplet.call({
|
87
|
+
"create_droplet_size_id" => options[:size],
|
88
|
+
"create_droplet_image_id" => options[:image],
|
89
|
+
"create_droplet_region_id" => options[:region],
|
90
|
+
"create_droplet_name" => name
|
91
|
+
})
|
92
|
+
end
|
93
|
+
|
94
|
+
desc "destroy FUZZY_NAME", "Destroy a droplet"
|
95
|
+
method_option "id",
|
96
|
+
:type => :string,
|
97
|
+
:aliases => "-i",
|
98
|
+
:desc => "The ID of the droplet."
|
99
|
+
method_option "name",
|
100
|
+
:type => :string,
|
101
|
+
:aliases => "-n",
|
102
|
+
:desc => "The exact name of the droplet"
|
103
|
+
def destroy(name=nil)
|
104
|
+
Middleware.sequence_destroy_droplet.call({
|
105
|
+
"user_droplet_id" => options[:id],
|
106
|
+
"user_droplet_name" => options[:name],
|
107
|
+
"user_droplet_fuzzy_name" => name
|
108
|
+
})
|
109
|
+
end
|
110
|
+
|
111
|
+
desc "restart FUZZY_NAME", "Restart a droplet"
|
112
|
+
method_option "id",
|
113
|
+
:type => :string,
|
114
|
+
:aliases => "-i",
|
115
|
+
:desc => "The ID of the droplet."
|
116
|
+
method_option "name",
|
117
|
+
:type => :string,
|
118
|
+
:aliases => "-n",
|
119
|
+
:desc => "The exact name of the droplet"
|
120
|
+
def restart(name=nil)
|
121
|
+
Middleware.sequence_restart_droplet.call({
|
122
|
+
"user_droplet_id" => options[:id],
|
123
|
+
"user_droplet_name" => options[:name],
|
124
|
+
"user_droplet_fuzzy_name" => name
|
125
|
+
})
|
126
|
+
end
|
127
|
+
|
128
|
+
desc "halt FUZZY_NAME", "Shutdown a droplet"
|
129
|
+
method_option "id",
|
130
|
+
:type => :string,
|
131
|
+
:aliases => "-i",
|
132
|
+
:desc => "The ID of the droplet."
|
133
|
+
method_option "name",
|
134
|
+
:type => :string,
|
135
|
+
:aliases => "-n",
|
136
|
+
:desc => "The exact name of the droplet"
|
137
|
+
def halt(name=nil)
|
138
|
+
Middleware.sequence_halt_droplet.call({
|
139
|
+
"user_droplet_id" => options[:id],
|
140
|
+
"user_droplet_name" => options[:name],
|
141
|
+
"user_droplet_fuzzy_name" => name
|
142
|
+
})
|
143
|
+
end
|
144
|
+
|
145
|
+
desc "info FUZZY_NAME [OPTIONS]", "Show a droplet's information"
|
146
|
+
method_option "id",
|
147
|
+
:type => :string,
|
148
|
+
:aliases => "-i",
|
149
|
+
:desc => "The ID of the droplet."
|
150
|
+
method_option "name",
|
151
|
+
:type => :string,
|
152
|
+
:aliases => "-n",
|
153
|
+
:desc => "The exact name of the droplet"
|
154
|
+
def info(name=nil)
|
155
|
+
Middleware.sequence_info_droplet.call({
|
156
|
+
"user_droplet_id" => options[:id],
|
157
|
+
"user_droplet_name" => options[:name],
|
158
|
+
"user_droplet_fuzzy_name" => name
|
159
|
+
})
|
160
|
+
end
|
161
|
+
|
162
|
+
desc "snapshot FUZZY_NAME [OPTIONS]", "Queue a snapshot of the droplet."
|
163
|
+
method_option "id",
|
164
|
+
:type => :string,
|
165
|
+
:aliases => "-i",
|
166
|
+
:desc => "The ID of the droplet."
|
167
|
+
method_option "name",
|
168
|
+
:type => :string,
|
169
|
+
:aliases => "-n",
|
170
|
+
: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)
|
176
|
+
Middleware.sequence_snapshot_droplet.call({
|
177
|
+
"user_droplet_id" => options[:id],
|
178
|
+
"user_droplet_name" => options[:name],
|
179
|
+
"user_droplet_fuzzy_name" => name,
|
180
|
+
"user_snapshot_name" => snapshot_name
|
181
|
+
})
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'singleton'
|
2
|
+
|
3
|
+
module Tugboat
|
4
|
+
# This is the configuration object. It reads in configuration
|
5
|
+
# from a .tugboat file located in the user's home directory
|
6
|
+
|
7
|
+
class Configuration
|
8
|
+
include Singleton
|
9
|
+
attr_reader :data
|
10
|
+
attr_reader :path
|
11
|
+
|
12
|
+
FILE_NAME = '.tugboat'
|
13
|
+
DEFAULT_SSH_KEY_PATH = '.ssh/id_rsa'
|
14
|
+
|
15
|
+
def initialize
|
16
|
+
@path = File.join(File.expand_path("~"), FILE_NAME)
|
17
|
+
@data = self.load_config_file
|
18
|
+
end
|
19
|
+
|
20
|
+
# If we can't load the config file, self.data is nil, which we can
|
21
|
+
# check for in CheckConfiguration
|
22
|
+
def load_config_file
|
23
|
+
require 'yaml'
|
24
|
+
YAML.load_file(@path)
|
25
|
+
rescue Errno::ENOENT
|
26
|
+
return
|
27
|
+
end
|
28
|
+
|
29
|
+
def client_key
|
30
|
+
@data['authentication']['client_key']
|
31
|
+
end
|
32
|
+
|
33
|
+
def api_key
|
34
|
+
@data['authentication']['api_key']
|
35
|
+
end
|
36
|
+
|
37
|
+
def ssh_key_path
|
38
|
+
@data['ssh']['ssh_key_path']
|
39
|
+
end
|
40
|
+
|
41
|
+
def ssh_user
|
42
|
+
@data['ssh']['ssh_user']
|
43
|
+
end
|
44
|
+
|
45
|
+
# Allow the path to be set.
|
46
|
+
def path=(path)
|
47
|
+
@path = path
|
48
|
+
path
|
49
|
+
end
|
50
|
+
|
51
|
+
# Re-runs initialize
|
52
|
+
def reset!
|
53
|
+
self.send(:initialize)
|
54
|
+
end
|
55
|
+
|
56
|
+
# Writes a config file
|
57
|
+
def create_config_file(client, api, ssh_key_path, ssh_user)
|
58
|
+
# Default SSH Key path
|
59
|
+
if ssh_key_path.empty?
|
60
|
+
ssh_key_path = File.join(File.expand_path("~"), DEFAULT_SSH_KEY_PATH)
|
61
|
+
end
|
62
|
+
|
63
|
+
if ssh_user.empty?
|
64
|
+
ssh_user = ENV['USER']
|
65
|
+
end
|
66
|
+
|
67
|
+
require 'yaml'
|
68
|
+
File.open(@path, File::RDWR|File::TRUNC|File::CREAT, 0600) do |file|
|
69
|
+
data = {"authentication" => { "client_key" => client, "api_key" => api },
|
70
|
+
"ssh" => { "ssh_user" => ssh_user, "ssh_key_path" => ssh_key_path }}
|
71
|
+
file.write data.to_yaml
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Tugboat
|
2
|
+
module Middleware
|
3
|
+
# Ask for user credentials from the command line, then write them out.
|
4
|
+
class AskForCredentials < Base
|
5
|
+
def call(env)
|
6
|
+
say "Note: You can get this information from digitalocean.com/api_access", :yellow
|
7
|
+
say
|
8
|
+
client_key = ask "Enter your client key:"
|
9
|
+
api_key = ask "Enter your API key:"
|
10
|
+
ssh_key_path = ask "Enter your SSH key path (optional, defaults to ~/.ssh/id_rsa):"
|
11
|
+
ssh_user = ask "Enter your SSH user (optional, defaults to #{ENV['USER']}):"
|
12
|
+
|
13
|
+
# Write the config file.
|
14
|
+
env['config'].create_config_file(client_key, api_key, ssh_key_path, ssh_user)
|
15
|
+
|
16
|
+
@app.call(env)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Tugboat
|
2
|
+
module Middleware
|
3
|
+
# A base middleware class to initalize.
|
4
|
+
class Base
|
5
|
+
# Some colors for making things pretty.
|
6
|
+
CLEAR = "\e[0m"
|
7
|
+
RED = "\e[31m"
|
8
|
+
GREEN = "\e[32m"
|
9
|
+
YELLOW = "\e[33m"
|
10
|
+
|
11
|
+
# We want access to all of the fun thor cli helper methods,
|
12
|
+
# like say, yes?, ask, etc.
|
13
|
+
include Thor::Shell
|
14
|
+
|
15
|
+
def initialize(app)
|
16
|
+
@app = app
|
17
|
+
# This resets the color to "clear" on the user's terminal.
|
18
|
+
say "", :clear, false
|
19
|
+
end
|
20
|
+
|
21
|
+
def call(env)
|
22
|
+
@app.call(env)
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Tugboat
|
2
|
+
module Middleware
|
3
|
+
# Check if the client has set-up configuration yet.
|
4
|
+
class CheckConfiguration < Base
|
5
|
+
def call(env)
|
6
|
+
config = env["config"]
|
7
|
+
|
8
|
+
if !config || !config.data || !config.api_key || !config.client_key
|
9
|
+
say "You must run `tugboat authorize` in order to connect to DigitalOcean", :red
|
10
|
+
return
|
11
|
+
end
|
12
|
+
|
13
|
+
@app.call(env)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'thor'
|
2
|
+
|
3
|
+
module Tugboat
|
4
|
+
module Middleware
|
5
|
+
# Check if the client can connect to the ocean
|
6
|
+
class CheckCredentials < Base
|
7
|
+
def call(env)
|
8
|
+
# We use a harmless API call to check if the authentication will
|
9
|
+
# work.
|
10
|
+
begin
|
11
|
+
env["ocean"].droplets.list
|
12
|
+
rescue
|
13
|
+
say "Authentication with DigitalOcean failed. Run `tugboat authorize`", :red
|
14
|
+
return
|
15
|
+
end
|
16
|
+
|
17
|
+
say "Authentication with DigitalOcean was successful.", :green
|
18
|
+
|
19
|
+
@app.call(env)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Tugboat
|
2
|
+
module Middleware
|
3
|
+
class ConfirmAction < Base
|
4
|
+
def call(env)
|
5
|
+
response = yes? "Warning! Potentially destructive action. Please confirm [y/n]:"
|
6
|
+
|
7
|
+
if !response
|
8
|
+
say "Aborted due to user request.", :red
|
9
|
+
# Quit
|
10
|
+
return
|
11
|
+
end
|
12
|
+
|
13
|
+
@app.call(env)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Tugboat
|
2
|
+
module Middleware
|
3
|
+
class CreateDroplet < Base
|
4
|
+
def call(env)
|
5
|
+
ocean = env["ocean"]
|
6
|
+
|
7
|
+
say "Queueing creation of droplet '#{env["create_droplet_name"]}'...", nil, false
|
8
|
+
|
9
|
+
req = ocean.droplets.create :name => env["create_droplet_name"],
|
10
|
+
:size_id => env["create_droplet_size_id"],
|
11
|
+
:image_id => env["create_droplet_image_id"],
|
12
|
+
:region_id => env["create_droplet_region_id"]
|
13
|
+
|
14
|
+
if req.status == "ERROR"
|
15
|
+
say req.error_message, :red
|
16
|
+
return
|
17
|
+
end
|
18
|
+
|
19
|
+
say "done", :green
|
20
|
+
|
21
|
+
@app.call(env)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Tugboat
|
2
|
+
module Middleware
|
3
|
+
class DestroyDroplet < Base
|
4
|
+
def call(env)
|
5
|
+
ocean = env["ocean"]
|
6
|
+
|
7
|
+
say "Queuing destroy for #{env["droplet_id"]} #{env["droplet_name"]}...", nil, false
|
8
|
+
|
9
|
+
req = ocean.droplets.delete env["droplet_id"]
|
10
|
+
|
11
|
+
if req.status == "ERROR"
|
12
|
+
say "#{req.status}: #{req.error_message}", :red
|
13
|
+
return
|
14
|
+
end
|
15
|
+
|
16
|
+
say "done", :green
|
17
|
+
|
18
|
+
@app.call(env)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|