configtoolkit 1.1.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,3 +1,7 @@
1
+ === 1.2.0 / 2008-05-20
2
+ Add BaseConfig.load_group, which allows a hash of configs to be loaded with
3
+ one call. Corrected a bug in how array parameters are printed.
4
+
1
5
  === 1.1.0 / 2008-05-14
2
6
  Finish the README.txt; this is the first production release!
3
7
 
data/README.txt CHANGED
@@ -211,6 +211,46 @@ This will produce the following apple.yaml:
211
211
  num_cpus: 32
212
212
  behind_firewall: false
213
213
 
214
+ To load a group of named configurations at once from this machines.yaml file:
215
+ db_cluster:
216
+ db1:
217
+ name: cherry
218
+ architecture: powerpc
219
+ os: Linux
220
+ num_cpus: 24
221
+ contains_sensitive_data: yes
222
+ addresses:
223
+ - http://cherrydb.designingpatterns.com
224
+ - http://db1.designingpatterns.com
225
+ db2:
226
+ name: strawberry
227
+ architecture: powerpc
228
+ os: Linux
229
+ num_cpus: 24
230
+ contains_sensitive_data: yes
231
+ addresses:
232
+ - http://strawberrydb.designingpatterns.com
233
+ - http://db2.designingpatterns.com
234
+ db3:
235
+ name: blueberry
236
+ architecture: powerpc
237
+ os: Linux
238
+ num_cpus: 24
239
+ contains_sensitive_data: yes
240
+ addresses:
241
+ - http://blueberrydb.designingpatterns.com
242
+ - http://db3.designingpatterns.com
243
+
244
+ do:
245
+ require 'rubygems'
246
+ require 'configtoolkit'
247
+ require 'configtoolkit/yamlreader'
248
+
249
+ configs = MachineConfig.load_group(ConfigToolkit::YAMLReader.new("machines.yaml"), "db_cluster")
250
+ configs.each do |name, config|
251
+ print "The #{name} configuration:\n#{config}\n"
252
+ end
253
+
214
254
  == REQUIREMENTS:
215
255
 
216
256
  Hoe is required, but only for running the tests.
data/Rakefile CHANGED
@@ -7,7 +7,7 @@ require 'hoe'
7
7
 
8
8
  $stderr = STDERR
9
9
 
10
- Hoe.new('configtoolkit', "1.1.0") do |p|
10
+ Hoe.new('configtoolkit', "1.2.0") do |p|
11
11
  p.remote_rdoc_dir = ''
12
12
  p.developer('DesigningPatterns', 'technical.inquiries@designingpatterns.com')
13
13
  end
@@ -287,8 +287,7 @@ class BaseConfig
287
287
  # webserver, then the containing_object_name would be
288
288
  # 'production.webserver'.
289
289
  #
290
- attr_accessor :containing_object_name
291
- protected :containing_object_name= # This is NOT part of the public interface
290
+ attr_reader :containing_object_name
292
291
 
293
292
  #
294
293
  # ====Description:
@@ -651,8 +650,7 @@ class BaseConfig
651
650
  nested_containing_object_name = "#{@containing_object_name}.#{param_name}"
652
651
  end
653
652
 
654
- child_config.containing_object_name = nested_containing_object_name
655
- child_config.load_impl(raw_value)
653
+ child_config.load_impl(raw_value, nested_containing_object_name)
656
654
  return child_config
657
655
  elsif((value_class < ConstrainedArray) && (raw_value.class == Array))
658
656
  #
@@ -735,8 +733,11 @@ class BaseConfig
735
733
  # ====Parameters:
736
734
  # [containing_object_hash]
737
735
  # The Hash containing the configuration's parameters
736
+ # [containing_object_name]
737
+ # The containing object name
738
738
  #
739
- def load_impl(containing_object_hash)
739
+ def load_impl(containing_object_hash, containing_object_name)
740
+ @containing_object_name = containing_object_name
740
741
  @params_with_values.clear()
741
742
 
742
743
  if(containing_object_hash != nil)
@@ -766,20 +767,21 @@ class BaseConfig
766
767
 
767
768
  #
768
769
  # ====Description:
