opennebula 3.9.80.beta
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.
- data/LICENSE +202 -0
- data/NOTICE +47 -0
- data/lib/opennebula.rb +58 -0
- data/lib/opennebula/acl.rb +266 -0
- data/lib/opennebula/acl_pool.rb +55 -0
- data/lib/opennebula/client.rb +119 -0
- data/lib/opennebula/cluster.rb +249 -0
- data/lib/opennebula/cluster_pool.rb +58 -0
- data/lib/opennebula/datastore.rb +171 -0
- data/lib/opennebula/datastore_pool.rb +55 -0
- data/lib/opennebula/document.rb +261 -0
- data/lib/opennebula/document_json.rb +131 -0
- data/lib/opennebula/document_pool.rb +102 -0
- data/lib/opennebula/document_pool_json.rb +58 -0
- data/lib/opennebula/error.rb +52 -0
- data/lib/opennebula/group.rb +163 -0
- data/lib/opennebula/group_pool.rb +56 -0
- data/lib/opennebula/host.rb +201 -0
- data/lib/opennebula/host_pool.rb +93 -0
- data/lib/opennebula/image.rb +297 -0
- data/lib/opennebula/image_pool.rb +79 -0
- data/lib/opennebula/ldap_auth.rb +99 -0
- data/lib/opennebula/ldap_auth_spec.rb +70 -0
- data/lib/opennebula/pool.rb +160 -0
- data/lib/opennebula/pool_element.rb +269 -0
- data/lib/opennebula/server_cipher_auth.rb +148 -0
- data/lib/opennebula/server_x509_auth.rb +104 -0
- data/lib/opennebula/ssh_auth.rb +139 -0
- data/lib/opennebula/system.rb +141 -0
- data/lib/opennebula/template.rb +213 -0
- data/lib/opennebula/template_pool.rb +79 -0
- data/lib/opennebula/user.rb +174 -0
- data/lib/opennebula/user_pool.rb +55 -0
- data/lib/opennebula/virtual_machine.rb +560 -0
- data/lib/opennebula/virtual_machine_pool.rb +323 -0
- data/lib/opennebula/virtual_network.rb +249 -0
- data/lib/opennebula/virtual_network_pool.rb +79 -0
- data/lib/opennebula/x509_auth.rb +288 -0
- data/lib/opennebula/xml_element.rb +427 -0
- data/lib/opennebula/xml_pool.rb +45 -0
- data/lib/opennebula/xml_utils.rb +34 -0
- metadata +118 -0
@@ -0,0 +1,79 @@
|
|
1
|
+
# -------------------------------------------------------------------------- #
|
2
|
+
# Copyright 2002-2013, OpenNebula Project (OpenNebula.org), C12G Labs #
|
3
|
+
# #
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License"); you may #
|
5
|
+
# not use this file except in compliance with the License. You may obtain #
|
6
|
+
# a copy of the License at #
|
7
|
+
# #
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0 #
|
9
|
+
# #
|
10
|
+
# Unless required by applicable law or agreed to in writing, software #
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS, #
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
|
13
|
+
# See the License for the specific language governing permissions and #
|
14
|
+
# limitations under the License. #
|
15
|
+
#--------------------------------------------------------------------------- #
|
16
|
+
|
17
|
+
|
18
|
+
require 'opennebula/pool'
|
19
|
+
|
20
|
+
module OpenNebula
|
21
|
+
class VirtualNetworkPool < Pool
|
22
|
+
#######################################################################
|
23
|
+
# Constants and Class attribute accessors
|
24
|
+
#######################################################################
|
25
|
+
|
26
|
+
|
27
|
+
VN_POOL_METHODS = {
|
28
|
+
:info => "vnpool.info"
|
29
|
+
}
|
30
|
+
|
31
|
+
#######################################################################
|
32
|
+
# Class constructor & Pool Methods
|
33
|
+
#######################################################################
|
34
|
+
|
35
|
+
# +client+ a Client object that represents a XML-RPC connection
|
36
|
+
# +user_id+ is to refer to a Pool with VirtualNetworks from that user
|
37
|
+
def initialize(client, user_id=0)
|
38
|
+
super('VNET_POOL','VNET',client)
|
39
|
+
|
40
|
+
@user_id = user_id
|
41
|
+
end
|
42
|
+
|
43
|
+
# Default Factory Method for the Pools
|
44
|
+
def factory(element_xml)
|
45
|
+
OpenNebula::VirtualNetwork.new(element_xml,@client)
|
46
|
+
end
|
47
|
+
|
48
|
+
#######################################################################
|
49
|
+
# XML-RPC Methods for the Virtual Network Object
|
50
|
+
#######################################################################
|
51
|
+
|
52
|
+
# Retrieves all or part of the VirtualMachines in the pool.
|
53
|
+
def info(*args)
|
54
|
+
case args.size
|
55
|
+
when 0
|
56
|
+
info_filter(VN_POOL_METHODS[:info],@user_id,-1,-1)
|
57
|
+
when 3
|
58
|
+
info_filter(VN_POOL_METHODS[:info],args[0],args[1],args[2])
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def info_all()
|
63
|
+
return super(VN_POOL_METHODS[:info])
|
64
|
+
end
|
65
|
+
|
66
|
+
def info_mine()
|
67
|
+
return super(VN_POOL_METHODS[:info])
|
68
|
+
end
|
69
|
+
|
70
|
+
def info_group()
|
71
|
+
return super(VN_POOL_METHODS[:info])
|
72
|
+
end
|
73
|
+
|
74
|
+
alias_method :info!, :info
|
75
|
+
alias_method :info_all!, :info_all
|
76
|
+
alias_method :info_mine!, :info_mine
|
77
|
+
alias_method :info_group!, :info_group
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,288 @@
|
|
1
|
+
# -------------------------------------------------------------------------- #
|
2
|
+
# Copyright 2002-2013, OpenNebula Project (OpenNebula.org), C12G Labs #
|
3
|
+
# #
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License"); you may #
|
5
|
+
# not use this file except in compliance with the License. You may obtain #
|
6
|
+
# a copy of the License at #
|
7
|
+
# #
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0 #
|
9
|
+
# #
|
10
|
+
# Unless required by applicable law or agreed to in writing, software #
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS, #
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
|
13
|
+
# See the License for the specific language governing permissions and #
|
14
|
+
# limitations under the License. #
|
15
|
+
#--------------------------------------------------------------------------- #
|
16
|
+
|
17
|
+
require 'openssl'
|
18
|
+
require 'base64'
|
19
|
+
require 'fileutils'
|
20
|
+
require 'yaml'
|
21
|
+
|
22
|
+
module OpenNebula; end
|
23
|
+
|
24
|
+
# X509 authentication class. It can be used as a driver for auth_mad
|
25
|
+
# as auth method is defined. It also holds some helper methods to be used
|
26
|
+
# by oneauth command
|
27
|
+
class OpenNebula::X509Auth
|
28
|
+
###########################################################################
|
29
|
+
#Constants with paths to relevant files and defaults
|
30
|
+
###########################################################################
|
31
|
+
if !ENV["ONE_LOCATION"]
|
32
|
+
ETC_LOCATION = "/etc/one"
|
33
|
+
else
|
34
|
+
ETC_LOCATION = ENV["ONE_LOCATION"] + "/etc"
|
35
|
+
end
|
36
|
+
|
37
|
+
LOGIN_PATH = ENV['HOME']+'/.one/one_x509'
|
38
|
+
|
39
|
+
X509_AUTH_CONF_PATH = ETC_LOCATION + "/auth/x509_auth.conf"
|
40
|
+
|
41
|
+
X509_DEFAULTS = {
|
42
|
+
:ca_dir => ETC_LOCATION + "/auth/certificates"
|
43
|
+
}
|
44
|
+
|
45
|
+
def self.escape_dn(dn)
|
46
|
+
dn.gsub(/\s/) { |s| "\\"+s[0].ord.to_s(16) }
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.unescape_dn(dn)
|
50
|
+
dn.gsub(/\\[0-9a-f]{2}/) { |s| s[1,2].to_i(16).chr }
|
51
|
+
end
|
52
|
+
|
53
|
+
###########################################################################
|
54
|
+
# Initialize x509Auth object
|
55
|
+
#
|
56
|
+
# @param [Hash] default options for path
|
57
|
+
# @option options [String] :certs_pem
|
58
|
+
# cert chain array in colon-separated pem format
|
59
|
+
# @option options [String] :key_pem
|
60
|
+
# key in pem format
|
61
|
+
# @option options [String] :ca_dir
|
62
|
+
# directory of trusted CA's. Needed for auth method, not for login.
|
63
|
+
def initialize(options={})
|
64
|
+
@options ||= X509_DEFAULTS
|
65
|
+
@options.merge!(options)
|
66
|
+
|
67
|
+
load_options(X509_AUTH_CONF_PATH)
|
68
|
+
|
69
|
+
@cert_chain = @options[:certs_pem].collect do |cert_pem|
|
70
|
+
OpenSSL::X509::Certificate.new(cert_pem)
|
71
|
+
end
|
72
|
+
|
73
|
+
if @options[:key_pem]
|
74
|
+
@key = OpenSSL::PKey::RSA.new(@options[:key_pem])
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
###########################################################################
|
79
|
+
# Client side
|
80
|
+
###########################################################################
|
81
|
+
|
82
|
+
# Creates the login file for x509 authentication at ~/.one/one_x509.
|
83
|
+
# By default it is valid as long as the certificate is valid. It can
|
84
|
+
# be changed to any number of seconds with expire parameter (sec.)
|
85
|
+
def login(user, expire=0)
|
86
|
+
write_login(login_token(user,expire))
|
87
|
+
end
|
88
|
+
|
89
|
+
# Returns a valid password string to create a user using this auth driver.
|
90
|
+
# In this case the dn of the user certificate.
|
91
|
+
def password
|
92
|
+
self.class.escape_dn(@cert_chain[0].subject.to_s)
|
93
|
+
end
|
94
|
+
|
95
|
+
# Generates a login token in the form:
|
96
|
+
# user_name:x509:user_name:time_expires:cert_chain
|
97
|
+
# - user_name:time_expires is encrypted with the user certificate
|
98
|
+
# - user_name:time_expires:cert_chain is base64 encoded
|
99
|
+
def login_token(user, expire)
|
100
|
+
if expire != 0
|
101
|
+
expires = Time.now.to_i + expire.to_i
|
102
|
+
else
|
103
|
+
expires = @cert_chain[0].not_after.to_i
|
104
|
+
end
|
105
|
+
|
106
|
+
text_to_sign = "#{user}:#{expires}"
|
107
|
+
signed_text = encrypt(text_to_sign)
|
108
|
+
|
109
|
+
certs_pem = @cert_chain.collect{|cert| cert.to_pem}.join(":")
|
110
|
+
|
111
|
+
token = "#{signed_text}:#{certs_pem}"
|
112
|
+
token64 = Base64::encode64(token).strip.delete("\n")
|
113
|
+
|
114
|
+
login_out = "#{user}:#{token64}"
|
115
|
+
|
116
|
+
login_out
|
117
|
+
end
|
118
|
+
|
119
|
+
###########################################################################
|
120
|
+
# Server side
|
121
|
+
###########################################################################
|
122
|
+
# auth method for auth_mad
|
123
|
+
def authenticate(user, pass, signed_text)
|
124
|
+
begin
|
125
|
+
# Decryption demonstrates that the user posessed the private key.
|
126
|
+
_user, expires = decrypt(signed_text).split(':')
|
127
|
+
|
128
|
+
return "User name missmatch" if user != _user
|
129
|
+
|
130
|
+
return "x509 proxy expired" if Time.now.to_i >= expires.to_i
|
131
|
+
|
132
|
+
# Some DN in the chain must match a DN in the password
|
133
|
+
dn_ok = @cert_chain.each do |cert|
|
134
|
+
if pass.split('|').include?(
|
135
|
+
self.class.escape_dn(cert.subject.to_s))
|
136
|
+
break true
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
unless dn_ok == true
|
141
|
+
return "Certificate subject missmatch"
|
142
|
+
end
|
143
|
+
|
144
|
+
validate
|
145
|
+
|
146
|
+
return true
|
147
|
+
rescue => e
|
148
|
+
return e.message
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
private
|
153
|
+
# Writes a login_txt to the login file as defined in LOGIN_PATH
|
154
|
+
# constant
|
155
|
+
def write_login(login_txt)
|
156
|
+
# Inits login file path and creates ~/.one directory if needed
|
157
|
+
# Set instance variables
|
158
|
+
login_dir = File.dirname(LOGIN_PATH)
|
159
|
+
|
160
|
+
begin
|
161
|
+
FileUtils.mkdir_p(login_dir)
|
162
|
+
rescue Errno::EEXIST
|
163
|
+
end
|
164
|
+
|
165
|
+
file = File.open(LOGIN_PATH, "w")
|
166
|
+
file.write(login_txt)
|
167
|
+
file.close
|
168
|
+
|
169
|
+
File.chmod(0600,LOGIN_PATH)
|
170
|
+
end
|
171
|
+
|
172
|
+
# Load class options form a configuration file (yaml syntax)
|
173
|
+
def load_options(conf_file)
|
174
|
+
if File.readable?(conf_file)
|
175
|
+
conf_txt = File.read(conf_file)
|
176
|
+
conf_opt = YAML::load(conf_txt)
|
177
|
+
|
178
|
+
@options.merge!(conf_opt) if conf_opt != false
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
###########################################################################
|
183
|
+
# Methods to encrpyt/decrypt keys
|
184
|
+
###########################################################################
|
185
|
+
# Encrypts data with the private key of the user and returns
|
186
|
+
# base 64 encoded output in a single line
|
187
|
+
def encrypt(data)
|
188
|
+
return nil if !@key
|
189
|
+
Base64::encode64(@key.private_encrypt(data)).delete("\n").strip
|
190
|
+
end
|
191
|
+
|
192
|
+
# Decrypts base 64 encoded data with pub_key (public key)
|
193
|
+
def decrypt(data)
|
194
|
+
@cert_chain[0].public_key.public_decrypt(Base64::decode64(data))
|
195
|
+
end
|
196
|
+
|
197
|
+
###########################################################################
|
198
|
+
# Validate the user certificate
|
199
|
+
###########################################################################
|
200
|
+
def validate
|
201
|
+
now = Time.now
|
202
|
+
|
203
|
+
# Check start time and end time of certificates
|
204
|
+
@cert_chain.each do |cert|
|
205
|
+
if cert.not_before > now || cert.not_after < now
|
206
|
+
raise failed + "Certificate not valid. Current time is " +
|
207
|
+
now.localtime.to_s + "."
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
begin
|
212
|
+
# Validate the proxy certifcates
|
213
|
+
signee = @cert_chain[0]
|
214
|
+
|
215
|
+
check_crl(signee)
|
216
|
+
|
217
|
+
@cert_chain[1..-1].each do |cert|
|
218
|
+
if !((signee.issuer.to_s == cert.subject.to_s) &&
|
219
|
+
(signee.verify(cert.public_key)))
|
220
|
+
raise failed + signee.subject.to_s + " with issuer " +
|
221
|
+
signee.issuer.to_s + " was not verified by " +
|
222
|
+
cert.subject.to_s + "."
|
223
|
+
end
|
224
|
+
signee = cert
|
225
|
+
end
|
226
|
+
|
227
|
+
# Validate the End Entity certificate
|
228
|
+
if !@options[:ca_dir]
|
229
|
+
raise failed + "No certifcate authority directory was specified."
|
230
|
+
end
|
231
|
+
|
232
|
+
begin
|
233
|
+
ca_hash = signee.issuer.hash.to_s(16)
|
234
|
+
ca_path = @options[:ca_dir] + '/' + ca_hash + '.0'
|
235
|
+
|
236
|
+
ca_cert = OpenSSL::X509::Certificate.new(File.read(ca_path))
|
237
|
+
|
238
|
+
if !((signee.issuer.to_s == ca_cert.subject.to_s) &&
|
239
|
+
(signee.verify(ca_cert.public_key)))
|
240
|
+
raise failed + signee.subject.to_s + " with issuer " +
|
241
|
+
signee.issuer.to_s + " was not verified by " +
|
242
|
+
ca_cert.subject.to_s + "."
|
243
|
+
end
|
244
|
+
|
245
|
+
signee = ca_cert
|
246
|
+
end while ca_cert.subject.to_s != ca_cert.issuer.to_s
|
247
|
+
rescue
|
248
|
+
raise
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
def check_crl(signee)
|
253
|
+
failed = "Could not validate user credentials: "
|
254
|
+
|
255
|
+
ca_hash = signee.issuer.hash.to_s(16)
|
256
|
+
ca_path = @options[:ca_dir] + '/' + ca_hash + '.0'
|
257
|
+
|
258
|
+
crl_path = @options[:ca_dir] + '/' + ca_hash + '.r0'
|
259
|
+
|
260
|
+
if !File.exist?(crl_path)
|
261
|
+
if @options[:check_crl]
|
262
|
+
raise failed + "CRL file #{crl_path} does not exist"
|
263
|
+
else
|
264
|
+
return
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
268
|
+
ca_cert = OpenSSL::X509::Certificate.new( File.read(ca_path) )
|
269
|
+
crl_cert = OpenSSL::X509::CRL.new( File.read(crl_path) )
|
270
|
+
|
271
|
+
# First verify the CRL itself with its signer
|
272
|
+
unless crl_cert.verify( ca_cert.public_key ) then
|
273
|
+
raise failed + "CRL is not verified by its Signer"
|
274
|
+
end
|
275
|
+
|
276
|
+
# Extract the list of revoked certificates from the CRL
|
277
|
+
rc_array = crl_cert.revoked
|
278
|
+
|
279
|
+
# Loop over the list and compare with the target personal
|
280
|
+
# certificate
|
281
|
+
rc_array.each do |e|
|
282
|
+
if e.serial.eql?(signee.serial) then
|
283
|
+
raise failed + "#{signee.subject.to_s} is found in the "<<
|
284
|
+
"CRL, i.e. it is revoked"
|
285
|
+
end
|
286
|
+
end
|
287
|
+
end
|
288
|
+
end
|
@@ -0,0 +1,427 @@
|
|
1
|
+
# -------------------------------------------------------------------------- #
|
2
|
+
# Copyright 2002-2013, OpenNebula Project (OpenNebula.org), C12G Labs #
|
3
|
+
# #
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License"); you may #
|
5
|
+
# not use this file except in compliance with the License. You may obtain #
|
6
|
+
# a copy of the License at #
|
7
|
+
# #
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0 #
|
9
|
+
# #
|
10
|
+
# Unless required by applicable law or agreed to in writing, software #
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS, #
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
|
13
|
+
# See the License for the specific language governing permissions and #
|
14
|
+
# limitations under the License. #
|
15
|
+
#--------------------------------------------------------------------------- #
|
16
|
+
|
17
|
+
|
18
|
+
module OpenNebula
|
19
|
+
# The XMLElement class provides an abstraction of the underlying
|
20
|
+
# XML parser engine. It provides XML-related methods for the Pool and
|
21
|
+
# PoolElement classes
|
22
|
+
class XMLElement
|
23
|
+
|
24
|
+
# xml:: _opaque xml object_ an xml object as returned by build_xml
|
25
|
+
def initialize(xml=nil)
|
26
|
+
@xml = xml
|
27
|
+
end
|
28
|
+
|
29
|
+
# Initialize a XML document for the element
|
30
|
+
# xml:: _String_ the XML document of the object
|
31
|
+
# root_element:: _String_ Base xml element
|
32
|
+
def initialize_xml(xml, root_element)
|
33
|
+
@xml = XMLElement.build_xml(xml, root_element)
|
34
|
+
|
35
|
+
if OpenNebula.is_error?(@xml)
|
36
|
+
@xml = nil
|
37
|
+
else
|
38
|
+
if NOKOGIRI
|
39
|
+
if @xml.size == 0
|
40
|
+
@xml = nil
|
41
|
+
end
|
42
|
+
else
|
43
|
+
if @xml.name != root_element
|
44
|
+
@xml = nil
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# Builds a XML document
|
51
|
+
# xml:: _String_ the XML document of the object
|
52
|
+
# root_element:: _String_ Base xml element
|
53
|
+
# [return] _XML_ object for the underlying XML engine
|
54
|
+
def self.build_xml(xml, root_element)
|
55
|
+
begin
|
56
|
+
if NOKOGIRI
|
57
|
+
doc = Nokogiri::XML(xml).xpath("/#{root_element}")
|
58
|
+
else
|
59
|
+
doc = REXML::Document.new(xml).root
|
60
|
+
end
|
61
|
+
rescue Exception => e
|
62
|
+
return OpenNebula::Error.new(e.message)
|
63
|
+
end
|
64
|
+
|
65
|
+
return doc
|
66
|
+
end
|
67
|
+
|
68
|
+
# Extract a text element from the XML description of the PoolElement.
|
69
|
+
#
|
70
|
+
# @param [String] key Xpath expression
|
71
|
+
#
|
72
|
+
# @return [String, nil] If a text element is found, the element's
|
73
|
+
# text value. Otherwise, an empty string or nil, depending
|
74
|
+
# on the backend
|
75
|
+
#
|
76
|
+
# @example
|
77
|
+
# vm['VID'] # gets VM id
|
78
|
+
# vm['HISTORY/HOSTNAME'] # get the hostname from the history
|
79
|
+
def [](key)
|
80
|
+
if NOKOGIRI
|
81
|
+
element=@xml.xpath(key.to_s)
|
82
|
+
|
83
|
+
return nil if element.size == 0
|
84
|
+
else
|
85
|
+
element=@xml.elements[key.to_s]
|
86
|
+
|
87
|
+
return "" if element && !element.has_text?
|
88
|
+
end
|
89
|
+
|
90
|
+
element.text if element
|
91
|
+
end
|
92
|
+
|
93
|
+
# Delete an element from the xml
|
94
|
+
# xpath::_String_ xpath expression that selects the elemnts to be deleted
|
95
|
+
def delete_element(xpath)
|
96
|
+
if NOKOGIRI
|
97
|
+
@xml.xpath(xpath.to_s).remove
|
98
|
+
else
|
99
|
+
@xml.delete_element(xpath.to_s)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
# Add a new element to the xml
|
104
|
+
# xpath::_String_ xpath xpression where the elemente will be added
|
105
|
+
# elems::_Hash_ Hash containing the pairs key-value to be included
|
106
|
+
# Examples:
|
107
|
+
# add_element('VM', 'NEW_ITEM' => 'NEW_VALUE')
|
108
|
+
# <VM><NEW_ITEM>NEW_VALUE</NEW_ITEM>...</VM>
|
109
|
+
#
|
110
|
+
# add_element('VM/TEMPLATE', 'V1' => {'X1' => 'A1', 'Y2' => 'A2'})
|
111
|
+
# <VM><TEMPLATE><V1><X1>A1</X1><Y2>A2</Y2>...</TEMPLATE></VM>
|
112
|
+
def add_element(xpath, elems)
|
113
|
+
elems.each { |key, value|
|
114
|
+
if value.instance_of?(Hash)
|
115
|
+
if NOKOGIRI
|
116
|
+
elem = Nokogiri::XML::Node.new key, @xml.document
|
117
|
+
value.each { |k2, v2|
|
118
|
+
child = Nokogiri::XML::Node.new k2, elem
|
119
|
+
child.content = v2
|
120
|
+
elem.add_child(child)
|
121
|
+
}
|
122
|
+
@xml.xpath(xpath.to_s).first.add_child(elem)
|
123
|
+
else
|
124
|
+
elem = REXML::Element.new(key)
|
125
|
+
value.each { |k2, v2|
|
126
|
+
elem.add_element(k2).text = v2
|
127
|
+
}
|
128
|
+
@xml.elements[xpath].add_element(elem)
|
129
|
+
end
|
130
|
+
else
|
131
|
+
if NOKOGIRI
|
132
|
+
elem = Nokogiri::XML::Node.new key, @xml.document
|
133
|
+
elem.content = value
|
134
|
+
@xml.xpath(xpath.to_s).first.add_child(elem)
|
135
|
+
else
|
136
|
+
@xml.elements[xpath].add_element(key).text = value
|
137
|
+
end
|
138
|
+
end
|
139
|
+
}
|
140
|
+
end
|
141
|
+
|
142
|
+
# Gets an array of text from elemenets extracted
|
143
|
+
# using the XPATH expression passed as filter
|
144
|
+
def retrieve_elements(filter)
|
145
|
+
elements_array = Array.new
|
146
|
+
|
147
|
+
if NOKOGIRI
|
148
|
+
@xml.xpath(filter.to_s).each { |pelem|
|
149
|
+
elements_array << pelem.text if pelem.text
|
150
|
+
}
|
151
|
+
else
|
152
|
+
@xml.elements.each(filter.to_s) { |pelem|
|
153
|
+
elements_array << pelem.text if pelem.text
|
154
|
+
}
|
155
|
+
end
|
156
|
+
|
157
|
+
if elements_array.size == 0
|
158
|
+
return nil
|
159
|
+
else
|
160
|
+
return elements_array
|
161
|
+
end
|
162
|
+
|
163
|
+
end
|
164
|
+
|
165
|
+
# Gets an attribute from an elemenT
|
166
|
+
# key:: _String_ xpath for the element
|
167
|
+
# name:: _String_ name of the attribute
|
168
|
+
def attr(key,name)
|
169
|
+
value = nil
|
170
|
+
|
171
|
+
if NOKOGIRI
|
172
|
+
element=@xml.xpath(key.to_s.upcase)
|
173
|
+
if element.size == 0
|
174
|
+
return nil
|
175
|
+
end
|
176
|
+
|
177
|
+
attribute = element.attr(name)
|
178
|
+
|
179
|
+
value = attribute.text if attribute != nil
|
180
|
+
else
|
181
|
+
element=@xml.elements[key.to_s.upcase]
|
182
|
+
|
183
|
+
value = element.attributes[name] if element != nil
|
184
|
+
end
|
185
|
+
|
186
|
+
return value
|
187
|
+
end
|
188
|
+
|
189
|
+
# Iterates over every Element in the XPath and calls the block with a
|
190
|
+
# a XMLElement
|
191
|
+
# block:: _Block_
|
192
|
+
def each(xpath_str,&block)
|
193
|
+
if NOKOGIRI
|
194
|
+
@xml.xpath(xpath_str).each { |pelem|
|
195
|
+
block.call XMLElement.new(pelem)
|
196
|
+
}
|
197
|
+
else
|
198
|
+
@xml.elements.each(xpath_str) { |pelem|
|
199
|
+
block.call XMLElement.new(pelem)
|
200
|
+
}
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
def each_xpath(xpath_str,&block)
|
205
|
+
if NOKOGIRI
|
206
|
+
@xml.xpath(xpath_str).each { |pelem|
|
207
|
+
block.call pelem.text
|
208
|
+
}
|
209
|
+
else
|
210
|
+
@xml.elements.each(xpath_str) { |pelem|
|
211
|
+
block.call pelem.text
|
212
|
+
}
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
def name
|
217
|
+
@xml.name
|
218
|
+
end
|
219
|
+
|
220
|
+
def text
|
221
|
+
if NOKOGIRI
|
222
|
+
@xml.content
|
223
|
+
else
|
224
|
+
@xml.text
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
# Returns wheter there are elements for a given XPath
|
229
|
+
# xpath_str:: _String_ XPath expression to locate the element
|
230
|
+
def has_elements?(xpath_str)
|
231
|
+
if NOKOGIRI
|
232
|
+
element = @xml.xpath(xpath_str.to_s.upcase)
|
233
|
+
return element != nil && element.children.size > 0
|
234
|
+
else
|
235
|
+
element = @xml.elements[xpath_str.to_s]
|
236
|
+
return element != nil && element.has_elements?
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
# Returns the <TEMPLATE> element in text form
|
241
|
+
# indent:: _Boolean_ indents the resulting string, default true
|
242
|
+
def template_str(indent=true)
|
243
|
+
template_like_str('TEMPLATE', indent)
|
244
|
+
end
|
245
|
+
|
246
|
+
# Returns the <TEMPLATE> element in XML form
|
247
|
+
def template_xml
|
248
|
+
if NOKOGIRI
|
249
|
+
@xml.xpath('TEMPLATE').to_s
|
250
|
+
else
|
251
|
+
@xml.elements['TEMPLATE'].to_s
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
# Returns the xml of an element
|
256
|
+
def element_xml(xpath)
|
257
|
+
if NOKOGIRI
|
258
|
+
@xml.xpath(xpath).to_s
|
259
|
+
else
|
260
|
+
@xml.elements[xpath].to_s
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
264
|
+
# Returns elements in text form
|
265
|
+
# root_element:: _String_ base element
|
266
|
+
# indent:: _Boolean_ indents the resulting string, default true
|
267
|
+
# xpath_exp:: _String_ filter elements with a XPath
|
268
|
+
def template_like_str(root_element, indent=true, xpath_exp=nil)
|
269
|
+
if NOKOGIRI
|
270
|
+
xml_template = @xml.xpath(root_element).to_s
|
271
|
+
rexml = REXML::Document.new(xml_template).root
|
272
|
+
else
|
273
|
+
rexml = @xml.elements[root_element]
|
274
|
+
end
|
275
|
+
|
276
|
+
if indent
|
277
|
+
ind_enter = "\n"
|
278
|
+
ind_tab = ' '
|
279
|
+
else
|
280
|
+
ind_enter = ''
|
281
|
+
ind_tab = ' '
|
282
|
+
end
|
283
|
+
|
284
|
+
str = rexml.elements.collect(xpath_exp) {|n|
|
285
|
+
next if n.class != REXML::Element
|
286
|
+
|
287
|
+
str_line = ""
|
288
|
+
|
289
|
+
if n.has_elements?
|
290
|
+
str_line << "#{n.name}=[#{ind_enter}" << n.collect { |n2|
|
291
|
+
|
292
|
+
next if n2.class != REXML::Element or !n2.has_text?
|
293
|
+
|
294
|
+
str = "#{ind_tab}#{n2.name}=#{attr_to_str(n2.text)}"
|
295
|
+
|
296
|
+
}.compact.join(",#{ind_enter}") << " ]"
|
297
|
+
else
|
298
|
+
next if !n.has_text?
|
299
|
+
|
300
|
+
str_line << "#{n.name}=#{attr_to_str(n.text)}"
|
301
|
+
end
|
302
|
+
|
303
|
+
str_line
|
304
|
+
}.compact.join("\n")
|
305
|
+
|
306
|
+
return str
|
307
|
+
end
|
308
|
+
|
309
|
+
#
|
310
|
+
#
|
311
|
+
#
|
312
|
+
def to_xml(pretty=false)
|
313
|
+
if NOKOGIRI && pretty
|
314
|
+
str = @xml.to_xml
|
315
|
+
elsif REXML_FORMATTERS && pretty
|
316
|
+
str = String.new
|
317
|
+
|
318
|
+
formatter = REXML::Formatters::Pretty.new
|
319
|
+
formatter.compact = true
|
320
|
+
|
321
|
+
formatter.write(@xml,str)
|
322
|
+
else
|
323
|
+
str = @xml.to_s
|
324
|
+
end
|
325
|
+
|
326
|
+
return str
|
327
|
+
end
|
328
|
+
|
329
|
+
# @return [Hash] a hash representing the resource
|
330
|
+
def to_hash
|
331
|
+
hash = {}
|
332
|
+
|
333
|
+
if NOKOGIRI
|
334
|
+
if @xml.instance_of?(Nokogiri::XML::NodeSet)
|
335
|
+
@xml.each { |c|
|
336
|
+
if c.element?
|
337
|
+
build_hash(hash, c)
|
338
|
+
end
|
339
|
+
}
|
340
|
+
else
|
341
|
+
build_hash(hash, @xml)
|
342
|
+
end
|
343
|
+
else
|
344
|
+
build_hash(hash, @xml)
|
345
|
+
end
|
346
|
+
|
347
|
+
hash
|
348
|
+
end
|
349
|
+
|
350
|
+
private
|
351
|
+
|
352
|
+
#
|
353
|
+
#
|
354
|
+
#
|
355
|
+
def build_hash(hash, element)
|
356
|
+
if NOKOGIRI
|
357
|
+
array = element.children
|
358
|
+
if array.length==1 and (array.first.text? or array.first.cdata?)
|
359
|
+
r = array.first.text
|
360
|
+
else
|
361
|
+
r = {}
|
362
|
+
array.each { |c|
|
363
|
+
if c.element?
|
364
|
+
build_hash(r, c)
|
365
|
+
end
|
366
|
+
}
|
367
|
+
end
|
368
|
+
else
|
369
|
+
r = {}
|
370
|
+
if element.has_elements?
|
371
|
+
element.each_element { |c| build_hash(r, c) }
|
372
|
+
elsif element.has_text?
|
373
|
+
r = element.text
|
374
|
+
end
|
375
|
+
end
|
376
|
+
|
377
|
+
key = element.name
|
378
|
+
if hash.has_key?(key)
|
379
|
+
if hash[key].instance_of?(Array)
|
380
|
+
hash[key] << r
|
381
|
+
else
|
382
|
+
hash[key] = [hash[key], r]
|
383
|
+
end
|
384
|
+
else
|
385
|
+
hash[key] = r
|
386
|
+
end
|
387
|
+
|
388
|
+
hash
|
389
|
+
end
|
390
|
+
|
391
|
+
#
|
392
|
+
#
|
393
|
+
#
|
394
|
+
def attr_to_str(attr)
|
395
|
+
attr.gsub!('"',"\\\"")
|
396
|
+
attr = "\"#{attr}\""
|
397
|
+
|
398
|
+
return attr
|
399
|
+
end
|
400
|
+
end
|
401
|
+
|
402
|
+
# The XMLUtilsPool module provides an abstraction of the underlying
|
403
|
+
# XML parser engine. It provides XML-related methods for the Pools
|
404
|
+
class XMLPool < XMLElement
|
405
|
+
|
406
|
+
def initialize(xml=nil)
|
407
|
+
super(xml)
|
408
|
+
end
|
409
|
+
|
410
|
+
#Executes the given block for each element of the Pool
|
411
|
+
#block:: _Block_
|
412
|
+
def each_element(block)
|
413
|
+
if NOKOGIRI
|
414
|
+
@xml.xpath(
|
415
|
+
"#{@element_name}").each {|pelem|
|
416
|
+
block.call self.factory(pelem)
|
417
|
+
}
|
418
|
+
else
|
419
|
+
@xml.elements.each(
|
420
|
+
"#{@element_name}") {|pelem|
|
421
|
+
block.call self.factory(pelem)
|
422
|
+
}
|
423
|
+
end
|
424
|
+
end
|
425
|
+
end
|
426
|
+
|
427
|
+
end
|