treequel 1.2.2 → 1.3.0pre384
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.tar.gz.sig +0 -0
- data/ChangeLog +3374 -0
- data/History.md +39 -0
- data/LICENSE +27 -0
- data/README.md +25 -2
- data/Rakefile +64 -29
- data/bin/treequel +16 -25
- data/bin/treewhat +318 -0
- data/lib/treequel.rb +13 -3
- data/lib/treequel/behavior/control.rb +40 -0
- data/lib/treequel/branch.rb +56 -28
- data/lib/treequel/branchset.rb +10 -2
- data/lib/treequel/controls/pagedresults.rb +4 -2
- data/lib/treequel/directory.rb +40 -50
- data/lib/treequel/exceptions.rb +18 -0
- data/lib/treequel/mixins.rb +44 -14
- data/lib/treequel/model.rb +338 -21
- data/lib/treequel/model/errors.rb +79 -0
- data/lib/treequel/model/objectclass.rb +26 -2
- data/lib/treequel/model/schemavalidations.rb +69 -0
- data/lib/treequel/monkeypatches.rb +99 -17
- data/lib/treequel/schema.rb +19 -5
- data/spec/lib/constants.rb +20 -2
- data/spec/lib/helpers.rb +25 -8
- data/spec/treequel/branch_spec.rb +73 -10
- data/spec/treequel/controls/contentsync_spec.rb +2 -11
- data/spec/treequel/controls/pagedresults_spec.rb +25 -9
- data/spec/treequel/controls/sortedresults_spec.rb +8 -10
- data/spec/treequel/directory_spec.rb +74 -63
- data/spec/treequel/model/errors_spec.rb +77 -0
- data/spec/treequel/model/objectclass_spec.rb +107 -35
- data/spec/treequel/model/schemavalidations_spec.rb +112 -0
- data/spec/treequel/model_spec.rb +294 -81
- data/spec/treequel/monkeypatches_spec.rb +49 -3
- metadata +28 -16
- metadata.gz.sig +0 -0
- data/spec/lib/control_behavior.rb +0 -47
@@ -0,0 +1,79 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# coding: utf-8
|
3
|
+
|
4
|
+
require 'treequel'
|
5
|
+
require 'treequel/model'
|
6
|
+
require 'treequel/mixins'
|
7
|
+
require 'treequel/constants'
|
8
|
+
|
9
|
+
|
10
|
+
# Mixin that provides Treequel::Model characteristics to a mixin module.
|
11
|
+
#
|
12
|
+
# The ideas and a large portion of the implementation of this class is borrowed from
|
13
|
+
# Sequel under the following license terms:
|
14
|
+
#
|
15
|
+
# Copyright (c) 2007-2008 Sharon Rosner
|
16
|
+
# Copyright (c) 2008-2010 Jeremy Evans
|
17
|
+
#
|
18
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
19
|
+
# of this software and associated documentation files (the "Software"), to
|
20
|
+
# deal in the Software without restriction, including without limitation the
|
21
|
+
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
22
|
+
# sell copies of the Software, and to permit persons to whom the Software is
|
23
|
+
# furnished to do so, subject to the following conditions:
|
24
|
+
#
|
25
|
+
# The above copyright notice and this permission notice shall be included in
|
26
|
+
# all copies or substantial portions of the Software.
|
27
|
+
#
|
28
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
29
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
30
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
31
|
+
# THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
32
|
+
# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
33
|
+
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
34
|
+
#
|
35
|
+
class Treequel::Model::Errors < ::Hash
|
36
|
+
include Treequel::HashUtilities,
|
37
|
+
Treequel::Loggable
|
38
|
+
|
39
|
+
# The word to use between attributes in error messages
|
40
|
+
ATTRIBUTE_CONJUNCTION = ' and '
|
41
|
+
|
42
|
+
|
43
|
+
### Set the initializer block to auto-create Array values.
|
44
|
+
def initialize( *args )
|
45
|
+
block = lambda {|h,k| h[k] = [] }
|
46
|
+
super( *args, &block )
|
47
|
+
end
|
48
|
+
|
49
|
+
|
50
|
+
### Adds an error for the given +attribute+.
|
51
|
+
### @param [Symbol, #to_sym] attribute the attribute with an error
|
52
|
+
### @param [String] message the description of the error condition
|
53
|
+
def add( attribute, message )
|
54
|
+
self[ attribute ] << message
|
55
|
+
end
|
56
|
+
|
57
|
+
|
58
|
+
### Get the number of errors that have been registered.
|
59
|
+
### @Return [Fixnum] the number of errors
|
60
|
+
def count
|
61
|
+
return self.values.inject( 0 ) {|num, val| num + val.length }
|
62
|
+
end
|
63
|
+
|
64
|
+
|
65
|
+
### Get an Array of messages describing errors which have occurred.
|
66
|
+
### @example
|
67
|
+
### errors.full_messages
|
68
|
+
### # => ['cn is not valid',
|
69
|
+
### # 'uid is not at least 2 letters']
|
70
|
+
def full_messages
|
71
|
+
return self.inject([]) do |full_messages, (attribute, messages)|
|
72
|
+
subject = Array( attribute ).join( ATTRIBUTE_CONJUNCTION )
|
73
|
+
messages.each {|part| full_messages << "#{subject} #{part}" }
|
74
|
+
full_messages
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
end # class Treequel::Model::Errors
|
79
|
+
|
@@ -9,12 +9,12 @@ require 'treequel/constants'
|
|
9
9
|
|
10
10
|
# Mixin that provides Treequel::Model characteristics to a mixin module.
|
11
11
|
module Treequel::Model::ObjectClass
|
12
|
+
include Treequel::HashUtilities
|
13
|
+
|
12
14
|
|
13
15
|
### Extension callback -- add data structures to the extending +mod+.
|
14
16
|
### @param [Module] mod the mixin module to be extended
|
15
17
|
def self::extended( mod )
|
16
|
-
# mod.instance_variable_set( :@model_directory, nil )
|
17
|
-
# mod.instance_variable_set( :@model_bases, [] )
|
18
18
|
mod.instance_variable_set( :@model_class, Treequel::Model )
|
19
19
|
mod.instance_variable_set( :@model_objectclasses, [] )
|
20
20
|
mod.instance_variable_set( :@model_bases, [] )
|
@@ -75,6 +75,30 @@ module Treequel::Model::ObjectClass
|
|
75
75
|
end
|
76
76
|
|
77
77
|
|
78
|
+
### Instantiate a new Treequel::Model object with given +dn+ and the objectclasses
|
79
|
+
### specified by the receiving module.
|
80
|
+
### @param [#to_s] dn the DN of the new model object
|
81
|
+
### @param [Hash] entryhash attributes to set on the new entry
|
82
|
+
def create( directory, dn, entryhash={} )
|
83
|
+
entryhash = stringify_keys( entryhash )
|
84
|
+
|
85
|
+
# Add the objectclasses from the mixin
|
86
|
+
entryhash['objectClass'] ||= []
|
87
|
+
entryhash['objectClass'].collect!( &:to_s )
|
88
|
+
entryhash['objectClass'] |= self.model_objectclasses.map( &:to_s )
|
89
|
+
|
90
|
+
# Add all the attribute pairs from the RDN bit of the DN to the entry
|
91
|
+
rdn_pair, _ = dn.split( /\s*,\s*/, 2 )
|
92
|
+
rdn_pair.split( /\+/ ).each do |attrpair|
|
93
|
+
k, v = attrpair.split( /\s*=\s*/ )
|
94
|
+
entryhash[ k ] ||= []
|
95
|
+
entryhash[ k ] << v
|
96
|
+
end
|
97
|
+
|
98
|
+
return self.model_class.new( directory, dn, entryhash )
|
99
|
+
end
|
100
|
+
|
101
|
+
|
78
102
|
### Return a Branchset (or BranchCollection if the receiver has more than one
|
79
103
|
### base) that can be used to search the given +directory+ for entries to which
|
80
104
|
### the receiver applies.
|
@@ -0,0 +1,69 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#encoding: utf-8
|
3
|
+
|
4
|
+
require 'treequel/model'
|
5
|
+
|
6
|
+
|
7
|
+
# A collection of schema-based validations for LDAP model objects.
|
8
|
+
module Treequel::Model::SchemaValidations
|
9
|
+
|
10
|
+
### Entrypoint -- run all the validations, adding any errors to the
|
11
|
+
### object's #error collector.
|
12
|
+
def validate( options={} )
|
13
|
+
return unless options[:with_schema]
|
14
|
+
|
15
|
+
self.validate_must_attributes
|
16
|
+
self.validate_may_attributes
|
17
|
+
self.validate_attribute_syntax
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
### Validate that all attributes that MUST be included according to the entry's
|
22
|
+
### objectClasses have at least one value.
|
23
|
+
def validate_must_attributes
|
24
|
+
self.must_attribute_types.each do |attrtype|
|
25
|
+
oid = attrtype.name
|
26
|
+
if attrtype.single?
|
27
|
+
self.errors.add( oid, "MUST have a value" ) unless self[ oid ]
|
28
|
+
else
|
29
|
+
self.errors.add( oid, "MUST have at least one value" ) if self[ oid ].empty?
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
|
35
|
+
### Validate that all attributes present in the entry are allowed by either a
|
36
|
+
### MUST or a MAY rule of one of its objectClasses.
|
37
|
+
def validate_may_attributes
|
38
|
+
hash = (self.entry || {} ).merge( @values )
|
39
|
+
attributes = hash.keys.map( &:to_sym ).uniq
|
40
|
+
valid_attributes = self.valid_attribute_oids
|
41
|
+
|
42
|
+
self.log.debug "Validating MAY attributes: %p against the list of valid OIDs: %p" %
|
43
|
+
[ attributes, valid_attributes ]
|
44
|
+
unknown_attributes = attributes - valid_attributes
|
45
|
+
unknown_attributes.each do |oid|
|
46
|
+
self.errors.add( oid, "is not allowed by entry's objectClasses" )
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
### Validate that the attribute values present in the entry are all valid according to
|
52
|
+
### the syntax rule for it.
|
53
|
+
def validate_attribute_syntax
|
54
|
+
@values.each do |attribute, values|
|
55
|
+
[ values ].flatten.each do |value|
|
56
|
+
begin
|
57
|
+
self.get_converted_attribute( attribute.to_sym, value )
|
58
|
+
rescue => err
|
59
|
+
self.log.error "validation for %p failed: %s: %s" %
|
60
|
+
[ attribute, err.class.name, err.message ]
|
61
|
+
attrtype = self.find_attribute_type( attribute )
|
62
|
+
self.errors.add( attribute, "isn't a valid %s value" % [attrtype.syntax.desc] )
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
end # module Treequel::Model::SchemaValidations
|
69
|
+
|
@@ -1,5 +1,10 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
+
require 'date'
|
4
|
+
|
5
|
+
require 'diff/lcs'
|
6
|
+
require 'diff/lcs/change'
|
7
|
+
|
3
8
|
require 'ldap'
|
4
9
|
require 'ldap/control'
|
5
10
|
|
@@ -57,26 +62,26 @@ module Treequel::TimeExtensions
|
|
57
62
|
if fraction_digits == 0
|
58
63
|
''
|
59
64
|
elsif fraction_digits <= 6
|
60
|
-
'.' + sprintf('%06d', usec)[0, fraction_digits]
|
65
|
+
'.' + sprintf('%06d', self.usec)[0, fraction_digits]
|
61
66
|
else
|
62
|
-
'.' + sprintf('%06d', usec) + '0' * (fraction_digits - 6)
|
67
|
+
'.' + sprintf('%06d', self.usec) + '0' * (fraction_digits - 6)
|
63
68
|
end
|
64
69
|
tz =
|
65
70
|
if self.utc?
|
66
71
|
'Z'
|
67
72
|
else
|
68
|
-
off = utc_offset
|
73
|
+
off = self.utc_offset
|
69
74
|
sign = off < 0 ? '-' : '+'
|
70
75
|
"%s%02d%02d" % [ sign, *(off.abs / 60).divmod(60) ]
|
71
76
|
end
|
72
77
|
|
73
78
|
return "%02d%02d%02d%02d%02d%02d%s%s" % [
|
74
|
-
year,
|
75
|
-
mon,
|
76
|
-
day,
|
77
|
-
hour,
|
78
|
-
min,
|
79
|
-
sec,
|
79
|
+
self.year,
|
80
|
+
self.mon,
|
81
|
+
self.day,
|
82
|
+
self.hour,
|
83
|
+
self.min,
|
84
|
+
self.sec,
|
80
85
|
fractional_seconds,
|
81
86
|
tz
|
82
87
|
]
|
@@ -87,21 +92,21 @@ module Treequel::TimeExtensions
|
|
87
92
|
### (UTC Time)
|
88
93
|
def ldap_utc
|
89
94
|
tz =
|
90
|
-
if utc?
|
95
|
+
if self.utc?
|
91
96
|
'Z'
|
92
97
|
else
|
93
|
-
off = utc_offset
|
98
|
+
off = self.utc_offset
|
94
99
|
sign = off < 0 ? '-' : '+'
|
95
100
|
"%s%02d%02d" % [ sign, *(off.abs / 60).divmod(60) ]
|
96
101
|
end
|
97
102
|
|
98
103
|
return "%02d%02d%02d%02d%02d%02d%s" % [
|
99
|
-
year.divmod(100).last,
|
100
|
-
mon,
|
101
|
-
day,
|
102
|
-
hour,
|
103
|
-
min,
|
104
|
-
sec,
|
104
|
+
self.year.divmod(100).last,
|
105
|
+
self.mon,
|
106
|
+
self.day,
|
107
|
+
self.hour,
|
108
|
+
self.min,
|
109
|
+
self.sec,
|
105
110
|
tz
|
106
111
|
]
|
107
112
|
end
|
@@ -113,3 +118,80 @@ class Time
|
|
113
118
|
end
|
114
119
|
|
115
120
|
|
121
|
+
### Extensions to the Date class to add LDAP (RFC4517) Generalized Time syntax
|
122
|
+
module Treequel::DateExtensions
|
123
|
+
|
124
|
+
### Return +self+ as a String formatted as specified in RFC4517
|
125
|
+
### (LDAP Generalized Time).
|
126
|
+
def ldap_generalized( fraction_digits=0 )
|
127
|
+
fractional_seconds =
|
128
|
+
if fraction_digits == 0
|
129
|
+
''
|
130
|
+
else
|
131
|
+
'.' + ('0' * fraction_digits)
|
132
|
+
end
|
133
|
+
|
134
|
+
off = Time.now.utc_offset
|
135
|
+
sign = off < 0 ? '-' : '+'
|
136
|
+
tz = "%s%02d%02d" % [ sign, *(off.abs / 60).divmod(60) ]
|
137
|
+
|
138
|
+
return "%02d%02d%02d%02d%02d%02d%s%s" % [
|
139
|
+
self.year,
|
140
|
+
self.mon,
|
141
|
+
self.day,
|
142
|
+
0,
|
143
|
+
0,
|
144
|
+
1,
|
145
|
+
fractional_seconds,
|
146
|
+
tz
|
147
|
+
]
|
148
|
+
|
149
|
+
end
|
150
|
+
|
151
|
+
### Returns +self+ as a String formatted as specified in RFC4517
|
152
|
+
### (UTC Time)
|
153
|
+
def ldap_utc
|
154
|
+
off = Time.now.utc_offset
|
155
|
+
sign = off < 0 ? '-' : '+'
|
156
|
+
tz = "%s%02d%02d" % [ sign, *(off.abs / 60).divmod(60) ]
|
157
|
+
|
158
|
+
return "%02d%02d%02d%02d%02d%02d%s" % [
|
159
|
+
self.year.divmod(100).last,
|
160
|
+
self.mon,
|
161
|
+
self.day,
|
162
|
+
0,
|
163
|
+
0,
|
164
|
+
1,
|
165
|
+
tz
|
166
|
+
]
|
167
|
+
end
|
168
|
+
|
169
|
+
end # module Treequel::TimeExtensions
|
170
|
+
|
171
|
+
class Date
|
172
|
+
include Treequel::DateExtensions
|
173
|
+
end
|
174
|
+
|
175
|
+
|
176
|
+
### These three predicates use the wrong instance variable in the library.
|
177
|
+
### :TODO: Submit a patch!
|
178
|
+
module Treequel::DiffLCSChangeTypeTestFixes
|
179
|
+
|
180
|
+
def changed?
|
181
|
+
@action == '!'
|
182
|
+
end
|
183
|
+
|
184
|
+
def finished_a?
|
185
|
+
@action == '>'
|
186
|
+
end
|
187
|
+
|
188
|
+
def finished_b?
|
189
|
+
@action == '<'
|
190
|
+
end
|
191
|
+
|
192
|
+
end
|
193
|
+
|
194
|
+
class Diff::LCS::ContextChange
|
195
|
+
include Treequel::DiffLCSChangeTypeTestFixes
|
196
|
+
end
|
197
|
+
|
data/lib/treequel/schema.rb
CHANGED
@@ -209,17 +209,21 @@ class Treequel::Schema
|
|
209
209
|
end
|
210
210
|
|
211
211
|
|
212
|
+
### Return the schema as a human-readable english string.
|
213
|
+
def to_s
|
214
|
+
parts = [ "Schema:" ]
|
215
|
+
parts << self.ivar_descriptions.collect {|desc| ' ' + desc }
|
216
|
+
return parts.join( $/ )
|
217
|
+
end
|
218
|
+
|
219
|
+
|
212
220
|
### Return a human-readable representation of the object suitable for debugging.
|
213
221
|
### @return [String]
|
214
222
|
def inspect
|
215
|
-
ivar_descs = self.instance_variables.sort.collect do |ivar|
|
216
|
-
len = self.instance_variable_get( ivar ).length
|
217
|
-
"%d %s" % [ len, ivar.to_s.gsub(/_/, ' ')[1..-1] ]
|
218
|
-
end
|
219
223
|
return %{#<%s:0x%0x %s>} % [
|
220
224
|
self.class.name,
|
221
225
|
self.object_id / 2,
|
222
|
-
|
226
|
+
self.ivar_descriptions.join( ', ' ),
|
223
227
|
]
|
224
228
|
end
|
225
229
|
|
@@ -308,5 +312,15 @@ class Treequel::Schema
|
|
308
312
|
end
|
309
313
|
|
310
314
|
|
315
|
+
### Return descriptions of the schema's artifacts.
|
316
|
+
### @return [Array<String>] the descriptions of the schema's artifacts, and how many of each
|
317
|
+
### it has.
|
318
|
+
def ivar_descriptions
|
319
|
+
self.instance_variables.sort.collect do |ivar|
|
320
|
+
len = self.instance_variable_get( ivar ).length
|
321
|
+
"%d %s" % [ len, ivar.to_s.gsub(/_/, ' ')[1..-1] ]
|
322
|
+
end
|
323
|
+
end
|
324
|
+
|
311
325
|
end # class Treequel::Schema
|
312
326
|
|
data/spec/lib/constants.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
+
require 'yaml'
|
3
4
|
require 'ldap'
|
4
5
|
require 'treequel'
|
5
6
|
|
@@ -40,7 +41,8 @@ module Treequel::TestConstants # :nodoc:all
|
|
40
41
|
"2.16.840.1.113730.3.4.2", "1.3.6.1.4.1.4203.1.10.1",
|
41
42
|
"1.2.840.113556.1.4.319", "1.2.826.0.1.334810.2.3",
|
42
43
|
"1.2.826.0.1.3344810.2.3", "1.3.6.1.1.13.2",
|
43
|
-
"1.3.6.1.1.13.1", "1.3.6.1.1.12"
|
44
|
+
"1.3.6.1.1.13.1", "1.3.6.1.1.12",
|
45
|
+
"1.2.840.113556.1.4.473", "1.2.840.113556.1.4.474"
|
44
46
|
],
|
45
47
|
"supportedExtension" => [
|
46
48
|
"1.3.6.1.4.1.1466.20037", "1.3.6.1.4.1.4203.1.11.1",
|
@@ -50,6 +52,9 @@ module Treequel::TestConstants # :nodoc:all
|
|
50
52
|
}]
|
51
53
|
TEST_DSE.first.keys.each {|key| TEST_DSE.first[key].freeze }
|
52
54
|
|
55
|
+
SCHEMA_DUMPFILE = Pathname( __FILE__ ).dirname.parent + 'data' + 'schema.yml'
|
56
|
+
SCHEMAHASH = LDAP::Schema.new( YAML.load_file(SCHEMA_DUMPFILE) )
|
57
|
+
SCHEMA = Treequel::Schema.new( SCHEMAHASH )
|
53
58
|
|
54
59
|
TEST_HOSTS_DN_ATTR = 'ou'
|
55
60
|
TEST_HOSTS_DN_VALUE = 'Hosts'
|
@@ -82,7 +87,7 @@ module Treequel::TestConstants # :nodoc:all
|
|
82
87
|
TEST_PEOPLE_DN = "#{TEST_PEOPLE_RDN},#{TEST_BASE_DN}"
|
83
88
|
|
84
89
|
TEST_PERSON_DN_ATTR = 'uid'
|
85
|
-
TEST_PERSON_DN_VALUE = '
|
90
|
+
TEST_PERSON_DN_VALUE = 'jrandom'
|
86
91
|
TEST_PERSON_RDN = "#{TEST_PERSON_DN_ATTR}=#{TEST_PERSON_DN_VALUE}"
|
87
92
|
TEST_PERSON_DN = "#{TEST_PERSON_RDN},#{TEST_PEOPLE_DN}"
|
88
93
|
|
@@ -106,6 +111,19 @@ module Treequel::TestConstants # :nodoc:all
|
|
106
111
|
TEST_ROOM_RDN = "#{TEST_ROOM_DN_ATTR}=#{TEST_ROOM_DN_VALUE}"
|
107
112
|
TEST_ROOM_DN = "#{TEST_ROOM_RDN},#{TEST_ROOMS_DN}"
|
108
113
|
|
114
|
+
# Multivalue DN
|
115
|
+
TEST_HOST_MULTIVALUE_DN_ATTR1 = 'cn'
|
116
|
+
TEST_HOST_MULTIVALUE_DN_VALUE1 = 'honcho'
|
117
|
+
TEST_HOST_MULTIVALUE_DN_ATTR2 = 'l'
|
118
|
+
TEST_HOST_MULTIVALUE_DN_VALUE2 = 'sandiego'
|
119
|
+
TEST_HOST_MULTIVALUE_RDN = "%s=%s+%s=%s" % [
|
120
|
+
TEST_HOST_MULTIVALUE_DN_ATTR1,
|
121
|
+
TEST_HOST_MULTIVALUE_DN_VALUE1,
|
122
|
+
TEST_HOST_MULTIVALUE_DN_ATTR2,
|
123
|
+
TEST_HOST_MULTIVALUE_DN_VALUE2,
|
124
|
+
]
|
125
|
+
TEST_HOST_MULTIVALUE_DN = "#{TEST_HOST_MULTIVALUE_RDN},#{TEST_HOSTS_DN}"
|
126
|
+
|
109
127
|
constants.each do |cname|
|
110
128
|
const_get(cname).freeze
|
111
129
|
end
|