769
- # This method loads self from reader (with containing object
770
- # containing_object_name).
770
+ # This method loads the configuration hash from reader for containing object
771
+ # containing_object_name. This method is not really part of BaseConfig's
772
+ # public API but, unfortunately, cannot be made private (since it is
773
+ # called from an instance method; there are no "protected" class methods,
774
+ # unfortunately).
771
775
  #
772
776
  # ====Parameters:
773
- # [reader]
774
- # The reader from which to load the parameter values. This method
775
- # will call the read method of reader, which will return
776
- # a Hash containing, in the most nested containing object entry,
777
- # the configuration parameter values for self.
778
- # [containing_object_name = ""]
779
- # The containing object name
777
+ # See the parameter list for load.
780
778
  #
781
- def load(reader, containing_object_name = "")
782
- param_hash = reader.read
779
+ # ====Returns:
780
+ # The configuration hash for containing_object_name (or nil, if none
781
+ # is found)
782
+ #
783
+ def self.load_containing_object_hash(reader, containing_object_name)
784
+ param_hash = reader.read()
783
785
 
784
786
  if(param_hash.class != Hash)
785
787
  message = "#{reader.class}#read returned #{param_hash.class} "
@@ -801,15 +803,14 @@ class BaseConfig
801
803
  # is empty). On the other hand, if an object is found but it is not
802
804
  # really an object (a Hash), then raise an exception.
803
805
  #
804
- @containing_object_name = containing_object_name
805
806
  containing_object_hash = param_hash
806
- @containing_object_name.split(".").each do |object_name|
807
+ containing_object_name.split(".").each do |object_name|
807
808
  containing_object_hash = containing_object_hash.fetch(object_name, nil)
808
809
 
809
810
  if(containing_object_hash == nil)
810
811
  break
811
812
  elsif(containing_object_hash.class != Hash)
812
- message = "error: #{self.class}#load found "
813
+ message = "error: #{self}.load_containing_object_hash found "
813
814
  message << "#{containing_object_hash.class} "
814
815
  message << "rather than Hash when reading the "
815
816
  message << "#{object_name} containing object"
@@ -817,7 +818,32 @@ class BaseConfig
817
818
  end
818
819
  end
819
820
 
820
- load_impl(containing_object_hash)
821
+ return containing_object_hash
822
+ end
823
+ private_class_method :load_containing_object_hash
824
+
825
+ #
826
+ # ====Description:
827
+ # This method loads self from reader (with containing object
828
+ # containing_object_name).
829
+ #
830
+ # ====Parameters:
831
+ # [reader]
832
+ # The reader from which to load the parameter values. This method
833
+ # will call the read method of reader, which will return
834
+ # a Hash containing, in the most nested containing object entry,
835
+ # the configuration parameter values for self.
836
+ # [containing_object_name = ""]
837
+ # The containing object name
838
+ #
839
+ def load(reader, containing_object_name = "")
840
+ #
841
+ # Not being able to call private class methods from one of the
842
+ # class' instance methods is a language bug.
843
+ #
844
+ containing_object_hash = self.class.send(:load_containing_object_hash, reader, containing_object_name)
845
+
846
+ load_impl(containing_object_hash, containing_object_name)
821
847
  end
822
848
 
823
849
  #
@@ -837,6 +863,52 @@ class BaseConfig
837
863
  instance.load(reader, containing_object_name)
838
864
  return instance
839
865
  end
866
+
867
+ #
868
+ # ====Description:
869
+ # This class method loads arbitrarily many configs from
870
+ # reader. This should be used when containing_object_name's
871
+ # elements all are hashes, each of which is a different instance of
872
+ # this class' configuration. This call returns a Hash containing
873
+ # elements mapping configuration name to configuration
874
+ # instance.
875
+ #
876
+ # ====Parameters:
877
+ # See the parameter list for load.
878
+ #
879
+ # ====Returns:
880
+ # A Hash of names (Strings) mapping to loaded configuration instances
881
+ #
882
+ def self.load_group(reader, containing_object_name = "")
883
+ containing_object_hash = load_containing_object_hash(reader, containing_object_name)
884
+
885
+ config_group = {}
886
+ if(containing_object_hash != nil)
887
+ containing_object_hash.each do |key, value|
888
+ value_containing_object_name = "#{containing_object_name}.#{key}"
889
+
890
+ if(!value.is_a?(Hash))
891
+ message = "error: #{self}.load_group found "
892
+ message << "#{value.class} "
893
+ message << "rather than Hash when reading the "
894
+ message << "#{value_containing_object_name} containing object"
895
+ raise Error, message
896
+ end
897
+
898
+ instance = new()
899
+
900
+ #
901
+ # Have to use send in order to call protected method.
902
+ # I think that not being able to call protected instance methods
903
+ # from class methods is a bug.
904
+ #
905
+ instance.send(:load_impl, value, value_containing_object_name)
906
+ config_group[key] = instance
907
+ end
908
+ end
909
+
910
+ return config_group
911
+ end
840
912
 
