poise-derived 0 → 1.0.0

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 (41) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +11 -0
  3. data/.kitchen.yml +3 -0
  4. data/.travis.yml +40 -0
  5. data/CHANGELOG.md +5 -0
  6. data/Gemfile +32 -0
  7. data/LICENSE +201 -0
  8. data/README.md +110 -0
  9. data/Rakefile +17 -0
  10. data/chef/recipes/default.rb +17 -0
  11. data/lib/poise_derived.rb +22 -0
  12. data/lib/poise_derived/cheftie.rb +18 -0
  13. data/lib/poise_derived/core_ext.rb +28 -0
  14. data/lib/poise_derived/core_ext/deep_merge.rb +55 -0
  15. data/lib/poise_derived/core_ext/string.rb +35 -0
  16. data/lib/poise_derived/dsl.rb +45 -0
  17. data/lib/poise_derived/handler.rb +43 -0
  18. data/lib/poise_derived/lazy_attribute.rb +125 -0
  19. data/lib/poise_derived/version.rb +20 -0
  20. data/poise-derived.gemspec +41 -0
  21. data/test/cookbook/attributes/default.rb +46 -0
  22. data/test/cookbook/metadata.rb +18 -0
  23. data/test/cookbook/recipes/default.rb +28 -0
  24. data/test/gemfiles/chef-12.10.gemfile +20 -0
  25. data/test/gemfiles/chef-12.11.gemfile +20 -0
  26. data/test/gemfiles/chef-12.12.gemfile +19 -0
  27. data/test/gemfiles/chef-12.13.gemfile +19 -0
  28. data/test/gemfiles/chef-12.14.gemfile +19 -0
  29. data/test/gemfiles/chef-12.3.gemfile +20 -0
  30. data/test/gemfiles/chef-12.4.gemfile +21 -0
  31. data/test/gemfiles/chef-12.5.gemfile +20 -0
  32. data/test/gemfiles/chef-12.6.gemfile +20 -0
  33. data/test/gemfiles/chef-12.7.gemfile +20 -0
  34. data/test/gemfiles/chef-12.8.gemfile +20 -0
  35. data/test/gemfiles/chef-12.9.gemfile +20 -0
  36. data/test/gemfiles/chef-12.gemfile +19 -0
  37. data/test/gemfiles/master.gemfile +21 -0
  38. data/test/integration/default/serverspec/default_spec.rb +35 -0
  39. data/test/spec/lazy_attribute_spec.rb +158 -0
  40. data/test/spec/spec_helper.rb +21 -0
  41. metadata +123 -8
