chef-resource 0.2.2

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.
Files changed (55) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +23 -0
  3. data/LICENSE +201 -0
  4. data/README.md +264 -0
  5. data/Rakefile +8 -0
  6. data/files/lib/chef_resource.rb +24 -0
  7. data/files/lib/chef_resource/camel_case.rb +23 -0
  8. data/files/lib/chef_resource/chef.rb +102 -0
  9. data/files/lib/chef_resource/chef_dsl/chef_cookbook_compiler.rb +44 -0
  10. data/files/lib/chef_resource/chef_dsl/chef_recipe.rb +10 -0
  11. data/files/lib/chef_resource/chef_dsl/chef_recipe_dsl_extensions.rb +84 -0
  12. data/files/lib/chef_resource/chef_dsl/chef_resource_base.rb +12 -0
  13. data/files/lib/chef_resource/chef_dsl/chef_resource_class_extensions.rb +30 -0
  14. data/files/lib/chef_resource/chef_dsl/chef_resource_extensions.rb +224 -0
  15. data/files/lib/chef_resource/chef_dsl/chef_resource_log.rb +54 -0
  16. data/files/lib/chef_resource/chef_dsl/resource_container_module.rb +80 -0
  17. data/files/lib/chef_resource/chef_dsl/resource_definition_dsl.rb +128 -0
  18. data/files/lib/chef_resource/constants.rb +8 -0
  19. data/files/lib/chef_resource/errors.rb +31 -0
  20. data/files/lib/chef_resource/lazy_proc.rb +82 -0
  21. data/files/lib/chef_resource/output/nested_converge.rb +91 -0
  22. data/files/lib/chef_resource/output/nested_converge/open_resource.rb +113 -0
  23. data/files/lib/chef_resource/output/region_stream.rb +145 -0
  24. data/files/lib/chef_resource/output/simple_output.rb +83 -0
  25. data/files/lib/chef_resource/resource.rb +428 -0
  26. data/files/lib/chef_resource/resource/resource_log.rb +197 -0
  27. data/files/lib/chef_resource/resource/resource_type.rb +74 -0
  28. data/files/lib/chef_resource/resource/struct_property.rb +39 -0
  29. data/files/lib/chef_resource/resource/struct_property_type.rb +185 -0
  30. data/files/lib/chef_resource/resource/struct_resource.rb +410 -0
  31. data/files/lib/chef_resource/resource/struct_resource_base.rb +11 -0
  32. data/files/lib/chef_resource/resource/struct_resource_type.rb +275 -0
  33. data/files/lib/chef_resource/simple_struct.rb +121 -0
  34. data/files/lib/chef_resource/type.rb +371 -0
  35. data/files/lib/chef_resource/types.rb +4 -0
  36. data/files/lib/chef_resource/types/boolean.rb +16 -0
  37. data/files/lib/chef_resource/types/byte_size.rb +10 -0
  38. data/files/lib/chef_resource/types/date_time_type.rb +18 -0
  39. data/files/lib/chef_resource/types/date_type.rb +18 -0
  40. data/files/lib/chef_resource/types/float_type.rb +28 -0
  41. data/files/lib/chef_resource/types/integer_type.rb +53 -0
  42. data/files/lib/chef_resource/types/interval.rb +21 -0
  43. data/files/lib/chef_resource/types/path.rb +39 -0
  44. data/files/lib/chef_resource/types/pathname_type.rb +34 -0
  45. data/files/lib/chef_resource/types/string_type.rb +16 -0
  46. data/files/lib/chef_resource/types/symbol_type.rb +18 -0
  47. data/files/lib/chef_resource/types/uri_type.rb +37 -0
  48. data/files/lib/chef_resource/version.rb +3 -0
  49. data/spec/integration/chef.rb +81 -0
  50. data/spec/integration/struct_spec.rb +611 -0
  51. data/spec/integration/struct_state_spec.rb +538 -0
  52. data/spec/integration/type_spec.rb +1123 -0
  53. data/spec/integration/validation_spec.rb +207 -0
  54. data/spec/support/spec_support.rb +7 -0
  55. metadata +167 -0
