rackspace 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +1 -0
- data/README.md +39 -0
- data/lib/rackspace.rb +2 -0
- data/lib/rackspace/loadbalancers.rb +172 -0
- data/lib/rackspace/rackspace.rb +307 -0
- data/rackspace.gemspec +18 -0
- metadata +66 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 5f21137370f518602031bc7bc0c3ec8983188ff1
|
4
|
+
data.tar.gz: 744d67b457cb92494713f485d7693bfdab7dbfa3
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 9faeabd8841aeed72d33d70b11777ae185f1c77d26ed92978d07491669597a244c1578d50cdf77e1426a205bb526b01555f138ee680207d91a082c84437560e3
|
7
|
+
data.tar.gz: 71eb99c1571bcaf16338f347f560b7eb251a392ffe6fa52b5e99eaac9830db54bf8cfbf26ee832c7eb108bd71eb8ffd53365bbca7adc5d307cd0095a1bd266c0
|
data/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
*.gem
|
data/README.md
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
rackspace
|
2
|
+
=========
|
3
|
+
|
4
|
+
(A reconstruction of the rackspace gem that was deleted from github).
|
5
|
+
|
6
|
+
A simple ruby interface to various Rackspace Cloud APIs.
|
7
|
+
Currently, the only implemented API is the Cloud Load Balancer API, but
|
8
|
+
others can be implemented very easily.
|
9
|
+
|
10
|
+
Load Balancers
|
11
|
+
==============
|
12
|
+
|
13
|
+
Using the API is fairly simple, and the methods are largely self-documenting:
|
14
|
+
|
15
|
+
```ruby
|
16
|
+
#!/usr/bin/env ruby
|
17
|
+
require 'rackspace'
|
18
|
+
require 'pp'
|
19
|
+
|
20
|
+
lbs = Rackspace::LoadBalancers.new()
|
21
|
+
lbs.authenticate({
|
22
|
+
:username => 'user',
|
23
|
+
:key => 'api-key-here'
|
24
|
+
})
|
25
|
+
|
26
|
+
# Pretty-print a list of load balancer names
|
27
|
+
puts "Load balancers:\n"
|
28
|
+
pp list = lbs.list()
|
29
|
+
|
30
|
+
# ... and the list of nodes
|
31
|
+
puts "Nodes on #{list[0]}:\n"
|
32
|
+
pp lbs.list_nodes(list[0])
|
33
|
+
```
|
34
|
+
|
35
|
+
TODO
|
36
|
+
====
|
37
|
+
|
38
|
+
* Implement other APIs/features
|
39
|
+
* Better documentation
|
data/lib/rackspace.rb
ADDED
@@ -0,0 +1,172 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
#
|
4
|
+
# Interface to Rackspace's Cloud Load Balancer API
|
5
|
+
#
|
6
|
+
# TODO: SSL Termination, Virtual IPs, ACLs, Usage Reports, Health Monitors,
|
7
|
+
# Connection Persistence/Logging/Throttling, Content Caching
|
8
|
+
#
|
9
|
+
class Rackspace::LoadBalancers < Rackspace
|
10
|
+
def initialize
|
11
|
+
super
|
12
|
+
@ltype = 'loadBalancers' # for list()
|
13
|
+
@service = 'cloudLoadBalancers' # from @@auth_config
|
14
|
+
@base_uri = '/loadbalancers' # for Rackspace::get/put/etc.
|
15
|
+
@metadata = {}
|
16
|
+
end
|
17
|
+
|
18
|
+
# Get load balancer details
|
19
|
+
def get_details(name)
|
20
|
+
if @x_list[name][@ltype.chop]
|
21
|
+
# Get metadata for this lb
|
22
|
+
md = get(name,'metadata')
|
23
|
+
@metadata[name] = { 'lb' => md[md.keys[0]] }
|
24
|
+
end
|
25
|
+
|
26
|
+
@x_list[name][@ltype.chop]
|
27
|
+
end
|
28
|
+
|
29
|
+
# Create a load balancer
|
30
|
+
def create(attributes)
|
31
|
+
post(attributes)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Update load balancer attributes
|
35
|
+
def update(name,attributes)
|
36
|
+
allowed_attribs = [ 'name', 'algorithm', 'protocol', 'port' ]
|
37
|
+
attributes.keys.each { |k| attributes.delete(k) unless allowed_attribs.index(k) }
|
38
|
+
put({ @ltype.chop => attributes },name)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Get load balancer stats
|
42
|
+
def stats(name)
|
43
|
+
get(name,'stats')
|
44
|
+
end
|
45
|
+
|
46
|
+
# Get Allowed Domains
|
47
|
+
def get_allowed_domains
|
48
|
+
out = []
|
49
|
+
ad = get(nil,'alloweddomains')
|
50
|
+
ad[ad.keys[0]].each { |x| out.push(x['allowedDomain']['name']) }
|
51
|
+
return out
|
52
|
+
end
|
53
|
+
|
54
|
+
# List nodes
|
55
|
+
def list_nodes(name)
|
56
|
+
@metadata[name] = { 'lb' => {} } unless @metadata[name]
|
57
|
+
@metadata[name]['nodes'] = {}
|
58
|
+
nodes = get_subitems(name,'nodes','address')
|
59
|
+
nodes.each { |node|
|
60
|
+
# Get metadata for this node
|
61
|
+
md = get(name,'nodes/' + @s2id[name]['nodes'][node['address']] + '/metadata')
|
62
|
+
@metadata[name]['nodes'][node['address']] = md[md.keys[0]]
|
63
|
+
}
|
64
|
+
end
|
65
|
+
|
66
|
+
# Add nodes
|
67
|
+
def add_nodes(name,nodes)
|
68
|
+
nodes.each { |node|
|
69
|
+
raise 'You must specify an address and a port when adding a node' unless node[:address] && node[:port]
|
70
|
+
node[:condition] = 'ENABLED' unless node[:condition]
|
71
|
+
node[:type] = 'PRIMARY' unless node[:type]
|
72
|
+
}
|
73
|
+
|
74
|
+
create_subitems(name,'nodes',nodes)
|
75
|
+
end
|
76
|
+
|
77
|
+
# Modify nodes
|
78
|
+
def update_nodes(name,nodes)
|
79
|
+
update_subitems(name,'nodes',[ 'condition', 'weight', 'type' ],nodes)
|
80
|
+
end
|
81
|
+
|
82
|
+
# Delete nodes
|
83
|
+
def delete_nodes(name,nodes)
|
84
|
+
delete_subitems(name,'nodes',nodes)
|
85
|
+
end
|
86
|
+
|
87
|
+
# Get error page
|
88
|
+
def get_error_page(name)
|
89
|
+
get(name,'errorpage')['content']
|
90
|
+
end
|
91
|
+
|
92
|
+
# Set error page
|
93
|
+
def set_error_page(name,content)
|
94
|
+
put({ :content => content },name,'errorpage')
|
95
|
+
end
|
96
|
+
|
97
|
+
# Delete error page
|
98
|
+
def delete_error_page(name)
|
99
|
+
delete(name,'errorpage')
|
100
|
+
end
|
101
|
+
|
102
|
+
# List Virtual IPs
|
103
|
+
def list_vips(name)
|
104
|
+
get_subitems(name,'virtualIps','address')
|
105
|
+
end
|
106
|
+
|
107
|
+
# Add Virtual IPs
|
108
|
+
def add_vips(name,vips)
|
109
|
+
vips.each { |vip|
|
110
|
+
raise 'You must specify the ipVersion and type when adding a virtual IP' unless vip[:ipVersion] && vip[:type]
|
111
|
+
}
|
112
|
+
|
113
|
+
create_subitems(name,'virtualIps',vips)
|
114
|
+
end
|
115
|
+
|
116
|
+
# Delete Virtual IPs
|
117
|
+
def delete_vips(name,vips)
|
118
|
+
delete_subitems(name,'virtualIps',vips)
|
119
|
+
end
|
120
|
+
|
121
|
+
# Get metadata
|
122
|
+
def get_metadata(name,node=nil)
|
123
|
+
raise "No such node: #{node} on #{name}" unless !node || @s2id[name]['nodes'][node]
|
124
|
+
mda = node ? @metadata[name]['nodes'][node] : @metadata[name]['lb']
|
125
|
+
out = {}
|
126
|
+
mda.each { |md| out[md['key']] = md['value'] }
|
127
|
+
return out
|
128
|
+
end
|
129
|
+
|
130
|
+
# Add metadata
|
131
|
+
def add_metadata(name,values,node=nil)
|
132
|
+
raise "No such node: #{node} on #{name}" unless !node || @s2id[name]['nodes'][node]
|
133
|
+
resp = post({ 'metadata' => values },name,(node ? 'nodes/' + @s2id[name]['nodes'][node] + '/' : '') + 'metadata')
|
134
|
+
resp['metadata'].each { |md|
|
135
|
+
if node
|
136
|
+
@metadata[name]['nodes'][node].push(md)
|
137
|
+
else
|
138
|
+
@metadata[name]['lb'].push(md)
|
139
|
+
end
|
140
|
+
}
|
141
|
+
end
|
142
|
+
|
143
|
+
# Update metadata
|
144
|
+
def update_metadata(name,values,node=nil)
|
145
|
+
raise "No such node: #{node} on #{name}" unless !node || @s2id[name]['nodes'][node]
|
146
|
+
mda = (node ? @metadata[name]['nodes'][node] : @metadata[name]['lb'])
|
147
|
+
|
148
|
+
values.keys.each { |k|
|
149
|
+
md = nil
|
150
|
+
mda.each { |mdx| md = mdx if mdx['key'] == k }
|
151
|
+
if md
|
152
|
+
put({ 'meta' => { 'value' => values[k] }},name,
|
153
|
+
(node ? 'nodes/' + @s2id[name]['nodes'][node] + '/' : '') + 'metadata/' + md['id'].to_s)
|
154
|
+
md['value'] = values[k]
|
155
|
+
end
|
156
|
+
}
|
157
|
+
end
|
158
|
+
|
159
|
+
# Delete metadata
|
160
|
+
def delete_metadata(name,key,node=nil)
|
161
|
+
raise "No such node: #{node} on #{name}" unless !node || @s2id[name]['nodes'][node]
|
162
|
+
md = nil
|
163
|
+
mda = (node ? @metadata[name]['nodes'][node] : @metadata[name]['lb'])
|
164
|
+
mda.each { |mdx| md = mdx if mdx['key'] == key }
|
165
|
+
if md
|
166
|
+
delete(name,(node ? 'nodes/' + @s2id[name]['nodes'][node] + '/' : '') + 'metadata/' + md['id'].to_s)
|
167
|
+
mda.delete(md)
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
# vi:set ts=2:
|
@@ -0,0 +1,307 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'net/http'
|
4
|
+
require 'uri'
|
5
|
+
require 'json'
|
6
|
+
|
7
|
+
#
|
8
|
+
# Base class for Rackspace Cloud API Services
|
9
|
+
#
|
10
|
+
# This class handles authentication, and all basic
|
11
|
+
# service features (GET,PUT,DELETE) transparently.
|
12
|
+
#
|
13
|
+
class Rackspace
|
14
|
+
def initialize
|
15
|
+
@@credentials = {}
|
16
|
+
@@authenticated = 0
|
17
|
+
@@auth_config = {}
|
18
|
+
@ltype = nil
|
19
|
+
@service = nil
|
20
|
+
@base_uri = nil
|
21
|
+
@region = 'ORD'
|
22
|
+
@name2id = {}
|
23
|
+
@x_list = {}
|
24
|
+
@s2id = {} # Subitem to ID
|
25
|
+
@nfxx = {} # name field lookup
|
26
|
+
end
|
27
|
+
|
28
|
+
# Get/Set region
|
29
|
+
def region(region=nil)
|
30
|
+
@region = (region ? region : @region)
|
31
|
+
return @region
|
32
|
+
end
|
33
|
+
|
34
|
+
# Do an actual request, returns a hash representing the JSON result (or do custom processing)
|
35
|
+
def do_req(verb,url,body=nil,fun=nil)
|
36
|
+
raise 'This method shouldn\'t be called directly' unless (@ltype && @service && @base_uri)
|
37
|
+
|
38
|
+
# We don't need to do this when authenticating (obviously)
|
39
|
+
unless url.index('v1.1/auth')
|
40
|
+
raise 'You must call authenticate() before anything else' unless @@authenticated
|
41
|
+
|
42
|
+
# Get the service map entries for this service
|
43
|
+
raise 'No service map for service "' + @service + '"' unless cfg = @@auth_config['auth']['serviceCatalog'][@service]
|
44
|
+
|
45
|
+
# Get the service URL based on region (TODO: handle snet)
|
46
|
+
av_regions = []
|
47
|
+
regioned = false
|
48
|
+
service_url = nil
|
49
|
+
cfg.each { |x|
|
50
|
+
if (x['region'])
|
51
|
+
av_regions.push(x['region'])
|
52
|
+
regioned = true
|
53
|
+
service_url = (x['region'] == @region) ? x['publicURL'] : service_url
|
54
|
+
elsif (x['publicURL'])
|
55
|
+
service_url = x['publicURL']
|
56
|
+
end
|
57
|
+
}
|
58
|
+
|
59
|
+
# Make sure region is valid (if applicable)
|
60
|
+
raise 'Invalid region "' + @region + '" for service "' + @service +
|
61
|
+
'" available: [' + av_regions.join(',') + "]" if !service_url && regioned
|
62
|
+
raise 'Unable to determine service_url for service "' + @service + '"' if !service_url
|
63
|
+
else
|
64
|
+
service_url = ''
|
65
|
+
end
|
66
|
+
|
67
|
+
# Extract info from the url
|
68
|
+
uri = URI(service_url + url)
|
69
|
+
http = Net::HTTP.new(uri.host,uri.port)
|
70
|
+
http.use_ssl = true if uri.scheme == 'https'
|
71
|
+
|
72
|
+
# Figure the type of request to make
|
73
|
+
case verb
|
74
|
+
when 'GET'
|
75
|
+
req = Net::HTTP::Get.new(uri.path)
|
76
|
+
when 'POST'
|
77
|
+
raise "POST requires a body" unless body
|
78
|
+
req = Net::HTTP::Post.new(uri.path)
|
79
|
+
when 'PUT'
|
80
|
+
raise "PUT requires a body" unless body
|
81
|
+
req = Net::HTTP::Put.new(uri.path)
|
82
|
+
when 'DELETE'
|
83
|
+
req = Net::HTTP::Delete.new(uri.path)
|
84
|
+
else
|
85
|
+
raise 'Invalid HTTP verb: "' + verb + '"'
|
86
|
+
end
|
87
|
+
|
88
|
+
# Add Accept
|
89
|
+
req.add_field('Accept','application/json')
|
90
|
+
|
91
|
+
# Add the body
|
92
|
+
req.content_type = 'application/json'
|
93
|
+
req.body = body
|
94
|
+
|
95
|
+
# Do it (and handle HTTP error responses)
|
96
|
+
fun = fun ? fun : proc { |resp| JSON.parse(resp.body) }
|
97
|
+
|
98
|
+
begin
|
99
|
+
# Delete and add the token (in case of 'retry')
|
100
|
+
req.delete('X-Auth-Token')
|
101
|
+
req.add_field('X-Auth-Token',@@auth_config['auth']['token']['id']) if @@authenticated && @@auth_config && !@@auth_config.empty?
|
102
|
+
|
103
|
+
# Send the request, get the response
|
104
|
+
resp = http.request(req)
|
105
|
+
|
106
|
+
case resp.code
|
107
|
+
when '200', '201', '202'
|
108
|
+
fun.call(resp)
|
109
|
+
when '400'
|
110
|
+
raise 'Bad Request: ' + resp.body
|
111
|
+
when '401'
|
112
|
+
bod = JSON.parse(resp.body)
|
113
|
+
raise Rackspace::ExpiredToken, bod if @@authenticated && @@auth_config && !@@auth_config.empty?
|
114
|
+
raise 'Unauthorized: ' + (bod.empty? ? resp.body : (bod['unauthorized'] ? bod['unauthorized']['message'] : bod['message']))
|
115
|
+
when '404'
|
116
|
+
raise 'Resource not found'
|
117
|
+
when '413'
|
118
|
+
raise 'Don\'t be a butt-knocker. You\'re sending too many requests.'
|
119
|
+
when '422'
|
120
|
+
bod = JSON.parse(resp.body)
|
121
|
+
raise Rackspace::Retry, bod['message'] if /considered immutable/.match(bod['message'])
|
122
|
+
raise bod['message']
|
123
|
+
when '500'
|
124
|
+
raise 'Internal Error: ' + resp.body
|
125
|
+
when '503'
|
126
|
+
raise 'Service Unavailable: ' + resp.body
|
127
|
+
else
|
128
|
+
raise 'Unknown Response: [' + resp.code + '] ' + resp.body
|
129
|
+
end
|
130
|
+
rescue Rackspace::ExpiredToken
|
131
|
+
authenticate(@@credentials)
|
132
|
+
retry
|
133
|
+
rescue Rackspace::Retry
|
134
|
+
sleep(5)
|
135
|
+
retry
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
#
|
140
|
+
# Authenticate to Rackspace (v1.1)
|
141
|
+
#
|
142
|
+
# credentials is a hash of:
|
143
|
+
# {
|
144
|
+
# :username => 'username',
|
145
|
+
# :key => 'api_key'
|
146
|
+
# }
|
147
|
+
#
|
148
|
+
# uk should be 1 for UK-based accounts
|
149
|
+
#
|
150
|
+
def authenticate(credentials, uk=0)
|
151
|
+
# Check to make sure we have all our credentials
|
152
|
+
raise 'Missing :username' unless credentials[:username]
|
153
|
+
raise 'Missing :key' unless credentials[:key]
|
154
|
+
creds = {'credentials' => credentials}.to_json
|
155
|
+
@@credentials = credentials
|
156
|
+
|
157
|
+
# Post, and handle the response
|
158
|
+
do_req('POST','https://' + ((uk == 1) ? 'lon.' : '') + 'identity.api.rackspacecloud.com/v1.1/auth',creds,proc { |resp|
|
159
|
+
@@auth_config = JSON.parse(resp.body)
|
160
|
+
@@authenticated = 1
|
161
|
+
})
|
162
|
+
end
|
163
|
+
|
164
|
+
# List all 'things' of the given type
|
165
|
+
def list
|
166
|
+
raise 'This method shouldn\'t be called directly' unless (@ltype && @service && @base_uri)
|
167
|
+
raise 'You must call authenticate() before anything else' unless @@authenticated
|
168
|
+
@x_list = {}
|
169
|
+
|
170
|
+
do_req('GET',@base_uri,nil,proc { |resp|
|
171
|
+
JSON.parse(resp.body)[@ltype].each { |z|
|
172
|
+
@name2id[z['name']] = z['id'].to_s
|
173
|
+
@x_list[z['name']] = get(z['name'])
|
174
|
+
}
|
175
|
+
})
|
176
|
+
|
177
|
+
return @x_list.keys
|
178
|
+
end
|
179
|
+
|
180
|
+
# Get one/all resource(s)
|
181
|
+
def get(name=nil,sub_uri=nil)
|
182
|
+
# Otherwise, assume the user knows what they want
|
183
|
+
raise 'Rackspace::get: ' + @ltype + '/' + name + ' doesn\'t exist' unless !name || (name && @name2id[name])
|
184
|
+
uri = @base_uri
|
185
|
+
uri = uri + '/' + @name2id[name] if name
|
186
|
+
uri = uri + '/' + sub_uri if sub_uri
|
187
|
+
do_req('GET',uri)
|
188
|
+
end
|
189
|
+
|
190
|
+
#
|
191
|
+
# Create one resource
|
192
|
+
#
|
193
|
+
# If name is specified, it is assumed that a sub-object is intended
|
194
|
+
# If sub_uri is specified it is assumed to be a URI relative to name
|
195
|
+
#
|
196
|
+
# This update the name -> id mapping for the main resource
|
197
|
+
# sub-resources should be handled by the actual API implementation.
|
198
|
+
#
|
199
|
+
def post(attributes,name=nil,sub_uri=nil)
|
200
|
+
raise 'Rackspace::post: name must be specified' unless (name || name = attributes['name'])
|
201
|
+
raise 'Rackspace::post: attributes must be specified' unless attributes
|
202
|
+
uri = @base_uri
|
203
|
+
uri = uri + '/' + @name2id[name] if name
|
204
|
+
uri = uri + '/' + sub_uri if sub_uri
|
205
|
+
do_req('POST',uri,attributes.to_json,proc { |resp|
|
206
|
+
data = JSON.parse(resp.body)
|
207
|
+
unless sub_uri
|
208
|
+
@x_list[name] = data
|
209
|
+
@name2id[name] = @x_list[name][@ltype]['id']
|
210
|
+
end
|
211
|
+
|
212
|
+
return data
|
213
|
+
})
|
214
|
+
end
|
215
|
+
|
216
|
+
# Modify one resource
|
217
|
+
def put(attributes,name,sub_uri=nil)
|
218
|
+
raise 'Rackspace::put: name must be specified' unless name
|
219
|
+
raise 'Rackspace::put: attributes must be specified' unless attributes && attributes[attributes.keys[0]]
|
220
|
+
raise 'Rackspace::put: ' + @ltype + '/' + name + ' doesn\'t exist' unless @name2id[name]
|
221
|
+
uri = @base_uri + '/' + @name2id[name]
|
222
|
+
uri = uri + '/' + sub_uri if sub_uri
|
223
|
+
do_req('PUT',uri,attributes.to_json,proc { |resp|
|
224
|
+
# Handle name changes
|
225
|
+
attributes = attributes[attributes.keys[0]]
|
226
|
+
if (!sub_uri && attributes['name'] && attributes['name'] != name)
|
227
|
+
@x_list[attributes['name']] = @x_list[name]
|
228
|
+
@name2id[attributes['name']] = @name2id[name]
|
229
|
+
@name2id.delete(name)
|
230
|
+
@x_list.delete(name)
|
231
|
+
name = attributes['name']
|
232
|
+
end
|
233
|
+
|
234
|
+
# Update attributes
|
235
|
+
attributes.keys.each { |k| @x_list[name][@ltype.chop][k] = attributes[k] } unless sub_uri
|
236
|
+
})
|
237
|
+
end
|
238
|
+
|
239
|
+
# Delete one resource
|
240
|
+
def delete(name,sub_uri=nil)
|
241
|
+
raise 'Rackspace::delete: ' + @ltype + '/' + name + ' doesn\'t exist' unless (name && @name2id[name])
|
242
|
+
uri = @base_uri + '/' + @name2id[name]
|
243
|
+
uri = uri + '/' + sub_uri if sub_uri
|
244
|
+
do_req('DELETE',uri,nil,proc { |resp|
|
245
|
+
unless sub_uri
|
246
|
+
@name2id.delete(name)
|
247
|
+
@x_list.delete(name)
|
248
|
+
end
|
249
|
+
})
|
250
|
+
end
|
251
|
+
|
252
|
+
# Get subitems
|
253
|
+
def get_subitems(name,type,namefield)
|
254
|
+
@s2id[name] = { type => {} }
|
255
|
+
@nfxx[type] = namefield unless @nfxx[type]
|
256
|
+
items = @x_list[name][@ltype.chop][type]
|
257
|
+
items.each { |item| @s2id[name][type][item[namefield]] = item['id'].to_s }
|
258
|
+
return items
|
259
|
+
end
|
260
|
+
|
261
|
+
# Create subitems
|
262
|
+
def create_subitems(name,type,attributes)
|
263
|
+
resp = post({ type => attributes },name,type.downcase)[type]
|
264
|
+
resp.each { |item|
|
265
|
+
@s2id[name][type][item[@nfxx[type]]] = item['id'].to_s
|
266
|
+
@x_list[name][@ltype.chop][type].push(item)
|
267
|
+
}
|
268
|
+
return resp
|
269
|
+
end
|
270
|
+
|
271
|
+
# Update subitems
|
272
|
+
def update_subitems(name,type,allowed_attribs,items)
|
273
|
+
items.keys.each { |item|
|
274
|
+
raise "No such #{type.chop}: #{item} on #{name}" unless @s2id[name][type][item]
|
275
|
+
items[item].keys.each { |k| items[item].delete(k) unless allowed_attribs.index(k) }
|
276
|
+
put({ type.chop => items[item] },name,type.downcase + '/' + @s2id[name][type][item])
|
277
|
+
|
278
|
+
# Update the local copy
|
279
|
+
@x_list[name][@ltype.chop][type].each { |xitem|
|
280
|
+
if xitem[@nfxx[type]] == item
|
281
|
+
items[item].keys.each { |k| xitem[k] = items[item][k] if xitem[k] && items[item][k] }
|
282
|
+
end
|
283
|
+
}
|
284
|
+
}
|
285
|
+
end
|
286
|
+
|
287
|
+
# Delete subitems
|
288
|
+
def delete_subitems(name,type,items)
|
289
|
+
items.each { |item|
|
290
|
+
if @s2id[name][type][item]
|
291
|
+
delete(name,type.downcase + '/' + @s2id[name][type][item])
|
292
|
+
@s2id[name][type].delete(item)
|
293
|
+
@x_list[name][@ltype.chop][type].each { |xitem|
|
294
|
+
@x_list[name][@ltype.chop][type].delete(xitem) if xitem[@nfxx[type]] == item
|
295
|
+
}
|
296
|
+
end
|
297
|
+
}
|
298
|
+
end
|
299
|
+
end
|
300
|
+
|
301
|
+
class Rackspace::ExpiredToken < Exception
|
302
|
+
end
|
303
|
+
|
304
|
+
class Rackspace::Retry < Exception
|
305
|
+
end
|
306
|
+
|
307
|
+
# vi:set ts=2:
|
data/rackspace.gemspec
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
Gem::Specification.new do |spec|
|
4
|
+
spec.name = "rackspace"
|
5
|
+
spec.version = "0.1.2"
|
6
|
+
spec.authors = ["Tim Hentenaar", "Richard Grainger"]
|
7
|
+
spec.email = ["grainger@gmail.com"]
|
8
|
+
|
9
|
+
spec.summary = "Simple interface to Rackspace's Cloud APIs"
|
10
|
+
spec.description = "\tRuby interface to various Rackspace Cloud APIs.\n\n\tCurrently only the Cloud Load Balancers API is supported. See the README for more\n\tdetails.\n"
|
11
|
+
spec.homepage = "https://gitlab.com/harbottle/rackspace"
|
12
|
+
|
13
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
14
|
+
spec.require_paths << "lib"
|
15
|
+
|
16
|
+
spec.add_dependency("json", ">=0")
|
17
|
+
|
18
|
+
end
|
metadata
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rackspace
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Tim Hentenaar
|
8
|
+
- Richard Grainger
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2018-04-20 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: json
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - ">="
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: '0'
|
21
|
+
type: :runtime
|
22
|
+
prerelease: false
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - ">="
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: '0'
|
28
|
+
description: "\tRuby interface to various Rackspace Cloud APIs.\n\n\tCurrently only
|
29
|
+
the Cloud Load Balancers API is supported. See the README for more\n\tdetails.\n"
|
30
|
+
email:
|
31
|
+
- grainger@gmail.com
|
32
|
+
executables: []
|
33
|
+
extensions: []
|
34
|
+
extra_rdoc_files: []
|
35
|
+
files:
|
36
|
+
- ".gitignore"
|
37
|
+
- README.md
|
38
|
+
- lib/rackspace.rb
|
39
|
+
- lib/rackspace/loadbalancers.rb
|
40
|
+
- lib/rackspace/rackspace.rb
|
41
|
+
- rackspace.gemspec
|
42
|
+
homepage: https://gitlab.com/harbottle/rackspace
|
43
|
+
licenses: []
|
44
|
+
metadata: {}
|
45
|
+
post_install_message:
|
46
|
+
rdoc_options: []
|
47
|
+
require_paths:
|
48
|
+
- lib
|
49
|
+
- lib
|
50
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
56
|
+
requirements:
|
57
|
+
- - ">="
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
version: '0'
|
60
|
+
requirements: []
|
61
|
+
rubyforge_project:
|
62
|
+
rubygems_version: 2.6.14
|
63
|
+
signing_key:
|
64
|
+
specification_version: 4
|
65
|
+
summary: Simple interface to Rackspace's Cloud APIs
|
66
|
+
test_files: []
|