openhosting 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
Binary file
data/Gemfile ADDED
@@ -0,0 +1,12 @@
1
+ source :rubygems
2
+
3
+ gem 'faraday'
4
+ gem 'patron'
5
+ gem 'json'
6
+
7
+ group :test do
8
+ gem 'rspec'
9
+ gem 'vcr'
10
+ gem 'turn'
11
+ gem 'rake'
12
+ end
@@ -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>.
@@ -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)
@@ -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-----
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -0,0 +1,9 @@
1
+ require_relative '../spec_helper'
2
+
3
+ describe OHConnection do
4
+ describe "default attributes" do
5
+ it "must include Faraday methods" do
6
+ OHConnection.must_include Faraday
7
+ end
8
+ end
9
+ end
@@ -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:
Binary file