rackspace 0.1.2
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 +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: []
|