ovirt 0.1.0
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 +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