@@ -0,0 +1,22 @@
1
+ #
2
+ # Copyright 2016, Noah Kantrowitz
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+
17
+
18
+ module PoiseDerived
19
+ autoload :DSL, 'poise_derived/dsl'
20
+ autoload :LazyAttribute, 'poise_derived/lazy_attribute'
21
+ autoload :VERSION, 'poise_derived/version'
22
+ end
@@ -0,0 +1,18 @@
1
+ #
2
+ # Copyright 2016, Noah Kantrowitz
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+
17
+ require 'poise_derived/core_ext'
18
+ require 'poise_derived/handler'
@@ -0,0 +1,28 @@
1
+ #
2
+ # Copyright 2016, Noah Kantrowitz
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+
17
+ require 'poise_derived/core_ext/deep_merge'
18
+ require 'poise_derived/core_ext/string'
19
+
20
+
21
+ module PoiseDerived
22
+ # Here be dragons.
23
+ #
24
+ # @since 1.0.0
25
+ # @api private
26
+ module CoreExt
27
+ end
28
+ end
@@ -0,0 +1,55 @@
1
+ #
2
+ # Copyright 2016, Noah Kantrowitz
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+
17
+ require 'chef/mixin/deep_merge'
18
+
19
+ require 'poise_derived/lazy_attribute'
20
+
21
+
22
+ module PoiseDerived
23
+ module CoreExt
24
+ # Monkey-patch deep merge to carry over LazyAttribute on override. This means
25
+ # that if a lazy is set in a cookbook and then that attribute is overridden
26
+ # in a role or another cookbook, the override value gets wrapped in a
27
+ # LazyAttribute too so it will act as a template string. There is no way to
28
+ # put a block in JSON data so that would have to be explicit.
29
+ #
30
+ # @since 1.0.0
31
+ # @api private
32
+ module DeepMerge
33
+ def deep_merge!(source, dest)
34
+ if source.is_a?(PoiseDerived::LazyAttribute) && dest.is_a?(String)
35
+ source._override(dest)
36
+ else
37
+ super
38
+ end
39
+ end
40
+
41
+ def hash_only_merge!(merge_onto, merge_with)
42
+ if merge_onto.is_a?(PoiseDerived::LazyAttribute) && merge_with.is_a?(String)
43
+ merge_onto._override(merge_with)
44
+ else
45
+ super
46
+ end
47
+ end
48
+
49
+ # Kinder, gentler monkey patching. The singleton_class is the important
50
+ # one since everything in Chef calls these are module methods.
51
+ Chef::Mixin::DeepMerge.prepend(self)
52
+ Chef::Mixin::DeepMerge.singleton_class.prepend(self)
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,35 @@
1
+ #
2
+ # Copyright 2016, Noah Kantrowitz
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+
17
+ require 'poise_derived/lazy_attribute'
18
+
19
+
20
+ # Monkeypatch String so that our magic works in case statements. If you're
21
+ # reading this, something has probably gone wrong. I'm so sorry.
22
+ #
23
+ # @since 1.0.0
24
+ # @api private
25
+ class String
26
+ old_method = method(:===)
27
+ define_singleton_method(:===) do |obj|
28
+ if obj.class <= ::PoiseDerived::LazyAttribute
29
+ true
30
+ else
31
+ old_method.call(obj)
32
+ end
33
+ end
34
+ end
35
+
@@ -0,0 +1,45 @@
1
+ #
2
+ # Copyright 2016, Noah Kantrowitz
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+
17
+ require 'chef/node'
18
+
19
+ require 'poise_derived/lazy_attribute'
20
+
21
+
22
+ module PoiseDerived
23
+ module DSL
24
+ def _lazy_attribute(str=nil, &block)
25
+ PoiseDerived::LazyAttribute.new(self, str, &block)
26
+ end
27
+
28
+ # Install the DSL addition globally.
29
+ #
30
+ # @return [void]
31
+ def self.install
32
+ Chef::Log.debug('[poise-derived] Installing node DSL')
33
+ Chef::Node.prepend(self)
34
+ alias_method(:lazy, :_lazy_attribute)
35
+ end
36
+
37
+ # Disable the DSL extension so `node.lazy` will no longer work.
38
+ #
39
+ # @return [void]
40
+ def self.uninstall
41
+ Chef::Log.debug('[poise-derived] Uninstalling node DSL')
42
+ remove_method(:lazy)
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,43 @@
1
+ #
2
+ # Copyright 2016, Noah Kantrowitz
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+
17
+ require 'chef/chef_class'
18
+ require 'chef/event_dispatch/base'
19
+
20
+ require 'poise_derived/dsl'
21
+
22
+
23
+ module PoiseDerived
24
+ # A small event handler to install and uninstall our DSL addition so it only
25
+ # appears during attribute compilation. Is this a good idea?
26
+ #
27
+ # @api private
28
+ class Handler < Chef::EventDispatch::Base
29
+ include Singleton
30
+
31
+ def attribute_load_start(count)
32
+ PoiseDerived::DSL.install
33
+ end
34
+
35
+ def attribute_load_complete
36
+ PoiseDerived::DSL.uninstall
37
+ end
38
+
39
+ # Install event handler.
40
+ Chef::Log.debug('[poise-derived] Installing event handler')
41
+ Chef.run_context.events.register(self.instance)
42
+ end
43
+ end
@@ -0,0 +1,125 @@
1
+ #
2
+ # Copyright 2016, Noah Kantrowitz
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+
17
+
18
+ module PoiseDerived
19
+ # A class to lazily evaluate node attributes either from format strings or
20
+ # code blocks.
21
+ #
22
+ # @example Cookbook attributes file
23
+ # default['mycookbook']['version'] = '1.0'
24
+ # default['mycookbook']['url'] = lazy 'https://example.com/myapp-%{mycookbook.version}.zip'
25
+ class LazyAttribute
26
+ # Parser regexp for format strings.
27
+ # @api private
28
+ FORMAT_STRING_REGEXP = /%\{([^}]+)\}/
29
+
30
+ def initialize(node, str=nil, &block)
31
+ unless (!!str) ^ (!!block)
32
+ raise ArgumentError.new('Either a string or block must be passed when creating a lazy attribute')
33
+ end
34
+ @node = node
35
+ @value = str || block
36
+ end
37
+
38
+ # Shorter inspect output for debugging so we don't vomit the node into the
39
+ # log or spec output.
40
+ #
41
+ # @return [String]
42
+ def inspect
43
+ if @value.is_a?(Proc)
44
+ "#<#{self.class.name} @value=proc>"
45
+ else
46
+ "#<#{self.class.name} @value=#{@value.inspect}>"
47
+ end
48
+ end
49
+
50
+ # Return a copy of this attribute with the same node context but an
51
+ # override value as the format string. This can't support blocks because of
52
+ # how it is called generally.
53
+ #
54
+ # @api private
55
+ # @see PoiseDerived::CoreExt
56
+ # @return [PoiseDerived::LazyAttribute]
57
+ def _override(str)
58
+ self.class.new(@node, str)
59
+ end
60
+
61
+ # @!group delegators
62
+ # Delegate to the lazy value.
63
+ # @api private
64
+ def method_missing(method, *args, &block)
65
+ _evaluate.send(method, *args, &block)
66
+ end
67
+
68
+ # Delegate to the lazy value.
69
+ # @api private
70
+ def respond_to_missing?(method, include_all)
71
+ _evaluate.respond_to?(method, include_all)
72
+ end
73
+
74
+ # Delegate to the lazy value.
75
+ # @api private
76
+ def to_s
77
+ _evaluate.to_s
78
+ end
79
+
80
+ # Make sure we implement to_str because that activates the == hack.
81
+ # @api private
82
+ def to_str
83
+ to_s
84
+ end
85
+
86
+ # Fake is_a? and kind_of? for params_validate and `_pv_kind_of`.
87
+ # @api private
88
+ def is_a?(klass)
89
+ return true if Object.instance_method(:is_a?).bind(self).call(klass)
90
+ klass <= String
91
+ end
92
+
93
+ # @api private
94
+ # @see is_a?
95
+ alias_method :kind_of?, :is_a?
96
+
97
+ # Specifically delegate == because not caught by method_missing.
98
+ # @api private
99
+ def ==(other)
100
+ _evaluate == other
101
+ end
102
+ # !@endgroup
103
+
104
+ private
105
+
106
+ # Evaluate the lazy attribute.
107
+ #
108
+ # @return [Object]
109
+ def _evaluate
110
+ return @evaluate_cache if defined?(@evaluate_cache)
111
+ @evaluate_cache = if @value.is_a?(Proc)
112
+ # Block mode, just run the block.
113
+ @node.instance_eval(&@value).to_s
114
+ else
115
+ # String mode, parse the template string and fill it in.
116
+ format_keys = @value.scan(FORMAT_STRING_REGEXP).inject({}) do |memo, (key)|
117
+ # Keys must be symbols because Ruby.
118
+ memo[key.to_sym] = key.split(/\./).inject(@node) {|n, k| n.nil? ? n : n[k] }
119
+ memo
120
+ end
121
+ @value % format_keys
122
+ end
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,20 @@
1
+ #
2
+ # Copyright 2016, Noah Kantrowitz
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+
17
+
18
+ module PoiseDerived
19
+ VERSION = '1.0.0'
20
+ end
@@ -0,0 +1,41 @@
1
+ #
2
+ # Copyright 2016, Noah Kantrowitz
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+
17
+ lib = File.expand_path('../lib', __FILE__)
18
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
19
+ require 'poise_derived/version'
20
+
21
+ Gem::Specification.new do |spec|
22
+ spec.name = 'poise-derived'
23
+ spec.version = PoiseDerived::VERSION
24
+ spec.authors = ['Noah Kantrowitz']
25
+ spec.email = %w{noah@coderanger.net}
26
+ spec.description = 'A Chef cookbook for defining lazily evaluated node attributes.'
27
+ spec.summary = spec.description
28
+ spec.homepage = 'https://github.com/poise/poise-derived'
29
+ spec.license = 'Apache 2.0'
30
+
31
+ spec.files = `git ls-files`.split($/)
32
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
33
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
34
+ spec.require_paths = %w{lib}
35
+
36
+ spec.add_dependency 'chef', '~> 12.3'
37
+ spec.add_dependency 'halite', '~> 1.0'
38
+
39
+ spec.add_development_dependency 'poise', '~> 2.0'
40
+ spec.add_development_dependency 'poise-boiler', '~> 1.0'
41
+ end
@@ -0,0 +1,46 @@
1
+ #
2
+ # Copyright 2016, Noah Kantrowitz
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+
17
+ # Baseline for sanity.
18
+ default['a']['value'] = 'a'
19
+
20
+ default['b']['value'] = lazy 'b'
21
+
22
+ default['c']['one'] = '1'
23
+ default['c']['value'] = lazy '%{c.one}'
24
+
25
+ default['d']['value'] = lazy { 'd' }
26
+
27
+ default['e']['one'] = 1
28
+ default['e']['two'] = '2'
29
+ default['e']['value'] = lazy '%{e.one} %{e.two}'
30
+
31
+ # This value is changed later.
32
+ default['f']['one'] = '1'
33
+ default['f']['value'] = lazy '%{f.one}'
34
+
35
+ # This value is changed later.
36
+ default['g']['one'] = '1'
37
+ default['g']['value'] = lazy { node['g']['one'] }
38
+
39
+ # This template is changed later.
40
+ default['h']['one'] = '1'
41
+ default['h']['two'] = '2'
42
+ default['h']['value'] = lazy 'one %{h.one}'
43
+
44
+ default['i']['one'] = '1'
45
+ default['i']['two'] = lazy 'one %{i.one}'
46
+ default['i']['value'] = lazy 'two %{i.two} 2'