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,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