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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +23 -0
- data/LICENSE +201 -0
- data/README.md +264 -0
- data/Rakefile +8 -0
- data/files/lib/chef_resource.rb +24 -0
- data/files/lib/chef_resource/camel_case.rb +23 -0
- data/files/lib/chef_resource/chef.rb +102 -0
- data/files/lib/chef_resource/chef_dsl/chef_cookbook_compiler.rb +44 -0
- data/files/lib/chef_resource/chef_dsl/chef_recipe.rb +10 -0
- data/files/lib/chef_resource/chef_dsl/chef_recipe_dsl_extensions.rb +84 -0
- data/files/lib/chef_resource/chef_dsl/chef_resource_base.rb +12 -0
- data/files/lib/chef_resource/chef_dsl/chef_resource_class_extensions.rb +30 -0
- data/files/lib/chef_resource/chef_dsl/chef_resource_extensions.rb +224 -0
- data/files/lib/chef_resource/chef_dsl/chef_resource_log.rb +54 -0
- data/files/lib/chef_resource/chef_dsl/resource_container_module.rb +80 -0
- data/files/lib/chef_resource/chef_dsl/resource_definition_dsl.rb +128 -0
- data/files/lib/chef_resource/constants.rb +8 -0
- data/files/lib/chef_resource/errors.rb +31 -0
- data/files/lib/chef_resource/lazy_proc.rb +82 -0
- data/files/lib/chef_resource/output/nested_converge.rb +91 -0
- data/files/lib/chef_resource/output/nested_converge/open_resource.rb +113 -0
- data/files/lib/chef_resource/output/region_stream.rb +145 -0
- data/files/lib/chef_resource/output/simple_output.rb +83 -0
- data/files/lib/chef_resource/resource.rb +428 -0
- data/files/lib/chef_resource/resource/resource_log.rb +197 -0
- data/files/lib/chef_resource/resource/resource_type.rb +74 -0
- data/files/lib/chef_resource/resource/struct_property.rb +39 -0
- data/files/lib/chef_resource/resource/struct_property_type.rb +185 -0
- data/files/lib/chef_resource/resource/struct_resource.rb +410 -0
- data/files/lib/chef_resource/resource/struct_resource_base.rb +11 -0
- data/files/lib/chef_resource/resource/struct_resource_type.rb +275 -0
- data/files/lib/chef_resource/simple_struct.rb +121 -0
- data/files/lib/chef_resource/type.rb +371 -0
- data/files/lib/chef_resource/types.rb +4 -0
- data/files/lib/chef_resource/types/boolean.rb +16 -0
- data/files/lib/chef_resource/types/byte_size.rb +10 -0
- data/files/lib/chef_resource/types/date_time_type.rb +18 -0
- data/files/lib/chef_resource/types/date_type.rb +18 -0
- data/files/lib/chef_resource/types/float_type.rb +28 -0
- data/files/lib/chef_resource/types/integer_type.rb +53 -0
- data/files/lib/chef_resource/types/interval.rb +21 -0
- data/files/lib/chef_resource/types/path.rb +39 -0
- data/files/lib/chef_resource/types/pathname_type.rb +34 -0
- data/files/lib/chef_resource/types/string_type.rb +16 -0
- data/files/lib/chef_resource/types/symbol_type.rb +18 -0
- data/files/lib/chef_resource/types/uri_type.rb +37 -0
- data/files/lib/chef_resource/version.rb +3 -0
- data/spec/integration/chef.rb +81 -0
- data/spec/integration/struct_spec.rb +611 -0
- data/spec/integration/struct_state_spec.rb +538 -0
- data/spec/integration/type_spec.rb +1123 -0
- data/spec/integration/validation_spec.rb +207 -0
- data/spec/support/spec_support.rb +7 -0
- metadata +167 -0
@@ -0,0 +1,410 @@
|
|
1
|
+
require 'chef_resource/errors'
|
2
|
+
require 'chef_resource/resource'
|
3
|
+
|
4
|
+
module ChefResource
|
5
|
+
module Resource
|
6
|
+
#
|
7
|
+
# A Resource with property_types and named getter/setters.
|
8
|
+
#
|
9
|
+
# The corresponding Type is
|
10
|
+
#
|
11
|
+
# @example
|
12
|
+
# class Address
|
13
|
+
# include ChefResource::Resource::StructResource
|
14
|
+
# extend ChefResource::Resource::StructResourceType
|
15
|
+
# property :street
|
16
|
+
# property :city
|
17
|
+
# property :state
|
18
|
+
# end
|
19
|
+
# class Person
|
20
|
+
# include ChefResource::Resource::StructResource
|
21
|
+
# extend ChefResource::Resource::StructResourceType
|
22
|
+
# property :name
|
23
|
+
# property :home_address, Address
|
24
|
+
# end
|
25
|
+
#
|
26
|
+
# p = Person.open
|
27
|
+
# a = Address.open
|
28
|
+
# p.home_address = a # Sets p.updates[:home_address] = P::HomeAddress.open(p.address)
|
29
|
+
# p.home_address.city = 'Malarky' # p.address.updates[:city] = 'Malarky'
|
30
|
+
# p.update
|
31
|
+
# # first does p.home_address.update
|
32
|
+
# # -> sets p.home_address.current_resource.city -> a.city = 'Malarky'
|
33
|
+
# # sets p.current_resource.home_address = p.home_address.current_resource
|
34
|
+
#
|
35
|
+
module StructResource
|
36
|
+
include Resource
|
37
|
+
|
38
|
+
#
|
39
|
+
# Resource read/modify interface: reopen, identity, explicit_property_values
|
40
|
+
#
|
41
|
+
|
42
|
+
#
|
43
|
+
# Get a new copy of the Resource with only identity values set.
|
44
|
+
#
|
45
|
+
# Note: the Resource remains in :created state, not :identity_defined as
|
46
|
+
# one would get from `open`. Call resource_identity_defined if you want
|
47
|
+
# to be able to retrieve actual values.
|
48
|
+
#
|
49
|
+
# This method is used by ResourceType.get() and Resource.reload.
|
50
|
+
#
|
51
|
+
def reopen_resource
|
52
|
+
# Create a new Resource of our same type, with just identity values.
|
53
|
+
resource = self.class.new
|
54
|
+
explicit_property_values.each do |name,value|
|
55
|
+
resource.explicit_property_values[name] = value if self.class.property_types[name].identity?
|
56
|
+
end
|
57
|
+
resource
|
58
|
+
end
|
59
|
+
|
60
|
+
#
|
61
|
+
# Get the identity string of the resource.
|
62
|
+
#
|
63
|
+
def resource_identity_string
|
64
|
+
positionals = []
|
65
|
+
named = {}
|
66
|
+
self.class.property_types.each do |name, type|
|
67
|
+
next if !explicit_property_values.has_key?(name)
|
68
|
+
if type.identity?
|
69
|
+
value = public_send(name)
|
70
|
+
if type.required?
|
71
|
+
positionals << value
|
72
|
+
else
|
73
|
+
named[name] = value
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
if named.empty?
|
78
|
+
if positionals.empty?
|
79
|
+
return ""
|
80
|
+
elsif positionals.size == 1
|
81
|
+
return positionals[0].to_s
|
82
|
+
end
|
83
|
+
end
|
84
|
+
(positionals.map { |value| value.inspect } +
|
85
|
+
named.map { |name,value| "#{name}: #{value.inspect}" }).join(",")
|
86
|
+
end
|
87
|
+
|
88
|
+
#
|
89
|
+
# Tell whether a particular attribute is set.
|
90
|
+
#
|
91
|
+
# @param name [Symbol] The name of the attribute
|
92
|
+
# @return [Boolean] Whether the attribute is set
|
93
|
+
#
|
94
|
+
def is_set?(name)
|
95
|
+
explicit_property_values.has_key?(name)
|
96
|
+
end
|
97
|
+
|
98
|
+
#
|
99
|
+
# Define the identity of this struct, based on the given arguments and
|
100
|
+
# block. After this method, the identity is frozen.
|
101
|
+
#
|
102
|
+
# @param *args The arguments. Generally the user passed you these from
|
103
|
+
# some other function, and you are trusting the struct to do the right
|
104
|
+
# thing with them.
|
105
|
+
# @param &define_identity_block A block that should run after the arguments
|
106
|
+
# are parsed but before the resource identity is frozen.
|
107
|
+
#
|
108
|
+
def define_identity(*args, &define_identity_block)
|
109
|
+
#
|
110
|
+
# Process named arguments - open(..., a: 1, b: 2, c: 3, d: 4)
|
111
|
+
#
|
112
|
+
if args[-1].is_a?(Hash)
|
113
|
+
named_args = args.pop
|
114
|
+
named_args.each do |name, value|
|
115
|
+
type = self.class.property_types[name]
|
116
|
+
raise ArgumentError, "Property #{name} was passed to #{self.class}.define_identity, but does not exist on #{self.class}!" if !type
|
117
|
+
raise ArgumentError, "#{self.class}.open only takes identity properties, and #{name} is not an identity property on #{self.class}!" if !type.identity?
|
118
|
+
public_send(name, value)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
#
|
123
|
+
# Process positional arguments - open(1, 2, 3, ...)
|
124
|
+
#
|
125
|
+
required_identity_properties = self.class.property_types.values.
|
126
|
+
select { |attr| attr.identity? && attr.required? }.
|
127
|
+
map { |attr| attr.property_name }
|
128
|
+
|
129
|
+
if args.size > required_identity_properties.size
|
130
|
+
raise ArgumentError, "Too many arguments to #{self.class}.define_identity! (#{args.size} for #{required_identity_properties.size})!"
|
131
|
+
end
|
132
|
+
required_identity_properties.each_with_index do |name, index|
|
133
|
+
if args.size > index
|
134
|
+
# If the argument was passed positionally (open(a, b, c ...)) set it from that.
|
135
|
+
if named_args && named_args.has_key?(name)
|
136
|
+
raise ArgumentError, "Property #{name} specified twice in #{self}.define_identity! Both as argument ##{index} and as a named argument."
|
137
|
+
end
|
138
|
+
public_send(name, args[index])
|
139
|
+
else
|
140
|
+
# If the argument wasn't passed positionally, check whether it was passed in the hash. If not, error.
|
141
|
+
if !named_args || !named_args.has_key?(name)
|
142
|
+
raise ArgumentError, "Required property #{name} not passed to #{self}.define_identity!"
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
#
|
148
|
+
# Run the block
|
149
|
+
#
|
150
|
+
instance_eval(&define_identity_block) if define_identity_block
|
151
|
+
|
152
|
+
#
|
153
|
+
# Freeze the identity properties
|
154
|
+
#
|
155
|
+
resource_identity_defined
|
156
|
+
end
|
157
|
+
|
158
|
+
#
|
159
|
+
# Reset changes to this struct (or to a property).
|
160
|
+
#
|
161
|
+
# Reset without parameters never resets identity properties--only normal
|
162
|
+
# properties.
|
163
|
+
#
|
164
|
+
# @param name Reset the property named `name`. If not passed, resets
|
165
|
+
# all properties.
|
166
|
+
# @raise PropertyDefinedError if the named property being referenced is
|
167
|
+
# defined (i.e. we are in identity_defined or fully_defined state).
|
168
|
+
# @raise ResourceStateError if we are in fully_defined state.
|
169
|
+
#
|
170
|
+
def reset(name=nil)
|
171
|
+
if name
|
172
|
+
property_type = self.class.property_types[name]
|
173
|
+
if !property_type
|
174
|
+
raise ArgumentError, "#{self.class} does not have property #{name}, cannot reset!"
|
175
|
+
end
|
176
|
+
if property_type.identity?
|
177
|
+
if resource_state != :created
|
178
|
+
raise PropertyDefinedError.new("Identity property #{self.class}.#{name} cannot be reset after open() or get() has been called (after the identity has been fully defined). Current sate: #{resource_state}", self, property_type)
|
179
|
+
end
|
180
|
+
else
|
181
|
+
if ![:created, :identity_defined].include?(resource_state)
|
182
|
+
raise PropertyDefinedError.new("Property #{self.class}.#{name} cannot be reset after the resource is fully defined.", self, property_type)
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
explicit_property_values.delete(name)
|
187
|
+
else
|
188
|
+
# We only ever reset non-identity values
|
189
|
+
if ![:created, :identity_defined].include?(resource_state)
|
190
|
+
raise ResourceStateError.new("#{self.class} cannot be reset after it is fully defined", self)
|
191
|
+
end
|
192
|
+
explicit_property_values.keep_if { |name,value| self.class.property_types[name].identity? }
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
#
|
197
|
+
# A hash of the changes the user has made to keys
|
198
|
+
#
|
199
|
+
def explicit_property_values
|
200
|
+
@explicit_property_values ||= {}
|
201
|
+
end
|
202
|
+
|
203
|
+
#
|
204
|
+
# Take an action to update the real resource, as long as the given keys have
|
205
|
+
# actually changed from their real values. Their real values are obtained
|
206
|
+
# via `load` and `load_value`.
|
207
|
+
#
|
208
|
+
# @param *names [Symbol] A list of property names which must be different
|
209
|
+
# from their actual / default value in order to set them. If the last parameter is a String, it
|
210
|
+
# is treated as the description of the update.
|
211
|
+
# @yield [new_values] a Set containing the list of keys whose values have
|
212
|
+
# changed. This block is run in the context of the Resource. Its
|
213
|
+
# return value is ignored.
|
214
|
+
# @return the list of changes, or nil if there are no changes
|
215
|
+
#
|
216
|
+
def converge(*names, &update_block)
|
217
|
+
#
|
218
|
+
# Grab the user's description from the last parameter, if it was passed
|
219
|
+
#
|
220
|
+
if names[-1].is_a?(String)
|
221
|
+
*names, description = *names if names[-1].is_a?(String)
|
222
|
+
end
|
223
|
+
|
224
|
+
#
|
225
|
+
# Decide on the header, and fix up the list of names to include all names
|
226
|
+
# if the user didn't pass any names
|
227
|
+
#
|
228
|
+
if names.empty?
|
229
|
+
change_header = ""
|
230
|
+
names = self.class.property_types.keys if names.empty?
|
231
|
+
else
|
232
|
+
change_header = "#{ChefResource.english_list(*names)}"
|
233
|
+
end
|
234
|
+
|
235
|
+
#
|
236
|
+
# Figure out if anything changed
|
237
|
+
#
|
238
|
+
exists = resource_exists?
|
239
|
+
changed_names = names.inject({}) do |h, name|
|
240
|
+
if explicit_property_values.has_key?(name)
|
241
|
+
type = self.class.property_types[name]
|
242
|
+
|
243
|
+
desired_value = public_send(name)
|
244
|
+
if exists
|
245
|
+
current_value = type.current_property_value(self)
|
246
|
+
if desired_value != current_value
|
247
|
+
h[name] = [ type.value_to_s(desired_value), type.value_to_s(current_value) ]
|
248
|
+
end
|
249
|
+
else
|
250
|
+
h[name] = [ type.value_to_s(desired_value), nil ]
|
251
|
+
end
|
252
|
+
end
|
253
|
+
h
|
254
|
+
end
|
255
|
+
|
256
|
+
#
|
257
|
+
# Skip the action if nothing was changed
|
258
|
+
#
|
259
|
+
if exists
|
260
|
+
if changed_names.empty?
|
261
|
+
skip_action "skipping #{change_header}: no values changed"
|
262
|
+
return nil
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
#
|
267
|
+
# Figure out the printout for what's changing:
|
268
|
+
#
|
269
|
+
# update file[x.txt]
|
270
|
+
# set abc to blah
|
271
|
+
# set abcdef to 12
|
272
|
+
# set a to nil
|
273
|
+
#
|
274
|
+
description ||= exists ? "update #{change_header}" : "create #{change_header}"
|
275
|
+
name_width = changed_names.keys.map { |name| name.size }.max
|
276
|
+
description_lines = [ description ] +
|
277
|
+
changed_names.map do |name, (desired, current)|
|
278
|
+
" set #{name.to_s.ljust(name_width)} to #{desired}#{current ? " (was #{current})" : ""}"
|
279
|
+
end
|
280
|
+
|
281
|
+
#
|
282
|
+
# Actually take the action
|
283
|
+
#
|
284
|
+
take_action(description_lines, &update_block)
|
285
|
+
|
286
|
+
changed_names
|
287
|
+
end
|
288
|
+
|
289
|
+
#
|
290
|
+
# Hash-like interface: to_h, to_hash, as_json, to_json, ==, [], []=
|
291
|
+
#
|
292
|
+
|
293
|
+
#
|
294
|
+
# Returns this struct as a hash, including all properties and their defaults.
|
295
|
+
#
|
296
|
+
# @param only [Symbol] Which values to include. Default: `:only_known`. One of:
|
297
|
+
# - :only_known :: Values explicitly set by the user or current values.
|
298
|
+
# If the current value has not been loaded, this will NOT load it or
|
299
|
+
# show any of those values.
|
300
|
+
# - :only_changed :: Values which the user has set and which have
|
301
|
+
# actually changed from their current or default value.
|
302
|
+
# - :only_explicit :: Values explicitly set by the user.
|
303
|
+
# - :all :: All values, including default values.
|
304
|
+
#
|
305
|
+
def to_h(only=:only_known)
|
306
|
+
case only
|
307
|
+
when :only_changed
|
308
|
+
result = {}
|
309
|
+
explicit_property_values.each do |name, value|
|
310
|
+
current_property_value = self.class.property_types[name].current_property_value(self)
|
311
|
+
if value != current_property_value
|
312
|
+
result[name] = value
|
313
|
+
end
|
314
|
+
end
|
315
|
+
result
|
316
|
+
|
317
|
+
when :only_explicit
|
318
|
+
explicit_property_values.dup
|
319
|
+
|
320
|
+
when :all
|
321
|
+
result = {}
|
322
|
+
self.class.property_types.each_key do |name|
|
323
|
+
result[name] = public_send(name)
|
324
|
+
end
|
325
|
+
result
|
326
|
+
|
327
|
+
else
|
328
|
+
if current_resource_loaded?
|
329
|
+
current_resource.explicit_property_values.merge(explicit_property_values)
|
330
|
+
else
|
331
|
+
explicit_property_values.dup
|
332
|
+
end
|
333
|
+
|
334
|
+
end
|
335
|
+
end
|
336
|
+
|
337
|
+
#alias :to_hash :to_h
|
338
|
+
|
339
|
+
#
|
340
|
+
# as_json does most of the to_json heavy lifted. It exists here in case activesupport
|
341
|
+
# is loaded. activesupport will call as_json and skip over to_json. This ensure
|
342
|
+
# json is encoded as expected
|
343
|
+
#
|
344
|
+
# @param only_changed Returns only values which have actually changed from
|
345
|
+
# their current or default value.
|
346
|
+
# @param only_explicit Returns only values which have been explicitly set
|
347
|
+
# by the user.
|
348
|
+
#
|
349
|
+
def as_json(only_changed: false, only_explicit: false, **options)
|
350
|
+
to_h(only_changed: false, only_explicit: false)
|
351
|
+
end
|
352
|
+
|
353
|
+
#
|
354
|
+
# Serialize this object as a hash
|
355
|
+
#
|
356
|
+
# @param only_changed Returns only values which have actually changed from
|
357
|
+
# their current or default value.
|
358
|
+
# @param only_explicit Returns only values which have been explicitly set
|
359
|
+
# by the user.
|
360
|
+
#
|
361
|
+
def to_json(only_changed: false, only_explicit: false, **options)
|
362
|
+
results = as_json(only_changed: only_changed, only_explicit: only_explicit)
|
363
|
+
Chef::JSONCompat.to_json(results, **options)
|
364
|
+
end
|
365
|
+
|
366
|
+
#
|
367
|
+
# Returns true if these are the same type and their values are the same.
|
368
|
+
# Avoids comparing things that aren't modified in either struct.
|
369
|
+
#
|
370
|
+
def ==(other)
|
371
|
+
return false if !other.is_a?(self.class)
|
372
|
+
|
373
|
+
# Try to rule out differences via explicit_property_values first (this should
|
374
|
+
# handle any identity keys and prevent us from accidentally pulling on
|
375
|
+
# current_resource).
|
376
|
+
(explicit_property_values.keys & other.explicit_property_values.keys).each do |name|
|
377
|
+
return false if public_send(name) != other.public_send(name)
|
378
|
+
end
|
379
|
+
|
380
|
+
# If one struct has more desired (set) values than the other, compare
|
381
|
+
# the values to the current/default on the other.
|
382
|
+
(explicit_property_values.keys - other.explicit_property_values.keys).each do |name|
|
383
|
+
return false if public_send(name) != other.public_send(name)
|
384
|
+
end
|
385
|
+
(other.explicit_property_values.keys - explicit_property_values.keys).each do |attr|
|
386
|
+
return false if public_send(name) != other.public_send(name)
|
387
|
+
end
|
388
|
+
end
|
389
|
+
|
390
|
+
#
|
391
|
+
# Get the value of the given property from the struct
|
392
|
+
#
|
393
|
+
def [](name)
|
394
|
+
name = name.to_sym
|
395
|
+
if !property_types.has_key?(name)
|
396
|
+
raise ArgumentError, "#{name} is not a property of #{self.class}."
|
397
|
+
end
|
398
|
+
|
399
|
+
public_send(name)
|
400
|
+
end
|
401
|
+
|
402
|
+
#
|
403
|
+
# Set the value of the given property in the struct
|
404
|
+
#
|
405
|
+
def []=(name, value)
|
406
|
+
public_send(name.to_sym, value)
|
407
|
+
end
|
408
|
+
end
|
409
|
+
end
|
410
|
+
end
|
@@ -0,0 +1,275 @@
|
|
1
|
+
require 'chef_resource/errors'
|
2
|
+
require 'chef_resource/resource/resource_type'
|
3
|
+
require 'chef_resource/constants'
|
4
|
+
require 'chef_resource/resource/struct_property_type'
|
5
|
+
require 'chef_resource/camel_case'
|
6
|
+
require 'chef_resource/simple_struct'
|
7
|
+
|
8
|
+
module ChefResource
|
9
|
+
module Resource
|
10
|
+
#
|
11
|
+
# The Type for a StructResource.
|
12
|
+
#
|
13
|
+
module StructResourceType
|
14
|
+
include ResourceType
|
15
|
+
|
16
|
+
#
|
17
|
+
# Coerce the input into a struct of this type.
|
18
|
+
#
|
19
|
+
# Constructor form: required identity parameters first, and then non-required properties in a hash.
|
20
|
+
# - MyStruct.coerce(identity_attr, identity_attr2, ..., { attr1: value, attr2: value, ... }) -> open(identity1, identity2, ... { identity properties }), and set non-identity properties afterwards
|
21
|
+
#
|
22
|
+
# Hash form: a hash with names and values representing struct names and values.
|
23
|
+
# - MyStruct.coerce({ identity_attr: value, attr1: value, attr2: value, ... }) -> open({ identity properties }), and set non-identity properties afterwards
|
24
|
+
#
|
25
|
+
# nil:
|
26
|
+
# - MyStruct.coerce(nil) -> nil
|
27
|
+
#
|
28
|
+
# Resource of this type:
|
29
|
+
# - MyStruct.coerce(x = MyStruct.open) -> x
|
30
|
+
#
|
31
|
+
# Simple constructor form: identity parameters
|
32
|
+
# - MyStruct.coerce(identity_attr) -> open(identity_attr)
|
33
|
+
# - MyStruct.coerce(identity_attr, identity_attr2, ...) -> open(identity_attr, identity_attr2, ...)
|
34
|
+
# - MyStruct.coerce() -> open()
|
35
|
+
#
|
36
|
+
# Struct Form:
|
37
|
+
# - MyStruct.coerce(other_my_struct_instance)
|
38
|
+
#
|
39
|
+
def coerce(parent, *args)
|
40
|
+
if args[-1].is_a?(Hash)
|
41
|
+
#
|
42
|
+
# Constructor form: required identity parameters first, and then non-required properties in a hash.
|
43
|
+
# - MyStruct.coerce(identity_attr, identity_attr2, ..., { attr1: value, attr2: value, ... }) -> open(identity1, identity2, ... { identity properties }), and set non-identity properties afterwards
|
44
|
+
#
|
45
|
+
# Hash form: a hash with names and values representing struct names and values.
|
46
|
+
# - MyStruct.coerce({ identity_attr: value, attr1: value, attr2: value, ... }) -> open({ identity properties }), and set non-identity properties afterwards
|
47
|
+
#
|
48
|
+
|
49
|
+
# Split the identity properties from normal so we can call open() with
|
50
|
+
# just identity properties
|
51
|
+
explicit_property_values = args[-1]
|
52
|
+
identity_values = {}
|
53
|
+
explicit_property_values.each_key do |name|
|
54
|
+
type = property_types[name]
|
55
|
+
raise ValidationError, "#{self.class}.coerce was passed property #{name}, but #{name} is not a property on #{self.class}." if !type
|
56
|
+
identity_values[name] = explicit_property_values.delete(name) if type.identity?
|
57
|
+
end
|
58
|
+
|
59
|
+
# open the resource
|
60
|
+
resource = open(*args[0..-2], identity_values)
|
61
|
+
|
62
|
+
# Set the non-identity properties before returning
|
63
|
+
explicit_property_values.each do |name, value|
|
64
|
+
resource.public_send(name, value)
|
65
|
+
end
|
66
|
+
|
67
|
+
resource.resource_fully_defined
|
68
|
+
|
69
|
+
super(parent, resource)
|
70
|
+
|
71
|
+
elsif args.size == 1 && is_valid?(parent, args[0])
|
72
|
+
# nil:
|
73
|
+
# - MyStruct.coerce(nil) -> nil
|
74
|
+
#
|
75
|
+
# Resource of this type:
|
76
|
+
# - MyStruct.coerce(x = MyStruct.open) -> x
|
77
|
+
super(parent, args[0])
|
78
|
+
|
79
|
+
else
|
80
|
+
# Simple constructor form: identity parameters
|
81
|
+
# - MyStruct.coerce(identity_attr) -> open(identity_attr)
|
82
|
+
# - MyStruct.coerce(identity_attr, identity_attr2, ...) -> open(identity_attr, identity_attr2, ...)
|
83
|
+
# - MyStruct.coerce() -> open()
|
84
|
+
super(parent, open(*args))
|
85
|
+
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
#
|
90
|
+
# Struct.open() takes the identity properties of the struct and opens it up.
|
91
|
+
# Supports these forms:
|
92
|
+
#
|
93
|
+
# - open(identity1, identity2[, { identity3: value, identity4: value } ])
|
94
|
+
# - open({ identity1: value, identity2: value, identity3: value, identity4: value })
|
95
|
+
# - open() (if no identity properties)
|
96
|
+
#
|
97
|
+
#
|
98
|
+
# @example
|
99
|
+
# class MyStruct
|
100
|
+
# include ChefResource::Resource::StructResource
|
101
|
+
# extend ChefResource::Resource::StructResourceType
|
102
|
+
# property :x, identity: true
|
103
|
+
# property :y, identity: true
|
104
|
+
# end
|
105
|
+
#
|
106
|
+
# # Allows these statements to work:
|
107
|
+
# s = MyStruct.open(1, 2)
|
108
|
+
# puts s.x # 1
|
109
|
+
# puts s.y # 2
|
110
|
+
# s = MyStruct.open(x: 3, y: 4)
|
111
|
+
# puts s.x # 3
|
112
|
+
# puts s.y # 4
|
113
|
+
#
|
114
|
+
def open(*args, &define_identity_block)
|
115
|
+
resource = new
|
116
|
+
resource.define_identity(*args, &define_identity_block)
|
117
|
+
resource
|
118
|
+
end
|
119
|
+
|
120
|
+
#
|
121
|
+
# Struct definition: MyStruct.property
|
122
|
+
#
|
123
|
+
|
124
|
+
#
|
125
|
+
# Create a property on this struct.
|
126
|
+
#
|
127
|
+
# Makes three method calls available to the struct:
|
128
|
+
# - `struct.name` - Get the value of `name`.
|
129
|
+
# - `struct.name <value...>` - Set `name`.
|
130
|
+
# - `struct.name = <value>` - Set `name`.
|
131
|
+
#
|
132
|
+
# If the property is marked as an identity property, it also modifies
|
133
|
+
# `Struct.open()` to take it as a named parameter. Multiple identity
|
134
|
+
# property_types means multiple parameters to `open()`.
|
135
|
+
#
|
136
|
+
# @param name [String] The name of the property.
|
137
|
+
# @param type [Class] The type of the property. If passed, the property
|
138
|
+
# will use `type.open()`
|
139
|
+
# @param identity [Boolean] `true` if this is an identity
|
140
|
+
# property. Default: `false`
|
141
|
+
# @param required [Boolean] `true` if this is a required parameter.
|
142
|
+
# Defaults to `true`. Non-identity property_types do not support `required`
|
143
|
+
# and will ignore it. Non-required identity property_types will not be
|
144
|
+
# available as positioned arguments in ResourceClass.open(); they can
|
145
|
+
# only be specified by name (ResourceClass.open(x: 1))
|
146
|
+
# @param default [Object] The value to return if the user asks for the property
|
147
|
+
# when it has not been set. `nil` is a valid value for this.
|
148
|
+
# @param default [Proc] An optional block that will be called when
|
149
|
+
# the user asks for a value that has not been set. Called in the
|
150
|
+
# context of the struct (instance_eval), so you can access other
|
151
|
+
# properties of the struct to compute the value. Value is *not* cached,
|
152
|
+
# but rather is called every time.
|
153
|
+
#
|
154
|
+
# @example Property referencing a resource type by "snake case name"
|
155
|
+
# class MyResource < StructResourceBase
|
156
|
+
# property :blah, :my_resource
|
157
|
+
# end
|
158
|
+
# @example Typeless, optionless property.
|
159
|
+
# class MyResource < StructResourceBase
|
160
|
+
# property :simple
|
161
|
+
# end
|
162
|
+
# x = MyResource.open
|
163
|
+
# puts x.simple # nil
|
164
|
+
# x.simple = 10
|
165
|
+
# puts x.simple # 10
|
166
|
+
#
|
167
|
+
# @example Property with default
|
168
|
+
# class MyResource < StructResourceBase
|
169
|
+
# property :b, default: 10
|
170
|
+
# end
|
171
|
+
# x = MyResource.open
|
172
|
+
# puts x.b # 10
|
173
|
+
#
|
174
|
+
# @example Property with default block
|
175
|
+
# class MyResource < StructResourceBase
|
176
|
+
# property :a, default: 3
|
177
|
+
# property :b do
|
178
|
+
# a * 2
|
179
|
+
# end
|
180
|
+
# end
|
181
|
+
# x = MyResource.open
|
182
|
+
# puts x.b # 6
|
183
|
+
# x.a = 10
|
184
|
+
# puts x.b # 20
|
185
|
+
#
|
186
|
+
# @example Property with identity
|
187
|
+
# class MyResource < StructResourceBase
|
188
|
+
# property :a, identity: true
|
189
|
+
# end
|
190
|
+
# x = MyResource.new(10)
|
191
|
+
# puts x.a # 10
|
192
|
+
#
|
193
|
+
# @example Property with multiple identity
|
194
|
+
# class MyResource < StructResourceBase
|
195
|
+
# property :a, identity: true
|
196
|
+
# property :b, identity: true
|
197
|
+
# end
|
198
|
+
# x = MyResource.open(10, 20)
|
199
|
+
# puts x.a # 10
|
200
|
+
# puts x.b # 20
|
201
|
+
# x = MyResource.open(b: 2, a: 1)
|
202
|
+
# puts x.a # 1
|
203
|
+
# puts x.b # 2
|
204
|
+
# x = MyResource.open
|
205
|
+
# puts x.a # nil
|
206
|
+
# puts x.b # nil
|
207
|
+
# x = MyResource.open(1)
|
208
|
+
# puts x.a # 1
|
209
|
+
# puts x.b # nil
|
210
|
+
#
|
211
|
+
# @example Property with non-required identity
|
212
|
+
# class MyResource < StructResourceBase
|
213
|
+
# property :a, identity: true, required: false
|
214
|
+
# property :b, identity: true
|
215
|
+
# end
|
216
|
+
# x = MyResource.open(1)
|
217
|
+
# x.a # nil
|
218
|
+
# x.b # 1
|
219
|
+
#
|
220
|
+
# @example Property with struct typed property
|
221
|
+
# class Address < StructResourceBase
|
222
|
+
# property :street
|
223
|
+
# property :city
|
224
|
+
# property :state
|
225
|
+
# property :zip
|
226
|
+
# end
|
227
|
+
# class Person < StructResourceBase
|
228
|
+
# property :name
|
229
|
+
# property :home_address, Address
|
230
|
+
# end
|
231
|
+
# p = Person.open
|
232
|
+
# p.home_address = Address.open
|
233
|
+
#
|
234
|
+
def property(name, type=nil, identity: nil, default: NOT_PASSED, required: NOT_PASSED, load_value: NOT_PASSED, **type_properties, &override_block)
|
235
|
+
parent = self
|
236
|
+
name = name.to_sym
|
237
|
+
result = self.type(name, type, **type_properties) do
|
238
|
+
extend StructPropertyType
|
239
|
+
self.property_parent_type = parent
|
240
|
+
self.property_name name
|
241
|
+
self.identity identity
|
242
|
+
self.default default unless default == NOT_PASSED
|
243
|
+
self.required required unless required == NOT_PASSED
|
244
|
+
self.load_value load_value unless load_value == NOT_PASSED
|
245
|
+
instance_eval(&override_block) if override_block
|
246
|
+
end
|
247
|
+
property_types[result.property_name] = result
|
248
|
+
result.emit_property_methods
|
249
|
+
result
|
250
|
+
end
|
251
|
+
|
252
|
+
extend SimpleStruct
|
253
|
+
|
254
|
+
#
|
255
|
+
# The property type for each property.
|
256
|
+
#
|
257
|
+
# TODO use real merging in the future. This carries
|
258
|
+
# danger that someone could modify types on the parent.
|
259
|
+
# But it at least gets us basic inheritance for the
|
260
|
+
# normal case where people are adding new properties
|
261
|
+
# rather than overriding old ones.
|
262
|
+
#
|
263
|
+
property :property_types,
|
264
|
+
default: "@property_types = {}",
|
265
|
+
inherited: "@property_types = superclass.property_types.dup"
|
266
|
+
|
267
|
+
#
|
268
|
+
# The list of identity property types (property types with identity=true), in order.
|
269
|
+
#
|
270
|
+
def identity_property_types
|
271
|
+
property_types.values.select { |attr| attr.identity? }
|
272
|
+
end
|
273
|
+
end
|
274
|
+
end
|
275
|
+
end
|