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,4 @@
1
+ module ChefResource
2
+ module Types
3
+ end
4
+ end
@@ -0,0 +1,16 @@
1
+ require 'chef_resource/type'
2
+
3
+ module ChefResource
4
+ module Types
5
+ class Boolean
6
+ extend Type
7
+
8
+ must_be_kind_of TrueClass, FalseClass
9
+ end
10
+ end
11
+ end
12
+
13
+ #
14
+ # Put Boolean, Interval and Path into the top level namespace so they can be used
15
+ #
16
+ ::Boolean = ChefResource::Types::Boolean
@@ -0,0 +1,10 @@
1
+ require 'chef_resource/type'
2
+
3
+ module ChefResource
4
+ module Types
5
+ class ByteSize
6
+ extend Type
7
+ must_be_kind_of Integer
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,18 @@
1
+ require 'chef_resource/type'
2
+
3
+ module ChefResource
4
+ module Types
5
+ #
6
+ # Represents a DateTime. Will always be in DateTime format.
7
+ #
8
+ # Accepts a number (# of seconds since 1970), a Date, a DateTime, or a
9
+ # string ("now", "2 hours from now", "second thursday in 1970", "2045/10/27").
10
+ # Has methods to denote the default date format for printing and disambiguation.
11
+ #
12
+ class DateTimeType
13
+ extend Type
14
+
15
+ must_be_kind_of DateTime
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,18 @@
1
+ require 'chef_resource/type'
2
+
3
+ module ChefResource
4
+ module Types
5
+ #
6
+ # Represents a Date. Will always be a Date object.
7
+ #
8
+ # Accepts a number (# of seconds since 1970), a Date, a DateTime, or a
9
+ # string ("now", "2 days from now", "second thursday in 1970", "2045/10/27").
10
+ # Has methods to denote the default date format for printing and disambiguation.
11
+ #
12
+ class DateTimeType
13
+ extend Type
14
+
15
+ must_be_kind_of Date
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,28 @@
1
+ require 'chef_resource/type'
2
+
3
+ module ChefResource
4
+ module Types
5
+ #
6
+ # Handles Float types.
7
+ #
8
+ # Supports strings and number types.
9
+ #
10
+ class FloatType
11
+ extend Type
12
+
13
+ must_be_kind_of Float
14
+
15
+ def self.coerce(parent, value)
16
+ if value.is_a?(String)
17
+ if value !~ /^[+-]?(\d+(\.\d+)?|.\d+)(e[+-]?\d+)?$/i
18
+ raise ValidationError.new("not a valid floating point string", value)
19
+ end
20
+ value = value.to_f
21
+ elsif value.is_a?(Numeric)
22
+ value = value.to_f
23
+ end
24
+ super
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,53 @@
1
+ require 'chef_resource/type'
2
+
3
+ module ChefResource
4
+ module Types
5
+ #
6
+ # Handles Integer types.
7
+ #
8
+ # Supports parse / to_s in bases other than 10.
9
+ #
10
+ class IntegerType
11
+ extend Type
12
+
13
+ must_be_kind_of Integer
14
+
15
+ def self.coerce(parent, value)
16
+ if value.is_a?(String)
17
+ if !base_regexp.match(value)
18
+ raise ValidationError.new("must be a base #{base||10} string", value)
19
+ end
20
+
21
+ if base
22
+ value = value.to_i(base)
23
+ else
24
+ value = value.to_i
25
+ end
26
+ end
27
+ super
28
+ end
29
+
30
+ def self.value_to_s(value)
31
+ str = base ? value.to_s(base) : value.to_s
32
+ str
33
+ end
34
+
35
+ def self.base_regexp
36
+ base = self.base || 10
37
+ if base <= 10
38
+ /^[-+]?[0-#{base-1}]+$/
39
+ elsif base <= 36
40
+ top_char = ('a'.ord + base-11).chr
41
+ /^[-+]?[0-9a-#{top_char}]+$/i
42
+ else
43
+ raise "Base #{base} strings not supported: nothing bigger than 36!"
44
+ end
45
+ end
46
+
47
+ class <<self
48
+ extend SimpleStruct
49
+ property :base
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,21 @@
1
+ require 'chef_resource/type'
2
+
3
+ module ChefResource
4
+ module Types
5
+ #
6
+ # Represents a time interval ("3 seconds", "1 day", etc.).
7
+ #
8
+ # Stored as an Interval object (no standard representation can accurately
9
+ # record this, because things like seconds per day and even the length of a
10
+ # second can vary depending on the exact date and time).
11
+ #
12
+ class Interval
13
+ extend Type
14
+ end
15
+ end
16
+ end
17
+
18
+ #
19
+ # Put Boolean, Interval and Path into the top level namespace so they can be used
20
+ #
21
+ ::Interval = ChefResource::Types::Interval
@@ -0,0 +1,39 @@
1
+ require 'chef_resource/types/pathname_type'
2
+
3
+ module ChefResource
4
+ module Types
5
+ #
6
+ # Type for Paths. Always stored as String.
7
+ #
8
+ # Allows paths to be specified as Pathname or as String. Can handle
9
+ # absolutizing relative URLs with #relative_to.
10
+ #
11
+ class Path
12
+ extend Type
13
+
14
+ must_be_kind_of String
15
+
16
+ class <<self
17
+ extend SimpleStruct
18
+ property :relative_to, coerced: "value.is_a?(Pathname) ? value.to_s : value"
19
+ end
20
+
21
+ def self.coerce(parent, path)
22
+ if path
23
+ rel = relative_to(parent: parent)
24
+ if rel
25
+ path = (Pathname.new(rel) + path).to_s
26
+ else
27
+ path = path.to_s if path.is_a?(Pathname)
28
+ end
29
+ end
30
+ super
31
+ end
32
+ end
33
+ end
34
+ end
35
+
36
+ #
37
+ # Put Boolean, Interval and Path into the top level namespace so they can be used
38
+ #
39
+ ::Path = ChefResource::Types::Path
@@ -0,0 +1,34 @@
1
+ require 'chef_resource/type'
2
+ require 'chef_resource/simple_struct'
3
+ require 'pathname'
4
+
5
+ module ChefResource
6
+ module Types
7
+ #
8
+ # Type for Paths. Always stored as Pathname
9
+ #
10
+ # Can handle absolutizing relative URLs with #relative_to.
11
+ #
12
+ class PathnameType
13
+ extend Type
14
+ must_be_kind_of Pathname
15
+
16
+ class <<self
17
+ extend SimpleStruct
18
+ property :relative_to, coerced: "value.is_a?(String) ? Pathname.new(value) : value"
19
+ end
20
+
21
+ def self.coerce(parent, path)
22
+ if path
23
+ rel = relative_to(parent: parent)
24
+ if rel
25
+ path = rel + path if rel
26
+ else
27
+ path = Pathname.new(path) if path.is_a?(String)
28
+ end
29
+ end
30
+ super
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,16 @@
1
+ require 'chef_resource/type'
2
+
3
+ module ChefResource
4
+ module Types
5
+ class StringType
6
+ extend Type
7
+
8
+ must_be_kind_of String
9
+
10
+ def self.coerce(parent, value)
11
+ value = value.to_s unless value.nil?
12
+ super
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,18 @@
1
+ require 'chef_resource/type'
2
+
3
+ module ChefResource
4
+ module Types
5
+ class SymbolType
6
+ extend Type
7
+
8
+ must_be_kind_of Symbol
9
+
10
+ def self.coerce(parent, value)
11
+ if value.respond_to?(:to_sym)
12
+ value = value.to_sym
13
+ end
14
+ super
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,37 @@
1
+ require 'chef_resource/type'
2
+ require 'chef_resource/simple_struct'
3
+ require 'uri'
4
+
5
+ module ChefResource
6
+ module Types
7
+ #
8
+ # Type for URIs.
9
+ #
10
+ # Allows URIs to be specified as URI or as String. Can also handle absolutizing
11
+ # relative URLs with #relative_to.
12
+ #
13
+ class URIType
14
+ extend Type
15
+ extend SimpleStruct
16
+
17
+ must_be_kind_of URI
18
+
19
+ def self.coerce(parent, uri)
20
+ if uri
21
+ rel = relative_to(parent: parent)
22
+ if rel
23
+ uri = rel + uri
24
+ else
25
+ uri = URI.parse(uri) if uri.is_a?(String)
26
+ end
27
+ end
28
+ super
29
+ end
30
+
31
+ class <<self
32
+ extend SimpleStruct
33
+ property :relative_to, coerced: "value.is_a?(String) ? URI.parse(value) : value"
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,3 @@
1
+ module ChefResource
2
+ VERSION = '0.2.2'
3
+ end
@@ -0,0 +1,81 @@
1
+ require 'cheffish/basic_chef_client'
2
+ require 'chef_resource/chef'
3
+ require 'tempfile'
4
+
5
+ module Cheffish
6
+ class BasicChefClient
7
+ prepend ChefResource::ChefDSL::ChefRecipeDSLExtensions
8
+ end
9
+ end
10
+
11
+ Chef.resource :simple_resource do
12
+ property :hi
13
+ attr_reader :did_it
14
+ recipe do
15
+ @did_it = true
16
+ end
17
+ end
18
+
19
+ Chef.resource :compound_resource do
20
+ property :lo
21
+ attr_reader :did_it
22
+ attr_reader :f
23
+ recipe do
24
+ @f = Tempfile.new('foo')
25
+ file @f.path do
26
+ content 'hi'
27
+ end
28
+ simple_resource do
29
+ hi 10
30
+ end
31
+ @did_it = true
32
+ end
33
+ end
34
+
35
+ Chef.resource :resource_with_error do
36
+ property :lo
37
+ recipe do
38
+ blarghfile 'wow.txt' do
39
+ content 'hi'
40
+ end
41
+ end
42
+ end
43
+
44
+
45
+ describe 'Chef integration' do
46
+ context "When simple_resource is a ChefResource resource" do
47
+ it "a recipe can run the resource" do
48
+ x = nil
49
+ Cheffish::BasicChefClient.converge_block do
50
+ x = simple_resource do
51
+ hi 10
52
+ end
53
+ end
54
+ expect(x.did_it).to be_truthy
55
+ end
56
+ end
57
+ context "When compound_resource has a file and a simple_resource in it" do
58
+ it "a recipe can run the resource and both sub-resources run" do
59
+ x = nil
60
+ Cheffish::BasicChefClient.converge_block do
61
+ x = compound_resource do
62
+ lo 100
63
+ end
64
+ end
65
+ expect(x.did_it).to be_truthy
66
+ expect(IO.read(x.f.path)).to eq 'hi'
67
+ end
68
+ end
69
+ context "When resource_with_error has a misspelled resource name" do
70
+ it "a recipe can run the resource and both sub-resources run" do
71
+ expect do
72
+ Cheffish::BasicChefClient.converge_block do
73
+ resource_with_error do
74
+ lo 100
75
+ end
76
+ end
77
+ end.to raise_error(NoMethodError)
78
+ end
79
+ end
80
+ # notifications and subscribes, both directions
81
+ end
@@ -0,0 +1,611 @@
1
+ require 'support/spec_support'
2
+ require 'chef_resource/resource/struct_resource_base'
3
+
4
+ describe ChefResource::Resource::StructResource do
5
+ def self.with_struct(name, &block)
6
+ before :each do
7
+ Object.send(:remove_const, name) if Object.const_defined?(name, false)
8
+ eval "class ::#{name} < ChefResource::Resource::StructResourceBase; end"
9
+ Object.const_get(name).class_eval(&block)
10
+ end
11
+ after :each do
12
+ end
13
+ end
14
+
15
+ describe :inheritance do
16
+ context "When A < B, and A has x and B has y" do
17
+ class A < ChefResource::Resource::StructResourceBase
18
+ property :x, identity: true do
19
+ default { y*2 }
20
+ end
21
+ end
22
+ class B < A
23
+ property :y, identity: true do
24
+ default { x*2 }
25
+ end
26
+ end
27
+
28
+ it "A.open(x: 1).y raises an error" do
29
+ expect { A.open(x: 1).y }.to raise_error
30
+ end
31
+ it "B.open(x: 1).y yields 2" do
32
+ expect(B.open(x: 1).y).to eq 2
33
+ end
34
+ it "B.open(y: 1).x yields 2" do
35
+ expect(B.open(y: 1).x).to eq 2
36
+ end
37
+ end
38
+ end
39
+
40
+ describe :reset do
41
+ context "When MyResource has both a set and not-set property" do
42
+ with_struct(:MyResource) do
43
+ property :identity_set, identity: true
44
+ property :normal_set, default: 20
45
+ property :normal_not_set, default: 30
46
+ end
47
+ let(:r) { r = MyResource.open(1); r.normal_set = 2; r }
48
+ it "explicit_property_values is missing values" do
49
+ expect(r.to_h(:only_explicit)).to eq({ identity_set: 1, normal_set: 2 })
50
+ expect(r.normal_set).to eq 2
51
+ expect(r.normal_not_set).to eq 30
52
+ end
53
+ it "reset(:normal_set) succeeds" do
54
+ r.reset(:normal_set)
55
+ expect(r.to_h(:only_explicit)).to eq({ identity_set: 1 })
56
+ expect(r.normal_set).to eq 20
57
+ expect(r.normal_not_set).to eq 30
58
+ end
59
+ it "reset(:normal_not_set) succeeds" do
60
+ r.reset(:normal_not_set)
61
+ expect(r.to_h(:only_explicit)).to eq({ identity_set: 1, normal_set: 2 })
62
+ expect(r.normal_set).to eq 2
63
+ expect(r.normal_not_set).to eq 30
64
+ end
65
+ it "reset(:normal_set) succeeds" do
66
+ r.reset(:normal_set)
67
+ expect(r.to_h(:only_explicit)).to eq({ identity_set: 1 })
68
+ expect(r.normal_set).to eq 20
69
+ expect(r.normal_not_set).to eq 30
70
+ end
71
+ it "reset() resets normal but not identity properties" do
72
+ r.reset
73
+ expect(r.to_h(:only_explicit)).to eq({ identity_set: 1 })
74
+ expect(r.normal_set).to eq 20
75
+ expect(r.normal_not_set).to eq 30
76
+ end
77
+ it "reset() twice in a row succeeds (but second reset does nothing)" do
78
+ r.reset
79
+ expect(r.to_h(:only_explicit)).to eq({ identity_set: 1 })
80
+ expect(r.normal_set).to eq 20
81
+ expect(r.normal_not_set).to eq 30
82
+ r.reset
83
+ expect(r.to_h(:only_explicit)).to eq({ identity_set: 1 })
84
+ expect(r.normal_set).to eq 20
85
+ expect(r.normal_not_set).to eq 30
86
+ end
87
+ end
88
+ end
89
+
90
+ describe :property do
91
+ context "When MyResource is a ResourceStruct with two properties" do
92
+ with_struct(:MyResource) do
93
+ property :x
94
+ property :y
95
+ end
96
+ it "You can create a new MyResource" do
97
+ expect(MyResource.open).to be_kind_of(MyResource)
98
+ end
99
+ it "You can set and get properties" do
100
+ r = MyResource.open
101
+ expect(r.x).to be_nil
102
+ expect(r.y).to be_nil
103
+ expect(r.x = 10).to eq 10
104
+ expect(r.y = 20).to eq 20
105
+ expect(r.x).to eq 10
106
+ expect(r.y).to eq 20
107
+ end
108
+ end
109
+
110
+ describe :type do
111
+ context "When MyResource is a ResourceStruct with property :x, ResourceStruct (resource struct reference)" do
112
+ with_struct(:MyResource) do
113
+ property :x, MyResource
114
+ property :y
115
+ end
116
+ it "x and y can be set to a resource" do
117
+ r = MyResource.open
118
+ r.y = 10
119
+ expect(r.x).to be_nil
120
+ r2 = MyResource.open
121
+ expect(r2.x = r).to eq r
122
+ r2.y = 20
123
+ expect(r2.x).to eq r
124
+ expect(r2.x.y).to eq 10
125
+ end
126
+ end
127
+ end
128
+
129
+ describe :identity do
130
+ context "When MyResource has property :x, identity: true" do
131
+ with_struct(:MyResource) do
132
+ property :x, identity: true
133
+ property :y
134
+ end
135
+ it "open() fails with 'x is required'" do
136
+ expect { MyResource.open() }.to raise_error ArgumentError
137
+ end
138
+ it "open(1) creates a MyResource where x = 1" do
139
+ expect(r = MyResource.open(1)).to be_kind_of(MyResource)
140
+ expect(r.x).to eq 1
141
+ expect(r.y).to be_nil
142
+ end
143
+ it "open(x: 1) creates a MyResource where x = 1" do
144
+ expect(r = MyResource.open(x: 1)).to be_kind_of(MyResource)
145
+ expect(r.x).to eq 1
146
+ expect(r.y).to be_nil
147
+ end
148
+ it "open(1, 2) fails with too many arguments" do
149
+ expect { MyResource.open(1, 2) }.to raise_error ArgumentError
150
+ end
151
+ end
152
+
153
+ context "When MyResource has property :x, identity: true, default: 10" do
154
+ with_struct(:MyResource) do
155
+ property :x, identity: true, default: 10
156
+ property :y
157
+ end
158
+ it "open() succeeds with x = 10" do
159
+ expect(r = MyResource.open()).to be_kind_of(MyResource)
160
+ expect(r.x).to eq 10
161
+ expect(r.y).to be_nil
162
+ end
163
+ it "open(1) fails due to non-required argument" do
164
+ expect { MyResource.open(1) }.to raise_error ArgumentError
165
+ end
166
+ it "open(x: 1) creates a MyResource where x = 1" do
167
+ expect(r = MyResource.open(x: 1)).to be_kind_of(MyResource)
168
+ expect(r.x).to eq 1
169
+ expect(r.y).to be_nil
170
+ end
171
+ it "open(1, 2) fails with too many arguments" do
172
+ expect { MyResource.open(1, 2) }.to raise_error ArgumentError
173
+ end
174
+ end
175
+
176
+ context "When MyResource has property :x, identity: true, required: false" do
177
+ with_struct(:MyResource) do
178
+ property :x, identity: true, required: false
179
+ property :y
180
+ end
181
+ it "open() creates a MyResource where x = nil" do
182
+ expect(r = MyResource.open()).to be_kind_of(MyResource)
183
+ expect(r.x).to be_nil
184
+ expect(r.y).to be_nil
185
+ end
186
+ it "open(1) fails with 'too many arguments'" do
187
+ expect { MyResource.open(1) }.to raise_error ArgumentError
188
+ end
189
+ it "open(x: 1) creates a MyResource where x = 1" do
190
+ expect(r = MyResource.open(x: 1)).to be_kind_of(MyResource)
191
+ expect(r.x).to eq 1
192
+ expect(r.y).to be_nil
193
+ end
194
+ end
195
+
196
+ context "When MyResource has property :x and :y, identity: true" do
197
+ with_struct(:MyResource) do
198
+ property :x, identity: true
199
+ property :y, identity: true
200
+ property :z
201
+ end
202
+ it "open() fails with 'x is required'" do
203
+ expect { MyResource.open() }.to raise_error ArgumentError
204
+ end
205
+ it "open(1) fails with 'y is required'" do
206
+ expect { MyResource.open(1) }.to raise_error ArgumentError
207
+ end
208
+ it "open(y: 1) fails with 'x is required'" do
209
+ expect { MyResource.open(y: 1) }.to raise_error ArgumentError
210
+ end
211
+ it "open(1, 2) creates a MyResource where x = 1 and y = 2" do
212
+ expect(r = MyResource.open(1, 2)).to be_kind_of(MyResource)
213
+ expect(r.x).to eq 1
214
+ expect(r.y).to eq 2
215
+ expect(r.z).to be_nil
216
+ end
217
+ it "open(1, 2, 3) fails with too many arguments" do
218
+ expect { MyResource.open(1, 2, 3) }.to raise_error ArgumentError
219
+ end
220
+ it "open(x: 1, y: 2) creates MyResource.x = 1, y = 2" do
221
+ expect(r = MyResource.open(x: 1, y: 2)).to be_kind_of(MyResource)
222
+ expect(r.x).to eq 1
223
+ expect(r.y).to eq 2
224
+ expect(r.z).to be_nil
225
+ end
226
+ it "open(3, 4, x: 1, y: 2) creates MyResource.x = 3, y = 4" do
227
+ expect { MyResource.open(3, 4, x: 1, y: 2) }.to raise_error ArgumentError
228
+ end
229
+ end
230
+
231
+ context "When MyResource has identity properties x and y, and x is not required" do
232
+ with_struct(:MyResource) do
233
+ property :x, identity: true, required: false
234
+ property :y, identity: true
235
+ end
236
+ it "open() fails with y is required" do
237
+ expect { MyResource.open() }.to raise_error ArgumentError
238
+ end
239
+ it "open(1) creates a MyResource where x = nil and y = 1" do
240
+ expect(r = MyResource.open(1)).to be_kind_of(MyResource)
241
+ expect(r.x).to be_nil
242
+ expect(r.y).to eq 1
243
+ end
244
+ it "open(1, 2) fails with 'too many arguments'" do
245
+ expect { MyResource.open(1, 2) }.to raise_error ArgumentError
246
+ end
247
+ it "open(y: 1) creates a MyResource where x = nil and y = 1" do
248
+ expect(r = MyResource.open(y: 1)).to be_kind_of(MyResource)
249
+ expect(r.x).to be_nil
250
+ expect(r.y).to eq 1
251
+ end
252
+ end
253
+ end
254
+
255
+ describe :override_block do
256
+ context "property overrides" do
257
+ context "When MyResource has a primitive property that overrides coerce" do
258
+ with_struct(:MyResource) do
259
+ property :x, String do
260
+ def self.coerce(parent, value)
261
+ "#{value} is awesome"
262
+ end
263
+ end
264
+ end
265
+ it "MyResource.coerce(nil, { x: 1 }) yields { x: '1 is awesome' }" do
266
+ expect(MyResource.coerce(nil, { x: 1 }).to_h(:only_explicit)).to eq({ x: "1 is awesome" })
267
+ end
268
+ end
269
+
270
+ context "When MyResource has an untyped property that overrides coerce" do
271
+ with_struct(:MyResource) do
272
+ property :x do
273
+ def self.coerce(parent, value)
274
+ "#{value} is awesome"
275
+ end
276
+ end
277
+ end
278
+ it "MyResource.coerce(nil, { x: 1 }) yields { x: '1 is awesome' }" do
279
+ expect(MyResource.coerce(nil, { x: 1 }).to_h(:only_explicit)).to eq({ x: "1 is awesome" })
280
+ end
281
+ end
282
+
283
+ context "When MyResource has a resource typed property that overrides coerce" do
284
+ with_struct(:MyResource) do
285
+ property :x, MyResource do
286
+ def self.coerce(parent, value)
287
+ if value.is_a?(Integer)
288
+ x = value
289
+ value = MyResource.open
290
+ value.x "#{x} is awesome"
291
+ end
292
+ super
293
+ end
294
+ end
295
+ end
296
+ it "MyResource.coerce(nil, { x: 1 }) yields MyResource{ x: '1 is awesome' }" do
297
+ r = MyResource.coerce(nil, { x: 1 })
298
+ expect(r.x).to be_kind_of(MyResource)
299
+ expect(r.x.to_h(:only_explicit)).to eq({ x: "1 is awesome" })
300
+ end
301
+ end
302
+
303
+ context "When MyResource has an override that sets must(be between 0 and 10)" do
304
+ with_struct(:MyResource) do
305
+ property :x, Integer, nullable: true do
306
+ def self.run_count
307
+ @run_count ||= 0
308
+ end
309
+ def self.run_count=(value)
310
+ @run_count = value
311
+ end
312
+ must("be between 0 and 10") do
313
+ MyResource::X.run_count += 1
314
+ self >= 0 && self <= 10
315
+ end
316
+ end
317
+ property :run_count, Integer, default: 0
318
+ end
319
+ it "MyResource.coerce(nil, {x: 1}) succeeds" do
320
+ expect(MyResource.coerce(nil, { x: 1 }).to_h(:only_explicit)).to eq({ x: 1 })
321
+ expect(MyResource::X.run_count).to eq 1
322
+ end
323
+ it "MyResource.coerce(nil, {x: nil}) succeeds" do
324
+ expect(MyResource.coerce(nil, { x: nil }).to_h(:only_explicit)).to eq({ x: nil })
325
+ expect(MyResource::X.run_count).to eq 0
326
+ end
327
+ it "MyResource.coerce(nil, {x: 11}) fails" do
328
+ expect { MyResource.coerce(nil, { x: 11 }).to_h(:all) }.to raise_error(ChefResource::ValidationError)
329
+ end
330
+ it "MyResource.coerce(nil, {}) never runs it" do
331
+ expect(MyResource.coerce(nil, {}).to_h(:only_explicit)).to eq({})
332
+ expect(MyResource::X.run_count).to eq 0
333
+ end
334
+ end
335
+ end
336
+ end
337
+
338
+ describe :default do
339
+ context "When MyResource is a ResourceStruct with property :x, default: 15" do
340
+ with_struct(:MyResource) do
341
+ property :x, default: 15
342
+ end
343
+ it "x returns the default if not set" do
344
+ r = MyResource.open
345
+ expect(r.x).to eq 15
346
+ end
347
+ it "x returns the new value if it is set" do
348
+ r = MyResource.open
349
+ expect(r.x).to eq 15
350
+ expect(r.x = 20).to eq 20
351
+ expect(r.x).to eq 20
352
+ end
353
+ end
354
+
355
+ context "When MyResource is a ResourceStruct with property :x, 15 and property :y { x*2 } (default block)" do
356
+ with_struct(:MyResource) do
357
+ property :x, default: 15
358
+ property :y, default: ChefResource::LazyProc.new { x*2 }
359
+ end
360
+ it "x and y return the default if not set" do
361
+ r = MyResource.open
362
+ expect(r.x).to eq 15
363
+ expect(r.y).to eq 30
364
+ end
365
+ it "y returns the new value if it is set" do
366
+ r = MyResource.open
367
+ expect(r.y).to eq 30
368
+ expect(r.y = 20).to eq 20
369
+ expect(r.y).to eq 20
370
+ end
371
+ it "y returns a value based on x if x is set" do
372
+ r = MyResource.open
373
+ expect(r.y).to eq 30
374
+ expect(r.x = 20).to eq 20
375
+ expect(r.y).to eq 40
376
+ end
377
+ end
378
+ end
379
+ end
380
+
381
+ describe :coerce do
382
+ context "With a struct with x, y and z" do
383
+ with_struct(:MyResource) do
384
+ property :a, identity: true
385
+ property :b, identity: true
386
+ property :c, nullable: :validate
387
+ property :d, nullable: :validate
388
+ nullable :validate
389
+ end
390
+
391
+ context "multi-arg form" do
392
+ it "coerce(nil, 1, 2) yields a=1,b=2" do
393
+ expect(MyResource.coerce(nil, 1, 2).to_h(:only_explicit)).to eq({ a: 1, b: 2 })
394
+ end
395
+ it "coerce(nil, 1, 2, c: 3, d: 4) yields a=1, b=2, c=3, d=4" do
396
+ expect(MyResource.coerce(nil, 1, 2, c: 3, d: 4).to_h(:only_explicit)).to eq({ a: 1, b: 2, c: 3, d: 4 })
397
+ end
398
+ end
399
+ context "hash form" do
400
+ it "coerce(nil, a: 1, b: 2) yields a=1, b=2" do
401
+ expect(MyResource.coerce(nil, a: 1, b: 2).to_h(:only_explicit)).to eq({ a: 1, b: 2 })
402
+ end
403
+ it "coerce(nil, a: 1, b: 2, c: 3, d: 4) yields a=1, b=2, c=3, d=4" do
404
+ expect(MyResource.coerce(nil, a: 1, b: 2, c: 3, d: 4).to_h(:only_explicit)).to eq({ a: 1, b: 2, c: 3, d: 4 })
405
+ end
406
+ it "coerce(nil, c: 3, d: 4) fails" do
407
+ expect { MyResource.coerce(nil, c: 3, d: 4) }.to raise_error(ArgumentError)
408
+ end
409
+ end
410
+ it "coerce(nil, another resource) yields that resource" do
411
+ x = MyResource.open(1,2)
412
+ expect(MyResource.coerce(nil, x).object_id).to eq x.object_id
413
+ end
414
+ it "coerce(nil, nil) yields nil" do
415
+ expect(MyResource.coerce(nil, nil)).to be_nil
416
+ end
417
+ end
418
+ end
419
+
420
+ describe :load do
421
+ context "When load sets y to x*2 and z to x*3" do
422
+ with_struct(:MyResource) do
423
+ property :x, identity: true
424
+ property :y
425
+ property :z
426
+ property :num_loads
427
+ def load
428
+ y x*2
429
+ z x*3
430
+ self.num_loads ||= 0
431
+ self.num_loads += 1
432
+ end
433
+ end
434
+
435
+ it "MyResource.open(1).y == 2 and .z == 3" do
436
+ r = MyResource.open(1)
437
+ expect(r.x).to eq 1
438
+ expect(r.y).to eq 2
439
+ expect(r.z).to eq 3
440
+ end
441
+
442
+ it "load is only called once" do
443
+ r = MyResource.open(1)
444
+ expect(r.x).to eq 1
445
+ expect(r.y).to eq 2
446
+ expect(r.z).to eq 3
447
+ expect(r.x).to eq 1
448
+ expect(r.y).to eq 2
449
+ expect(r.z).to eq 3
450
+ expect(r.num_loads).to eq 1
451
+ end
452
+ end
453
+
454
+ context "When load sets y to x*2 and z has its own load_value that does x*3" do
455
+ with_struct(:MyResource) do
456
+ property :x, identity: true
457
+ property :y
458
+ property :z, load_value: ChefResource::LazyProc.new { self.num_loads += 1; x*3 }
459
+ property :num_loads, default: 0
460
+ def load
461
+ y x*2
462
+ self.num_loads += 1
463
+ end
464
+ end
465
+
466
+ it "MyResource.open(1).y == 2 and .z == 3" do
467
+ r = MyResource.open(1)
468
+ expect(r.x).to eq 1
469
+ expect(r.y).to eq 2
470
+ expect(r.z).to eq 3
471
+ end
472
+
473
+ it "current_resource.z yields x*3" do
474
+ r = MyResource.open(1)
475
+ expect(r.current_resource.z).to eq 3
476
+ end
477
+
478
+ it "load is only called twice" do
479
+ r = MyResource.open(1)
480
+ expect(r.x).to eq 1
481
+ expect(r.y).to eq 2
482
+ expect(r.z).to eq 3
483
+ expect(r.x).to eq 1
484
+ expect(r.y).to eq 2
485
+ expect(r.z).to eq 3
486
+ expect(r.num_loads).to eq 2
487
+ end
488
+ end
489
+
490
+ context "Primitive values" do
491
+ context "With a struct with Integers and Strings" do
492
+ with_struct(:MyResource) do
493
+ property :s1, String, identity: true, nullable: :validate
494
+ property :n1, Integer, identity: true, nullable: :validate
495
+ property :s2, String, nullable: :validate
496
+ property :n2, Integer, nullable: :validate
497
+ end
498
+
499
+ it "coerce(nil, s1: 'hi', n1: 1, s2: 'lo', n2: 2) succeeds" do
500
+ expect(MyResource.coerce(nil, s1: 'hi', n1: 1, s2: 'lo', n2: 2).to_h(:only_explicit)).to eq(s1: 'hi', n1: 1, s2: 'lo', n2: 2)
501
+ end
502
+
503
+ it "coerce(nil, s1: nil, n1: nil, s2: nil, n2: nil) succeeds" do
504
+ expect(MyResource.coerce(nil, s1: nil, n1: nil, s2: nil, n2: nil).to_h(:only_explicit)).to eq(s1: nil, n1: nil, s2: nil, n2: nil)
505
+ end
506
+
507
+ it "coerce(nil, s1: 'hi', n1: 1) succeeds" do
508
+ expect(MyResource.coerce(nil, s1: 'hi', n1: 1).to_h(:only_explicit)).to eq(s1: 'hi', n1: 1)
509
+ end
510
+
511
+ it "coerce(nil, s1: 'hi', n1: 'lo') fails" do
512
+ expect { MyResource.coerce(nil, s1: 'hi', n1: 'lo') }.to raise_error(ChefResource::ValidationError)
513
+ end
514
+
515
+ it "coerce(nil, s1: 'hi', n1: 'lo') fails" do
516
+ expect { MyResource.coerce(nil, s1: 'hi', n1: 'lo') }.to raise_error(ChefResource::ValidationError)
517
+ end
518
+ end
519
+ end
520
+
521
+ context "Lazy values" do
522
+ context "With a struct with x and some default values with instance_eval in various states" do
523
+ with_struct(:MyResource) do
524
+ def self.x
525
+ "outside.x"
526
+ end
527
+ property :x, default: "instance.x" do
528
+ def self.coerce(parent, value)
529
+ "coerce(#{value})"
530
+ end
531
+ end
532
+ property :default_no_params, default: ChefResource::LazyProc.new { "#{x} lazy_default" } do
533
+ def self.coerce(parent, value)
534
+ "coerce(#{value})"
535
+ end
536
+ end
537
+ property :default_instance_eval_symbol, default: ChefResource::LazyProc.new(:should_instance_eval) { "#{x} lazy_default" } do
538
+ def self.coerce(parent, value)
539
+ "coerce(#{value})"
540
+ end
541
+ end
542
+ property :default_instance_eval_true, default: ChefResource::LazyProc.new(should_instance_eval: true) { "#{x} lazy_default" } do
543
+ def self.coerce(parent, value)
544
+ "coerce(#{value})"
545
+ end
546
+ end
547
+ property :default_instance_eval_false, default: ChefResource::LazyProc.new(should_instance_eval: false) { "#{x} lazy_default" } do
548
+ def self.coerce(parent, value)
549
+ "coerce(#{value})"
550
+ end
551
+ end
552
+ property :default_block do
553
+ default { "#{x} lazy_default" }
554
+ def self.coerce(parent, value)
555
+ "coerce(#{value})"
556
+ end
557
+ end
558
+
559
+ property :z, default: "instance.z"
560
+ end
561
+
562
+ it "lazy default does instance_eval and coerces" do
563
+ r = MyResource.open
564
+ expect(r.default_no_params).to eq "coerce(coerce(instance.x) lazy_default)"
565
+ end
566
+ it "lazy default with :should_instance_eval does instance_eval and coerces" do
567
+ r = MyResource.open
568
+ expect(r.default_instance_eval_symbol).to eq "coerce(coerce(instance.x) lazy_default)"
569
+ end
570
+ it "lazy default with should_instance_eval: true does instance_eval and coerces" do
571
+ r = MyResource.open
572
+ expect(r.default_instance_eval_true).to eq "coerce(coerce(instance.x) lazy_default)"
573
+ end
574
+ it "lazy default with should_instance_eval: false does not do instance_eval, and coerces" do
575
+ r = MyResource.open
576
+ expect(r.default_instance_eval_false).to eq "coerce(outside.x lazy_default)"
577
+ end
578
+ it "default block does instance_eval and coerces" do
579
+ r = MyResource.open
580
+ expect(r.default_block).to eq "coerce(coerce(instance.x) lazy_default)"
581
+ end
582
+
583
+ def z
584
+ "outside.z"
585
+ end
586
+
587
+ it "lazy on x does not do instance_eval but coerces" do
588
+ r = MyResource.open
589
+ r.x ChefResource::LazyProc.new { "#{z} set_lazy" }
590
+ expect(r.x).to eq "coerce(outside.z set_lazy)"
591
+ end
592
+ it "lazy on x with :should_instance_eval does instance_eval and coerces" do
593
+ r = MyResource.open
594
+ r.x ChefResource::LazyProc.new(:should_instance_eval) { "#{z} set_lazy" }
595
+ expect(r.x).to eq "coerce(instance.z set_lazy)"
596
+ end
597
+ it "lazy on x should_instance_eval: true does instance_eval and coerces" do
598
+ r = MyResource.open
599
+ r.x ChefResource::LazyProc.new(:should_instance_eval) { "#{z} set_lazy" }
600
+ expect(r.x).to eq "coerce(instance.z set_lazy)"
601
+ end
602
+ it "lazy on x with should_instance_eval: false does instance_eval and coerces" do
603
+ r = MyResource.open
604
+ r.x ChefResource::LazyProc.new(should_instance_eval: false) { "#{z} set_lazy" }
605
+ expect(r.x).to eq "coerce(outside.z set_lazy)"
606
+ end
607
+
608
+ end
609
+ end
610
+ end
611
+ end