chef-resource 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
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