pearl 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/Gemfile +21 -0
- data/LICENSE.txt +22 -0
- data/README.md +133 -0
- data/Rakefile +21 -0
- data/bin/pearl +12 -0
- data/lib/pearl/cli.rb +215 -0
- data/lib/pearl/client/droplets.rb +79 -0
- data/lib/pearl/client/images.rb +24 -0
- data/lib/pearl/client/regions.rb +10 -0
- data/lib/pearl/client/sizes.rb +10 -0
- data/lib/pearl/client.rb +35 -0
- data/lib/pearl/error.rb +35 -0
- data/lib/pearl/http.rb +51 -0
- data/lib/pearl/version.rb +3 -0
- data/lib/pearl.rb +26 -0
- data/pearl.gemspec +25 -0
- data/test/pearl/client/droplets_test.rb +51 -0
- data/test/pearl/client/images_test.rb +12 -0
- data/test/pearl/client/regions_test.rb +6 -0
- data/test/pearl/client/sizes_test.rb +6 -0
- data/test/pearl/client_test.rb +25 -0
- data/test/pearl_test.rb +14 -0
- data/test/support/client_macros.rb +6 -0
- data/test/test_helper.rb +24 -0
- metadata +107 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: d6467e461a96b75d2007d98c2d649df5cc46a653
|
4
|
+
data.tar.gz: e3159cde72e0b07c87275980bb02a0e50eec0784
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 1907733bf543d07eef72e81f8f211e002d6d929fab70b7477db52bcfd247432c5271e86e337e95026c4df4b87ee22d4c0c3ab21aa7c416c777cd0dbb09344b06
|
7
|
+
data.tar.gz: 8e3751f899dc90f914c1e44229c8d7ed5a1c6e4d92663a657f957aa57e195f57da4127b0cd5840549d28e2eabd3ee7c18f8184876da6eb655f5f6e0b5dc47c67
|
data/.gitignore
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
source 'https://rubygems.org'
|
2
|
+
|
3
|
+
# Specify your gem's dependencies in pearl.gemspec
|
4
|
+
gemspec
|
5
|
+
|
6
|
+
gem 'rake', group: [:development, :test]
|
7
|
+
|
8
|
+
# Development dependencies
|
9
|
+
group :development do
|
10
|
+
gem 'yard'
|
11
|
+
end
|
12
|
+
|
13
|
+
group :test do
|
14
|
+
gem 'minitest'
|
15
|
+
gem 'minitest-wscolor' if RUBY_VERSION >= '1.9.3'
|
16
|
+
gem 'mocha', require: 'mocha/setup'
|
17
|
+
gem 'simplecov', require: false
|
18
|
+
gem 'turn'
|
19
|
+
gem 'vcr'
|
20
|
+
gem 'webmock', require: 'webmock/minitest'
|
21
|
+
end
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Manish Valechha
|
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,133 @@
|
|
1
|
+
# Pearl
|
2
|
+
|
3
|
+
Pearl is a command line interface to DigitlOcean's API.
|
4
|
+
|
5
|
+
## Setup
|
6
|
+
|
7
|
+
Run the following command to install
|
8
|
+
|
9
|
+
$ gem install pearl
|
10
|
+
|
11
|
+
Add your client id and api key as enviroment variables
|
12
|
+
|
13
|
+
ENV['DIGITAL_OCEAN_CLIENT_ID']
|
14
|
+
ENV['DIGITAL_OCEAN_API_KEY']
|
15
|
+
|
16
|
+
## Usage
|
17
|
+
|
18
|
+
### Droplets
|
19
|
+
|
20
|
+
To see all your droplets
|
21
|
+
|
22
|
+
$ pearl droplets
|
23
|
+
|
24
|
+
To view a droplet
|
25
|
+
|
26
|
+
$ pearl droplet -d <droplet id>
|
27
|
+
|
28
|
+
To create a droplet
|
29
|
+
|
30
|
+
$ pearl droplet add 'name' --size <size id> --image <image id> --region <region id>
|
31
|
+
|
32
|
+
or
|
33
|
+
|
34
|
+
$ pearl droplet add 'name' -s <size id> -i <image id> -r <region id>
|
35
|
+
|
36
|
+
Reboot a droplet
|
37
|
+
|
38
|
+
$ pearl droplet reboot -d <droplet id>
|
39
|
+
|
40
|
+
Power cycle droplet
|
41
|
+
|
42
|
+
$ pearl droplet power cycle -d <droplet id>
|
43
|
+
|
44
|
+
Shutdown droplet
|
45
|
+
|
46
|
+
$ pearl droplet shutdown -d <droplet id>
|
47
|
+
|
48
|
+
Power Off droplet
|
49
|
+
|
50
|
+
$ pearl droplet power off -d <droplet id>
|
51
|
+
|
52
|
+
Power On droplet
|
53
|
+
|
54
|
+
$ pearl droplet power_on -d <droplet id>
|
55
|
+
|
56
|
+
Reset root password
|
57
|
+
|
58
|
+
$ pearl droplet password_reset -d <droplet id>
|
59
|
+
|
60
|
+
Resize droplet
|
61
|
+
|
62
|
+
$ pearl droplet resize -d <droplet id> -s <size id>
|
63
|
+
|
64
|
+
Take a snapshot
|
65
|
+
|
66
|
+
$ pearl droplet snapshot 'snapshot_name' -d <droplet id>
|
67
|
+
|
68
|
+
Restore droplet
|
69
|
+
|
70
|
+
$ pearl droplet restore -d <droplet id> -i <image id>
|
71
|
+
|
72
|
+
Rebuild droplet
|
73
|
+
|
74
|
+
$ pearl droplet rebuild -d <droplet id> -i <image id>
|
75
|
+
|
76
|
+
Enable automatic backups
|
77
|
+
|
78
|
+
$ pearl droplet enable backups -d <droplet id>
|
79
|
+
|
80
|
+
Disable automatic backups
|
81
|
+
|
82
|
+
$ pearl droplet disable backups -d <droplet id>
|
83
|
+
|
84
|
+
Destroy droplet
|
85
|
+
|
86
|
+
$ pearl droplet destroy -d <droplet id>
|
87
|
+
|
88
|
+
### Regions
|
89
|
+
|
90
|
+
Display regions
|
91
|
+
|
92
|
+
$ pearl regions
|
93
|
+
|
94
|
+
### Sizes
|
95
|
+
|
96
|
+
Display regions
|
97
|
+
|
98
|
+
$ pearl sizes
|
99
|
+
|
100
|
+
### Images
|
101
|
+
|
102
|
+
Display all images
|
103
|
+
|
104
|
+
$ pearl images
|
105
|
+
|
106
|
+
To display your images
|
107
|
+
|
108
|
+
$ pearl images --filter my
|
109
|
+
|
110
|
+
To display global images (default)
|
111
|
+
|
112
|
+
$ pearl images --filter global
|
113
|
+
|
114
|
+
View a single image
|
115
|
+
|
116
|
+
$ pearl image -i <image id>
|
117
|
+
|
118
|
+
To destroy an image
|
119
|
+
|
120
|
+
$ pearl image destroy -i <image id>
|
121
|
+
|
122
|
+
## TODO
|
123
|
+
|
124
|
+
* Format output instead of just printing the json response.
|
125
|
+
* Add SSH resource.
|
126
|
+
|
127
|
+
## Contributing
|
128
|
+
|
129
|
+
1. Fork it
|
130
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
131
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
132
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
133
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'bundler'
|
2
|
+
Bundler::GemHelper.install_tasks
|
3
|
+
|
4
|
+
require 'rake/testtask'
|
5
|
+
Rake::TestTask.new(:test) do |t|
|
6
|
+
t.libs << 'test'
|
7
|
+
t.pattern = 'test/**/*_test.rb'
|
8
|
+
end
|
9
|
+
task default: :test
|
10
|
+
|
11
|
+
begin
|
12
|
+
require 'yard'
|
13
|
+
YARD::Rake::YardocTask.new(:doc) do |task|
|
14
|
+
task.files = ['READEME.md', 'LICENSE.txt', 'lib/**/*.rb']
|
15
|
+
task.options = [
|
16
|
+
'--output-dir', 'doc',
|
17
|
+
'--markup', 'markdown',
|
18
|
+
]
|
19
|
+
end
|
20
|
+
rescue LoadError
|
21
|
+
end
|
data/bin/pearl
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# encoding: UTF-8
|
3
|
+
|
4
|
+
# resolve bin path, ignoring symlinks
|
5
|
+
require "pathname"
|
6
|
+
bin_file = Pathname.new(__FILE__).realpath
|
7
|
+
|
8
|
+
# add self to libpath
|
9
|
+
$:.unshift File.expand_path("../../lib", bin_file)
|
10
|
+
|
11
|
+
require 'pearl/cli'
|
12
|
+
Pearl::CLI.execute(*ARGV)
|
data/lib/pearl/cli.rb
ADDED
@@ -0,0 +1,215 @@
|
|
1
|
+
require "optparse"
|
2
|
+
|
3
|
+
require 'pearl'
|
4
|
+
|
5
|
+
module Pearl
|
6
|
+
module CLI
|
7
|
+
# Parse command line arguments
|
8
|
+
def self.execute(*args)
|
9
|
+
droplet = nil
|
10
|
+
image = nil
|
11
|
+
region = nil
|
12
|
+
size = nil
|
13
|
+
|
14
|
+
images_filter = nil
|
15
|
+
|
16
|
+
opts = OptionParser.new do |opts|
|
17
|
+
opts.banner = "Usage: pearl [resource] [options]"
|
18
|
+
|
19
|
+
opts.on('-d', '--droplet [DROPLET ID]', 'Select the droplet with this id') do |d|
|
20
|
+
droplet = d
|
21
|
+
end
|
22
|
+
|
23
|
+
opts.on('-f', '--filter', 'Display either your images or global images') do |filter|
|
24
|
+
images_filter = filter
|
25
|
+
end
|
26
|
+
|
27
|
+
opts.on('-h', '--help', 'Display this message') do
|
28
|
+
puts opts
|
29
|
+
exit
|
30
|
+
end
|
31
|
+
|
32
|
+
opts.on('-i', '--image [IMAGE ID]', 'Set the image for the droplet') do |i|
|
33
|
+
image = i
|
34
|
+
end
|
35
|
+
|
36
|
+
opts.on('-r', '--region [REGION ID]', 'Set the region for the droplet') do |r|
|
37
|
+
region = r
|
38
|
+
end
|
39
|
+
|
40
|
+
opts.on('-s', '--size [SIZE ID]', 'Set the size forthe droplet') do |s|
|
41
|
+
size = s
|
42
|
+
end
|
43
|
+
|
44
|
+
opts.on('-v', '--version', 'Display version') do
|
45
|
+
puts Pearl::VERSION
|
46
|
+
exit
|
47
|
+
end
|
48
|
+
|
49
|
+
begin
|
50
|
+
opts.parse!(args)
|
51
|
+
|
52
|
+
if $stdin.tty? && args[0] != '-'
|
53
|
+
|
54
|
+
# Print help when args is empty
|
55
|
+
if args.empty?
|
56
|
+
puts opts
|
57
|
+
exit
|
58
|
+
end
|
59
|
+
|
60
|
+
# Join command(s) to make it easier to catch using regex
|
61
|
+
command = args.join(' ')
|
62
|
+
|
63
|
+
case command.to_s
|
64
|
+
when /\Adroplets\z/i
|
65
|
+
Pearl.droplets
|
66
|
+
exit
|
67
|
+
when /\Adroplet\z/i
|
68
|
+
droplet_id = droplet.to_i
|
69
|
+
raise 'Error: Invalid droplet id.' if droplet_id == 0 || !droplet_id.is_a?(Fixnum)
|
70
|
+
|
71
|
+
Pearl.droplet(droplet_id)
|
72
|
+
exit
|
73
|
+
when /\Adroplet add \w{3,}\z/i
|
74
|
+
image_id = image.to_i
|
75
|
+
raise 'Error: Invalid image id.' if image_id == 0 || !image_id.is_a?(Fixnum)
|
76
|
+
|
77
|
+
region_id = region.to_i
|
78
|
+
raise 'Error: Invalid region id.' if region_id == 0 || !region_id.is_a?(Fixnum)
|
79
|
+
|
80
|
+
size_id = size.to_i
|
81
|
+
raise 'Error: Invalid size id.' if size_id == 0 || !size_id.is_a?(Fixnum)
|
82
|
+
|
83
|
+
name = command.split(' ', 3)[2]
|
84
|
+
raise 'Error: Invalid droplet name.' if name.nil? || name.length <= 0
|
85
|
+
|
86
|
+
options = { name: name, image_id: image_id, region_id: region_id, size_id: size_id }
|
87
|
+
|
88
|
+
Pearl.create_droplet(options)
|
89
|
+
exit
|
90
|
+
when /\Adroplet reboot\z/i
|
91
|
+
droplet_id = droplet.to_i
|
92
|
+
raise 'Error: Invalid droplet id.' if droplet_id == 0 || !droplet_id.is_a?(Fixnum)
|
93
|
+
|
94
|
+
Pearl.reboot(droplet_id)
|
95
|
+
exit
|
96
|
+
when /\Adroplet power cycle\z/i
|
97
|
+
droplet_id = droplet.to_i
|
98
|
+
raise 'Error: Invalid droplet id.' if droplet_id == 0 || !droplet_id.is_a?(Fixnum)
|
99
|
+
|
100
|
+
Pearl.power_cycle(droplet_id)
|
101
|
+
exit
|
102
|
+
when /\Adroplet shutdown\z/i
|
103
|
+
droplet_id = droplet.to_i
|
104
|
+
raise 'Error: Invalid droplet id.' if droplet_id == 0 || !droplet_id.is_a?(Fixnum)
|
105
|
+
|
106
|
+
Pearl.shutdown(droplet_id)
|
107
|
+
exit
|
108
|
+
when /\Adroplet power off\z/i
|
109
|
+
droplet_id = droplet.to_i
|
110
|
+
raise 'Error: Invalid droplet id.' if droplet_id == 0 || !droplet_id.is_a?(Fixnum)
|
111
|
+
|
112
|
+
Pearl.power_off(droplet_id)
|
113
|
+
exit
|
114
|
+
when /\Adroplet power on\z/i
|
115
|
+
droplet_id = droplet.to_i
|
116
|
+
raise 'Error: Invalid droplet id.' if droplet_id == 0 || !droplet_id.is_a?(Fixnum)
|
117
|
+
|
118
|
+
Pearl.power_on(droplet_id)
|
119
|
+
exit
|
120
|
+
when /\Adroplet password reset\z/i
|
121
|
+
droplet_id = droplet.to_i
|
122
|
+
raise 'Error: Invalid droplet id.' if droplet_id == 0 || !droplet_id.is_a?(Fixnum)
|
123
|
+
|
124
|
+
Pearl.password_reset(droplet_id)
|
125
|
+
exit
|
126
|
+
when /\Adroplet resize\z/i
|
127
|
+
droplet_id = droplet.to_i
|
128
|
+
raise 'Error: Invalid droplet id.' if droplet_id == 0 || !droplet_id.is_a?(Fixnum)
|
129
|
+
|
130
|
+
size_id = size.to_i
|
131
|
+
raise 'Error: Invalid size id.' if size_id == 0 || !size_id.is_a?(Fixnum)
|
132
|
+
|
133
|
+
Pearl.resize(droplet_id, size_id)
|
134
|
+
exit
|
135
|
+
when /\Adroplet snapshot \w{3,}\z/i
|
136
|
+
droplet_id = droplet.to_i
|
137
|
+
raise 'Error: Invalid droplet id.' if droplet_id == 0 || !droplet_id.is_a?(Fixnum)
|
138
|
+
|
139
|
+
name = command.split(' ', 2)
|
140
|
+
raise 'Error: Invalid snapshot name.' if name.nil? || name.length <= 0
|
141
|
+
|
142
|
+
Pearl.snapshot(droplet_id, name)
|
143
|
+
exit
|
144
|
+
when /\Adroplet restore\z/i
|
145
|
+
droplet_id = droplet.to_i
|
146
|
+
raise 'Error: Invalid droplet id.' if droplet_id == 0 || !droplet_id.is_a?(Fixnum)
|
147
|
+
|
148
|
+
image_id = image.to_i
|
149
|
+
raise 'Error: Invalid size id.' if image_id == 0 || !image_id.is_a?(Fixnum)
|
150
|
+
|
151
|
+
Pearl.restore(droplet_id, image_id)
|
152
|
+
exit
|
153
|
+
when /\Adroplet rebuild\z/i
|
154
|
+
droplet_id = droplet.to_i
|
155
|
+
raise 'Error: Invalid droplet id.' if droplet_id == 0 || !droplet_id.is_a?(Fixnum)
|
156
|
+
|
157
|
+
image_id = image.to_i
|
158
|
+
raise 'Error: Invalid size id.' if image_id == 0 || !image_id.is_a?(Fixnum)
|
159
|
+
|
160
|
+
Pearl.rebuild(droplet_id, image_id)
|
161
|
+
exit
|
162
|
+
when /\Adroplet enable backups\z/i
|
163
|
+
droplet_id = droplet.to_i
|
164
|
+
raise 'Error: Invalid droplet id.' if droplet_id == 0 || !droplet_id.is_a?(Fixnum)
|
165
|
+
|
166
|
+
Pearl.enable_backups(droplet_id)
|
167
|
+
exit
|
168
|
+
when /\Adroplet disable backups\z/i
|
169
|
+
droplet_id = droplet.to_i
|
170
|
+
raise 'Error: Invalid droplet id.' if droplet_id == 0 || !droplet_id.is_a?(Fixnum)
|
171
|
+
|
172
|
+
Pearl.disable_backups(droplet_id)
|
173
|
+
exit
|
174
|
+
when /\Adroplet (destroy|delete)\z/i
|
175
|
+
droplet_id = droplet.to_i
|
176
|
+
raise 'Error: Invalid droplet id.' if droplet_id == 0 || !droplet_id.is_a?(Fixnum)
|
177
|
+
|
178
|
+
Pearl.destroy_droplet(droplet_id)
|
179
|
+
exit
|
180
|
+
when /\Aimages\z/i
|
181
|
+
Pearl.images(images_filter)
|
182
|
+
exit
|
183
|
+
when /\Aimage\z/i
|
184
|
+
image_id = image.to_i
|
185
|
+
raise 'Error: Invalid image id.' if image_id == 0 || !image_id.is_a?(Fixnum)
|
186
|
+
|
187
|
+
Pearl.image(image_id)
|
188
|
+
exit
|
189
|
+
when /\Aimage (destroy|delete)\z/i
|
190
|
+
image_id = image.to_i
|
191
|
+
raise 'Error: Invalid image id.' if image_id == 0 || !image_id.is_a?(Fixnum)
|
192
|
+
|
193
|
+
Pearl.destroy_image(image_id)
|
194
|
+
exit
|
195
|
+
when /\Aregions\z/i
|
196
|
+
Pearl.regions
|
197
|
+
exit
|
198
|
+
when /\Asizes\z/i
|
199
|
+
Pearl.sizes
|
200
|
+
exit
|
201
|
+
else
|
202
|
+
puts "Error: '#{command}' is an invalid command."
|
203
|
+
exit
|
204
|
+
end
|
205
|
+
end
|
206
|
+
rescue Interrupt
|
207
|
+
error("Command cancelled.")
|
208
|
+
rescue => error
|
209
|
+
warn error
|
210
|
+
puts opts
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
module Pearl
|
2
|
+
class Client
|
3
|
+
|
4
|
+
module Droplets
|
5
|
+
def droplets
|
6
|
+
puts request("droplets").body
|
7
|
+
end
|
8
|
+
|
9
|
+
def droplet(id)
|
10
|
+
puts request("droplets/#{id}").body
|
11
|
+
end
|
12
|
+
|
13
|
+
def create_droplet(options = {})
|
14
|
+
params = { name: options[:name],
|
15
|
+
size_id: options[:size_id],
|
16
|
+
image_id: options[:image_id],
|
17
|
+
region_id: options[:region_id]
|
18
|
+
}
|
19
|
+
puts request("droplets/new", params).body
|
20
|
+
end
|
21
|
+
|
22
|
+
def reboot(id)
|
23
|
+
puts request("droplets/#{id}/reboot").body
|
24
|
+
end
|
25
|
+
|
26
|
+
def power_cycle(id)
|
27
|
+
puts request("droplets/#{id}/power_cycle").body
|
28
|
+
end
|
29
|
+
|
30
|
+
def shutdown(id)
|
31
|
+
puts request("droplets/#{id}/shutdown").body
|
32
|
+
end
|
33
|
+
|
34
|
+
def power_off(id)
|
35
|
+
puts request("droplets/#{id}/power_off").body
|
36
|
+
end
|
37
|
+
|
38
|
+
def power_on(id)
|
39
|
+
puts request("droplets/#{id}/power_on").body
|
40
|
+
end
|
41
|
+
|
42
|
+
def password_reset(id)
|
43
|
+
puts request("droplets/#{id}/password_reset").body
|
44
|
+
end
|
45
|
+
|
46
|
+
def resize(id, size_id)
|
47
|
+
params = { size_id: size_id }
|
48
|
+
puts request("droplets/#{id}/resize", params).body
|
49
|
+
end
|
50
|
+
|
51
|
+
def snapshot(id, name)
|
52
|
+
params = { name: name }
|
53
|
+
puts request("droplets/#{id}/snapshot", params).body
|
54
|
+
end
|
55
|
+
|
56
|
+
def restore(id, image_id)
|
57
|
+
params = { image_id: image_id }
|
58
|
+
puts request("droplets/#{id}/restore", params).body
|
59
|
+
end
|
60
|
+
|
61
|
+
def rebuild(id, image_id)
|
62
|
+
params = { image_id: image_id }
|
63
|
+
puts request("droplets/#{id}/rebuild", params).body
|
64
|
+
end
|
65
|
+
|
66
|
+
def enable_backups(id)
|
67
|
+
puts request("droplets/#{id}/enable_backups").body
|
68
|
+
end
|
69
|
+
|
70
|
+
def disable_backups(id)
|
71
|
+
puts request("droplets/#{id}/disable_backups").body
|
72
|
+
end
|
73
|
+
|
74
|
+
def destroy_droplet(id)
|
75
|
+
puts request("droplets/#{id}/destroy").body
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Pearl
|
2
|
+
class Client
|
3
|
+
|
4
|
+
module Images
|
5
|
+
# TODO: image filters
|
6
|
+
def images(filter)
|
7
|
+
if filter === /\A(my|global)\z/
|
8
|
+
params = { filter: name }
|
9
|
+
puts request("images", params).body
|
10
|
+
else
|
11
|
+
puts request("images").body
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def image(id)
|
16
|
+
puts request("images/#{id}").body
|
17
|
+
end
|
18
|
+
|
19
|
+
def destroy_image(id)
|
20
|
+
puts request("images/#{id}").body
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/lib/pearl/client.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'pearl/http'
|
2
|
+
|
3
|
+
module Pearl
|
4
|
+
class Client
|
5
|
+
Dir[File.expand_path('../client/*.rb', __FILE__)].each { |f| require f }
|
6
|
+
|
7
|
+
include Droplets
|
8
|
+
include Images
|
9
|
+
include Regions
|
10
|
+
include Sizes
|
11
|
+
|
12
|
+
attr_reader :client_id
|
13
|
+
attr_reader :api_key
|
14
|
+
attr_reader :api_host
|
15
|
+
|
16
|
+
def initialize(options = {})
|
17
|
+
@client_id = options[:client_id] || ENV['DIGITAL_OCEAN_CLIENT_ID']
|
18
|
+
@api_key = options[:api_key] || ENV['DIGITAL_OCEAN_API_KEY']
|
19
|
+
@api_host = options[:api_host] || 'api.digitalocean.com'
|
20
|
+
|
21
|
+
extend Pearl::HTTP
|
22
|
+
|
23
|
+
raise 'Error: Invalid client id.' if @client_id.nil?
|
24
|
+
raise 'Error: Invalid api key.' if @api_key.nil?
|
25
|
+
end
|
26
|
+
|
27
|
+
def base_url
|
28
|
+
"https://#{@api_host}/"
|
29
|
+
end
|
30
|
+
|
31
|
+
def authenticated?
|
32
|
+
@client_id != nil && @api_key != nil
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
data/lib/pearl/error.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
module Pearl
|
2
|
+
|
3
|
+
# Standard Pearl Error for rescuing form all Peal errors
|
4
|
+
class Error < StandardError; end
|
5
|
+
|
6
|
+
# Raised when Peal returns a 400 HTTP status code
|
7
|
+
class BadRequest < Error; end
|
8
|
+
|
9
|
+
# Raised when Peal returns a 401 HTTP status code
|
10
|
+
class Unauthorized < Error; end
|
11
|
+
|
12
|
+
# Raised when Peal returns a 403 HTTP status code
|
13
|
+
class Forbidden < Error; end
|
14
|
+
|
15
|
+
# Raised when Peal returns a 404 HTTP status code
|
16
|
+
class NotFound < Error; end
|
17
|
+
|
18
|
+
# Raised when Peal returns a 406 HTTP status code
|
19
|
+
class NotAcceptable < Error; end
|
20
|
+
|
21
|
+
# Raised when Peal returns a 422 HTTP status code
|
22
|
+
class UnprocessableEntity < Error; end
|
23
|
+
|
24
|
+
# Raised when Peal returns a 500 HTTP status code
|
25
|
+
class InternalServerError < Error; end
|
26
|
+
|
27
|
+
# Raised when Peal returns a 501 HTTP status code
|
28
|
+
class NotImplemented < Error; end
|
29
|
+
|
30
|
+
# Raised when Peal returns a 502 HTTP status code
|
31
|
+
class BadGateway < Error; end
|
32
|
+
|
33
|
+
# Raised when Peal returns a 503 HTTP status code
|
34
|
+
class ServiceUnavailable < Error; end
|
35
|
+
end
|
data/lib/pearl/http.rb
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
module Pearl
|
2
|
+
module HTTP
|
3
|
+
require 'net/https'
|
4
|
+
require 'uri'
|
5
|
+
|
6
|
+
private
|
7
|
+
def http
|
8
|
+
return @http if @http
|
9
|
+
|
10
|
+
uri = URI.parse(self.base_url)
|
11
|
+
@http = Net::HTTP.new(uri.host, uri.port)
|
12
|
+
|
13
|
+
@http.use_ssl = true
|
14
|
+
@http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
15
|
+
|
16
|
+
@http
|
17
|
+
end
|
18
|
+
|
19
|
+
def request(path, params = nil)
|
20
|
+
uri = URI.parse("#{self.base_url}#{path}")
|
21
|
+
|
22
|
+
auth_params = { client_id: self.client_id, api_key: self.api_key }
|
23
|
+
|
24
|
+
if params.nil? || params.empty?
|
25
|
+
params = auth_params
|
26
|
+
else
|
27
|
+
params.merge!(auth_params)
|
28
|
+
end
|
29
|
+
|
30
|
+
uri.query = URI.encode_www_form(params)
|
31
|
+
|
32
|
+
request = Net::HTTP::Get.new(uri.request_uri)
|
33
|
+
|
34
|
+
request['Content-Type'] = 'application/json'
|
35
|
+
|
36
|
+
response = http.request(request)
|
37
|
+
|
38
|
+
Response.new(response)
|
39
|
+
end
|
40
|
+
|
41
|
+
class Response
|
42
|
+
attr_accessor :code
|
43
|
+
attr_accessor :body
|
44
|
+
|
45
|
+
def initialize(response)
|
46
|
+
@code = response.code
|
47
|
+
@body = response.body
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
data/lib/pearl.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
require "pearl/version"
|
2
|
+
require "pearl/error"
|
3
|
+
require "pearl/client"
|
4
|
+
|
5
|
+
module Pearl
|
6
|
+
class << self
|
7
|
+
|
8
|
+
# Alias for Pearl::Client.new
|
9
|
+
#
|
10
|
+
# @return [Peal::Client]
|
11
|
+
def new(options = {})
|
12
|
+
Client.new(options)
|
13
|
+
end
|
14
|
+
|
15
|
+
# Delegate to Pearl::Client
|
16
|
+
def method_missing(method, *args, &block)
|
17
|
+
return super unless new.respond_to?(method)
|
18
|
+
new.send(method, *args, &block)
|
19
|
+
end
|
20
|
+
|
21
|
+
# Forward respond_to? to Pearl::Client
|
22
|
+
def respond_to?(method, include_private = false)
|
23
|
+
new.respond_to?(method, include_private) || super(method, include_private)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/pearl.gemspec
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'pearl/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "pearl"
|
8
|
+
spec.version = Pearl::VERSION
|
9
|
+
spec.authors = ["Manish Valechha"]
|
10
|
+
spec.email = ["manishval@hotmail.com"]
|
11
|
+
spec.description = "Command line interface for DigitalOcean's API"
|
12
|
+
spec.summary = spec.description
|
13
|
+
spec.homepage = "https://github.com/manishval/pearl"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.executables = "pearl"
|
22
|
+
|
23
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
24
|
+
spec.add_development_dependency "rake"
|
25
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class DropletsTest < Pearl::TestCase
|
4
|
+
def test_droplets
|
5
|
+
end
|
6
|
+
|
7
|
+
def test_droplet
|
8
|
+
end
|
9
|
+
|
10
|
+
def test_create_droplet
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_reboot
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_power_cycle
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_shutdown
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_power_off
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_power_on
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_password_reset
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_resize
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_snapshot
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_restore
|
38
|
+
end
|
39
|
+
|
40
|
+
def test_rebuild
|
41
|
+
end
|
42
|
+
|
43
|
+
def test_enable_backups
|
44
|
+
end
|
45
|
+
|
46
|
+
def test_disable_backups
|
47
|
+
end
|
48
|
+
|
49
|
+
def test_destroy_droplet
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class ClientTest < Pearl::TestCase
|
4
|
+
def test_initialization
|
5
|
+
client = Pearl::Client.new(client_id: 'qwerty')
|
6
|
+
assert_equal 'qwerty', client.client_id
|
7
|
+
|
8
|
+
client = Pearl::Client.new(api_key: 'ytrewq')
|
9
|
+
assert_equal 'ytrewq', client.api_key
|
10
|
+
|
11
|
+
client = Pearl::Client.new(client_id: 'qwerty', api_key: 'ytrewq')
|
12
|
+
assert_equal true, client.authenticated?
|
13
|
+
|
14
|
+
client = Pearl::Client.new
|
15
|
+
refute client.authenticated?
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_base_url
|
19
|
+
client = Pearl::Client.new
|
20
|
+
assert_equal 'https://api.digitalocean.com/', client.base_url
|
21
|
+
|
22
|
+
client = Pearl::Client.new(api_host: 'www.example.com')
|
23
|
+
assert_equal 'https://www.example.com/', client.base_url
|
24
|
+
end
|
25
|
+
end
|
data/test/pearl_test.rb
ADDED
data/test/test_helper.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler'
|
3
|
+
Bundler.require :test
|
4
|
+
|
5
|
+
require 'simplecov'
|
6
|
+
SimpleCov.start
|
7
|
+
|
8
|
+
require 'minitest/autorun'
|
9
|
+
require 'pearl'
|
10
|
+
|
11
|
+
# Support files
|
12
|
+
Dir["#{File.expand_path(File.dirname(__FILE__))}/support/*.rb"].each do |file|
|
13
|
+
require file
|
14
|
+
end
|
15
|
+
|
16
|
+
# VCR
|
17
|
+
VCR.configure do |c|
|
18
|
+
c.cassette_library_dir = 'test/cassettes'
|
19
|
+
c.hook_into :webmock
|
20
|
+
end
|
21
|
+
|
22
|
+
class Pearl::TestCase < MiniTest::Unit::TestCase
|
23
|
+
include ::ClientMacros
|
24
|
+
end
|
metadata
ADDED
@@ -0,0 +1,107 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: pearl
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.3
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Manish Valechha
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-03-05 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.3'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.3'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
description: Command line interface for DigitalOcean's API
|
42
|
+
email:
|
43
|
+
- manishval@hotmail.com
|
44
|
+
executables:
|
45
|
+
- pearl
|
46
|
+
extensions: []
|
47
|
+
extra_rdoc_files: []
|
48
|
+
files:
|
49
|
+
- .gitignore
|
50
|
+
- Gemfile
|
51
|
+
- LICENSE.txt
|
52
|
+
- README.md
|
53
|
+
- Rakefile
|
54
|
+
- bin/pearl
|
55
|
+
- lib/pearl.rb
|
56
|
+
- lib/pearl/cli.rb
|
57
|
+
- lib/pearl/client.rb
|
58
|
+
- lib/pearl/client/droplets.rb
|
59
|
+
- lib/pearl/client/images.rb
|
60
|
+
- lib/pearl/client/regions.rb
|
61
|
+
- lib/pearl/client/sizes.rb
|
62
|
+
- lib/pearl/error.rb
|
63
|
+
- lib/pearl/http.rb
|
64
|
+
- lib/pearl/version.rb
|
65
|
+
- pearl.gemspec
|
66
|
+
- test/pearl/client/droplets_test.rb
|
67
|
+
- test/pearl/client/images_test.rb
|
68
|
+
- test/pearl/client/regions_test.rb
|
69
|
+
- test/pearl/client/sizes_test.rb
|
70
|
+
- test/pearl/client_test.rb
|
71
|
+
- test/pearl_test.rb
|
72
|
+
- test/support/client_macros.rb
|
73
|
+
- test/test_helper.rb
|
74
|
+
homepage: https://github.com/manishval/pearl
|
75
|
+
licenses:
|
76
|
+
- MIT
|
77
|
+
metadata: {}
|
78
|
+
post_install_message:
|
79
|
+
rdoc_options: []
|
80
|
+
require_paths:
|
81
|
+
- lib
|
82
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
83
|
+
requirements:
|
84
|
+
- - '>='
|
85
|
+
- !ruby/object:Gem::Version
|
86
|
+
version: '0'
|
87
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
88
|
+
requirements:
|
89
|
+
- - '>='
|
90
|
+
- !ruby/object:Gem::Version
|
91
|
+
version: '0'
|
92
|
+
requirements: []
|
93
|
+
rubyforge_project:
|
94
|
+
rubygems_version: 2.0.0
|
95
|
+
signing_key:
|
96
|
+
specification_version: 4
|
97
|
+
summary: Command line interface for DigitalOcean's API
|
98
|
+
test_files:
|
99
|
+
- test/pearl/client/droplets_test.rb
|
100
|
+
- test/pearl/client/images_test.rb
|
101
|
+
- test/pearl/client/regions_test.rb
|
102
|
+
- test/pearl/client/sizes_test.rb
|
103
|
+
- test/pearl/client_test.rb
|
104
|
+
- test/pearl_test.rb
|
105
|
+
- test/support/client_macros.rb
|
106
|
+
- test/test_helper.rb
|
107
|
+
has_rdoc:
|