openhosting 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- data.tar.gz.sig +0 -0
- data/Gemfile +12 -0
- data/LICENSE.txt +20 -0
- data/README.md +152 -0
- data/Rakefile +30 -0
- data/certs/lazzarello.pem +20 -0
- data/lib/connection.rb +32 -0
- data/lib/drives.rb +172 -0
- data/lib/openhosting.rb +39 -0
- data/lib/resources.rb +61 -0
- data/lib/servers.rb +166 -0
- data/openhosting.gemspec +25 -0
- data/spec/lib/connection_spec.rb +9 -0
- data/spec/spec_helper.rb +20 -0
- metadata +93 -0
- metadata.gz.sig +0 -0
data.tar.gz.sig
ADDED
Binary file
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
This is a library which implements the Open Hosting API. It can be
|
2
|
+
used to create, modify and delete virtual servers, drives and associated
|
3
|
+
resources like IP addresses and vlans. It can also be used as a general purpose
|
4
|
+
upload/download interface for the contents of virtual drives. It is based on
|
5
|
+
the Elastic Hosts API.
|
6
|
+
|
7
|
+
Copyright (C) 2015 Lee Azzarello <lee@openhosting.com>
|
8
|
+
|
9
|
+
This program is free software: you can redistribute it and/or modify
|
10
|
+
it under the terms of the GNU General Public License as published by
|
11
|
+
the Free Software Foundation, either version 3 of the License, or
|
12
|
+
(at your option) any later version.
|
13
|
+
|
14
|
+
This program is distributed in the hope that it will be useful,
|
15
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
16
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
17
|
+
GNU General Public License for more details.
|
18
|
+
|
19
|
+
You should have received a copy of the GNU General Public License
|
20
|
+
along with this program. If not, see <http://opensource.org/licenses/gpl-3.0.html>.
|
data/README.md
ADDED
@@ -0,0 +1,152 @@
|
|
1
|
+
# Open Hosting API client library
|
2
|
+
|
3
|
+
This library implements every method in the
|
4
|
+
[Open Hosting API](http://www.openhosting.com/api/)
|
5
|
+
in the Ruby programming language. It depends on the
|
6
|
+
[Faraday HTTP client library](https://github.com/lostisland/faraday),
|
7
|
+
which has the advantage of abstracting various backends. It has a
|
8
|
+
clearly defined interface and works well for small programs and large scale web
|
9
|
+
applications alike.
|
10
|
+
|
11
|
+
By default it uses the
|
12
|
+
[Patron adapter](https://toland.github.io/patron/)
|
13
|
+
for maximum compatibility with the
|
14
|
+
[reference implementation](http://www.elastichosts.com/support/api/)
|
15
|
+
designed by Elastic Hosts. Patron requires the libcurl native library for high performance and features.
|
16
|
+
|
17
|
+
If you wish to use the default Ruby Net::HTTP library, modify the file
|
18
|
+
`lib/connection.rb` to read `faraday.adapter :default_adapter` during connection
|
19
|
+
initialization. This will eventually be wrapped into some initialization
|
20
|
+
option.
|
21
|
+
|
22
|
+
## Connecting
|
23
|
+
|
24
|
+
To get started, install the gem and set a single environment variable in your
|
25
|
+
shell containing your account's user id and secret key. This information is available through your account [Profile under the Authentication tab](https://east1.openhosting.com/accounts/profile/#auth). Put this into
|
26
|
+
`~/.bashrc` to load these automatically.
|
27
|
+
|
28
|
+
```
|
29
|
+
$ gem install openhosting
|
30
|
+
$ export OHAUTH=<userid>:<secret key>
|
31
|
+
```
|
32
|
+
|
33
|
+
From there, establish a connection to the API like so
|
34
|
+
|
35
|
+
```
|
36
|
+
require 'openhosting'
|
37
|
+
oh = Openhosting.new
|
38
|
+
```
|
39
|
+
|
40
|
+
From here, you can pass around the connection to other objects.
|
41
|
+
|
42
|
+
## Drives
|
43
|
+
### Create a drive
|
44
|
+
|
45
|
+
```
|
46
|
+
drives = OHDrives.new(oh)
|
47
|
+
drive = OHDrive.new("Foo", "1G")
|
48
|
+
foo_drive = drives.create drive
|
49
|
+
```
|
50
|
+
|
51
|
+
The drive API supports advanced methods not available through the web GUI. They
|
52
|
+
are:
|
53
|
+
|
54
|
+
"avoid": when set to a list of drive UUIDs, weight the load balancing algorithm
|
55
|
+
to ensure this drive is created on different physical storage than those in the
|
56
|
+
list
|
57
|
+
|
58
|
+
"encryption:cipher": Open Hosting drives are transparently encrypted by
|
59
|
+
default. If this value is set to "none" it will bypass the encryption.
|
60
|
+
|
61
|
+
### Uploading and Downloading
|
62
|
+
|
63
|
+
The API supports the upload and download of raw data to and from your local
|
64
|
+
disk. This can be used for many things, including the upload of a raw QEMU
|
65
|
+
image to boot into a Virtual Machine. This is also useful for backing up the
|
66
|
+
binary data of a drive that isn't mounted.
|
67
|
+
|
68
|
+
## Servers
|
69
|
+
|
70
|
+
```
|
71
|
+
servers = OHServers.new(oh)
|
72
|
+
server = OHServer.new("Foo",true,1500,1024,{"nic:0:dhcp" => "auto", "vnc" => "auto", "password" => "changeme" })
|
73
|
+
foo_server = servers.create server, false
|
74
|
+
```
|
75
|
+
|
76
|
+
This will create a persistent server named Foo with no drives attached in the
|
77
|
+
stopped state. The second required argument to `OHServer#new` determines if the
|
78
|
+
server is persistant or not. It should usually be true. The second argument to
|
79
|
+
OHServers#create determines if the server should be started on creation. This
|
80
|
+
is also useful for autoscaling setups.
|
81
|
+
|
82
|
+
To get it to boot, you'll have to create an an empty drive and use the image
|
83
|
+
method to copy a prebuilt image into it. The following will do that for the
|
84
|
+
Debian 7 prebuilt, using the above new drive creation semantics.
|
85
|
+
|
86
|
+
```
|
87
|
+
source = "2e4a8cc1-734e-465a-8f27-889baffd4e56"
|
88
|
+
drives.image foo_drive['drive'], source
|
89
|
+
drives.info source['drive']
|
90
|
+
```
|
91
|
+
|
92
|
+
When it's done imaging, you can attach it to the server and start it up.
|
93
|
+
|
94
|
+
```
|
95
|
+
servers.set foo_server['server'], {"ide:0:0" => foo_drive['drive'], "boot" => "ide:0:0"}
|
96
|
+
servers.start foo_server['server']
|
97
|
+
```
|
98
|
+
|
99
|
+
You'll get "imaging" => true and a bunch of other options about it being
|
100
|
+
claimed.
|
101
|
+
|
102
|
+
The server API supports more options than available through the GUI
|
103
|
+
control panel. Notably you can set a Virtual Machine to be non-persistant so
|
104
|
+
that it is automatically destroyed when stopped. With this option it is
|
105
|
+
possible to implement auto-scaling infrastructure.
|
106
|
+
|
107
|
+
It also gives you the option to influence the load balancing algorithm to
|
108
|
+
determine the physical infrastructure where your servers and drives are located.
|
109
|
+
This can be used for Virtual Machines which are expected to have heavy load, so
|
110
|
+
they are evenly spread out over the physical infrastructure.
|
111
|
+
|
112
|
+
You can also set the MAC address of the optional NICs 1 through 3.
|
113
|
+
|
114
|
+
## Resources
|
115
|
+
### List all resources
|
116
|
+
|
117
|
+
```
|
118
|
+
resources = OHResources.new(oh)
|
119
|
+
all_resources = resources.list
|
120
|
+
```
|
121
|
+
|
122
|
+
The resources are defined by their type. There are only two resource types at
|
123
|
+
the moment, "ip" and "vlan".
|
124
|
+
|
125
|
+
Each resource requires a name, though only the vlan resource shows this name
|
126
|
+
through any meaningful interface. It appears that the ip resource automatically
|
127
|
+
assigns a two letter name when an object of this type is created through the
|
128
|
+
web GUI.
|
129
|
+
|
130
|
+
Each resource type has extended metadata associated with it. This information
|
131
|
+
is acquired through the "info" verb. An individual resource requires a location
|
132
|
+
that resembles this:
|
133
|
+
|
134
|
+
```
|
135
|
+
conn.get '/resources/#{type}/#{resource}/info'
|
136
|
+
```
|
137
|
+
|
138
|
+
Where the resource value is the "resource" key in the output of the
|
139
|
+
`/resources/list` method. That means an IP address for the ip type and a UUID
|
140
|
+
of a vlan for that type. *Very confusing!*
|
141
|
+
|
142
|
+
## Testing
|
143
|
+
|
144
|
+
This library has a full set of tests which operate on mocks and stubs. HTTP
|
145
|
+
request mocks are generated with [Webmock](https://github.com/bblimke/webmock).
|
146
|
+
Fixtures are generated with the [VCR framework](https://github.com/vcr/vcr).
|
147
|
+
Unit tests are done with [Minitest](https://github.com/seattlerb/minitest),
|
148
|
+
which is built into Ruby > 1.8.
|
149
|
+
|
150
|
+
The basic test architecture is based on [an
|
151
|
+
article](http://code.tutsplus.com/tutorials/writing-an-api-wrapper-in-ruby-with-tdd--net-23875)
|
152
|
+
by [Claudio Ortolina](http://tutsplus.com/authors/claudio-ortolina)
|
data/Rakefile
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'rake/testtask'
|
2
|
+
|
3
|
+
Rake::TestTask.new do |t|
|
4
|
+
t.test_files = FileList['spec/lib/*_spec.rb']
|
5
|
+
t.verbose = true
|
6
|
+
end
|
7
|
+
|
8
|
+
task :default => :test
|
9
|
+
|
10
|
+
def name
|
11
|
+
@name ||= Dir['*.gemspec'].first.split('.').first
|
12
|
+
end
|
13
|
+
|
14
|
+
def version
|
15
|
+
line = File.read("lib/#{name}.rb")[/^\s*VERSION\s*=\s*.*/]
|
16
|
+
line.match(/.*VERSION\s*=\s*['"](.*)['"]/)[1]
|
17
|
+
end
|
18
|
+
|
19
|
+
def gemspec_file
|
20
|
+
"#{name}.gemspec"
|
21
|
+
end
|
22
|
+
|
23
|
+
def gem_file
|
24
|
+
"#{name}-#{version}.gem"
|
25
|
+
end
|
26
|
+
|
27
|
+
desc "Run all tests"
|
28
|
+
task :test do
|
29
|
+
exec 'script/test'
|
30
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
-----BEGIN CERTIFICATE-----
|
2
|
+
MIIDNDCCAhygAwIBAgIBADANBgkqhkiG9w0BAQUFADBAMQwwCgYDVQQDDANsZWUx
|
3
|
+
GzAZBgoJkiaJk/IsZAEZFgtvcGVuaG9zdGluZzETMBEGCgmSJomT8ixkARkWA2Nv
|
4
|
+
bTAeFw0xNTAyMDgxNzQ2MThaFw0xNjAyMDgxNzQ2MThaMEAxDDAKBgNVBAMMA2xl
|
5
|
+
ZTEbMBkGCgmSJomT8ixkARkWC29wZW5ob3N0aW5nMRMwEQYKCZImiZPyLGQBGRYD
|
6
|
+
Y29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2qQdzRDeyP1kq9YZ
|
7
|
+
tjKauy9xrBssns3FGFSUR/LPNQ4vhwPll16m5udnTx9atUJk4k16WFQFEL+FWQqa
|
8
|
+
ROxpvDrO95R1u4XYilfKrVYV5O1fJl29ANlIgARdulmlUUQda3Tl/am2SUKVTQUd
|
9
|
+
S2MZgFCGGccjkrY671N0RlXkExS6s29y+wY9t8f1o6XK+Ye04jUlD4C/v+Pp+xvf
|
10
|
+
REjj585mFYi5sL/a0eQWeyndSb1GlKDU/KyCW0sHVruSrMERgY8Fo7FQNZ0CrPRk
|
11
|
+
201XPEpV+vNYB2KAEHXk/yp7ZWHk/CcRLBGhLqMXOvjVkQTIJ4XmitGI7Cmlk0XY
|
12
|
+
Bb+4OQIDAQABozkwNzAJBgNVHRMEAjAAMB0GA1UdDgQWBBSx5G4JVvMW6/AgP1iq
|
13
|
+
sfi04cH5XzALBgNVHQ8EBAMCBLAwDQYJKoZIhvcNAQEFBQADggEBALYpabQ88BEO
|
14
|
+
d3z39u/ukHqFc4i59vnRBHV2AefsIcieCf8yGtLk3LjtzQuxDJQuVG/iTVMqBFWG
|
15
|
+
vyXRIFevJ8LweEaUYEs70ysxwFimW9DZqByJSFA+tWrNpkvSQENAoxYponOTW5tb
|
16
|
+
odWgh2q2o8QUOIrovKQVWfsfK8PJUNRVoqSvdCyh9D8W+rnAcHre3bB3xJ/fdMhU
|
17
|
+
NtMX2pF9W5kD88F+xvEuC8uCZuHmX8o7EvjK5WDh+JxZwdYcvj6V7mVq04sWidJx
|
18
|
+
xIetRfyDRRrpaI40D5AvKNVdlWvW5wxgbdUd8+KK568lPf5TocC3picUMdKNqn+h
|
19
|
+
Xr4wD7DM4Y0=
|
20
|
+
-----END CERTIFICATE-----
|
data/lib/connection.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
ENDPOINT="https://api-east1.openhosting.com"
|
2
|
+
OHAUTH=ENV['OHAUTH']
|
3
|
+
|
4
|
+
if ( OHAUTH.nil? )
|
5
|
+
raise "Please set the OHAUTH environment variable to your user UUID and secret key"
|
6
|
+
end
|
7
|
+
|
8
|
+
s = OHAUTH.split(':')
|
9
|
+
|
10
|
+
USERNAME = s[0]
|
11
|
+
PASSWORD = s[1]
|
12
|
+
|
13
|
+
class OHConnection
|
14
|
+
include Faraday
|
15
|
+
# remember, this is a method, not a symbol and takes a symbol to a method as arguments!
|
16
|
+
attr_accessor :connect
|
17
|
+
|
18
|
+
@@conn = Faraday.new( :url => ENDPOINT, :headers => {"Content-Type" => "application/json", "Accept" => "application/json"}) do |faraday|
|
19
|
+
# Prolly will be relevant!
|
20
|
+
# https://github.com/lostisland/faraday/wiki/Setting-up-SSL-certificates
|
21
|
+
faraday.ssl
|
22
|
+
faraday.request :multipart
|
23
|
+
faraday.request :url_encoded
|
24
|
+
faraday.response :logger
|
25
|
+
faraday.adapter :patron
|
26
|
+
faraday.basic_auth USERNAME, PASSWORD
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.connect
|
30
|
+
return @@conn
|
31
|
+
end
|
32
|
+
end
|
data/lib/drives.rb
ADDED
@@ -0,0 +1,172 @@
|
|
1
|
+
class OHDrives
|
2
|
+
attr_accessor :conn, :options
|
3
|
+
@@subject = '/drives' # this will prepend to all locations
|
4
|
+
|
5
|
+
def initialize(conn=OHConnection.connect, options={})
|
6
|
+
@conn = conn
|
7
|
+
@options = options
|
8
|
+
end
|
9
|
+
|
10
|
+
def list
|
11
|
+
# the __method__ instance var is the name of the method
|
12
|
+
resp = @conn.get "#{@@subject}/#{__method__}"
|
13
|
+
# returns an array of Hashes, convert to an array of OHDriveUUID objects
|
14
|
+
return JSON.parse(resp.body)
|
15
|
+
end
|
16
|
+
|
17
|
+
def info(*uuid)
|
18
|
+
# add our subject, in this case "drives"
|
19
|
+
loc = @@subject
|
20
|
+
|
21
|
+
if ( uuid.size > 0 )
|
22
|
+
if ( uuid.first.length == 36 )
|
23
|
+
loc += "/#{uuid.first}"
|
24
|
+
else
|
25
|
+
raise TypeError, "The Drive UUID passed is invalid. It is #{uuid.first}. It must be 36 characters"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
# add the verb, which is the method name
|
29
|
+
loc += "/#{__method__}"
|
30
|
+
resp = @conn.get loc
|
31
|
+
return JSON.parse(resp.body)
|
32
|
+
end
|
33
|
+
|
34
|
+
def create(drive,options={})
|
35
|
+
if ( drive.is_a? OHDrive )
|
36
|
+
# a data structure of optional user metadata and tags
|
37
|
+
ctags = options[:ctags]
|
38
|
+
user_data = options[:user]
|
39
|
+
# we're going to have to serialize the object to JSON
|
40
|
+
# http://www.skorks.com/2010/04/serializing-and-deserializing-objects-with-ruby/
|
41
|
+
body = drive.to_json
|
42
|
+
resp = @conn.post "#{@@subject}/#{__method__}", body
|
43
|
+
return JSON.parse(resp.body)
|
44
|
+
else
|
45
|
+
raise TypeError, "First argument must be an instance of OHDrive with a unique name"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def destroy(drive)
|
50
|
+
loc = @@subject
|
51
|
+
if ( drive.length == 36 )
|
52
|
+
loc += "/#{drive}"
|
53
|
+
else
|
54
|
+
raise TypeError, "The Drive UUID passed is invalid. It is #{drive}. It must be 36 characters"
|
55
|
+
end
|
56
|
+
loc += "/#{__method__}"
|
57
|
+
resp = @conn.post loc
|
58
|
+
return resp.status
|
59
|
+
end
|
60
|
+
|
61
|
+
def set(drive, options={})
|
62
|
+
# This should accept a drive object, not a UUID
|
63
|
+
# drive.drive should return the UUID
|
64
|
+
options = options.to_json
|
65
|
+
loc = @@subject
|
66
|
+
if ( drive.length == 36 )
|
67
|
+
loc += "/#{drive}"
|
68
|
+
else
|
69
|
+
raise TypeError, "The Drive UUID passed is invalid. It is #{drive}. It must be 36 characters"
|
70
|
+
end
|
71
|
+
loc += "/#{__method__}"
|
72
|
+
resp = @conn.post loc, options
|
73
|
+
return JSON.parse(resp.body)
|
74
|
+
end
|
75
|
+
|
76
|
+
def image(drive, source, *conversion)
|
77
|
+
# drive and source should be Drive objects
|
78
|
+
loc = @@subject
|
79
|
+
if ( drive.length == 36)
|
80
|
+
loc += "/#{drive}"
|
81
|
+
else
|
82
|
+
raise TypeError, "The Drive UUID passed is invalid. It is #{drive}. It must be 36 characters"
|
83
|
+
end
|
84
|
+
loc += "/#{__method__}"
|
85
|
+
if ( source.length == 36)
|
86
|
+
loc += "/#{source}"
|
87
|
+
else
|
88
|
+
raise TypeError, "The Source UUID passed is invalid. It is #{source}. It must be 36 characters"
|
89
|
+
end
|
90
|
+
if ( conversion.size > 0 )
|
91
|
+
loc += "/#{conversion.first.to_s}"
|
92
|
+
end
|
93
|
+
resp = @conn.post loc
|
94
|
+
return resp.status
|
95
|
+
end
|
96
|
+
|
97
|
+
# this will take some weird options for Faraday
|
98
|
+
# http://www.rubydoc.info/gems/faraday/
|
99
|
+
def read(drive, offset=0, size="4M")
|
100
|
+
content_type = "application/octet-stream"
|
101
|
+
loc = @@subject
|
102
|
+
if ( drive.length == 36 )
|
103
|
+
loc += "/#{drive}"
|
104
|
+
else
|
105
|
+
raise TypeError, "The Drive UUID passed is invalid. It is #{drive}. It must be 36 characters"
|
106
|
+
end
|
107
|
+
loc += "/#{__method__}/#{offset.to_s}/#{size.to_s}"
|
108
|
+
return loc
|
109
|
+
#resp = @conn.get loc
|
110
|
+
#return JSON.parse(resp.body)
|
111
|
+
# this'll have to take a file as an argument, since we are streaming binary data to disk.
|
112
|
+
end
|
113
|
+
|
114
|
+
def write(drive, offset=0)
|
115
|
+
content_type = "application/octet-stream"
|
116
|
+
content_encoding = "gzip"
|
117
|
+
loc = @@subject
|
118
|
+
if ( drive.length == 36 )
|
119
|
+
loc += "/#{drive}"
|
120
|
+
else
|
121
|
+
raise TypeError, "The Drive UUID passed is invalid. It is #{drive}. It must be 36 characters"
|
122
|
+
end
|
123
|
+
loc += "/#{__method__}/#{offset.to_s}"
|
124
|
+
return loc
|
125
|
+
# this'll have to take a file as an argument, since we are streaming binary data to a URL.
|
126
|
+
#resp = @conn.post loc, body
|
127
|
+
#return resp.status
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
class OHDrive
|
132
|
+
|
133
|
+
=begin
|
134
|
+
The /drives/<uuid>/info location returns a hash which looks like this as of API v?? on Fri Feb 6 01:40:45 UTC 2015
|
135
|
+
|
136
|
+
It appears the only required parameters are :name and :size, :drive is the UUID and automatically assigned
|
137
|
+
|
138
|
+
{"drive"=>"e173e5a2-d8ff-4e82-9365-5f5aec240add", "encryption:cipher"=>"aes-xts-plain", "name"=>"teamnerds.cool (diaspora fun) (backup)", "size"=>17179869184, "status"=>"active", "tier"=>"disk", "user"=>"e179a513-5a36-4ffa-92a2-bbdc497ecd21"}
|
139
|
+
=end
|
140
|
+
|
141
|
+
attr_accessor :name, :size, :claim_type, :readers, :ctags, :user, :avoid, :encryption_cipher
|
142
|
+
|
143
|
+
def initialize(name, size="1G", *options)
|
144
|
+
@name = name.to_s
|
145
|
+
@size = size.to_s
|
146
|
+
# let's pause with the optional args for now.
|
147
|
+
#@claim_type = options['claim:type']
|
148
|
+
#@readers = options['readers']
|
149
|
+
#@ctags = options['ctags']
|
150
|
+
#@user = options['user']
|
151
|
+
#@avoid = options['avoid']
|
152
|
+
#@encryption_cipher = options['encryption:cipher']
|
153
|
+
# if we only need an instance from a JSON response, we get some more attributes.
|
154
|
+
# I need to figure out how to take raw JSON from the options hash and make this
|
155
|
+
# object from it
|
156
|
+
#@uuid = options['drive']
|
157
|
+
end
|
158
|
+
|
159
|
+
# weird little object to JSON serialization method
|
160
|
+
def to_json(*a)
|
161
|
+
h = {}
|
162
|
+
self.instance_variables.each do |i|
|
163
|
+
n = i.to_s.delete("@")
|
164
|
+
h[n] = self.instance_variable_get(i)
|
165
|
+
end
|
166
|
+
return h.to_json(*a)
|
167
|
+
end
|
168
|
+
|
169
|
+
def self.from_json(o)
|
170
|
+
new(options.first)
|
171
|
+
end
|
172
|
+
end
|
data/lib/openhosting.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'faraday'
|
2
|
+
require 'patron'
|
3
|
+
require 'json'
|
4
|
+
Dir[File.dirname(__FILE__) + '/*.rb'].each do |file|
|
5
|
+
require file
|
6
|
+
end
|
7
|
+
|
8
|
+
module Openhosting
|
9
|
+
VERSION = "0.0.3"
|
10
|
+
|
11
|
+
class << self
|
12
|
+
attr_accessor :connection, :debian, :foo
|
13
|
+
|
14
|
+
def new
|
15
|
+
@connection = OHConnection.connect
|
16
|
+
end
|
17
|
+
|
18
|
+
def connection
|
19
|
+
return @connection
|
20
|
+
end
|
21
|
+
|
22
|
+
def foo
|
23
|
+
puts "bar"
|
24
|
+
end
|
25
|
+
|
26
|
+
def debian(conn=@connection, uuid="2e4a8cc1-734e-465a-8f27-889baffd4e56")
|
27
|
+
# this is a junky method to generate a prebuilt server like the control panel.
|
28
|
+
drive = OHDrive.new("Debian", "2G")
|
29
|
+
d = OHDrives.new(conn)
|
30
|
+
did = d.create(drive)
|
31
|
+
pw = (0...8).map { (65 + rand(26)).chr }.join
|
32
|
+
server = OHServer.new("Debian",true,500,256,{"nic:0:dhcp" => "auto", "vnc" => "auto", "password" => pw, "ide:0:0" => did['drive'], "boot" => "ide:0:0" })
|
33
|
+
s = OHServers.new(conn)
|
34
|
+
sid = s.create(server, false)
|
35
|
+
d.image(did['drive'], uuid)
|
36
|
+
d.info did['drive']
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
data/lib/resources.rb
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
class OHResources
|
2
|
+
attr_accessor :conn, :options
|
3
|
+
@@subject = '/resources' # this will prepend to all locations
|
4
|
+
|
5
|
+
def initialize(conn=OHConnection.connect, options={})
|
6
|
+
@conn = conn
|
7
|
+
@options = options
|
8
|
+
end
|
9
|
+
|
10
|
+
def list(*type)
|
11
|
+
# the __method__ instance var is the name of the method
|
12
|
+
loc = @@subject
|
13
|
+
if ( type.size > 0 )
|
14
|
+
loc += "/#{type.first}"
|
15
|
+
end
|
16
|
+
loc += "/#{__method__}"
|
17
|
+
resp = @conn.get loc
|
18
|
+
return JSON.parse(resp.body)
|
19
|
+
end
|
20
|
+
|
21
|
+
def info(*options)
|
22
|
+
# add our subject, in this case "resources"
|
23
|
+
loc = @@subject
|
24
|
+
if ( options.size > 0)
|
25
|
+
loc += "/#{options[0].to_s}/#{options[1]}"
|
26
|
+
end
|
27
|
+
# add the verb, which is the method name
|
28
|
+
loc += "/#{__method__}"
|
29
|
+
resp = @conn.get loc
|
30
|
+
return JSON.parse(resp.body)
|
31
|
+
end
|
32
|
+
|
33
|
+
def create(type, name, options={})
|
34
|
+
options = options.to_json
|
35
|
+
resp = @conn.post "#{@@subject}/#{type.to_s}/#{__method__}", options
|
36
|
+
return JSON.parse(resp.body)
|
37
|
+
end
|
38
|
+
|
39
|
+
def destroy(type, resource)
|
40
|
+
loc = "#{@@subject}/#{type.to_s}/#{resource.to_s}/#{__method__}"
|
41
|
+
resp = @conn.post loc
|
42
|
+
return resp.status
|
43
|
+
end
|
44
|
+
|
45
|
+
def set(type, resource, options={})
|
46
|
+
options = options.to_json
|
47
|
+
loc = @@subject
|
48
|
+
loc += "/#{type.to_s}/#{resource.to_s}/#{__method__}"
|
49
|
+
resp = @conn.post loc, options
|
50
|
+
return JSON.parse(resp.body)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
class Resource
|
55
|
+
attr_accessor :type
|
56
|
+
|
57
|
+
types = ["ip", "vlan"]
|
58
|
+
def initialize( type )
|
59
|
+
@type = type
|
60
|
+
end
|
61
|
+
end
|
data/lib/servers.rb
ADDED
@@ -0,0 +1,166 @@
|
|
1
|
+
class OHServers
|
2
|
+
attr_accessor :conn, :options
|
3
|
+
@@subject = '/servers' # this will prepend to all locations
|
4
|
+
|
5
|
+
def initialize(conn=OHConnection.connect, options={})
|
6
|
+
@conn = conn
|
7
|
+
@options = options
|
8
|
+
end
|
9
|
+
|
10
|
+
def list
|
11
|
+
# the __method__ instance var is the name of the method
|
12
|
+
resp = @conn.get "#{@@subject}/#{__method__}"
|
13
|
+
return JSON.parse(resp.body)
|
14
|
+
end
|
15
|
+
|
16
|
+
def info(*uuid)
|
17
|
+
# add our subject, in this case "servers"
|
18
|
+
loc = @@subject
|
19
|
+
|
20
|
+
if ( uuid.size > 0 )
|
21
|
+
if ( uuid.first.length == 36 )
|
22
|
+
loc += "/#{uuid.first}"
|
23
|
+
else
|
24
|
+
raise TypeError, "The Server UUID passed is invalid. It is #{uuid.first}. It must be 36 characters"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
# add the verb, which is the method name
|
28
|
+
loc += "/#{__method__}"
|
29
|
+
resp = @conn.get loc
|
30
|
+
return JSON.parse(resp.body)
|
31
|
+
end
|
32
|
+
|
33
|
+
def create(server, start=true)
|
34
|
+
if ( server.is_a? OHServer )
|
35
|
+
body = server.to_json
|
36
|
+
if ( start )
|
37
|
+
resp = @conn.post "#{@@subject}/#{__method__}", body
|
38
|
+
return JSON.parse(resp.body)
|
39
|
+
# rescue JSON::ParserError resp.body
|
40
|
+
else
|
41
|
+
resp = @conn.post "#{@@subject}/#{__method__}/stopped", body
|
42
|
+
#would be cool if this returned an OHServer object to use later
|
43
|
+
return JSON.parse(resp.body)
|
44
|
+
#rescue JSON::ParserError resp.body
|
45
|
+
end
|
46
|
+
else
|
47
|
+
raise TypeError, "First argument must be an instance of OHServer with a unique name"
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
def start(uuid)
|
53
|
+
loc = @@subject
|
54
|
+
if ( uuid.length == 36 )
|
55
|
+
loc += "/#{uuid}"
|
56
|
+
else
|
57
|
+
raise TypeError, "The Server UUID passed is invalid. It is #{uuid}. It must be 36 characters"
|
58
|
+
end
|
59
|
+
loc += "/#{__method__}"
|
60
|
+
resp = @conn.post loc
|
61
|
+
return resp.status
|
62
|
+
end
|
63
|
+
|
64
|
+
def stop(uuid)
|
65
|
+
loc = @@subject
|
66
|
+
if ( uuid.length == 36 )
|
67
|
+
loc += "/#{uuid}"
|
68
|
+
else
|
69
|
+
raise TypeError, "The Server UUID passed is invalid. It is #{uuid}. It must be 36 characters"
|
70
|
+
end
|
71
|
+
loc += "/#{__method__}"
|
72
|
+
resp = @conn.post loc
|
73
|
+
return resp.status
|
74
|
+
end
|
75
|
+
|
76
|
+
def shutdown(uuid)
|
77
|
+
loc = @@subject
|
78
|
+
if ( uuid.length == 36 )
|
79
|
+
loc += "/#{uuid}"
|
80
|
+
else
|
81
|
+
raise TypeError, "The Server UUID passed is invalid. It is #{uuid}. It must be 36 characters"
|
82
|
+
end
|
83
|
+
loc += "/#{__method__}"
|
84
|
+
resp = @conn.post loc
|
85
|
+
return resp.status
|
86
|
+
end
|
87
|
+
|
88
|
+
def reset(uuid)
|
89
|
+
loc = @@subject
|
90
|
+
if ( uuid.length == 36 )
|
91
|
+
loc += "/#{uuid}"
|
92
|
+
else
|
93
|
+
raise TypeError, "The Server UUID passed is invalid. It is #{uuid}. It must be 36 characters"
|
94
|
+
end
|
95
|
+
loc += "/#{__method__}"
|
96
|
+
resp = @conn.post loc
|
97
|
+
return resp.status
|
98
|
+
end
|
99
|
+
|
100
|
+
def destroy(uuid)
|
101
|
+
loc = @@subject
|
102
|
+
if ( uuid.length == 36 )
|
103
|
+
loc += "/#{uuid}"
|
104
|
+
else
|
105
|
+
raise TypeError, "The Server UUID passed is invalid. It is #{uuid}. It must be 36 characters"
|
106
|
+
end
|
107
|
+
loc += "/#{__method__}"
|
108
|
+
resp = @conn.post loc
|
109
|
+
return resp.status
|
110
|
+
end
|
111
|
+
|
112
|
+
def set(uuid, options={})
|
113
|
+
# gonna need some schema validation logic here, as the options to set are significantly reduced when a server is active.
|
114
|
+
options = options.to_json
|
115
|
+
loc = @@subject
|
116
|
+
if ( uuid.length == 36 )
|
117
|
+
loc += "/#{uuid}"
|
118
|
+
else
|
119
|
+
raise TypeError, "The Server UUID passed is invalid. It is #{uuid}. It must be 36 characters"
|
120
|
+
end
|
121
|
+
loc += "/#{__method__}"
|
122
|
+
resp = @conn.post loc, options
|
123
|
+
return JSON.parse(resp.body)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
class OHServer
|
128
|
+
|
129
|
+
=begin
|
130
|
+
The /servers/<uuid>/info location returns a hash which looks like this as of API v?? on Fri Feb 6 01:40:45 UTC 2015
|
131
|
+
|
132
|
+
=end
|
133
|
+
|
134
|
+
attr_accessor :name, :persistent, :cpu, :mem, :options
|
135
|
+
|
136
|
+
def initialize(name, persistent=true, cpu=1000, mem=512, *options)
|
137
|
+
# method argument fun times
|
138
|
+
# http://www.skorks.com/2009/08/method-arguments-in-ruby/
|
139
|
+
@name = name.to_s
|
140
|
+
@persistent = persistent.to_s
|
141
|
+
@cpu = cpu.to_s
|
142
|
+
@mem = mem.to_s
|
143
|
+
@options = options.first
|
144
|
+
end
|
145
|
+
|
146
|
+
# we're going to have to serialize the object to JSON and merge the options
|
147
|
+
# http://www.skorks.com/2010/04/serializing-and-deserializing-objects-with-ruby/
|
148
|
+
def to_json(*a)
|
149
|
+
# here's a more concise version of what I figured out myself!
|
150
|
+
# https://stackoverflow.com/questions/5030553/ruby-convert-object-to-hash
|
151
|
+
h = {}
|
152
|
+
self.instance_variables.each do |i|
|
153
|
+
n = i.to_s.delete("@")
|
154
|
+
h[n] = self.instance_variable_get(i)
|
155
|
+
end
|
156
|
+
# merge in options hash and delete raw argument post-merge
|
157
|
+
# http://www.ruby-doc.org/core-1.9.3/Hash.html#method-i-delete
|
158
|
+
obj = h.merge @options
|
159
|
+
obj.delete("options")
|
160
|
+
return obj.to_json(*a)
|
161
|
+
end
|
162
|
+
|
163
|
+
def self.from_json(o)
|
164
|
+
new(options.first)
|
165
|
+
end
|
166
|
+
end
|
data/openhosting.gemspec
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
lib = "openhosting"
|
2
|
+
lib_file = File.expand_path("../lib/#{lib}.rb", __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
File.read(lib_file) =~ /\bVERSION\s*=\s*["'](.+?)["']/
|
5
|
+
version = $1
|
6
|
+
|
7
|
+
Gem::Specification.new do |gem|
|
8
|
+
gem.name = lib
|
9
|
+
gem.version = version
|
10
|
+
gem.authors = ["Lee Azzarello"]
|
11
|
+
gem.email = ["lee@openhosting.com"]
|
12
|
+
gem.description = "This is a library which implements the Open Hosting API. It can be used to create, modify and delete virtual servers, drives and associated resources like IP addresses and vlans. It can also be used as a general purpose upload/download interface for the contents of virtual drives. It is based on the Elastic Hosts API."
|
13
|
+
gem.summary = "Open Hosting API client library"
|
14
|
+
gem.homepage = "https://code.seriesdigital.com/lee/openhosting-api-clients/tree/master/oh-api-ruby"
|
15
|
+
gem.license = "GPLv3"
|
16
|
+
|
17
|
+
gem.files = `git ls-files`.split($/)
|
18
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
19
|
+
gem.require_paths = ["lib"]
|
20
|
+
gem.cert_chain = ['certs/lazzarello.pem']
|
21
|
+
gem.signing_key = File.expand_path("~/etc/gem-private_key.pem") if $0 =~ /gem\z/
|
22
|
+
|
23
|
+
#gem.add_dependency = 'faraday', '>= 0.9.1'
|
24
|
+
#gem.add_development_dependency 'bundler', '~> 1.0'
|
25
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require_relative '../lib'
|
2
|
+
|
3
|
+
require 'minitest/autorun'
|
4
|
+
require 'webmock/minitest'
|
5
|
+
require 'vcr'
|
6
|
+
require 'turn'
|
7
|
+
|
8
|
+
Turn.config do |c|
|
9
|
+
# :outline - turn's original case/test outline mode [default]
|
10
|
+
c.format = :outline
|
11
|
+
# turn on invoke/execute tracing, enable full backtrace
|
12
|
+
c.trace = true
|
13
|
+
# use humanized test names (works only with :outline format)
|
14
|
+
c.natural = true
|
15
|
+
end
|
16
|
+
|
17
|
+
VCR.config do |c|
|
18
|
+
c.cassette_library_dir = 'spec/fixtures/oh_cassettes'
|
19
|
+
c.stub_with :webmock
|
20
|
+
end
|
metadata
ADDED
@@ -0,0 +1,93 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: openhosting
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.3
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Lee Azzarello
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain:
|
12
|
+
- !binary |-
|
13
|
+
LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURORENDQWh5Z0F3SUJB
|
14
|
+
Z0lCQURBTkJna3Foa2lHOXcwQkFRVUZBREJBTVF3d0NnWURWUVFEREFOc1pX
|
15
|
+
VXgKR3pBWkJnb0praWFKay9Jc1pBRVpGZ3R2Y0dWdWFHOXpkR2x1WnpFVE1C
|
16
|
+
RUdDZ21TSm9tVDhpeGtBUmtXQTJOdgpiVEFlRncweE5UQXlNRGd4TnpRMk1U
|
17
|
+
aGFGdzB4TmpBeU1EZ3hOelEyTVRoYU1FQXhEREFLQmdOVkJBTU1BMnhsClpU
|
18
|
+
RWJNQmtHQ2dtU0pvbVQ4aXhrQVJrV0MyOXdaVzVvYjNOMGFXNW5NUk13RVFZ
|
19
|
+
S0NaSW1pWlB5TEdRQkdSWUQKWTI5dE1JSUJJakFOQmdrcWhraUc5dzBCQVFF
|
20
|
+
RkFBT0NBUThBTUlJQkNnS0NBUUVBMnFRZHpSRGV5UDFrcTlZWgp0akthdXk5
|
21
|
+
eHJCc3NuczNGR0ZTVVIvTFBOUTR2aHdQbGwxNm01dWRuVHg5YXRVSms0azE2
|
22
|
+
V0ZRRkVMK0ZXUXFhClJPeHB2RHJPOTVSMXU0WFlpbGZLclZZVjVPMWZKbDI5
|
23
|
+
QU5sSWdBUmR1bG1sVVVRZGEzVGwvYW0yU1VLVlRRVWQKUzJNWmdGQ0dHY2Nq
|
24
|
+
a3JZNjcxTjBSbFhrRXhTNnMyOXkrd1k5dDhmMW82WEsrWWUwNGpVbEQ0Qy92
|
25
|
+
K1BwK3h2ZgpSRWpqNTg1bUZZaTVzTC9hMGVRV2V5bmRTYjFHbEtEVS9LeUNX
|
26
|
+
MHNIVnJ1U3JNRVJnWThGbzdGUU5aMENyUFJrCjIwMVhQRXBWK3ZOWUIyS0FF
|
27
|
+
SFhrL3lwN1pXSGsvQ2NSTEJHaExxTVhPdmpWa1FUSUo0WG1pdEdJN0NtbGsw
|
28
|
+
WFkKQmIrNE9RSURBUUFCb3prd056QUpCZ05WSFJNRUFqQUFNQjBHQTFVZERn
|
29
|
+
UVdCQlN4NUc0SlZ2TVc2L0FnUDFpcQpzZmkwNGNINVh6QUxCZ05WSFE4RUJB
|
30
|
+
TUNCTEF3RFFZSktvWklodmNOQVFFRkJRQURnZ0VCQUxZcGFiUTg4QkVPCmQz
|
31
|
+
ejM5dS91a0hxRmM0aTU5dm5SQkhWMkFlZnNJY2llQ2Y4eUd0TGszTGp0elF1
|
32
|
+
eERKUXVWRy9pVFZNcUJGV0cKdnlYUklGZXZKOEx3ZUVhVVlFczcweXN4d0Zp
|
33
|
+
bVc5RFpxQnlKU0ZBK3RXck5wa3ZTUUVOQW94WXBvbk9UVzV0YgpvZFdnaDJx
|
34
|
+
Mm84UVVPSXJvdktRVldmc2ZLOFBKVU5SVm9xU3ZkQ3loOUQ4VytybkFjSHJl
|
35
|
+
M2JCM3hKL2ZkTWhVCk50TVgycEY5VzVrRDg4Rit4dkV1Qzh1Q1p1SG1YOG83
|
36
|
+
RXZqSzVXRGgrSnhad2RZY3ZqNlY3bVZxMDRzV2lkSngKeElldFJmeURSUnJw
|
37
|
+
YUk0MEQ1QXZLTlZkbFd2VzV3eGdiZFVkOCtLSzU2OGxQZjVUb2NDM3BpY1VN
|
38
|
+
ZEtOcW4raApYcjR3RDdETTRZMD0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0t
|
39
|
+
LQo=
|
40
|
+
date: 2015-02-08 00:00:00.000000000 Z
|
41
|
+
dependencies: []
|
42
|
+
description: This is a library which implements the Open Hosting API. It can be used
|
43
|
+
to create, modify and delete virtual servers, drives and associated resources like
|
44
|
+
IP addresses and vlans. It can also be used as a general purpose upload/download
|
45
|
+
interface for the contents of virtual drives. It is based on the Elastic Hosts API.
|
46
|
+
email:
|
47
|
+
- lee@openhosting.com
|
48
|
+
executables: []
|
49
|
+
extensions: []
|
50
|
+
extra_rdoc_files: []
|
51
|
+
files:
|
52
|
+
- Gemfile
|
53
|
+
- LICENSE.txt
|
54
|
+
- README.md
|
55
|
+
- Rakefile
|
56
|
+
- certs/lazzarello.pem
|
57
|
+
- lib/connection.rb
|
58
|
+
- lib/drives.rb
|
59
|
+
- lib/openhosting.rb
|
60
|
+
- lib/resources.rb
|
61
|
+
- lib/servers.rb
|
62
|
+
- openhosting.gemspec
|
63
|
+
- spec/lib/connection_spec.rb
|
64
|
+
- spec/spec_helper.rb
|
65
|
+
homepage: https://code.seriesdigital.com/lee/openhosting-api-clients/tree/master/oh-api-ruby
|
66
|
+
licenses:
|
67
|
+
- GPLv3
|
68
|
+
post_install_message:
|
69
|
+
rdoc_options: []
|
70
|
+
require_paths:
|
71
|
+
- lib
|
72
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
79
|
+
none: false
|
80
|
+
requirements:
|
81
|
+
- - ! '>='
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: '0'
|
84
|
+
requirements: []
|
85
|
+
rubyforge_project:
|
86
|
+
rubygems_version: 1.8.24
|
87
|
+
signing_key:
|
88
|
+
specification_version: 3
|
89
|
+
summary: Open Hosting API client library
|
90
|
+
test_files:
|
91
|
+
- spec/lib/connection_spec.rb
|
92
|
+
- spec/spec_helper.rb
|
93
|
+
has_rdoc:
|
metadata.gz.sig
ADDED
Binary file
|