841
913
  #
842
914
  # ====Description:
@@ -862,7 +934,7 @@ class BaseConfig
862
934
  str << "{\n"
863
935
  value.write_impl(str, nesting_indent)
864
936
  str << indent << "}"
865
- elsif(value.class < Array)
937
+ elsif(value.is_a?(Array))
866
938
  #
867
939
  # Writing an array requires calling write_value_impl for each element,
868
940
  # just at one further indentation level.
@@ -8,6 +8,17 @@ addresses:
8
8
  - http://default.designingpatterns.com
9
9
  - http://apple.designingpatterns.com
10
10
 
11
+ qa:
12
+ name: pear
13
+ architecture: x86_64
14
+ os: FreeBSD
15
+ num_cpus: 8
16
+ behind_firewall: true
17
+ contains_sensitive_data: true
18
+ addresses:
19
+ - http://release.designingpatterns.com
20
+ - http://pear.designingpatterns.com
21
+
11
22
  production:
12
23
  name: orange
13
24
  architecture: ultrasparc
@@ -28,6 +39,69 @@ test:
28
39
  - http://test.designingpatterns.com
29
40
  - http://bananna.designingpatterns.com
30
41
 
42
+ #
43
+ # db1, db2, and db3 are duplicated at the top-level and
44
+ # within db_cluster, in order to properly test load_group.
45
+ #
46
+ db1:
47
+ name: cherry
48
+ architecture: powerpc
49
+ os: Linux
50
+ num_cpus: 24
51
+ contains_sensitive_data: yes
52
+ addresses:
53
+ - http://cherrydb.designingpatterns.com
54
+ - http://db1.designingpatterns.com
55
+
56
+ db2:
57
+ name: strawberry
58
+ architecture: powerpc
59
+ os: Linux
60
+ num_cpus: 24
61
+ contains_sensitive_data: yes
62
+ addresses:
63
+ - http://strawberrydb.designingpatterns.com
64
+ - http://db2.designingpatterns.com
65
+
66
+ db3:
67
+ name: blueberry
68
+ architecture: powerpc
69
+ os: Linux
70
+ num_cpus: 24
71
+ contains_sensitive_data: yes
72
+ addresses:
73
+ - http://blueberrydb.designingpatterns.com
74
+ - http://db3.designingpatterns.com
75
+
76
+ db_cluster1:
77
+ db1:
78
+ name: cherry
79
+ architecture: powerpc
80
+ os: Linux
81
+ num_cpus: 24
82
+ contains_sensitive_data: yes
83
+ addresses:
84
+ - http://cherrydb.designingpatterns.com
85
+ - http://db1.designingpatterns.com
86
+ db2:
87
+ name: strawberry
88
+ architecture: powerpc
89
+ os: Linux
90
+ num_cpus: 24
91
+ contains_sensitive_data: yes
92
+ addresses:
93
+ - http://strawberrydb.designingpatterns.com
94
+ - http://db2.designingpatterns.com
95
+ db3:
96
+ name: blueberry
97
+ architecture: powerpc
98
+ os: Linux
99
+ num_cpus: 24
100
+ contains_sensitive_data: yes
101
+ addresses:
102
+ - http://blueberrydb.designingpatterns.com
103
+ - http://db3.designingpatterns.com
104
+
31
105
  bad_containing_object: 3
32
106
 
33
107
  missing_num_cpus:
