pearl 0.0.3
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.
- 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:
|