@@ -0,0 +1,121 @@
1
+ module ChefResource
2
+ #
3
+ # Lets you create simple properties of a similar form to ChefResource properties
4
+ # (with the right getters and setters), without the type system. Largely used
5
+ # to create the structs in the type system itself.
6
+ #
7
+ module SimpleStruct
8
+ #
9
+ # Create a property with getter (struct.name), setter (struct.name value)
10
+ # and equal setter (struct.name = value).
11
+ #
12
+ # Supports lazy values and asks for the superclass's value if the value has
13
+ # not been set locally. Also flips on instance_eval automatically for lazy
14
+ # procs.
15
+ #
16
+ def property(name, default: "nil", coerced: "value", coerced_set: coerced, inherited: "superclass.#{name}")
17
+ module_eval <<-EOM, __FILE__, __LINE__+1
18
+ module SimpleStructProperties
19
+ def #{name}=(value)
20
+ #{name} value
21
+ end
22
+ def #{name}(value=NOT_PASSED, parent: self, &block)
23
+ if block
24
+ @#{name} = ChefResource::LazyProc.new(:should_instance_eval, &block)
25
+ elsif value == NOT_PASSED
26
+ if defined?(@#{name})
27
+ if @#{name}.is_a?(LazyProc)
28
+ value = @#{name}.get(instance: parent, instance_eval_by_default: true)
29
+ else
30
+ value = @#{name}
31
+ end
32
+ value = #{coerced}
33
+ elsif defined?(super)
34
+ return super
35
+ # Go to extraordinary lengths to get the superclass's value (inheritance)
36
+ elsif respond_to?(:superclass) && superclass.respond_to?(#{name.inspect})
37
+ #{inherited}
38
+ else
39
+ value = #{default}
40
+ end
41
+ elsif value.is_a?(LazyProc)
42
+ @#{name} = value
43
+ else
44
+ @#{name} = #{coerced_set}
45
+ end
46
+ end
47
+ end
48
+ include SimpleStructProperties
49
+ EOM
50
+ end
51
+
52
+ #
53
+ # Create a block property.
54
+ #
55
+ # Call <attr_name>.get(instance: instance, args: [...]) if <attr_name>
56
+ # to invoke the block.
57
+ #
58
+ # Has several setter forms:
59
+ #
60
+ # @example Block form
61
+ # attr do
62
+ # # do stuff here
63
+ # end
64
+ # # This form sets attr to a LazyProc with :should_instance_eval enabled.
65
+ # @example Proc setter: sets attr to a proc that will NOT be instance_eval'd
66
+ # attr proc { do stuff here }
67
+ # @example LazyProc setter: sets attr to a proc that will be instance_eval'd unless instance_eval is already set on the LazyProc (in which case it is obeyed).
68
+ # @example attr= <proc or lazy proc> - does the same thing as the others
69
+ # struct.attr = proc { do stuff here }
70
+ # struct.attr = lazy { do stuff here }
71
+ #
72
+ def block_property(name, coerced: "value")
73
+ module_eval <<-EOM, __FILE__, __LINE__+1
74
+ module SimpleStructProperties
75
+ def #{name}=(value)
76
+ #{name} value
77
+ end
78
+ def #{name}(value=NOT_PASSED, &block)
79
+ if block
80
+ value = LazyProc.new(:should_instance_eval, &block)
81
+ @#{name} = #{coerced}
82
+ elsif value == NOT_PASSED
83
+ if defined?(@#{name})
84
+ @#{name}
85
+ elsif respond_to?(:superclass) && superclass.respond_to?(#{name.inspect})
86
+ superclass.#{name}
87
+ else
88
+ nil
89
+ end
90
+ elsif value.is_a?(LazyProc)
91
+ @#{name} = #{coerced}
92
+ elsif value.is_a?(Proc)
93
+ value = LazyProc.new(:should_instance_eval, &value)
94
+ @#{name} = #{coerced}
95
+ else
96
+ @#{name} = #{coerced}
97
+ end
98
+ end
99
+ end
100
+ include SimpleStructProperties
101
+ EOM
102
+ end
103
+
104
+ #
105
+ # Create a boolean property with getter, setter and getter?.
106
+ #
107
+ def boolean_property(name, default: "nil", coerced: "value")
108
+ property(name, default: default, coerced: coerced)
109
+ module_eval <<-EOM, __FILE__, __LINE__+1
110
+ module SimpleStructProperties
111
+ def #{name}?
112
+ #{name}
113
+ end
114
+ end
115
+ include SimpleStructProperties
116
+ EOM
117
+ end
118
+ end
119
+ end
120
+
121
+ require 'chef_resource/lazy_proc'
@@ -0,0 +1,371 @@
1
+ require 'chef_resource/errors'
2
+ require 'chef_resource/simple_struct'
3
+ require 'chef_resource/lazy_proc'
4
+
5
+ module ChefResource
6
+ module Type
7
+ extend SimpleStruct
8
+
9
+ #
10
+ # Take the input value and coerce it to a desired value (which means
11
+ # different things in different contexts). For a Resource Reference,
12
+ # this will return a non-open Resource. For a primitive, it casts the
13
+ # value to the right type.
14
+ #
15
+ # @param parent The parent context which is asking for the value
16
+ # (for a struct, the parent is the struct)
17
+ #
18
+ # @return A value which:
19
+ # - Implements the desired type
20
+ # - May or may not be open (depends on whether it's a reference or not)
21
+ # - May have any and all values set (not just identity values, unlike get)
22
+ #
23
+ def coerce(parent, value)
24
+ validate(parent, value)
25
+ value
26
+ end
27
+
28
+ #
29
+ # Take the raw (stored) value and coerce it to a value (used when the user
30
+ # asks for a property or other value). The default implementation simply
31
+ # delazifies the value (and coerces/validates it if it is lazy).
32
+ #
33
+ # @return A value which:
34
+ # - Implements the desired type
35
+ # - May or may not be open (depends on whether it's a reference or not)
36
+ # - May have any and all values set (not just identity values, unlike get)
37
+ #
38
+ def coerce_to_user(parent, value)
39
+ if value.is_a?(ChefResource::LazyProc)
40
+ coerce(parent, value.get(instance: parent))
41
+ else
42
+ value
43
+ end
44
+ end
45
+
46
+ #
47
+ # Returns whether the given value could have been returned by a call to
48
+ # `coerce`, `open` or `get`.
49
+ #
50
+ def is_valid?(parent, value)
51
+ begin
52
+ validate(parent, value)
53
+ true
54
+ rescue ValidationError
55
+ false
56
+ end
57
+ end
58
+
59
+ #
60
+ # Validates a value against this type.
61
+ #
62
+ def validate(parent, value)
63
+ # Handle nullable=true/false
64
+ if value.nil?
65
+ if nullable?
66
+ # It's OK for the value to be nulalble, so return.
67
+ # (Unless nullable? == :validate, in which case we
68
+ # continue to run validations.)
69
+ return unless nullable? == :validate
70
+ else
71
+ # TODO error message sucks. Needs to include property name. Start
72
+ # passing parent and self in, and have common methods to print out what
73
+ # thing needs to not be null.
74
+ # If the value is null and isn't supposed to be, raise an error
75
+ raise MustNotBeNullError.new("must not be null", value)
76
+ end
77
+ end
78
+
79
+ # Check must_be_kind_of
80
+ if value && !must_be_kind_of.empty? && !must_be_kind_of.any? { |type| value.is_a?(type) }
81
+ raise ValidationError.new("must be a #{ChefResource.english_list(*must_be_kind_of, conjunction: 'or')}", value)
82
+ end
83
+
84
+ validators.each do |message, must_be_true|
85
+ if !must_be_true.get(instance: value, args: [parent])
86
+ raise ValidationError.new(message, value)
87
+ end
88
+ end
89
+ end
90
+
91
+ #
92
+ # Adds a validation rule to this class that must always be true.
93
+ #
94
+ # @param message The failure message (will be prefaced with "must")
95
+ # @param &must_be_true Validation block, called in the context of the
96
+ # value (self == value), with two parameters: (type, parent), where
97
+ # parent is the parent value (if any) and type is the type "must" was
98
+ # declared on.
99
+ #
100
+ # @example
101
+ # class MyStruct < StructResource
102
+ # property :x, Integer do
103
+ # must "be between 0 and 10" { self >= 0 && self <= 10 }
104
+ # end
105
+ # end
106
+ #
107
+ def must(description, must_be_true_block=nil, &must_be_true)
108
+ must_be_true_block ||= must_be_true
109
+ must_be_true_block = LazyProc.new(:should_instance_eval, &must_be_true_block) if !must_be_true_block.is_a?(LazyProc)
110
+ validators << [ "must #{description}", must_be_true_block ]
111
+ end
112
+
113
+ #
114
+ # Whether or not this type accepts nil values.
115
+ #
116
+ # @param nullable If passed, this sets the value.
117
+ # - If true, validation always succeeds on nil values (and validators are not run).
118
+ # - If false, validation fails on nil values.
119
+ # - If :validate, validation is run on nil values like normal.
120
+ #
121
+ # Defaults to false unless the value has a `nil` default, in which case it is `true`.
122
+ #
123
+ # TODO this is wrong: @default does not take into account superclasses
124
+ boolean_property :nullable, default: "defined?(@default) && @default.nil?"
125
+
126
+ #
127
+ # A set of validators that will be run. An array of pairs [ message, proc ],
128
+ # where validate will run each `proc`, and throw `message` if it returns nil
129
+ # or false.
130
+ #
131
+ def validators
132
+ @validators ||= begin
133
+ if is_a?(Class) && superclass && superclass.respond_to?(:validators)
134
+ superclass.validators.dup
135
+ else
136
+ []
137
+ end
138
+ end
139
+ end
140
+
141
+ #
142
+ # An array of classes or modules which values of this type must implement
143
+ # (`value.kind_of?(class_or_module)` must be true).
144
+ #
145
+ # @param classes_or_modules If passed, this will *set* the list of classes
146
+ # or modules (or a single class or module) which values of this type
147
+ # must implement.
148
+ #
149
+ property :must_be_kind_of, default: "[]"
150
+
151
+ def must_be_kind_of(*classes_or_modules)
152
+ case classes_or_modules.size
153
+ when 0
154
+ @must_be_kind_of ||= super.dup
155
+ when 1
156
+ super(classes_or_modules[0].is_a?(Array) ? classes_or_modules[0] : classes_or_modules)
157
+ else
158
+ super(classes_or_modules)
159
+ end
160
+ end
161
+
162
+ #
163
+ # The default value for things of this type.
164
+ #
165
+ # @param value The default value. If this is a LazyProc, the block will
166
+ # be run in the context of the struct (`struct.instance_eval`) unless
167
+ # the block is explicitly set to `should_instance_eval: false`. If `nil`, the
168
+ # type is assumed to be nullable.
169
+ #
170
+ property :default, coerced: "coerce(parent, value)", coerced_set: "value.nil? ? value : coerce(parent, value)"
171
+
172
+ #
173
+ # Turn the value into a string in just the context of this Type.
174
+ #
175
+ def value_to_s(value)
176
+ value.to_s
177
+ end
178
+
179
+ #
180
+ # Create a type class for the given type.
181
+ #
182
+ # #get_type will be called to get the parent type information before the
183
+ # Type is created.
184
+ #
185
+ # @param name The snake case name for the type.
186
+ # @param type [Class,Type,Symbol] If a Class, sets `instance_class`.
187
+ # If a Type, sets `type_class`.
188
+ # If a `Symbol`, looks up the constant with the given name (translated from
189
+ # snake_case to CamelCase), and sets either `instance_class` or
190
+ # `type_class` if the symbol exists.
191
+ # @param create_class [Boolean] If true, will create a Class instead of
192
+ # a Module.
193
+ # @param instance_class [Class] If passed, the resulting Type will ensure that
194
+ # values are restricted to instances of the class. May also add some default
195
+ # coercion (for example, `Integer` subclasses call `to_i` and `Numeric`
196
+ # subclasses call `to_f`). Generally, `get_type`
197
+ # @param type_class [Type] If passed, the
198
+ #
199
+ def type(name,
200
+ type=nil,
201
+ instance_class: NOT_PASSED,
202
+ type_class: NOT_PASSED,
203
+ create_class: nil,
204
+ superclass: nil,
205
+ **type_properties,
206
+ &override_block
207
+ )
208
+ # Get the actual Type class (user may have passed a Symbol or non-type Class)
209
+ type_class = get_type(type, instance_class: instance_class, type_class: type_class)
210
+ # Create the actual Type class (not filled in)
211
+ result = emit_type_class(name, type_class, superclass, create_class)
212
+ result.must_be_kind_of instance_class if instance_class != NOT_PASSED
213
+
214
+ # Set other properties
215
+ type_properties.each do |name, value|
216
+ if result.respond_to?(name)
217
+ result.public_send(name, value)
218
+ else
219
+ raise ArgumentError, "#{name} not supported by type #{type_class}!"
220
+ end
221
+ end
222
+
223
+ if override_block
224
+ result.class_eval(&override_block)
225
+ end
226
+ result
227
+ end
228
+
229
+ #
230
+ # Create the type class
231
+ #
232
+ # @param name The name of the class to create. Will be converted from
233
+ # `snake_case` to `CamelCase` before creation.
234
+ # @param type_class The type class of which
235
+ #
236
+ def emit_type_class(name, type_class, superclass, create_class)
237
+ #
238
+ # Emit the class
239
+ #
240
+ class_name = CamelCase.from_snake_case(name)
241
+
242
+ #
243
+ # Translate create_class: true/false to Class/Module (leave nil alone)
244
+ #
245
+ case create_class
246
+ when true
247
+ create_class = Class
248
+ when false
249
+ create_class = Module
250
+ end
251
+
252
+ #
253
+ # If the type_class is a Class, use it as the superclass of our new thing
254
+ #
255
+ if type_class && type_class.is_a?(Class)
256
+ if superclass
257
+ raise "Cannot declare a superclass for #{name}: #{type_class} is a class and will be used as the superclass!"
258
+ end
259
+ superclass = type_class
260
+ type_class = nil
261
+ end
262
+
263
+ #
264
+ # If we have a superclass, and `create_class` was not declared, create a class instead of a module
265
+ #
266
+ if superclass
267
+ if create_class.nil?
268
+ create_class = Class
269
+ elsif !(create_class <= Class)
270
+ raise "Cannot have superclass #{superclass} for #{name}: #{create_class} is not a class and cannot have a superclass!"
271
+ end
272
+ end
273
+
274
+ # Default to creating a Module
275
+ create_class ||= Module
276
+ type_class ||= Type
277
+
278
+ result = superclass ? create_class.new(superclass) : create_class.new
279
+ eval "self::#{class_name} = result", nil, __FILE__, __LINE__
280
+
281
+ result.extend type_class if !result.is_a?(type_class)
282
+ result
283
+ end
284
+
285
+ #
286
+ # Get the existing type class for the given type (or `nil` if there is none).
287
+ #
288
+ # @param type [Symbol]
289
+ # @param instance_class [Class]
290
+ # @return The existing type class for the given type. If none, returns `nil`.
291
+ #
292
+ # @example Symbols
293
+ # type(:boolean) #=> ChefResource::Types::Boolean
294
+ # type(:rational) #=> ChefResource::Types::NumericType
295
+ # type(:my_resource) #=> MyResource
296
+ # @example Primitive types
297
+ # type(Fixnum) #=> ChefResource::Types::IntegerType
298
+ # type(Rational) #=> ChefResource::Types::NumericType
299
+ # type(Hash) #=> nil
300
+ # @example Actual type class
301
+ # type(Blah) #=> Blah
302
+ #
303
+ def get_type(type=nil, instance_class: NOT_PASSED, type_class: NOT_PASSED, &override_block)
304
+ type = const_get(CamelCase.from_snake_case(type)) if type.is_a?(Symbol)
305
+
306
+ case type
307
+ when Type
308
+ type_class = type
309
+ when Module
310
+ instance_class = type
311
+ when nil
312
+ else
313
+ raise ArgumentError, "Cannot resolve type #{type}: class #{type.class} not recognized"
314
+ end
315
+
316
+ if instance_class != NOT_PASSED
317
+ type_class = get_type_for_class(instance_class)
318
+ elsif type_class == NOT_PASSED
319
+ type_class = nil
320
+ end
321
+
322
+ type_class
323
+ end
324
+
325
+ #
326
+ # Get the Type that should manage instances of the given class.
327
+ #
328
+ def get_type_for_class(instance_class)
329
+ if instance_class <= Integer
330
+ Types::IntegerType
331
+
332
+ elsif instance_class <= Float
333
+ Types::FloatType
334
+
335
+ elsif instance_class <= URI
336
+ Types::URIType
337
+
338
+ elsif instance_class <= Pathname
339
+ Types::PathnameType
340
+
341
+ elsif instance_class <= DateTime
342
+ Types::DateTimeType
343
+
344
+ elsif instance_class <= Date
345
+ Types::DateType
346
+
347
+ elsif instance_class <= Symbol
348
+ Types::SymbolType
349
+
350
+ elsif instance_class <= String
351
+ Types::StringType
352
+
353
+ else
354
+ nil
355
+ end
356
+ end
357
+ end
358
+
359
+ require 'chef_resource/types/boolean'
360
+ require 'chef_resource/types/interval'
361
+ require 'chef_resource/types/byte_size'
362
+ require 'chef_resource/types/path'
363
+
364
+ require 'chef_resource/types/integer_type'
365
+ require 'chef_resource/types/float_type'
366
+ require 'chef_resource/types/uri_type'
367
+ require 'chef_resource/types/date_time_type'
368
+ require 'chef_resource/types/date_type'
369
+ require 'chef_resource/types/symbol_type'
370
+ require 'chef_resource/types/string_type'
371
+ end