ovirt 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/.rspec +3 -0
- data/LICENSE.txt +13 -0
- data/README.md +35 -0
- data/lib/ovirt/api.rb +31 -0
- data/lib/ovirt/cdrom.rb +12 -0
- data/lib/ovirt/cluster.rb +27 -0
- data/lib/ovirt/data_center.rb +24 -0
- data/lib/ovirt/disk.rb +30 -0
- data/lib/ovirt/domain.rb +12 -0
- data/lib/ovirt/event.rb +492 -0
- data/lib/ovirt/event_monitor.rb +36 -0
- data/lib/ovirt/exception.rb +13 -0
- data/lib/ovirt/file.rb +13 -0
- data/lib/ovirt/group.rb +12 -0
- data/lib/ovirt/host.rb +44 -0
- data/lib/ovirt/host_nic.rb +38 -0
- data/lib/ovirt/inventory.rb +177 -0
- data/lib/ovirt/network.rb +16 -0
- data/lib/ovirt/nic.rb +31 -0
- data/lib/ovirt/object.rb +315 -0
- data/lib/ovirt/permission.rb +20 -0
- data/lib/ovirt/permit.rb +14 -0
- data/lib/ovirt/role.rb +13 -0
- data/lib/ovirt/service.rb +332 -0
- data/lib/ovirt/snapshot.rb +28 -0
- data/lib/ovirt/statistic.rb +37 -0
- data/lib/ovirt/storage.rb +14 -0
- data/lib/ovirt/storage_domain.rb +48 -0
- data/lib/ovirt/tag.rb +23 -0
- data/lib/ovirt/template.rb +185 -0
- data/lib/ovirt/user.rb +15 -0
- data/lib/ovirt/version.rb +3 -0
- data/lib/ovirt/vm.rb +327 -0
- data/lib/ovirt/vmpool.rb +14 -0
- data/lib/ovirt.rb +35 -0
- data/spec/event_spec.rb +26 -0
- data/spec/object_spec.rb +12 -0
- data/spec/service_spec.rb +30 -0
- data/spec/spec_helper.rb +24 -0
- data/spec/template_spec.rb +141 -0
- data/spec/vm_spec.rb +189 -0
- metadata +224 -0
@@ -0,0 +1,177 @@
|
|
1
|
+
module Ovirt
|
2
|
+
class Inventory
|
3
|
+
attr_accessor :service
|
4
|
+
|
5
|
+
def initialize(options={})
|
6
|
+
@service = Service.new(options)
|
7
|
+
end
|
8
|
+
|
9
|
+
def api
|
10
|
+
standard_collection('api').first
|
11
|
+
end
|
12
|
+
|
13
|
+
def capabilities
|
14
|
+
standard_collection("capabilities")
|
15
|
+
end
|
16
|
+
|
17
|
+
def clusters
|
18
|
+
standard_collection("clusters")
|
19
|
+
end
|
20
|
+
|
21
|
+
def datacenters
|
22
|
+
standard_collection("datacenters", "data_center")
|
23
|
+
end
|
24
|
+
|
25
|
+
def domains
|
26
|
+
standard_collection("domains")
|
27
|
+
end
|
28
|
+
|
29
|
+
def events(since = nil)
|
30
|
+
if since.nil?
|
31
|
+
standard_collection("events")
|
32
|
+
else
|
33
|
+
standard_collection("events?from=#{since}", "event")
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def groups
|
38
|
+
standard_collection("groups")
|
39
|
+
end
|
40
|
+
|
41
|
+
def hosts
|
42
|
+
standard_collection("hosts", nil, true)
|
43
|
+
end
|
44
|
+
|
45
|
+
def networks
|
46
|
+
standard_collection("networks")
|
47
|
+
end
|
48
|
+
|
49
|
+
def roles
|
50
|
+
standard_collection("roles")
|
51
|
+
end
|
52
|
+
|
53
|
+
def storagedomains
|
54
|
+
standard_collection("storagedomains", "storage_domain", true)
|
55
|
+
end
|
56
|
+
|
57
|
+
def tags
|
58
|
+
standard_collection("tags")
|
59
|
+
end
|
60
|
+
|
61
|
+
def templates
|
62
|
+
standard_collection("templates")
|
63
|
+
end
|
64
|
+
|
65
|
+
def users
|
66
|
+
standard_collection("users")
|
67
|
+
end
|
68
|
+
|
69
|
+
def vms
|
70
|
+
standard_collection("vms", nil, true)
|
71
|
+
end
|
72
|
+
|
73
|
+
def vmpools
|
74
|
+
standard_collection("vmpools")
|
75
|
+
end
|
76
|
+
|
77
|
+
def get_vm(path)
|
78
|
+
vm_guid = File.basename(path, '.*')
|
79
|
+
vm = get_resource_by_ems_ref("/api/vms/#{vm_guid}") rescue nil
|
80
|
+
vm = get_resource_by_ems_ref("/api/templates/#{vm_guid}") if vm.blank?
|
81
|
+
return vm
|
82
|
+
end
|
83
|
+
|
84
|
+
def get_resource_by_ems_ref(uri_suffix, element_name = nil)
|
85
|
+
@service.get_resource_by_ems_ref(uri_suffix, element_name)
|
86
|
+
end
|
87
|
+
|
88
|
+
def refresh
|
89
|
+
# TODO: Change to not return native objects to the caller. The caller
|
90
|
+
# should just expect raw data.
|
91
|
+
primary_items = collect_primary_items
|
92
|
+
collect_secondary_items(primary_items)
|
93
|
+
end
|
94
|
+
|
95
|
+
private
|
96
|
+
|
97
|
+
def standard_collection(uri_suffix, element_name = nil, paginate=false, sort_by=:name)
|
98
|
+
@service.standard_collection(uri_suffix, element_name, paginate, sort_by)
|
99
|
+
end
|
100
|
+
|
101
|
+
# TODO: Remove this key/method translation and just use the method name as
|
102
|
+
# the key directly.
|
103
|
+
PRIMARY_ITEMS = {
|
104
|
+
# Key RHEVM API method
|
105
|
+
:cluster => :clusters,
|
106
|
+
:vmpool => :vmpools,
|
107
|
+
:network => :networks,
|
108
|
+
:storage => :storagedomains,
|
109
|
+
:datacenter => :datacenters,
|
110
|
+
:host => :hosts,
|
111
|
+
:vm => :vms,
|
112
|
+
:template => :templates
|
113
|
+
}
|
114
|
+
|
115
|
+
SECONDARY_ITEMS = {
|
116
|
+
# Key RHEVM API methods
|
117
|
+
:datacenter => [:storagedomains],
|
118
|
+
:host => [:statistics, :host_nics], # :cdroms, tags
|
119
|
+
:vm => [:disks, :snapshots, :nics],
|
120
|
+
:template => [:disks]
|
121
|
+
}
|
122
|
+
|
123
|
+
def primary_item_jobs
|
124
|
+
PRIMARY_ITEMS.to_a
|
125
|
+
end
|
126
|
+
|
127
|
+
# Returns all combinations of primary resources and the methods to run on those resources.
|
128
|
+
#
|
129
|
+
# > secondary_item_jobs({:vm, => [v1, v2]})
|
130
|
+
# => [[v1, :disks], [v1, :snapshots], [v1, :nics], [v2, :disks], [v2, :snapshots], [v2, :nics]]
|
131
|
+
def secondary_item_jobs(primary_items)
|
132
|
+
SECONDARY_ITEMS.collect do |key, methods|
|
133
|
+
primary_items[key].product(methods)
|
134
|
+
end.flatten(1)
|
135
|
+
end
|
136
|
+
|
137
|
+
def collect_primary_items
|
138
|
+
jobs = primary_item_jobs
|
139
|
+
|
140
|
+
results = collect_in_parallel(jobs) do |_, method|
|
141
|
+
self.send(method)
|
142
|
+
end
|
143
|
+
|
144
|
+
jobs.zip(results).each_with_object({}) do |((key, _), result), hash|
|
145
|
+
hash[key] = result
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
def collect_secondary_items(primary_items)
|
150
|
+
jobs = secondary_item_jobs(primary_items)
|
151
|
+
|
152
|
+
results = collect_in_parallel(jobs) do |resource, method|
|
153
|
+
resource.send(method) rescue nil
|
154
|
+
end
|
155
|
+
|
156
|
+
jobs.zip(results).each do |(resource, method), result|
|
157
|
+
resource.attributes[method] = result
|
158
|
+
end
|
159
|
+
|
160
|
+
primary_items
|
161
|
+
end
|
162
|
+
|
163
|
+
def collect_in_parallel(jobs, &block)
|
164
|
+
require 'parallel'
|
165
|
+
Parallel.map(jobs, :in_threads => num_threads, &block)
|
166
|
+
end
|
167
|
+
|
168
|
+
def num_threads
|
169
|
+
use_threads? ? 8 : 0
|
170
|
+
end
|
171
|
+
|
172
|
+
# HACK: VCR is not threadsafe, and so tests running under VCR fail
|
173
|
+
def use_threads?
|
174
|
+
!defined?(VCR)
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Ovirt
|
2
|
+
class Network < Object
|
3
|
+
|
4
|
+
self.top_level_strings = [:name, :description]
|
5
|
+
self.top_level_booleans = [:stp, :display]
|
6
|
+
self.top_level_objects = [:data_center, :cluster, :vlan]
|
7
|
+
|
8
|
+
def self.parse_xml(xml)
|
9
|
+
node, hash = xml_to_hash(xml)
|
10
|
+
|
11
|
+
parse_first_node(node, :status, hash, :node => [:state])
|
12
|
+
|
13
|
+
hash
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
data/lib/ovirt/nic.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
module Ovirt
|
2
|
+
class Nic < Object
|
3
|
+
|
4
|
+
self.top_level_strings = [:name, :interface]
|
5
|
+
self.top_level_objects = [:vm, :network]
|
6
|
+
|
7
|
+
def self.parse_xml(xml)
|
8
|
+
node, hash = xml_to_hash(xml)
|
9
|
+
|
10
|
+
parse_first_node(node, :network, hash, :node => [:name])
|
11
|
+
parse_first_node(node, :mac, hash, :attribute => [:address])
|
12
|
+
|
13
|
+
hash
|
14
|
+
end
|
15
|
+
|
16
|
+
def attributes_for_new_nic
|
17
|
+
attrs = attributes.dup
|
18
|
+
attrs[:network_id] = self[:network][:id]
|
19
|
+
attrs
|
20
|
+
end
|
21
|
+
|
22
|
+
def apply_options!(options)
|
23
|
+
update! do |xml|
|
24
|
+
xml.name options[:name] if options[:name]
|
25
|
+
xml.interface options[:interface] if options[:interface]
|
26
|
+
xml.network(:id => options[:network_id]) if options[:network_id]
|
27
|
+
xml.mac(:address => options[:mac_address]) if options[:mac_address]
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/lib/ovirt/object.rb
ADDED
@@ -0,0 +1,315 @@
|
|
1
|
+
module Ovirt
|
2
|
+
class Object
|
3
|
+
def self.create_from_xml(service, xml)
|
4
|
+
self.new(service, parse_xml(xml))
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.xml_to_relationships(xml)
|
8
|
+
node = xml_to_nokogiri(xml)
|
9
|
+
relationships = {}
|
10
|
+
node.xpath('link').each do |link|
|
11
|
+
relationships[link['rel'].to_sym] = link['href']
|
12
|
+
end
|
13
|
+
|
14
|
+
relationships
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.xml_to_actions(xml)
|
18
|
+
node = xml_to_nokogiri(xml)
|
19
|
+
actions = {}
|
20
|
+
node.xpath('actions/link').each do |link|
|
21
|
+
actions[link['rel'].to_sym] = link['href']
|
22
|
+
end
|
23
|
+
|
24
|
+
actions
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.hash_from_id_and_href(node)
|
28
|
+
hash = {}
|
29
|
+
[:id, :href].each { |key| hash[key] = node[key.to_s] unless node.nil? || node[key.to_s].nil? }
|
30
|
+
hash
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.parse_boolean(what)
|
34
|
+
return true if what == 'true'
|
35
|
+
return false if what == 'false'
|
36
|
+
raise "Cannot parse boolean for value: <#{what.inspect}>"
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.parse_first_text(node, hash, key, modifier=nil)
|
40
|
+
text_node = node.xpath(key.to_s).first
|
41
|
+
value = text_node.text unless text_node.nil?
|
42
|
+
self.set_value(value, hash, key, modifier)
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.parse_attribute(node, hash, key, modifier=nil)
|
46
|
+
value = node[key.to_s]
|
47
|
+
self.set_value(value, hash, key, modifier)
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.set_value(value, hash, key, modifier)
|
51
|
+
return if value.nil?
|
52
|
+
hash[key] = case modifier
|
53
|
+
when :to_i then value.to_i
|
54
|
+
when :to_f then value.to_f
|
55
|
+
when :to_bool then self.parse_boolean(value)
|
56
|
+
else value
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.parse_first_node(node, path, hash, options)
|
61
|
+
self.parse_first_node_with_hash(node, path, nh = {}, options)
|
62
|
+
unless nh.empty?
|
63
|
+
hash[path.to_sym] = hash[path.to_sym].nil? ? nh : hash[path.to_sym].merge(nh)
|
64
|
+
end
|
65
|
+
nh
|
66
|
+
end
|
67
|
+
|
68
|
+
def self.parse_first_node_with_hash(node, path, hash, options)
|
69
|
+
xnode = node.xpath(path.to_s).first
|
70
|
+
unless xnode.blank?
|
71
|
+
options[:attribute].to_a.each {|key| self.parse_attribute( xnode, hash, key)}
|
72
|
+
options[:attribute_to_i].to_a.each {|key| self.parse_attribute( xnode, hash, key, :to_i)}
|
73
|
+
options[:attribute_to_f].to_a.each {|key| self.parse_attribute( xnode, hash, key, :to_f)}
|
74
|
+
options[:node].to_a.each {|key| self.parse_first_text(xnode, hash, key)}
|
75
|
+
options[:node_to_i].to_a.each {|key| self.parse_first_text(xnode, hash, key, :to_i)}
|
76
|
+
options[:node_to_bool].to_a.each {|key| self.parse_first_text(xnode, hash, key, :to_bool)}
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def self.top_level_objects=(keys)
|
81
|
+
@top_level_objects = keys
|
82
|
+
end
|
83
|
+
|
84
|
+
def self.top_level_objects
|
85
|
+
@top_level_objects ||= []
|
86
|
+
end
|
87
|
+
|
88
|
+
def self.top_level_strings=(keys)
|
89
|
+
@top_level_strings = keys
|
90
|
+
end
|
91
|
+
|
92
|
+
def self.top_level_strings
|
93
|
+
@top_level_strings ||= []
|
94
|
+
end
|
95
|
+
|
96
|
+
def self.top_level_integers=(keys)
|
97
|
+
@top_level_integers = keys
|
98
|
+
end
|
99
|
+
|
100
|
+
def self.top_level_integers
|
101
|
+
@top_level_integers ||= []
|
102
|
+
end
|
103
|
+
|
104
|
+
def self.top_level_booleans=(keys)
|
105
|
+
@top_level_booleans = keys
|
106
|
+
end
|
107
|
+
|
108
|
+
def self.top_level_booleans
|
109
|
+
@top_level_booleans ||= []
|
110
|
+
end
|
111
|
+
|
112
|
+
def self.top_level_timestamps=(keys)
|
113
|
+
@top_level_timestamps = keys
|
114
|
+
end
|
115
|
+
|
116
|
+
def self.top_level_timestamps
|
117
|
+
@top_level_timestamps ||= []
|
118
|
+
end
|
119
|
+
|
120
|
+
|
121
|
+
def self.xml_to_hash(xml)
|
122
|
+
node = xml_to_nokogiri(xml)
|
123
|
+
hash = hash_from_id_and_href(node)
|
124
|
+
hash[:relationships] = xml_to_relationships(node)
|
125
|
+
hash[:actions] = xml_to_actions(node)
|
126
|
+
|
127
|
+
top_level_objects.each do |key|
|
128
|
+
object_node = node.xpath(key.to_s).first
|
129
|
+
hash[key] = hash_from_id_and_href(object_node) unless object_node.nil?
|
130
|
+
end
|
131
|
+
|
132
|
+
top_level_strings.each do |key|
|
133
|
+
object_node = node.xpath(key.to_s).first
|
134
|
+
hash[key] = object_node.text unless object_node.nil?
|
135
|
+
end
|
136
|
+
|
137
|
+
top_level_integers.each do |key|
|
138
|
+
object_node = node.xpath(key.to_s).first
|
139
|
+
hash[key] = object_node.text.to_i unless object_node.nil?
|
140
|
+
end
|
141
|
+
|
142
|
+
top_level_booleans.each do |key|
|
143
|
+
object_node = node.xpath(key.to_s).first
|
144
|
+
hash[key] = parse_boolean(object_node.text) unless object_node.nil?
|
145
|
+
end
|
146
|
+
|
147
|
+
top_level_timestamps.each do |key|
|
148
|
+
object_node = node.xpath(key.to_s).first
|
149
|
+
hash[key] = Time.parse(object_node.text) unless object_node.nil?
|
150
|
+
end
|
151
|
+
|
152
|
+
return node, hash
|
153
|
+
end
|
154
|
+
|
155
|
+
def self.xml_to_nokogiri(xml)
|
156
|
+
if xml.kind_of?(Nokogiri::XML::Element)
|
157
|
+
nokogiri = xml
|
158
|
+
else
|
159
|
+
nokogiri = Nokogiri::XML(xml).root
|
160
|
+
end
|
161
|
+
nokogiri
|
162
|
+
end
|
163
|
+
|
164
|
+
def self.href_from_creation_status_link(link)
|
165
|
+
# "/api/vms/5024ab49-19b5-4176-9568-c004d1c9f256/creation_status/d0e45003-d490-4551-9911-05b3bec682dc"
|
166
|
+
# => "/api/vms/5024ab49-19b5-4176-9568-c004d1c9f256"
|
167
|
+
link.split("/")[0,4].join("/")
|
168
|
+
end
|
169
|
+
|
170
|
+
def self.href_to_guid(href)
|
171
|
+
href.split("/").last
|
172
|
+
end
|
173
|
+
|
174
|
+
def self.object_to_id(object)
|
175
|
+
case object
|
176
|
+
when Ovirt::Object
|
177
|
+
object = object[:id]
|
178
|
+
when String
|
179
|
+
raise ArgumentError, "object must be a valid guid" unless object.guid?
|
180
|
+
object = href_to_guid(object)
|
181
|
+
else
|
182
|
+
raise ArgumentError, "object must be a valid guid or an Ovirt Object"
|
183
|
+
end
|
184
|
+
object
|
185
|
+
end
|
186
|
+
|
187
|
+
def self.api_endpoint
|
188
|
+
namespace.last.pluralize.downcase
|
189
|
+
end
|
190
|
+
|
191
|
+
def self.element_names
|
192
|
+
element_name.pluralize
|
193
|
+
end
|
194
|
+
|
195
|
+
def self.element_name
|
196
|
+
api_endpoint.singularize
|
197
|
+
end
|
198
|
+
|
199
|
+
def self.all_xml_objects(service)
|
200
|
+
response = service.resource_get(api_endpoint)
|
201
|
+
doc = Nokogiri::XML(response)
|
202
|
+
objects = doc.xpath("//#{element_names}/#{element_name}")
|
203
|
+
end
|
204
|
+
|
205
|
+
def self.all(service)
|
206
|
+
all_xml_objects(service).collect { |xml| self.create_from_xml(service, xml) }
|
207
|
+
end
|
208
|
+
|
209
|
+
def self.find_by_name(service, name)
|
210
|
+
all_xml_objects(service).each do |xml|
|
211
|
+
obj = self.create_from_xml(service, xml)
|
212
|
+
return obj if obj[:name] == name
|
213
|
+
end
|
214
|
+
nil
|
215
|
+
end
|
216
|
+
|
217
|
+
def self.find_by_id(service, id)
|
218
|
+
find_by_href(service, "#{api_endpoint}/#{id}")
|
219
|
+
end
|
220
|
+
|
221
|
+
def self.find_by_href(service, href)
|
222
|
+
response = service.resource_get(href)
|
223
|
+
doc = Nokogiri::XML(response)
|
224
|
+
xml = doc.xpath("//#{element_name}").first
|
225
|
+
self.create_from_xml(service, xml)
|
226
|
+
rescue RestClient::ResourceNotFound
|
227
|
+
return nil
|
228
|
+
end
|
229
|
+
|
230
|
+
attr_accessor :attributes, :operations, :relationships, :service
|
231
|
+
|
232
|
+
def initialize(service, options = {})
|
233
|
+
@service = service
|
234
|
+
@relationships = options.delete(:relationships) || {}
|
235
|
+
@operations = options.delete(:actions) || {}
|
236
|
+
@attributes = options
|
237
|
+
end
|
238
|
+
|
239
|
+
def replace(obj)
|
240
|
+
@relationships = obj.relationships
|
241
|
+
@operations = obj.operations
|
242
|
+
@attributes = obj.attributes
|
243
|
+
end
|
244
|
+
|
245
|
+
def reload
|
246
|
+
self.replace(self.class.find_by_href(@service, self[:href]))
|
247
|
+
end
|
248
|
+
|
249
|
+
def method_missing(m, *args)
|
250
|
+
if @relationships.has_key?(m)
|
251
|
+
rel_str = m.to_s
|
252
|
+
rel_str = 'storage_domains' if rel_str == 'storagedomains'
|
253
|
+
rel_str = 'data_centers' if rel_str == 'datacenters'
|
254
|
+
singular = rel_str.singularize
|
255
|
+
klass = singular.camelize.constantize
|
256
|
+
xml = @service.resource_get(@relationships[m])
|
257
|
+
doc = Nokogiri::XML(xml)
|
258
|
+
return doc.root.xpath(singular).collect { |node| klass.create_from_xml(@service, node) }
|
259
|
+
end
|
260
|
+
|
261
|
+
return operation(m, args) if @operations.has_key?(m)
|
262
|
+
|
263
|
+
super
|
264
|
+
end
|
265
|
+
|
266
|
+
def operation(method, *args)
|
267
|
+
if @operations.has_key?(method.to_sym)
|
268
|
+
builder = Nokogiri::XML::Builder.new do |xml|
|
269
|
+
xml.action { yield xml if block_given? }
|
270
|
+
end
|
271
|
+
data = builder.doc.root.to_xml
|
272
|
+
|
273
|
+
@service.resource_post(@operations[method.to_sym], data)
|
274
|
+
else
|
275
|
+
raise "Method:<#{method}> is not available for object <#{self.class.name}>"
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
279
|
+
def destroy
|
280
|
+
@service.resource_delete(@attributes[:href])
|
281
|
+
end
|
282
|
+
|
283
|
+
def class_suffix
|
284
|
+
self.class.name[5..-1]
|
285
|
+
end
|
286
|
+
|
287
|
+
def api_endpoint
|
288
|
+
self[:href] || "#{self.class.api_endpoint}/#{self[:id]}"
|
289
|
+
end
|
290
|
+
|
291
|
+
def update!(&block)
|
292
|
+
response = update(&block)
|
293
|
+
|
294
|
+
obj = self.class.create_from_xml(@service, response)
|
295
|
+
self.replace(obj)
|
296
|
+
end
|
297
|
+
|
298
|
+
def update
|
299
|
+
builder = Nokogiri::XML::Builder.new do |xml|
|
300
|
+
xml.send(namespace.last.downcase) { yield xml if block_given? }
|
301
|
+
end
|
302
|
+
data = builder.doc.root.to_xml
|
303
|
+
|
304
|
+
@service.resource_put(api_endpoint, data)
|
305
|
+
end
|
306
|
+
|
307
|
+
def keys
|
308
|
+
@attributes.keys
|
309
|
+
end
|
310
|
+
|
311
|
+
def [](key)
|
312
|
+
@attributes[key]
|
313
|
+
end
|
314
|
+
end
|
315
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Ovirt
|
2
|
+
class Permission < Object
|
3
|
+
|
4
|
+
self.top_level_objects = [:role, :user]
|
5
|
+
|
6
|
+
def self.parse_xml(xml)
|
7
|
+
node, hash = xml_to_hash(xml)
|
8
|
+
|
9
|
+
[:template].each do |type|
|
10
|
+
subject_node = node.xpath(type.to_s).first
|
11
|
+
next if subject_node.nil?
|
12
|
+
subject = hash_from_id_and_href(subject_node)
|
13
|
+
subject[:type] = type
|
14
|
+
hash[:subject] = subject
|
15
|
+
end
|
16
|
+
|
17
|
+
hash
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
data/lib/ovirt/permit.rb
ADDED
data/lib/ovirt/role.rb
ADDED