@@ -98,3 +172,9 @@ bad_contains_sensitive_data_behind_firewall_combo:
98
172
  addresses:
99
173
  - http://test.designingpatterns.com
100
174
  - http://bananna.designingpatterns.com
175
+
176
+ bad_group:
177
+ bad_element:
178
+ - 3
179
+ - 4
180
+ - 5
@@ -107,13 +107,13 @@ class BaseConfigTest < Test::Unit::TestCase
107
107
  assert_equal(param_value, config.send(param_name))
108
108
  end
109
109
 
110
- puts config
111
- end
110
+ #
111
+ # Ensure that the programatically set config is equal to one
112
+ # sourcing the same information via load.
113
+ #
114
+ assert_equal(MachineConfig.load(ConfigToolkit::YAMLReader.new(MACHINES_CONFIG_FILE_NAME), "qa"), config)
112
115
 
113
- def verify_param_values(config, expected_config_param_values)
114
- expected_config_param_values.each do |param_name, param_value|
115
- assert_equal(param_value, config.send(param_name))
116
- end
116
+ puts config
117
117
  end
118
118
 
119
119
  def verify_correct_dump(config_file_name, containing_object_name)
@@ -182,13 +182,25 @@ class BaseConfigTest < Test::Unit::TestCase
182
182
 
183
183
  def verify_correct_load(config_file_name,
184
184
  containing_object_name,
185
- expected_param_values)
185
+ expected_config)
186
186
  reader = ConfigToolkit::YAMLReader.new(config_file_name)
187
187
  config = MachineConfig.load(reader, containing_object_name)
188
- verify_param_values(config, expected_param_values)
188
+ assert_equal(expected_config, config)
189
189
  puts config
190
190
  end
191
191
 
192
+ def verify_correct_load_group(config_file_name,
193
+ containing_object_name,
194
+ expected_configs)
195
+ reader = ConfigToolkit::YAMLReader.new(config_file_name)
196
+ configs = MachineConfig.load_group(reader, containing_object_name)
197
+ assert_equal(expected_configs, configs)
198
+
199
+ configs.each do |name, config|
200
+ assert_equal("#{containing_object_name}.#{name}", config.containing_object_name)
201
+ end
202
+ end
203
+
192
204
  def verify_load_error(config_file_name, containing_object_name, message)
193
205
  reader = ConfigToolkit::YAMLReader.new(config_file_name)
194
206
 
@@ -203,52 +215,68 @@ class BaseConfigTest < Test::Unit::TestCase
203
215
  assert(rescued_exception)
204
216
  end
205
217
 
218
+ def verify_load_group_error(config_file_name, containing_object_name, message)
219
+ reader = ConfigToolkit::YAMLReader.new(config_file_name)
220
+
221
+ rescued_exception = false
222
+ begin
223
+ config = MachineConfig.load_group(reader, containing_object_name)
224
+ rescue ConfigToolkit::Error => e
225
+ assert_equal(message, e.message)
226
+ rescued_exception = true
227
+ end
228
+
229
+ assert(rescued_exception)
230
+ end
231
+
206
232
  #
207
233
  # Test whether a canned config file can be load correctly (whether
208
234
  # the config instances ends up having the expected values).
209
235
  #
210
236
  def test_load
