shopify-junos-ez-stdlib 1.0.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 +7 -0
- data/CHANGELOG.md +91 -0
- data/LICENSE +26 -0
- data/README.md +199 -0
- data/docs/Facts.md +192 -0
- data/docs/Providers/Group.md +61 -0
- data/docs/Providers/IPports.md +61 -0
- data/docs/Providers/L1ports.md +29 -0
- data/docs/Providers/L2ports.md +43 -0
- data/docs/Providers/LAGports.md +57 -0
- data/docs/Providers/StaticHosts.md +26 -0
- data/docs/Providers/StaticRoutes.md +37 -0
- data/docs/Providers/UserAuths.md +32 -0
- data/docs/Providers/Users.md +122 -0
- data/docs/Providers/Vlans.md +43 -0
- data/docs/Providers_Resources.md +353 -0
- data/docs/README_FIRST.md +27 -0
- data/docs/Utils/Config.md +160 -0
- data/docs/Utils/Filesystem.md +360 -0
- data/docs/Utils/Routing-Engine.md +379 -0
- data/docs/Utils/SCP.md +24 -0
- data/examples/config/config_file.rb +72 -0
- data/examples/config/config_template_object.rb +81 -0
- data/examples/config/config_template_simple.rb +76 -0
- data/examples/config/multi_config.rb +60 -0
- data/examples/fs_utils.rb +31 -0
- data/examples/lag_port.rb +27 -0
- data/examples/re_upgrade.rb +99 -0
- data/examples/re_utils.rb +33 -0
- data/examples/simple.rb +46 -0
- data/examples/st_hosts.rb +33 -0
- data/examples/user.rb +32 -0
- data/examples/vlans.rb +31 -0
- data/junos-ez-stdlib.gemspec +15 -0
- data/lib/junos-ez/exceptions.rb +3 -0
- data/lib/junos-ez/facts.rb +83 -0
- data/lib/junos-ez/facts/chassis.rb +51 -0
- data/lib/junos-ez/facts/ifd_style.rb +17 -0
- data/lib/junos-ez/facts/personality.rb +25 -0
- data/lib/junos-ez/facts/switch_style.rb +31 -0
- data/lib/junos-ez/facts/version.rb +58 -0
- data/lib/junos-ez/group.rb +206 -0
- data/lib/junos-ez/ip_ports.rb +30 -0
- data/lib/junos-ez/ip_ports/classic.rb +188 -0
- data/lib/junos-ez/l1_ports.rb +121 -0
- data/lib/junos-ez/l1_ports/classic.rb +87 -0
- data/lib/junos-ez/l1_ports/switch.rb +134 -0
- data/lib/junos-ez/l2_ports.rb +66 -0
- data/lib/junos-ez/l2_ports/bridge_domain.rb +499 -0
- data/lib/junos-ez/l2_ports/vlan.rb +433 -0
- data/lib/junos-ez/l2_ports/vlan_l2ng.rb +502 -0
- data/lib/junos-ez/lag_ports.rb +268 -0
- data/lib/junos-ez/provider.rb +619 -0
- data/lib/junos-ez/stdlib.rb +18 -0
- data/lib/junos-ez/system.rb +48 -0
- data/lib/junos-ez/system/st_hosts.rb +92 -0
- data/lib/junos-ez/system/st_routes.rb +159 -0
- data/lib/junos-ez/system/syscfg.rb +103 -0
- data/lib/junos-ez/system/userauths.rb +84 -0
- data/lib/junos-ez/system/users.rb +217 -0
- data/lib/junos-ez/utils/config.rb +236 -0
- data/lib/junos-ez/utils/fs.rb +385 -0
- data/lib/junos-ez/utils/re.rb +558 -0
- data/lib/junos-ez/version.rb +6 -0
- data/lib/junos-ez/vlans.rb +38 -0
- data/lib/junos-ez/vlans/bridge_domain.rb +89 -0
- data/lib/junos-ez/vlans/vlan.rb +119 -0
- data/lib/junos-ez/vlans/vlan_l2ng.rb +126 -0
- data/shipit.yml +4 -0
- data/tmp +7 -0
- metadata +129 -0
@@ -0,0 +1,217 @@
|
|
1
|
+
=begin
|
2
|
+
=end
|
3
|
+
|
4
|
+
require 'junos-ez/system/userauths'
|
5
|
+
|
6
|
+
module Junos::Ez::Users
|
7
|
+
|
8
|
+
PROPERTIES = [
|
9
|
+
:uid, # User-ID, Number
|
10
|
+
:class, # User Class, String
|
11
|
+
:fullname, # Full Name, String
|
12
|
+
:password, # Encrypted password
|
13
|
+
:ssh_keys, # READ-ONLY, Hash of SSH public keys
|
14
|
+
]
|
15
|
+
|
16
|
+
def self.Provider( ndev, varsym )
|
17
|
+
newbie = Junos::Ez::Users::Provider.new( ndev )
|
18
|
+
newbie.properties = Junos::Ez::Provider::PROPERTIES + PROPERTIES
|
19
|
+
Junos::Ez::Provider.attach_instance_variable( ndev, varsym, newbie )
|
20
|
+
end
|
21
|
+
|
22
|
+
class Provider < Junos::Ez::Provider::Parent; end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
|
27
|
+
##### ---------------------------------------------------------------
|
28
|
+
##### Provider Resource Methods
|
29
|
+
##### ---------------------------------------------------------------
|
30
|
+
|
31
|
+
class Junos::Ez::Users::Provider
|
32
|
+
|
33
|
+
### ---------------------------------------------------------------
|
34
|
+
### XML top placement
|
35
|
+
### ---------------------------------------------------------------
|
36
|
+
|
37
|
+
def xml_at_top
|
38
|
+
Nokogiri::XML::Builder.new{|x| x.configuration{
|
39
|
+
x.system { x.login { x.user {
|
40
|
+
x.name @name
|
41
|
+
return x
|
42
|
+
}}}}}
|
43
|
+
end
|
44
|
+
|
45
|
+
### ---------------------------------------------------------------
|
46
|
+
### XML readers
|
47
|
+
### ---------------------------------------------------------------
|
48
|
+
|
49
|
+
def xml_get_has_xml( xml )
|
50
|
+
xml.xpath('//user')[0]
|
51
|
+
end
|
52
|
+
|
53
|
+
def xml_read_parser( as_xml, as_hash )
|
54
|
+
set_has_status( as_xml, as_hash )
|
55
|
+
|
56
|
+
as_hash[:uid] = as_xml.xpath('uid').text
|
57
|
+
as_hash[:class] = as_xml.xpath('class').text
|
58
|
+
|
59
|
+
xml_when_item(as_xml.xpath('full-name')) {|i|
|
60
|
+
as_hash[:fullname] = i.text
|
61
|
+
}
|
62
|
+
|
63
|
+
xml_when_item(as_xml.xpath('authentication/encrypted-password')) {|i|
|
64
|
+
as_hash[:password] = i.text
|
65
|
+
}
|
66
|
+
|
67
|
+
# READ-ONLY capture the keys
|
68
|
+
unless (keys = as_xml.xpath('authentication/ssh-rsa')).empty?
|
69
|
+
as_hash[:ssh_keys] ||= {}
|
70
|
+
as_hash[:ssh_keys]['ssh-rsa'] = keys.collect{|key| key.text.strip}
|
71
|
+
end
|
72
|
+
unless (keys = as_xml.xpath('authentication/ssh-dsa')).empty?
|
73
|
+
as_hash[:ssh_keys] ||= {}
|
74
|
+
as_hash[:ssh_keys]['ssh-dsa'] = keys.collect{|key| key.text.strip}
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
### ---------------------------------------------------------------
|
79
|
+
### XML writers
|
80
|
+
### ---------------------------------------------------------------
|
81
|
+
|
82
|
+
def xml_change_password( xml )
|
83
|
+
xml.authentication {
|
84
|
+
xml_set_or_delete( xml, 'encrypted-password', @should[:password] )
|
85
|
+
}
|
86
|
+
end
|
87
|
+
|
88
|
+
def xml_change_fullname( xml )
|
89
|
+
xml_set_or_delete( xml, 'full-name', @should[:fullname] )
|
90
|
+
end
|
91
|
+
|
92
|
+
# changing the 'gid' is changing the Junos 'class' element
|
93
|
+
# so, what is tough here is that the Nokogiri Builder mech
|
94
|
+
# won't allow us to use the string 'class' since it conflicts
|
95
|
+
# with the Ruby language. So we need to add the 'class' element
|
96
|
+
# the hard way, yo! ...
|
97
|
+
|
98
|
+
def xml_change_class( xml )
|
99
|
+
par = xml.instance_variable_get(:@parent)
|
100
|
+
doc = xml.instance_variable_get(:@doc)
|
101
|
+
user_class = Nokogiri::XML::Node.new('class', doc )
|
102
|
+
user_class.content = @should[:class]
|
103
|
+
par.add_child( user_class )
|
104
|
+
end
|
105
|
+
|
106
|
+
def xml_change_uid( xml )
|
107
|
+
xml_set_or_delete( xml, 'uid', @should[:uid] )
|
108
|
+
end
|
109
|
+
|
110
|
+
end
|
111
|
+
|
112
|
+
##### ---------------------------------------------------------------
|
113
|
+
##### Provider Collection Methods
|
114
|
+
##### ---------------------------------------------------------------
|
115
|
+
|
116
|
+
class Junos::Ez::Users::Provider
|
117
|
+
|
118
|
+
def build_list
|
119
|
+
@ndev.rpc.get_configuration{ |x| x.system {
|
120
|
+
x.login {
|
121
|
+
x.user({:recurse => 'false'})
|
122
|
+
}
|
123
|
+
}}
|
124
|
+
.xpath('//user/name').collect{ |i| i.text }
|
125
|
+
end
|
126
|
+
|
127
|
+
def build_catalog
|
128
|
+
@catalog = {}
|
129
|
+
@ndev.rpc.get_configuration{ |x| x.system {
|
130
|
+
x.login
|
131
|
+
}}
|
132
|
+
.xpath('//user').each do |user|
|
133
|
+
name = user.xpath('name').text
|
134
|
+
@catalog[name] = {}
|
135
|
+
xml_read_parser( user, @catalog[name] )
|
136
|
+
end
|
137
|
+
@catalog
|
138
|
+
end
|
139
|
+
|
140
|
+
end
|
141
|
+
|
142
|
+
##### ---------------------------------------------------------------
|
143
|
+
##### Resource Methods
|
144
|
+
##### ---------------------------------------------------------------
|
145
|
+
|
146
|
+
class Junos::Ez::Users::Provider
|
147
|
+
|
148
|
+
## ----------------------------------------------------------------
|
149
|
+
## change the password by providing it in plain-text
|
150
|
+
## ----------------------------------------------------------------
|
151
|
+
|
152
|
+
def password=(plain_text)
|
153
|
+
xml = xml_at_top
|
154
|
+
xml.authentication {
|
155
|
+
xml.send(:'plain-text-password-value', plain_text)
|
156
|
+
}
|
157
|
+
@ndev.rpc.load_configuration( xml )
|
158
|
+
return true
|
159
|
+
end
|
160
|
+
|
161
|
+
## ----------------------------------------------------------------
|
162
|
+
## get a Hash that is used as the 'name' for obtaining a resource
|
163
|
+
## for Junos::Ez::UserAuths::Provider
|
164
|
+
## ----------------------------------------------------------------
|
165
|
+
|
166
|
+
def ssh_key( keytype, index = 0 )
|
167
|
+
return nil unless @has[:ssh_keys]
|
168
|
+
return nil unless @has[:ssh_keys][keytype]
|
169
|
+
ret_h = {:user => @name, :keytype => keytype}
|
170
|
+
ret_h[:publickey] = @has[:ssh_keys][keytype][index]
|
171
|
+
ret_h
|
172
|
+
end
|
173
|
+
|
174
|
+
##
|
175
|
+
## @@ need to move this code into the main provider
|
176
|
+
## @@ as a utility ...
|
177
|
+
##
|
178
|
+
|
179
|
+
def get_userauth_provd
|
180
|
+
@ndev.providers.each do |p|
|
181
|
+
obj = @ndev.send(p)
|
182
|
+
return obj if obj.class == Junos::Ez::UserAuths::Provider
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
## ----------------------------------------------------------------
|
187
|
+
## load an SSH public key & return the resulting key object.
|
188
|
+
## You can provide the publickey either as :publickey or
|
189
|
+
## contents will be read from :filename
|
190
|
+
## ----------------------------------------------------------------
|
191
|
+
|
192
|
+
def load_ssh_key!( opts = {} )
|
193
|
+
publickey = opts[:publickey] || File.read( opts[:filename] ).strip
|
194
|
+
raise ArgumentError, "no public-key specified" unless publickey
|
195
|
+
|
196
|
+
# nab the provider for handling ssh-keys, since we'll use that
|
197
|
+
# for key resource management
|
198
|
+
|
199
|
+
@auth_provd ||= get_userauth_provd
|
200
|
+
raise StandardError, "No Junos::Ez::UserAuths::Provider" unless @auth_provd
|
201
|
+
|
202
|
+
# extract the key-type from the public key.
|
203
|
+
keytype = publickey[0..6]
|
204
|
+
keytype = 'ssh-dsa' if keytype == 'ssh-dss'
|
205
|
+
raise ArgumentError, "Unknown ssh key-type #{keytype}" unless Junos::Ez::UserAuths::VALID_KEY_TYPES.include? keytype
|
206
|
+
|
207
|
+
# ok, we've got everything we need to add the key, so here we go.
|
208
|
+
key_name = {:user => @name, :keytype => keytype, :publickey => publickey }
|
209
|
+
key = @auth_provd[ key_name ]
|
210
|
+
key.write!
|
211
|
+
|
212
|
+
# return the key in case the caller wants it
|
213
|
+
key
|
214
|
+
end
|
215
|
+
|
216
|
+
end
|
217
|
+
|
@@ -0,0 +1,236 @@
|
|
1
|
+
=begin
|
2
|
+
---------------------------------------------------------------------
|
3
|
+
Config::Utils is a collection of methods used for loading
|
4
|
+
configuration files/templates and software images
|
5
|
+
|
6
|
+
commit! - commit configuration
|
7
|
+
commit? - see if a candidate config is OK (commit-check)
|
8
|
+
diff? - shows the diff of the candidate config w/current | rolback
|
9
|
+
load! - load configuration onto device
|
10
|
+
lock! - take exclusive lock on config
|
11
|
+
unlock! - release exclusive lock on config
|
12
|
+
rollback! - perform a config rollback
|
13
|
+
get_config - returns requested config in "text" format-style
|
14
|
+
|
15
|
+
---------------------------------------------------------------------
|
16
|
+
=end
|
17
|
+
|
18
|
+
module Junos::Ez::Config
|
19
|
+
def self.Utils( ndev, varsym )
|
20
|
+
newbie = Junos::Ez::Config::Provider.new( ndev )
|
21
|
+
Junos::Ez::Provider.attach_instance_variable( ndev, varsym, newbie )
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
### -----------------------------------------------------------------
|
26
|
+
### PUBLIC METHODS
|
27
|
+
### -----------------------------------------------------------------
|
28
|
+
### -----------------------------------------------------------------
|
29
|
+
|
30
|
+
class Junos::Ez::Config::Provider < Junos::Ez::Provider::Parent
|
31
|
+
|
32
|
+
### ---------------------------------------------------------------
|
33
|
+
### load! - used to load configuration files / templates. This
|
34
|
+
### does not perform a 'commit', just the equivalent of the
|
35
|
+
### load-configuration RPC
|
36
|
+
###
|
37
|
+
### --- options ---
|
38
|
+
###
|
39
|
+
### :filename => path - indcates the filename of content
|
40
|
+
### note: filename extension will also define format
|
41
|
+
### .{conf,text,txt} <==> :text
|
42
|
+
### .xml <==> :xml
|
43
|
+
### .set <==> :set
|
44
|
+
###
|
45
|
+
### :content => String - string content of data (vs. :filename)
|
46
|
+
###
|
47
|
+
### :format => [:text, :set, :xml], default :text (curly-brace)
|
48
|
+
### this will override any auto-format from the :filename
|
49
|
+
###
|
50
|
+
### :binding - indicates file/content is an ERB
|
51
|
+
### => <object> - will grab the binding from this object
|
52
|
+
### using a bit of meta-programming magic
|
53
|
+
### => <binding> - will use this binding
|
54
|
+
###
|
55
|
+
### :replace! => true - enables the 'replace' option
|
56
|
+
### :overwrite! => true - enables the 'overwrite' optoin
|
57
|
+
###
|
58
|
+
### --- returns ---
|
59
|
+
### true if the configuration is loaded OK
|
60
|
+
### raise Netconf::EditError otherwise
|
61
|
+
### ---------------------------------------------------------------
|
62
|
+
|
63
|
+
def load!( opts = {} )
|
64
|
+
raise ArgumentError unless opts[:content] || opts[:filename]
|
65
|
+
|
66
|
+
content = opts[:content] || File.read( opts[:filename] )
|
67
|
+
|
68
|
+
attrs = {}
|
69
|
+
attrs[:action] = 'replace' if opts[:replace!]
|
70
|
+
attrs[:action] = 'override' if opts[:override!]
|
71
|
+
|
72
|
+
if opts[:format]
|
73
|
+
attrs[:format] = opts[:format].to_s
|
74
|
+
elsif opts[:filename]
|
75
|
+
case f_ext = File.extname( opts[:filename] )
|
76
|
+
when '.conf','.text','.txt'; attrs[:format] = 'text'
|
77
|
+
when '.set'; attrs[:format] = 'set'
|
78
|
+
when '.xml'; # default is XML
|
79
|
+
else
|
80
|
+
raise ArgumentError, "unknown format from extension: #{f_ext}"
|
81
|
+
end
|
82
|
+
else
|
83
|
+
raise ArgumentError "unspecified format"
|
84
|
+
end
|
85
|
+
|
86
|
+
if opts[:binding]
|
87
|
+
erb = ERB.new( content, nil, '>' )
|
88
|
+
case opts[:binding]
|
89
|
+
when Binding
|
90
|
+
# binding was provided to use
|
91
|
+
content = erb.result( opts[:binding] )
|
92
|
+
when Object
|
93
|
+
obj = opts[:binding]
|
94
|
+
def obj.junos_ez_binding; binding end
|
95
|
+
content = erb.result( obj.junos_ez_binding )
|
96
|
+
class << obj; remove_method :junos_ez_binding end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
@ndev.rpc.load_configuration( content, attrs )
|
101
|
+
true # everthing OK!
|
102
|
+
end
|
103
|
+
|
104
|
+
### ---------------------------------------------------------------
|
105
|
+
### commit! - commits the configuration to the device
|
106
|
+
###
|
107
|
+
### --- options ---
|
108
|
+
###
|
109
|
+
### :confirm => true | timeout
|
110
|
+
### :comment => commit log comment
|
111
|
+
###
|
112
|
+
### --- returns ---
|
113
|
+
### true if commit completed
|
114
|
+
### raises Netconf::CommitError otherwise
|
115
|
+
### ---------------------------------------------------------------
|
116
|
+
|
117
|
+
def commit!( opts = {} )
|
118
|
+
|
119
|
+
args = {}
|
120
|
+
args[:log] = opts[:comment] if opts[:comment]
|
121
|
+
if opts[:confirm]
|
122
|
+
args[:confirmed] = true
|
123
|
+
if opts[:confirm] != true
|
124
|
+
timeout = Integer( opts[:confirm] ) rescue false
|
125
|
+
raise ArgumentError "invalid timeout #{opts[:confirm]}" unless timeout
|
126
|
+
args[:confirm_timeout] = timeout
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
@ndev.rpc.commit_configuration( args )
|
131
|
+
true
|
132
|
+
end
|
133
|
+
|
134
|
+
### ---------------------------------------------------------------
|
135
|
+
### commit? - perform commit configuration check
|
136
|
+
###
|
137
|
+
### --- returns ---
|
138
|
+
### true if candidate config is OK to commit
|
139
|
+
### Array of rpc-error data otherwise
|
140
|
+
### ---------------------------------------------------------------
|
141
|
+
|
142
|
+
def commit?
|
143
|
+
begin
|
144
|
+
@ndev.rpc.commit_configuration( :check => true )
|
145
|
+
rescue => e
|
146
|
+
return Junos::Ez::rpc_errors( e.rsp )
|
147
|
+
end
|
148
|
+
true # commit check OK!
|
149
|
+
end
|
150
|
+
|
151
|
+
### ---------------------------------------------------------------
|
152
|
+
### rollback! - used to rollback the configuration
|
153
|
+
### ---------------------------------------------------------------
|
154
|
+
|
155
|
+
def rollback!( rollback_id = 0 )
|
156
|
+
raise ArgumentError, "invalid rollback #{rollback_id}" unless ( rollback_id >= 0 and rollback_id <= 50 )
|
157
|
+
@ndev.rpc.load_configuration( :compare=>'rollback', :rollback=> rollback_id.to_s )
|
158
|
+
true # rollback OK!
|
159
|
+
end
|
160
|
+
|
161
|
+
### ---------------------------------------------------------------
|
162
|
+
### diff? - displays diff (patch format) between
|
163
|
+
### current candidate configuration loaded and the rollback_id
|
164
|
+
###
|
165
|
+
### --- returns ---
|
166
|
+
### nil if no diff
|
167
|
+
### String of diff output otherwise
|
168
|
+
### ---------------------------------------------------------------
|
169
|
+
|
170
|
+
def diff?( rollback_id = 0 )
|
171
|
+
raise ArgumentError, "invalid rollback #{rollback_id}" unless ( rollback_id >= 0 and rollback_id <= 50 )
|
172
|
+
got = ndev.rpc.get_configuration( :compare=>'rollback', :rollback=> rollback_id.to_s )
|
173
|
+
diff = got.xpath('configuration-output').text
|
174
|
+
return nil if diff == "\n"
|
175
|
+
diff
|
176
|
+
end
|
177
|
+
|
178
|
+
### ---------------------------------------------------------------
|
179
|
+
### lock! - takes an exclusive lock on the candidate config
|
180
|
+
###
|
181
|
+
### --- returns ---
|
182
|
+
### true if lock acquired
|
183
|
+
### raise Netconf::LockError otherwise
|
184
|
+
### ---------------------------------------------------------------
|
185
|
+
|
186
|
+
def lock!
|
187
|
+
@ndev.rpc.lock_configuration
|
188
|
+
true
|
189
|
+
end
|
190
|
+
|
191
|
+
### ---------------------------------------------------------------
|
192
|
+
### unlock! - releases exclusive lock on candidate config
|
193
|
+
###
|
194
|
+
### --- returns ---
|
195
|
+
### true if lock release
|
196
|
+
### raise Netconf::RpcError otherwise
|
197
|
+
### ---------------------------------------------------------------
|
198
|
+
|
199
|
+
def unlock!
|
200
|
+
@ndev.rpc.unlock_configuration
|
201
|
+
true
|
202
|
+
end
|
203
|
+
|
204
|
+
### ---------------------------------------------------------------
|
205
|
+
### get_config - returns String of requested (or entire) config
|
206
|
+
### in "text" (curly-brace) format. The 'rqst' argument
|
207
|
+
### identifies the scope of the config, for example:
|
208
|
+
###
|
209
|
+
### .get_config( "interfaces ge-0/0/0" )
|
210
|
+
###
|
211
|
+
### If there is no configuration available, 'nil' is returned
|
212
|
+
###
|
213
|
+
### If there is an error in the request, that will be returned
|
214
|
+
### as a String with "ERROR!" prepended
|
215
|
+
### ---------------------------------------------------------------
|
216
|
+
|
217
|
+
def get_config( rqst = nil )
|
218
|
+
scope = "show configuration"
|
219
|
+
scope.concat( " " + rqst ) if rqst
|
220
|
+
begin
|
221
|
+
@ndev.rpc.command( scope, :format => 'text' ).xpath('configuration-output').text
|
222
|
+
rescue NoMethodError
|
223
|
+
# indicates no configuration found
|
224
|
+
nil
|
225
|
+
rescue => e
|
226
|
+
# indicates error in request
|
227
|
+
err = e.rsp.xpath('rpc-error')[0]
|
228
|
+
err_info = err.xpath('error-info/bad-element').text
|
229
|
+
err_msg = err.xpath('error-message').text
|
230
|
+
"ERROR! " + err_msg + ": " + err_info
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
end # class Provider
|
235
|
+
|
236
|
+
|