211
- verify_correct_load(MACHINES_CONFIG_FILE_NAME, "", {
212
- :name => "apple",
213
- :architecture => "powerpc",
214
- :os => "AIX",
215
- :num_cpus => 32,
216
- :behind_firewall => false,
217
- :contains_sensitive_data => false,
218
- :addresses =>
219
- [
220
- URI("http://default.designingpatterns.com"),
221
- URI("http://apple.designingpatterns.com")
222
- ]
223
- })
224
-
225
- verify_correct_load(MACHINES_CONFIG_FILE_NAME, "production", {
226
- :name => "orange",
227
- :architecture => "ultrasparc",
228
- :os => "Solaris",
229
- :num_cpus => 64,
230
- :behind_firewall => true,
231
- :contains_sensitive_data => true,
232
- :addresses =>
233
- [
234
- URI("http://production.designingpatterns.com"),
235
- URI("http://orange.designingpatterns.com")
236
- ]
237
- })
238
-
239
- verify_correct_load(MACHINES_CONFIG_FILE_NAME, "test", {
240
- :name => "bananna",
241
- :architecture => "itanium",
242
- :os => "Linux",
243
- :num_cpus => 16,
244
- :behind_firewall => true,
245
- :contains_sensitive_data => false,
246
- :addresses =>
247
- [
248
- URI("http://test.designingpatterns.com"),
249
- URI("http://bananna.designingpatterns.com")
250
- ]
251
- })
237
+ config = MachineConfig.new()
238
+ config.name = "apple"
239
+ config.architecture = "powerpc"
240
+ config.os = "AIX"
241
+ config.num_cpus = 32
242
+ config.behind_firewall = false
243
+ config.contains_sensitive_data = false
244
+ config.addresses = [
245
+ URI("http://default.designingpatterns.com"),
246
+ URI("http://apple.designingpatterns.com")
247
+ ]
248
+ verify_correct_load(MACHINES_CONFIG_FILE_NAME, "", config)
249
+
250
+ config = MachineConfig.new()
251
+ config.name = "orange"
252
+ config.architecture = "ultrasparc"
253
+ config.os = "Solaris"
254
+ config.num_cpus = 64
255
+ config.behind_firewall = true
256
+ config.contains_sensitive_data = true
257
+ config.addresses = [
258
+ URI("http://production.designingpatterns.com"),
259
+ URI("http://orange.designingpatterns.com")
260
+ ]
261
+ verify_correct_load(MACHINES_CONFIG_FILE_NAME, "production", config)
262
+
263
+ config = MachineConfig.new()
264
+ config.name = "bananna"
265
+ config.architecture = "itanium"
266
+ config.os = "Linux"
267
+ config.num_cpus = 16
268
+ config.behind_firewall = true
269
+ config.contains_sensitive_data = false
270
+ config.addresses = [
271
+ URI("http://test.designingpatterns.com"),
272
+ URI("http://bananna.designingpatterns.com")
273
+ ]
274
+ verify_correct_load(MACHINES_CONFIG_FILE_NAME, "test", config)
275
+
276
+ verify_correct_load_group(MACHINES_CONFIG_FILE_NAME, "db_cluster1",
277
+ "db1" => MachineConfig.load(ConfigToolkit::YAMLReader.new(MACHINES_CONFIG_FILE_NAME), "db1"),
278
+ "db2" => MachineConfig.load(ConfigToolkit::YAMLReader.new(MACHINES_CONFIG_FILE_NAME), "db2"),
279
+ "db3" => MachineConfig.load(ConfigToolkit::YAMLReader.new(MACHINES_CONFIG_FILE_NAME), "db3"))
252
280
  end
253
281
 
254
282
  def test_load_errors
@@ -258,7 +286,7 @@ class BaseConfigTest < Test::Unit::TestCase
258
286
 
259
287
  verify_load_error(MACHINES_CONFIG_FILE_NAME,
260
288
  "bad_containing_object",
261
- "error: BaseConfigTest::MachineConfig#load found Fixnum rather than Hash when reading the bad_containing_object containing object")
289
+ "error: BaseConfigTest::MachineConfig.load_containing_object_hash found Fixnum rather than Hash when reading the bad_containing_object containing object")
262
290
 
263
291
  verify_load_error(MACHINES_CONFIG_FILE_NAME,
264
292
  "missing_num_cpus",
@@ -288,5 +316,8 @@ class BaseConfigTest < Test::Unit::TestCase
288
316
  "bad_contains_sensitive_data_behind_firewall_combo",
289
317
  "BaseConfigTest::MachineConfig#validate_all_values load error for bad_contains_sensitive_data_behind_firewall_combo: a machine cannot contain sensitive data and not be behind the firewall")
290
318
 
319
+ verify_load_group_error(MACHINES_CONFIG_FILE_NAME,
320
+ "bad_group",
321
+ "error: BaseConfigTest::MachineConfig.load_group found Array rather than Hash when reading the bad_group.bad_element containing object")
291
322
  end
292
323
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: configtoolkit
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - DesigningPatterns
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2008-05-15 00:00:00 -04:00
12
+ date: 2008-05-21 00:00:00 -04:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency