Initial Chef repository
This commit is contained in:
78
cookbooks/poise/CHANGELOG.md
Normal file
78
cookbooks/poise/CHANGELOG.md
Normal file
@@ -0,0 +1,78 @@
|
||||
# Changelog
|
||||
|
||||
## v2.0.1
|
||||
|
||||
* Make the ChefspecHelpers helper a no-op if chefspec is not already loaded.
|
||||
* Fix for finding the correct cookbook for a file when using vendored gems.
|
||||
* New flag for the OptionCollector helper, `parser`:
|
||||
|
||||
```ruby
|
||||
class Resource < Chef::Resource
|
||||
include Poise
|
||||
attribute(:options, option_collector: true, parser: proc {|val| parse(val) })
|
||||
|
||||
def parse(val)
|
||||
{name: val}
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
* Fix for a possible infinite loop when using `ResourceProviderMixin` in a nested
|
||||
module structure.
|
||||
|
||||
## v2.0.0
|
||||
|
||||
Major overhaul! Poise is now a Halite gem/cookbook. New helpers:
|
||||
|
||||
* ChefspecMatchers – Automatically create Chefspec matchers for Poise resources.
|
||||
* DefinedIn – Track which file (and cookbook) a resource or provider is defined in.
|
||||
* Fused – Experimental support for defining provider actions in the resource class.
|
||||
* Inversion – Support for end-user dependency inversion with providers.
|
||||
|
||||
All helpers are compatible with Chef >= 12.0. Chef 11 is now deprecated, if you
|
||||
need to support Chef 11 please continue to use Poise 1.
|
||||
|
||||
## v1.0.12
|
||||
|
||||
* Correctly propagate errors from inside notifying_block.
|
||||
|
||||
## v1.0.10
|
||||
|
||||
* Fixes an issue with the LWRPPolyfill helper and false values.
|
||||
|
||||
|
||||
## v1.0.8
|
||||
|
||||
* Delayed notifications from nested converges will still only run at the end of
|
||||
the main converge.
|
||||
|
||||
## v1.0.6
|
||||
|
||||
* The include_recipe helper now works correctly when used at compile time.
|
||||
|
||||
## v1.0.4
|
||||
|
||||
* Redeclaring a template attribute with the same name as a parent class will
|
||||
inherit its options.
|
||||
|
||||
## v1.0.2
|
||||
|
||||
* New template attribute pattern.
|
||||
|
||||
```ruby
|
||||
attribute(:config, template: true)
|
||||
|
||||
...
|
||||
|
||||
resource 'name' do
|
||||
config_source 'template.erb'
|
||||
end
|
||||
|
||||
...
|
||||
|
||||
new_resource.config_content
|
||||
```
|
||||
|
||||
## v1.0.0
|
||||
|
||||
* Initial release!
|
||||
198
cookbooks/poise/README.md
Normal file
198
cookbooks/poise/README.md
Normal file
@@ -0,0 +1,198 @@
|
||||
# Poise
|
||||
|
||||
[](https://travis-ci.org/poise/poise)
|
||||
[](https://rubygems.org/gems/poise)
|
||||
[](https://supermarket.chef.io/cookbooks/poise)
|
||||
[](https://codecov.io/github/poise/poise)
|
||||
[](https://gemnasium.com/poise/poise)
|
||||
[](https://www.apache.org/licenses/LICENSE-2.0)
|
||||
|
||||
## What is Poise?
|
||||
|
||||
The poise cookbook is a set of libraries for writing reusable cookbooks. It
|
||||
providers helpers for common patterns and a standard structure to make it easier to create flexible cookbooks.
|
||||
|
||||
## Writing your first resource
|
||||
|
||||
Rather than LWRPs, Poise promotes the idea of using normal, or "heavy weight"
|
||||
resources, while including helpers to reduce much of boilerplate needed for this. Each resource goes in its own file under `libraries/` named to match
|
||||
the resource, which is in turn based on the class name. This means that the file `libraries/my_app.rb` would contain `Chef::Resource::MyApp` which maps to the resource `my_app`.
|
||||
|
||||
An example of a simple shell to start from:
|
||||
|
||||
```ruby
|
||||
class Chef
|
||||
class Resource::MyApp < Resource
|
||||
include Poise
|
||||
|
||||
actions(:enable)
|
||||
|
||||
attribute(:path, kind_of: String)
|
||||
... # Other attribute definitions
|
||||
end
|
||||
|
||||
class Provider::MyApp < Provider
|
||||
include Poise
|
||||
|
||||
def action_enable
|
||||
converge_by("enable resource #{new_resource.name}") do
|
||||
notifying_block do
|
||||
... # Normal Chef recipe code goes here
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
Starting from the top, first we declare the resource class, which inherits from
|
||||
`Chef::Resource`. This is similar to the `resources/` file in an LWRP, and a similar DSL can be used. In order to load the helpers into the class, we
|
||||
include the `Poise` mixin. Then we use the familiar DSL, though with a few additions we'll cover later.
|
||||
|
||||
Then we declare the provider class, again similar to the `providers/` file in an LWRP. We include the `Poise` mixin again to get access to all the helpers. Rather than use the `action :enable do ... end` DSL from LWRPs, we just define the action method directly, and use the `converge_by` method to provide a description of what the action does. The implementation of action comes from a block of recipe code wrapped with `notifying_block` to capture changes in much the same way as `use_inline_resources`, see below for more information about all the features of `notifying_block`.
|
||||
|
||||
We can then use this resource like any other Chef resource:
|
||||
|
||||
```ruby
|
||||
my_app 'one' do
|
||||
path '/tmp'
|
||||
end
|
||||
```
|
||||
|
||||
## Helpers
|
||||
|
||||
While not exposed as a specific method, Poise will automatically set the
|
||||
`resource_name` based on the class name.
|
||||
|
||||
### Notifying Block
|
||||
|
||||
As mentioned above, `notifying_block` is similar to `use_inline_resources` in LWRPs. Any Chef resource created inside the block will be converged in a sub-context and if any have updated it will trigger notifications on the current resource. Unlike `use_inline_resources`, resources inside the sub-context can still see resources outside of it, with lookups propagating up sub-contexts until a match is found. Also any delayed notifications are scheduled to run at the end of the main converge cycle, instead of the end of this inner converge.
|
||||
|
||||
This can be used to write action methods using the normal Chef recipe DSL, while still offering more flexibility through subclassing and other forms of code reuse.
|
||||
|
||||
### Include Recipe
|
||||
|
||||
In keeping with `notifying_block` to implement action methods using the Chef DSL, Poise adds an `include_recipe` helper to match the method of the same name in recipes. This will load and converge the requested recipe.
|
||||
|
||||
### Resource DSL
|
||||
|
||||
To make writing resource classes easier, Poise exposes a DSL similar to LWRPs for defining actions and attributes. Both `actions` and
|
||||
`default_action` are just like in LWRPs, though `default_action` is rarely needed as the first action becomes the default. `attribute` is also available just like in LWRPs, but with some enhancements noted below.
|
||||
|
||||
One notable difference over the standard DSL method is that Poise attributes
|
||||
can take a block argument.
|
||||
|
||||
#### Template Content
|
||||
|
||||
A common pattern with resources is to allow passing either a template filename or raw file content to be used in a configuration file. Poise exposes a new attribute flag to help with this behavior:
|
||||
|
||||
```ruby
|
||||
attribute(:name, template: true)
|
||||
```
|
||||
|
||||
This creates four methods on the class, `name_source`, `name_cookbook`,
|
||||
`name_content`, and `name_options`. If the name is set to `''`, no prefix is applied to the function names. The content method can be set directly, but if not set and source is set, then it will render the template and return it as a string. Default values can also be set for any of these:
|
||||
|
||||
```ruby
|
||||
attribute(:name, template: true, default_source: 'app.cfg.erb',
|
||||
default_options: {host: 'localhost'})
|
||||
```
|
||||
|
||||
As an example, you can replace this:
|
||||
|
||||
```ruby
|
||||
if new_resource.source
|
||||
template new_resource.path do
|
||||
source new_resource.source
|
||||
owner 'app'
|
||||
group 'app'
|
||||
variables new_resource.options
|
||||
end
|
||||
else
|
||||
file new_resource.path do
|
||||
content new_resource.content
|
||||
owner 'app'
|
||||
group 'app'
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
with simply:
|
||||
|
||||
```ruby
|
||||
file new_resource.path do
|
||||
content new_resource.content
|
||||
owner 'app'
|
||||
group 'app'
|
||||
end
|
||||
```
|
||||
|
||||
As the content method returns the rendered template as a string, this can also
|
||||
be useful within other templates to build from partials.
|
||||
|
||||
#### Lazy Initializers
|
||||
|
||||
One issue with Poise-style resources is that when the class definition is executed, Chef hasn't loaded very far so things like the node object are not
|
||||
yet available. This means setting defaults based on node attributes does not work directly:
|
||||
|
||||
```ruby
|
||||
attribute(:path, default: node['myapp']['path'])
|
||||
...
|
||||
NameError: undefined local variable or method 'node'
|
||||
```
|
||||
|
||||
To work around this, Poise extends the idea of lazy initializers from Chef recipes to work with resource definitions as well:
|
||||
|
||||
```ruby
|
||||
attribute(:path, default: lazy { node['myapp']['path'] })
|
||||
```
|
||||
|
||||
These initializers are run in the context of the resource object, allowing
|
||||
complex default logic to be moved to a method if desired:
|
||||
|
||||
```ruby
|
||||
attribute(:path, default: lazy { my_default_path })
|
||||
|
||||
def my_default_path
|
||||
...
|
||||
end
|
||||
```
|
||||
|
||||
#### Option Collector
|
||||
|
||||
Another common pattern with resources is to need a set of key/value pairs for
|
||||
configuration data or options. This can done with a simple Hash, but an option collector attribute can offer a nicer syntax:
|
||||
|
||||
```ruby
|
||||
attribute(:mydata, option_collector: true)
|
||||
...
|
||||
|
||||
my_app 'name' do
|
||||
mydata do
|
||||
key1 'value1'
|
||||
key2 'value2'
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
This will be converted to `{key1: 'value1', key2: 'value2'}`. You can also pass a Hash to an option collector attribute just as you would with a normal attribute.
|
||||
|
||||
## Sponsors
|
||||
|
||||
The Poise test server infrastructure is generously sponsored by [Rackspace](https://rackspace.com/). Thanks Rackspace!
|
||||
|
||||
## License
|
||||
|
||||
Copyright 2013-2015, Noah Kantrowitz
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
71
cookbooks/poise/files/halite_gem/poise.rb
Normal file
71
cookbooks/poise/files/halite_gem/poise.rb
Normal file
@@ -0,0 +1,71 @@
|
||||
#
|
||||
# Copyright 2013-2015, Noah Kantrowitz
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
require 'chef/provider'
|
||||
require 'chef/resource'
|
||||
|
||||
require 'poise/utils/resource_provider_mixin'
|
||||
|
||||
|
||||
module Poise
|
||||
include Poise::Utils::ResourceProviderMixin
|
||||
autoload :Helpers, 'poise/helpers'
|
||||
autoload :Provider, 'poise/provider'
|
||||
autoload :Resource, 'poise/resource'
|
||||
autoload :Subcontext, 'poise/subcontext'
|
||||
autoload :Utils, 'poise/utils'
|
||||
autoload :VERSION, 'poise/version'
|
||||
end
|
||||
|
||||
# Callable form to allow passing in options:
|
||||
# include Poise(ParentResource)
|
||||
# include Poise(parent: ParentResource)
|
||||
# include Poise(container: true)
|
||||
def Poise(options={})
|
||||
# Allow passing a class as a shortcut
|
||||
if options.is_a?(Class)
|
||||
options = {parent: options}
|
||||
end
|
||||
|
||||
# Create a new anonymous module
|
||||
mod = Module.new
|
||||
|
||||
# Fake the name.
|
||||
mod.define_singleton_method(:name) do
|
||||
super() || 'Poise'
|
||||
end
|
||||
|
||||
mod.define_singleton_method(:included) do |klass|
|
||||
super(klass)
|
||||
# Pull in the main helper to cover most of the needed logic.
|
||||
klass.class_exec { include Poise }
|
||||
# Set the defined_in values as needed.
|
||||
klass.poise_defined!(caller)
|
||||
# Resource-specific options.
|
||||
if klass < Chef::Resource
|
||||
klass.poise_subresource(options[:parent], options[:parent_optional], options[:parent_auto]) if options[:parent]
|
||||
klass.poise_subresource_container(options[:container_namespace]) if options[:container]
|
||||
klass.poise_fused if options[:fused]
|
||||
klass.poise_inversion(options[:inversion_options_resource]) if options[:inversion]
|
||||
end
|
||||
# Provider-specific options.
|
||||
if klass < Chef::Provider
|
||||
klass.poise_inversion(options[:inversion], options[:inversion_attribute]) if options[:inversion]
|
||||
end
|
||||
end
|
||||
|
||||
mod
|
||||
end
|
||||
24
cookbooks/poise/files/halite_gem/poise/error.rb
Normal file
24
cookbooks/poise/files/halite_gem/poise/error.rb
Normal file
@@ -0,0 +1,24 @@
|
||||
#
|
||||
# Copyright 2015, Noah Kantrowitz
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
|
||||
module Poise
|
||||
# Base exception class for Poise errors.
|
||||
#
|
||||
# @since 2.0.0
|
||||
class Error < Exception
|
||||
end
|
||||
end
|
||||
33
cookbooks/poise/files/halite_gem/poise/helpers.rb
Normal file
33
cookbooks/poise/files/halite_gem/poise/helpers.rb
Normal file
@@ -0,0 +1,33 @@
|
||||
#
|
||||
# Copyright 2015, Noah Kantrowitz
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
|
||||
module Poise
|
||||
module Helpers
|
||||
autoload :ChefspecMatchers, 'poise/helpers/chefspec_matchers'
|
||||
autoload :DefinedIn, 'poise/helpers/defined_in'
|
||||
autoload :Fused, 'poise/helpers/fused'
|
||||
autoload :IncludeRecipe, 'poise/helpers/include_recipe'
|
||||
autoload :Inversion, 'poise/helpers/inversion'
|
||||
autoload :LazyDefault, 'poise/helpers/lazy_default'
|
||||
autoload :LWRPPolyfill, 'poise/helpers/lwrp_polyfill'
|
||||
autoload :NotifyingBlock, 'poise/helpers/notifying_block'
|
||||
autoload :OptionCollector, 'poise/helpers/option_collector'
|
||||
autoload :ResourceName, 'poise/helpers/resource_name'
|
||||
autoload :Subresources, 'poise/helpers/subresources'
|
||||
autoload :TemplateContent, 'poise/helpers/template_content'
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,88 @@
|
||||
#
|
||||
# Copyright 2013-2015, Noah Kantrowitz
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
# Not requiring chefspec or rspec/expectations since this code should only
|
||||
# activate if they are already loaded.
|
||||
|
||||
require 'poise/helpers/lwrp_polyfill'
|
||||
require 'poise/helpers/resource_name'
|
||||
|
||||
|
||||
module Poise
|
||||
module Helpers
|
||||
# A resource mixin to register ChefSpec matchers for a resource
|
||||
# automatically.
|
||||
#
|
||||
# If you are using the provides() form for naming resources, ensure that is
|
||||
# set before declaring actions.
|
||||
#
|
||||
# @since 2.0.0
|
||||
# @example Define a class
|
||||
# class Chef::Resource::MyResource < Chef::Resource
|
||||
# include Poise::Helpers::ChefspecMatchers
|
||||
# actions(:run)
|
||||
# end
|
||||
# @example Use a matcher
|
||||
# expect(chef_run).to run_my_resource('...')
|
||||
module ChefspecMatchers
|
||||
include Poise::Helpers::LWRPPolyfill::Resource
|
||||
include Poise::Helpers::ResourceName
|
||||
|
||||
# Create a matcher for a given resource type and action. This is
|
||||
# idempotent so if a matcher already exists, it will not be recreated.
|
||||
#
|
||||
# @api private
|
||||
def self.create_matcher(resource, action)
|
||||
# Check that we have everything we need.
|
||||
return unless defined?(ChefSpec) && defined?(RSpec::Matchers) && resource
|
||||
method = :"#{action}_#{resource}"
|
||||
return if RSpec::Matchers.method_defined?(method)
|
||||
RSpec::Matchers.send(:define_method, method) do |resource_name|
|
||||
ChefSpec::Matchers::ResourceMatcher.new(resource, action, resource_name)
|
||||
end
|
||||
end
|
||||
|
||||
# @!classmethods
|
||||
module ClassMethods
|
||||
# Create a resource-level matcher for this resource.
|
||||
#
|
||||
# @see Resource::ResourceName.provides
|
||||
def provides(name)
|
||||
ChefSpec.define_matcher(name) if defined?(ChefSpec)
|
||||
super
|
||||
end
|
||||
|
||||
# Create matchers for all declared actions.
|
||||
#
|
||||
# @see Resource::LWRPPolyfill.actions
|
||||
def actions(*names)
|
||||
super.tap do |actions|
|
||||
actions.each do |action|
|
||||
ChefspecMatchers.create_matcher(resource_name, action)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def included(klass)
|
||||
super
|
||||
klass.extend ClassMethods
|
||||
end
|
||||
end
|
||||
|
||||
extend ClassMethods
|
||||
end
|
||||
end
|
||||
end
|
||||
111
cookbooks/poise/files/halite_gem/poise/helpers/defined_in.rb
Normal file
111
cookbooks/poise/files/halite_gem/poise/helpers/defined_in.rb
Normal file
@@ -0,0 +1,111 @@
|
||||
#
|
||||
# Copyright 2015, Noah Kantrowitz
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
require 'poise/error'
|
||||
require 'poise/utils'
|
||||
|
||||
|
||||
module Poise
|
||||
module Helpers
|
||||
# A mixin to track where a resource or provider was defined. This can
|
||||
# provide either the filename of the class or the cookbook it was defined in.
|
||||
#
|
||||
# @since 2.0.0
|
||||
# @example
|
||||
# class MyProvider < Chef::provider
|
||||
# include Poise::Helpers::DefinedIn
|
||||
#
|
||||
# def action_create
|
||||
# template '...' do
|
||||
# # ...
|
||||
# cookbook new_resource.poise_defined_in
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
module DefinedIn
|
||||
# Wrapper for {.poise_defined_in_cookbook} to pass the run context for you.
|
||||
#
|
||||
# @see .poise_defined_in_cookbook
|
||||
# @param file [String, nil] Optional file path to check instead of the path
|
||||
# this class was defined in.
|
||||
# @return [String]
|
||||
def poise_defined_in_cookbook(file=nil)
|
||||
self.class.poise_defined_in_cookbook(run_context, file)
|
||||
end
|
||||
|
||||
# @!classmethods
|
||||
module ClassMethods
|
||||
# The file this class or module was defined in, or nil if it isn't found.
|
||||
#
|
||||
# @return [String]
|
||||
def poise_defined_in
|
||||
raise Poise::Error.new("Unable to determine location of #{self.name}") unless @poise_defined_in
|
||||
@poise_defined_in
|
||||
end
|
||||
|
||||
# The cookbook this class or module was defined in. Can pass a file to
|
||||
# check that instead.
|
||||
#
|
||||
# @param run_context [Chef::RunContext] Run context to check cookbooks in.
|
||||
# @param file [String, nil] Optional file path to check instead of the
|
||||
# path this class was defined in.
|
||||
# @return [String]
|
||||
def poise_defined_in_cookbook(run_context, file=nil)
|
||||
file ||= poise_defined_in
|
||||
Chef::Log.debug("[#{self.name}] Checking cookbook name for #{file}")
|
||||
Poise::Utils.find_cookbook_name(run_context, file).tap do |cookbook|
|
||||
Chef::Log.debug("[#{self.name}] found cookbook #{cookbook.inspect}")
|
||||
end
|
||||
end
|
||||
|
||||
# Record that the class/module was defined. Called automatically by Ruby
|
||||
# for all normal cases.
|
||||
#
|
||||
# @param caller_array [Array<String>] A strack trace returned by #caller.
|
||||
# @return [void]
|
||||
def poise_defined!(caller_array)
|
||||
# Only try to set this once.
|
||||
return if @poise_defined_in
|
||||
# Path to ignore, assumes Halite transformation which I'm not thrilled
|
||||
# about.
|
||||
poise_libraries = File.expand_path('../..', __FILE__)
|
||||
# Parse out just the filenames.
|
||||
caller_array = caller_array.map {|line| line.split(/:/, 2).first }
|
||||
# Find the first non-poise line.
|
||||
caller_path = caller_array.find do |line|
|
||||
!line.start_with?(poise_libraries)
|
||||
end
|
||||
Chef::Log.debug("[#{self.name}] Recording poise_defined_in as #{caller_path}")
|
||||
@poise_defined_in = caller_path
|
||||
end
|
||||
|
||||
# @api private
|
||||
def inherited(klass)
|
||||
super
|
||||
klass.poise_defined!(caller)
|
||||
end
|
||||
|
||||
def included(klass)
|
||||
super
|
||||
klass.extend(ClassMethods)
|
||||
klass.poise_defined!(caller)
|
||||
end
|
||||
end
|
||||
|
||||
extend ClassMethods
|
||||
end
|
||||
end
|
||||
end
|
||||
127
cookbooks/poise/files/halite_gem/poise/helpers/fused.rb
Normal file
127
cookbooks/poise/files/halite_gem/poise/helpers/fused.rb
Normal file
@@ -0,0 +1,127 @@
|
||||
#
|
||||
# Copyright 2013-2015, Noah Kantrowitz
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
require 'chef/provider'
|
||||
|
||||
|
||||
module Poise
|
||||
module Helpers
|
||||
# Resource mixin to create "fused" resources where the resource and provider
|
||||
# are implemented in the same class.
|
||||
#
|
||||
# @since 2.0.0
|
||||
# @example
|
||||
# class Chef::Resource::MyResource < Chef::Resource
|
||||
# include Poise(fused: true)
|
||||
# attribute(:path, kind_of: String)
|
||||
# attribute(:message, kind_of: String)
|
||||
# action(:run) do
|
||||
# file new_resource.path do
|
||||
# content new_resource.message
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
module Fused
|
||||
# Hack is_a? so that the DSL will consider this a Provider for the
|
||||
# purposes of attaching enclosing_provider.
|
||||
#
|
||||
# @api private
|
||||
# @param klass [Class]
|
||||
# @return [Boolean]
|
||||
def is_a?(klass)
|
||||
if klass == Chef::Provider
|
||||
# Lies, damn lies, and Ruby code.
|
||||
true
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
# Hack provider_for_action so that the resource is also the provider.
|
||||
#
|
||||
# @api private
|
||||
# @param action [Symbol]
|
||||
# @return [Chef::Provider]
|
||||
def provider_for_action(action)
|
||||
provider(self.class.fused_provider_class) unless provider
|
||||
super
|
||||
end
|
||||
|
||||
# @!classmethods
|
||||
module ClassMethods
|
||||
# Define a provider action. The block should contain the usual provider
|
||||
# code.
|
||||
#
|
||||
# @param name [Symbol] Name of the action.
|
||||
# @param block [Proc] Action implementation.
|
||||
# @example
|
||||
# action(:run) do
|
||||
# file '/temp' do
|
||||
# user 'root'
|
||||
# content 'temp'
|
||||
# end
|
||||
# end
|
||||
def action(name, &block)
|
||||
fused_actions[name.to_sym] = block
|
||||
# Make sure this action is allowed, also sets the default if first.
|
||||
if respond_to?(:actions)
|
||||
actions(name.to_sym)
|
||||
end
|
||||
end
|
||||
|
||||
# Storage accessor for fused action blocks. Maps action name to proc.
|
||||
#
|
||||
# @api private
|
||||
# @return [Hash<Symbol, Proc>]
|
||||
def fused_actions
|
||||
(@fused_actions ||= {})
|
||||
end
|
||||
|
||||
# Create a provider class for the fused actions in this resource.
|
||||
# Inherits from the fused provider class of the resource's superclass if
|
||||
# present.
|
||||
#
|
||||
# @api private
|
||||
# @return [Class]
|
||||
def fused_provider_class
|
||||
@fused_provider_class ||= begin
|
||||
provider_superclass = begin
|
||||
self.superclass.fused_provider_class
|
||||
rescue NoMethodError
|
||||
Chef::Provider
|
||||
end
|
||||
actions = fused_actions
|
||||
class_name = self.name
|
||||
Class.new(provider_superclass) do
|
||||
include Poise
|
||||
define_singleton_method(:name) { class_name + ' (fused)' }
|
||||
actions.each do |action, block|
|
||||
define_method(:"action_#{action}", &block)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def included(klass)
|
||||
super
|
||||
klass.extend(ClassMethods)
|
||||
end
|
||||
end
|
||||
|
||||
extend ClassMethods
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,62 @@
|
||||
#
|
||||
# Copyright 2013-2015, Noah Kantrowitz
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
require 'poise/helpers/subcontext_block'
|
||||
require 'poise/subcontext/runner'
|
||||
|
||||
|
||||
module Poise
|
||||
module Helpers
|
||||
# A provider mixin to add #include_recipe that can be called from action
|
||||
# methods.
|
||||
#
|
||||
# @since 2.0.0
|
||||
module IncludeRecipe
|
||||
include Poise::Helpers::SubcontextBlock
|
||||
|
||||
def include_recipe(*recipes)
|
||||
loaded_recipes = []
|
||||
subcontext = subcontext_block do
|
||||
recipes.each do |recipe|
|
||||
case recipe
|
||||
when String
|
||||
# Process normally
|
||||
Chef::Log.debug("Loading recipe #{recipe} via include_recipe (poise)")
|
||||
loaded_recipes += run_context.include_recipe(recipe)
|
||||
when Proc
|
||||
# Pretend its a block of recipe code
|
||||
fake_recipe = Chef::Recipe.new(cookbook_name, new_resource.recipe_name, run_context)
|
||||
fake_recipe.instance_eval(&recipe)
|
||||
loaded_recipes << fake_recipe
|
||||
end
|
||||
end
|
||||
end
|
||||
# Converge the new context.
|
||||
Poise::Subcontext::Runner.new(new_resource, subcontext).converge
|
||||
collection = global_resource_collection
|
||||
subcontext.resource_collection.each do |r|
|
||||
Chef::Log.debug("Poise::IncludeRecipe: Adding #{r} to global collection #{collection.object_id}")
|
||||
# Insert the local resource into the global context
|
||||
collection.insert(r)
|
||||
# Skip the iterator forward so we don't double-execute the inserted resource
|
||||
# If running at compile time, the iterator is nil
|
||||
collection.iterator.skip_forward if collection.iterator
|
||||
end
|
||||
loaded_recipes
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
374
cookbooks/poise/files/halite_gem/poise/helpers/inversion.rb
Normal file
374
cookbooks/poise/files/halite_gem/poise/helpers/inversion.rb
Normal file
@@ -0,0 +1,374 @@
|
||||
#
|
||||
# Copyright 2015, Noah Kantrowitz
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
require 'chef/node'
|
||||
require 'chef/node_map'
|
||||
require 'chef/provider'
|
||||
require 'chef/resource'
|
||||
|
||||
require 'poise/helpers/defined_in'
|
||||
require 'poise/error'
|
||||
require 'poise/helpers/inversion/options_resource'
|
||||
require 'poise/utils/resource_provider_mixin'
|
||||
|
||||
|
||||
module Poise
|
||||
module Helpers
|
||||
# A mixin for dependency inversion in Chef.
|
||||
#
|
||||
# @since 2.0.0
|
||||
module Inversion
|
||||
autoload :OptionsResource, 'poise/helpers/inversion/options_resource'
|
||||
autoload :OptionsProvider, 'poise/helpers/inversion/options_provider'
|
||||
|
||||
include Poise::Utils::ResourceProviderMixin
|
||||
|
||||
# Resource implementation for {Poise::Helpers::Inversion}.
|
||||
# @see Poise::Helpers::Inversion
|
||||
module Resource
|
||||
# @overload options(val=nil)
|
||||
# Set or return provider options for all providers.
|
||||
# @param val [Hash] Provider options to set.
|
||||
# @return [Hash]
|
||||
# @example
|
||||
# my_resource 'thing_one' do
|
||||
# options depends: 'thing_two'
|
||||
# end
|
||||
# @overload options(provider, val=nil)
|
||||
# Set or return provider options for a specific provider.
|
||||
# @param provider [Symbol] Provider to set for.
|
||||
# @param val [Hash] Provider options to set.
|
||||
# @return [Hash]
|
||||
# @example
|
||||
# my_resource 'thing_one' do
|
||||
# options :my_provider, depends: 'thing_two'
|
||||
# end
|
||||
def options(provider=nil, val=nil)
|
||||
key = :options
|
||||
if !val && provider.is_a?(Hash)
|
||||
val = provider
|
||||
elsif provider
|
||||
key = :"options_#{provider}"
|
||||
end
|
||||
set_or_return(key, val ? Mash.new(val) : val, kind_of: Hash, default: lazy { Mash.new })
|
||||
end
|
||||
|
||||
# Allow setting the provider directly using the same names as the attribute
|
||||
# settings.
|
||||
#
|
||||
# @param val [String, Symbol, Class, nil] Value to set the provider to.
|
||||
# @return [Class]
|
||||
# @example
|
||||
# my_resource 'thing_one' do
|
||||
# provider :my_provider
|
||||
# end
|
||||
def provider(val=nil)
|
||||
if val && !val.is_a?(Class)
|
||||
provider_class = Poise::Helpers::Inversion.provider_for(resource_name, node, val)
|
||||
Chef::Log.debug("[#{self}] Checking for an inversion provider for #{val}: #{provider_class && provider_class.name}")
|
||||
val = provider_class if provider_class
|
||||
end
|
||||
super
|
||||
end
|
||||
|
||||
# @!classmethods
|
||||
module ClassMethods
|
||||
# Options resource class.
|
||||
attr_reader :inversion_options_resource_class
|
||||
# Options provider class.
|
||||
attr_reader :inversion_options_provider_class
|
||||
|
||||
# @overload inversion_options_resource()
|
||||
# Return the options resource mode for this class.
|
||||
# @return [Boolean]
|
||||
# @overload inversion_options_resource(val)
|
||||
# Set the options resource mode for this class. Set to true to
|
||||
# automatically create an options resource. Defaults to true.
|
||||
# @param val [Boolean] Enable/disable setting.
|
||||
# @return [Boolean]
|
||||
def inversion_options_resource(val=nil)
|
||||
@poise_inversion_options_resource = val unless val.nil?
|
||||
@poise_inversion_options_resource
|
||||
end
|
||||
|
||||
# Create resource and provider classes for an options resource.
|
||||
#
|
||||
# @param name [String, Symbol] DSL name for the base resource.
|
||||
# @return [void]
|
||||
def create_inversion_options_resource!(name)
|
||||
enclosing_class = self
|
||||
options_resource_name = :"#{name}_options"
|
||||
# Create the resource class.
|
||||
@inversion_options_resource_class = Class.new(Chef::Resource) do
|
||||
include Poise::Helpers::Inversion::OptionsResource
|
||||
define_singleton_method(:name) do
|
||||
"#{enclosing_class}::OptionsResource"
|
||||
end
|
||||
provides(options_resource_name)
|
||||
inversion_resource(name)
|
||||
end
|
||||
# Create the provider class.
|
||||
@inversion_options_provider_class = Class.new(Chef::Provider) do
|
||||
include Poise::Helpers::Inversion::OptionsProvider
|
||||
define_singleton_method(:name) do
|
||||
"#{enclosing_class}::OptionsProvider"
|
||||
end
|
||||
provides(options_resource_name)
|
||||
end
|
||||
end
|
||||
|
||||
# Wrap #provides() to create an options resource if desired.
|
||||
#
|
||||
# @param name [Symbol] Resource name
|
||||
# return [void]
|
||||
def provides(name)
|
||||
create_inversion_options_resource!(name) if inversion_options_resource
|
||||
super if defined?(super)
|
||||
end
|
||||
|
||||
def included(klass)
|
||||
super
|
||||
klass.extend(ClassMethods)
|
||||
end
|
||||
end
|
||||
|
||||
extend ClassMethods
|
||||
end
|
||||
|
||||
# Provider implementation for {Poise::Helpers::Inversion}.
|
||||
# @see Poise::Helpers::Inversion
|
||||
module Provider
|
||||
include DefinedIn
|
||||
|
||||
# Compile all the different levels of inversion options together.
|
||||
#
|
||||
# @return [Hash]
|
||||
# @example
|
||||
# def action_run
|
||||
# if options['depends']
|
||||
# # ...
|
||||
# end
|
||||
# end
|
||||
def options
|
||||
@options ||= self.class.inversion_options(node, new_resource)
|
||||
end
|
||||
|
||||
# @!classmethods
|
||||
module ClassMethods
|
||||
# @overload inversion_resource()
|
||||
# Return the inversion resource name for this class.
|
||||
# @return [Symbol]
|
||||
# @overload inversion_resource(val)
|
||||
# Set the inversion resource name for this class. You can pass either
|
||||
# a symbol in DSL format or a resource class that uses Poise. This
|
||||
# name is used to determine which resources the inversion provider is
|
||||
# a candidate for.
|
||||
# @param val [Symbol, Class] Name to set.
|
||||
# @return [Symbol]
|
||||
def inversion_resource(val=nil)
|
||||
if val
|
||||
val = val.resource_name if val.is_a?(Class)
|
||||
Chef::Log.debug("[#{self.name}] Setting inversion resource to #{val}")
|
||||
@poise_inversion_resource = val.to_sym
|
||||
end
|
||||
@poise_inversion_resource || (superclass.respond_to?(:inversion_resource) ? superclass.inversion_resource : nil)
|
||||
end
|
||||
|
||||
# @overload inversion_attribute()
|
||||
# Return the inversion attribute name(s) for this class.
|
||||
# @return [Array<String>]
|
||||
# @overload inversion_attribute(val)
|
||||
# Set the inversion attribute name(s) for this class. This is
|
||||
# used by {.resolve_inversion_attribute} to load configuration data
|
||||
# from node attributes. To specify a nested attribute pass an array
|
||||
# of strings corresponding to the keys.
|
||||
# @param val [String, Array<String>] Attribute path.
|
||||
# @return [Array<String>]
|
||||
def inversion_attribute(val=nil)
|
||||
if val
|
||||
# Coerce to an array of strings.
|
||||
val = Array(val).map {|name| name.to_s }
|
||||
@poise_inversion_attribute = val
|
||||
end
|
||||
@poise_inversion_attribute || (superclass.respond_to?(:inversion_attribute) ? superclass.inversion_attribute : nil)
|
||||
end
|
||||
|
||||
# Default attribute paths to check for inversion options. Based on
|
||||
# the cookbook this class and its superclasses are defined in.
|
||||
#
|
||||
# @param node [Chef::Node] Node to load from.
|
||||
# @return [Array<Array<String>>]
|
||||
def default_inversion_attributes(node)
|
||||
klass = self
|
||||
tried = []
|
||||
while klass.respond_to?(:poise_defined_in_cookbook)
|
||||
cookbook = klass.poise_defined_in_cookbook(node.run_context)
|
||||
if node[cookbook]
|
||||
return [cookbook]
|
||||
end
|
||||
tried << cookbook
|
||||
klass = klass.superclass
|
||||
end
|
||||
raise Poise::Error.new("Unable to find inversion attributes, tried: #{tried.join(', ')}")
|
||||
end
|
||||
|
||||
# Resolve the node attribute used as the base for inversion options
|
||||
# for this class. This can be set explicitly with {.inversion_attribute}
|
||||
# or the default is to use the name of the cookbook the provider is
|
||||
# defined in.
|
||||
#
|
||||
# @param node [Chef::Node] Node to load from.
|
||||
# @return [Chef::Node::Attribute]
|
||||
def resolve_inversion_attribute(node)
|
||||
# Default to using just the name of the cookbook.
|
||||
attribute_names = inversion_attribute || default_inversion_attributes(node)
|
||||
attribute_names.inject(node) do |memo, key|
|
||||
memo[key] || begin
|
||||
raise Poise::Error.new("Attribute #{key} not set when expanding inversion attribute for #{self.name}: #{memo}")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Compile all the different levels of inversion options together.
|
||||
#
|
||||
# @param node [Chef::Node] Node to load from.
|
||||
# @param resource [Chef::Resource] Resource to load from.
|
||||
# @return [Hash]
|
||||
def inversion_options(node, resource)
|
||||
Mash.new.tap do |opts|
|
||||
attrs = resolve_inversion_attribute(node)
|
||||
# Cast the run state to a Mash because string vs. symbol keys. I can
|
||||
# at least promise poise_inversion will be a str so cut down on the
|
||||
# amount of data to convert.
|
||||
run_state = Mash.new(node.run_state.fetch('poise_inversion', {}).fetch(inversion_resource, {}))[resource.name] || {}
|
||||
# Class-level defaults.
|
||||
opts.update(default_inversion_options(node, resource))
|
||||
# Resource options for all providers.
|
||||
opts.update(resource.options)
|
||||
# Global provider from node attributes.
|
||||
opts.update(provider: attrs['provider']) if attrs['provider']
|
||||
# Attribute options for all providers.
|
||||
opts.update(attrs['options']) if attrs['options']
|
||||
# Resource options for this provider.
|
||||
opts.update(resource.options(provides))
|
||||
# Attribute options for this resource name.
|
||||
opts.update(attrs[resource.name]) if attrs[resource.name]
|
||||
# Options resource options for all providers.
|
||||
opts.update(run_state['*']) if run_state['*']
|
||||
# Options resource options for this provider.
|
||||
opts.update(run_state[provides]) if run_state[provides]
|
||||
end
|
||||
end
|
||||
|
||||
# Default options data for this provider class.
|
||||
#
|
||||
# @param node [Chef::Node] Node to load from.
|
||||
# @param resource [Chef::Resource] Resource to load from.
|
||||
# @return [Hash]
|
||||
def default_inversion_options(node, resource)
|
||||
{}
|
||||
end
|
||||
|
||||
# Resolve which provider name should be used for a resource.
|
||||
#
|
||||
# @param node [Chef::Node] Node to load from.
|
||||
# @param resource [Chef::Resource] Resource to query.
|
||||
# @return [String]
|
||||
def resolve_inversion_provider(node, resource)
|
||||
inversion_options(node, resource)['provider'] || 'auto'
|
||||
end
|
||||
|
||||
# Override the normal #provides to set the inversion provider name
|
||||
# instead of adding to the normal provider map.
|
||||
#
|
||||
# @overload provides()
|
||||
# Return the inversion provider name for the class.
|
||||
# @return [Symbol]
|
||||
# @overload provides(name, opts={}, &block)
|
||||
# Set the inversion provider name for the class.
|
||||
# @param name [Symbol] Provider name.
|
||||
# @param opts [Hash] NodeMap filter options.
|
||||
# @param block [Proc] NodeMap filter proc.
|
||||
# @return [Symbol]
|
||||
def provides(name=nil, opts={}, &block)
|
||||
if name
|
||||
raise Poise::Error.new("Inversion resource name not set for #{self.name}") unless inversion_resource
|
||||
@poise_inversion_provider = name
|
||||
Chef::Log.debug("[#{self.name}] Setting inversion provider name to #{name}")
|
||||
Poise::Helpers::Inversion.provider_map(inversion_resource).set(name.to_sym, self, opts, &block)
|
||||
# Set the actual Chef-level provides name for DSL dispatch.
|
||||
super(inversion_resource)
|
||||
end
|
||||
@poise_inversion_provider
|
||||
end
|
||||
|
||||
# Override the default #provides? to check for our inverted providers.
|
||||
#
|
||||
# @api private
|
||||
# @param node [Chef::Node] Node to use for attribute checks.
|
||||
# @param resource [Chef::Resource] Resource instance to match.
|
||||
# @return [Boolean]
|
||||
def provides?(node, resource)
|
||||
raise Poise::Error.new("Inversion resource name not set for #{self.name}") unless inversion_resource
|
||||
return false unless resource.resource_name == inversion_resource
|
||||
provider_name = resolve_inversion_provider(node, resource)
|
||||
Chef::Log.debug("[#{resource}] Checking provides? on #{self.name}. Got provider_name #{provider_name.inspect}")
|
||||
provider_name == provides.to_s || ( provider_name == 'auto' && provides_auto?(node, resource) )
|
||||
end
|
||||
|
||||
# Subclass hook to provide auto-detection for providers.
|
||||
#
|
||||
# @param node [Chef::Node] Node to check against.
|
||||
# @param resource [Chef::Resource] Resource to check against.
|
||||
# @return [Boolean]
|
||||
def provides_auto?(node, resource)
|
||||
false
|
||||
end
|
||||
|
||||
def included(klass)
|
||||
super
|
||||
klass.extend(ClassMethods)
|
||||
end
|
||||
end
|
||||
|
||||
extend ClassMethods
|
||||
end
|
||||
|
||||
# The provider map for a given resource type.
|
||||
#
|
||||
# @param resource_type [Symbol] Resource type in DSL format.
|
||||
# @return [Chef::NodeMap]
|
||||
# @example
|
||||
# Poise::Helpers::Inversion.provider_map(:my_resource)
|
||||
def self.provider_map(resource_type)
|
||||
@provider_maps ||= {}
|
||||
@provider_maps[resource_type.to_sym] ||= Chef::NodeMap.new
|
||||
end
|
||||
|
||||
# Find a specific provider class for a resource.
|
||||
#
|
||||
# @param resource_type [Symbol] Resource type in DSL format.
|
||||
# @param node [Chef::Node] Node to use for the lookup.
|
||||
# @param provider_type [Symbol] Provider type in DSL format.
|
||||
# @return [Class]
|
||||
# @example
|
||||
# Poise::Helpers::Inversion.provider_for(:my_resource, node, :my_provider)
|
||||
def self.provider_for(resource_type, node, provider_type)
|
||||
provider_map(resource_type).get(node, provider_type.to_sym)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,41 @@
|
||||
#
|
||||
# Copyright 2015, Noah Kantrowitz
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
|
||||
module Poise
|
||||
module Helpers
|
||||
module Inversion
|
||||
# A mixin for inversion options providers.
|
||||
#
|
||||
# @api private
|
||||
# @since 2.0.0
|
||||
# @see Poise::Helper::Inversion
|
||||
module OptionsProvider
|
||||
# @api private
|
||||
def self.included(klass)
|
||||
klass.class_exec { include Poise }
|
||||
end
|
||||
|
||||
# A blank run action.
|
||||
#
|
||||
# @return [void]
|
||||
def action_run
|
||||
# This space left intentionally blank.
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,101 @@
|
||||
#
|
||||
# Copyright 2015, Noah Kantrowitz
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
require 'chef/mash'
|
||||
|
||||
require 'poise/error'
|
||||
|
||||
|
||||
module Poise
|
||||
module Helpers
|
||||
module Inversion
|
||||
# A mixin for inversion options resources.
|
||||
#
|
||||
# @api private
|
||||
# @since 2.0.0
|
||||
# @see Poise::Helpers::Inversion
|
||||
module OptionsResource
|
||||
include Poise
|
||||
|
||||
# Method missing delegation to allow DSL-style options.
|
||||
#
|
||||
# @example
|
||||
# my_app_options 'app' do
|
||||
# key1 'value1'
|
||||
# key2 'value2'
|
||||
# end
|
||||
def method_missing(method_sym, *args, &block)
|
||||
super(method_sym, *args, &block)
|
||||
rescue NoMethodError
|
||||
# First time we've seen this key and using it as an rvalue, NOPE.GIF.
|
||||
raise unless !args.empty? || block || _options[method_sym]
|
||||
if !args.empty? || block
|
||||
_options[method_sym] = block || args.first
|
||||
end
|
||||
_options[method_sym]
|
||||
end
|
||||
|
||||
# Insert the options data in to the run state. This has to match the
|
||||
# layout used in {Poise::Helpers::Inversion::Provider.inversion_options}.
|
||||
#
|
||||
# @api private
|
||||
def after_created
|
||||
raise Poise::Error.new("Inversion resource name not set for #{self.class.name}") unless self.class.inversion_resource
|
||||
node.run_state['poise_inversion'] ||= {}
|
||||
node.run_state['poise_inversion'][self.class.inversion_resource] ||= {}
|
||||
node.run_state['poise_inversion'][self.class.inversion_resource][resource] ||= {}
|
||||
node.run_state['poise_inversion'][self.class.inversion_resource][resource][for_provider] ||= {}
|
||||
node.run_state['poise_inversion'][self.class.inversion_resource][resource][for_provider].update(_options)
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
# @overload inversion_resource()
|
||||
# Return the inversion resource name for this class.
|
||||
# @return [Symbol]
|
||||
# @overload inversion_resource(val)
|
||||
# Set the inversion resource name for this class. You can pass either
|
||||
# a symbol in DSL format or a resource class that uses Poise. This
|
||||
# name is used to determine which resources the inversion provider is
|
||||
# a candidate for.
|
||||
# @param val [Symbol, Class] Name to set.
|
||||
# @return [Symbol]
|
||||
def inversion_resource(val=nil)
|
||||
if val
|
||||
val = val.resource_name if val.is_a?(Class)
|
||||
Chef::Log.debug("[#{self.name}] Setting inversion resource to #{val}")
|
||||
@poise_inversion_resource = val.to_sym
|
||||
end
|
||||
@poise_inversion_resource || (superclass.respond_to?(:inversion_resource) ? superclass.inversion_resource : nil)
|
||||
end
|
||||
|
||||
# @api private
|
||||
def included(klass)
|
||||
super
|
||||
klass.extend(ClassMethods)
|
||||
klass.class_exec do
|
||||
actions(:run)
|
||||
attribute(:resource, kind_of: String, name_attribute: true)
|
||||
attribute(:for_provider, kind_of: [String, Symbol], default: '*')
|
||||
attribute(:_options, kind_of: Hash, default: lazy { Mash.new })
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
extend ClassMethods
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,62 @@
|
||||
#
|
||||
# Copyright 2013-2015, Noah Kantrowitz
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
|
||||
module Poise
|
||||
module Helpers
|
||||
# Resource mixin to allow lazyily-evaluated defaults in resource attributes.
|
||||
# This is designed to be used with {LWRPPolyfill} or a similar #attributes
|
||||
# method.
|
||||
#
|
||||
# @since 1.0.0
|
||||
# @example
|
||||
# class MyResource < Chef::Resource
|
||||
# include Poise::Helpers::LWRPPolyfill
|
||||
# include Poise::Helpers::LazyDefault
|
||||
# attribute(:path, default: lazy { name + '_temp' })
|
||||
# end
|
||||
module LazyDefault
|
||||
# Override the default set_or_return to support lazy evaluation of the
|
||||
# default value. This only actually matters when it is called from a class
|
||||
# level context via #attributes.
|
||||
def set_or_return(symbol, arg, validation)
|
||||
if validation && validation[:default].is_a?(Chef::DelayedEvaluator)
|
||||
validation = validation.dup
|
||||
validation[:default] = instance_eval(&validation[:default])
|
||||
end
|
||||
super(symbol, arg, validation)
|
||||
end
|
||||
|
||||
# @!classmethods
|
||||
module ClassMethods
|
||||
# Create a lazyily-evaluated block.
|
||||
#
|
||||
# @param block [Proc] Callable to return the default value.
|
||||
# @return [Chef::DelayedEvaluator]
|
||||
def lazy(&block)
|
||||
Chef::DelayedEvaluator.new(&block)
|
||||
end
|
||||
|
||||
def included(klass)
|
||||
super
|
||||
klass.extend(ClassMethods)
|
||||
end
|
||||
end
|
||||
|
||||
extend ClassMethods
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,96 @@
|
||||
#
|
||||
# Copyright 2013-2015, Noah Kantrowitz
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
require 'poise/utils/resource_provider_mixin'
|
||||
|
||||
|
||||
module Poise
|
||||
module Helpers
|
||||
# A resource and provider mixin to add back some compatability with Chef's
|
||||
# LWRPBase classes.
|
||||
#
|
||||
# @since 1.0.0
|
||||
module LWRPPolyfill
|
||||
include Poise::Utils::ResourceProviderMixin
|
||||
|
||||
# Provide default_action and actions like LWRPBase but better equipped for subclassing.
|
||||
module Resource
|
||||
def initialize(*args)
|
||||
super
|
||||
# Try to not stomp on stuff if already set in a parent
|
||||
@action = self.class.default_action if @action == :nothing
|
||||
(@allowed_actions << self.class.actions).flatten!.uniq!
|
||||
end
|
||||
|
||||
# @!classmethods
|
||||
module ClassMethods
|
||||
def default_action(name=nil)
|
||||
if name
|
||||
@default_action = name
|
||||
actions(name)
|
||||
end
|
||||
@default_action || ( respond_to?(:superclass) && superclass.respond_to?(:default_action) && superclass.default_action ) || actions.first || :nothing
|
||||
end
|
||||
|
||||
def actions(*names)
|
||||
@actions ||= ( respond_to?(:superclass) && superclass.respond_to?(:actions) ? superclass.actions.dup : [] )
|
||||
(@actions << names).flatten!.uniq!
|
||||
@actions
|
||||
end
|
||||
|
||||
def attribute(name, opts)
|
||||
# Ruby 1.8 can go to hell
|
||||
define_method(name) do |arg=nil, &block|
|
||||
arg = block if arg.nil? # Try to allow passing either
|
||||
set_or_return(name, arg, opts)
|
||||
end
|
||||
end
|
||||
|
||||
def included(klass)
|
||||
super
|
||||
klass.extend(ClassMethods)
|
||||
end
|
||||
end
|
||||
|
||||
extend ClassMethods
|
||||
end
|
||||
|
||||
# Helper to handle load_current_resource for direct subclasses of Provider
|
||||
module Provider
|
||||
# @!classmethods
|
||||
module ClassMethods
|
||||
def included(klass)
|
||||
super
|
||||
klass.extend(ClassMethods)
|
||||
|
||||
# Mask Chef::Provider#load_current_resource because it throws NotImplementedError.
|
||||
if klass.is_a?(Class) && klass.superclass == Chef::Provider
|
||||
klass.class_exec do
|
||||
def load_current_resource
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Reinstate the Chef DSL, removed in Chef 12.
|
||||
klass.class_exec { include Chef::DSL::Recipe }
|
||||
end
|
||||
end
|
||||
|
||||
extend ClassMethods
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,78 @@
|
||||
#
|
||||
# Copyright 2013-2015, Noah Kantrowitz
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
require 'poise/helpers/subcontext_block'
|
||||
require 'poise/subcontext/runner'
|
||||
|
||||
|
||||
module Poise
|
||||
module Helpers
|
||||
# A provider mixin to provide #notifying_block, a scoped form of Chef's
|
||||
# use_inline_resources.
|
||||
#
|
||||
# @since 1.0.0
|
||||
# @example
|
||||
# class MyProvider < Chef::Provider
|
||||
# include Chef::Helpers::NotifyingBlock
|
||||
#
|
||||
# def action_run
|
||||
# notifying_block do
|
||||
# template '/etc/myapp.conf' do
|
||||
# # ...
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
module NotifyingBlock
|
||||
include Poise::Helpers::SubcontextBlock
|
||||
|
||||
private
|
||||
|
||||
# Create and converge a subcontext for the recipe DSL. This is similar to
|
||||
# Chef's use_inline_resources but is scoped to a block. All DSL resources
|
||||
# declared inside the block will be converged when the block returns, and
|
||||
# the updated_by_last_action flag will be set if any of the inner
|
||||
# resources are updated.
|
||||
#
|
||||
# @api public
|
||||
# @param block [Proc] Block to run in the subcontext.
|
||||
# @return [void]
|
||||
# @example
|
||||
# def action_run
|
||||
# notifying_block do
|
||||
# template '/etc/myapp.conf' do
|
||||
# # ...
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
def notifying_block(&block)
|
||||
# Make sure to mark the resource as updated-by-last-action if
|
||||
# any sub-run-context resources were updated (any actual
|
||||
# actions taken against the system) during the
|
||||
# sub-run-context convergence.
|
||||
begin
|
||||
subcontext = subcontext_block(&block)
|
||||
# Converge the new context.
|
||||
Poise::Subcontext::Runner.new(new_resource, subcontext).converge
|
||||
ensure
|
||||
new_resource.updated_by_last_action(
|
||||
subcontext && subcontext.resource_collection.any?(&:updated?)
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,131 @@
|
||||
#
|
||||
# Copyright 2013-2015, Noah Kantrowitz
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
require 'chef/mash'
|
||||
|
||||
require 'poise/error'
|
||||
|
||||
|
||||
module Poise
|
||||
module Helpers
|
||||
# A resource mixin to add a new kind of attribute, an option collector.
|
||||
# These attributes can act as mini-DSLs for things which would otherwise be
|
||||
# key/value pairs.
|
||||
#
|
||||
# @since 1.0.0
|
||||
# @example Defining an option collector
|
||||
# class MyResource < Chef::Resource
|
||||
# include Poise::Helpers::OptionCollector
|
||||
# attribute(:my_options, option_collector: true)
|
||||
# end
|
||||
# @example Using an option collector
|
||||
# my_resource 'name' do
|
||||
# my_options do
|
||||
# key1 'value1'
|
||||
# key2 'value2'
|
||||
# end
|
||||
# end
|
||||
module OptionCollector
|
||||
# Instance context used to eval option blocks.
|
||||
# @api private
|
||||
class OptionEvalContext
|
||||
attr_reader :_options
|
||||
|
||||
def initialize(parent)
|
||||
@parent = parent
|
||||
@_options = {}
|
||||
end
|
||||
|
||||
def method_missing(method_sym, *args, &block)
|
||||
@parent.send(method_sym, *args, &block)
|
||||
rescue NameError
|
||||
# Even though method= in the block will set a variable instead of
|
||||
# calling method_missing, still try to cope in case of self.method=.
|
||||
method_sym = method_sym.to_s.chomp('=').to_sym
|
||||
if args.length > 0 || block
|
||||
@_options[method_sym] = args.first || block
|
||||
elsif !@_options.include?(method_sym)
|
||||
# We haven't seen this name before, re-raise the NameError.
|
||||
raise
|
||||
end
|
||||
@_options[method_sym]
|
||||
end
|
||||
end
|
||||
|
||||
# @!classmethods
|
||||
module ClassMethods
|
||||
# Override the normal #attribute() method to support defining option
|
||||
# collectors too.
|
||||
def attribute(name, options={})
|
||||
# If present but false-y, make sure it is removed anyway so it
|
||||
# doesn't confuse ParamsValidate.
|
||||
if options.delete(:option_collector)
|
||||
option_collector_attribute(name, options)
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
# Define an option collector attribute. Normally used via {.attribute}.
|
||||
#
|
||||
# @param name [String, Symbol] Name of the attribute to define.
|
||||
# @param default [Hash] Default value for the options.
|
||||
# @param parser [Proc, Symbol] Optional parser method. If a symbol it is
|
||||
# called as a method on self. Takes a non-hash value and returns a
|
||||
# hash of its parsed representation.
|
||||
def option_collector_attribute(name, default: {}, parser: nil)
|
||||
raise Poise::Error.new("Parser must be a Proc or Symbol: #{parser.inspect}") if parser && !(parser.is_a?(Proc) || parser.is_a?(Symbol))
|
||||
# Unlike LWRPBase.attribute, I don't care about Ruby 1.8. Worlds tiniest violin.
|
||||
define_method(name.to_sym) do |arg=nil, &block|
|
||||
iv_sym = :"@#{name}"
|
||||
|
||||
value = instance_variable_get(iv_sym) || begin
|
||||
default = instance_eval(&default) if default.is_a?(Chef::DelayedEvaluator) # Handle lazy{}
|
||||
Mash.new(default) # Wrap in a mash because fuck str vs sym.
|
||||
end
|
||||
if arg
|
||||
if !arg.is_a?(Hash) && parser
|
||||
arg = case parser
|
||||
when Proc
|
||||
instance_exec(arg, &parser)
|
||||
when Symbol
|
||||
send(parser, arg)
|
||||
end
|
||||
end
|
||||
raise Exceptions::ValidationFailed, "Option #{name} must be a Hash" if !arg.is_a?(Hash)
|
||||
# Should this and the update below be a deep merge?
|
||||
value.update(arg)
|
||||
end
|
||||
if block
|
||||
ctx = OptionEvalContext.new(self)
|
||||
ctx.instance_exec(&block)
|
||||
value.update(ctx._options)
|
||||
end
|
||||
instance_variable_set(iv_sym, value)
|
||||
value
|
||||
end
|
||||
end
|
||||
|
||||
def included(klass)
|
||||
super
|
||||
klass.extend(ClassMethods)
|
||||
end
|
||||
end
|
||||
|
||||
extend ClassMethods
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,99 @@
|
||||
#
|
||||
# Copyright 2013-2015, Noah Kantrowitz
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
require 'chef/mixin/convert_to_class_name'
|
||||
|
||||
|
||||
module Poise
|
||||
module Helpers
|
||||
# A resource mixin to automatically set @resource_name.
|
||||
#
|
||||
# @since 1.0.0
|
||||
# @example
|
||||
# class MyResource < Chef::Resource
|
||||
# include Poise::Helpers::ResourceName
|
||||
# provides(:my_resource)
|
||||
# end
|
||||
module ResourceName
|
||||
def initialize(*args)
|
||||
super
|
||||
# If provides() was explicitly set, unconditionally set @resource_name.
|
||||
# This helps when subclassing core Chef resources which set it
|
||||
# themselves in #initialize.
|
||||
if self.class.resource_name(false)
|
||||
@resource_name = self.class.resource_name
|
||||
else
|
||||
@resource_name ||= self.class.resource_name
|
||||
end
|
||||
end
|
||||
|
||||
# @!classmethods
|
||||
module ClassMethods
|
||||
# Set the DSL name for the the resource class.
|
||||
#
|
||||
# @param name [Symbol] Name of the resource.
|
||||
# @return [void]
|
||||
# @example
|
||||
# class MyResource < Chef::Resource
|
||||
# include Poise::Resource::ResourceName
|
||||
# provides(:my_resource)
|
||||
# end
|
||||
def provides(name)
|
||||
# Patch self.constantize so this can cope with anonymous classes.
|
||||
# This does require that the anonymous class define self.name though.
|
||||
if self.name && respond_to?(:constantize)
|
||||
old_constantize = instance_method(:constantize)
|
||||
define_singleton_method(:constantize) do |const_name|
|
||||
( const_name == self.name ) ? self : old_constantize.bind(self).call(const_name)
|
||||
end
|
||||
end
|
||||
# Store the name for later.
|
||||
@provides_name = name
|
||||
# Call the original if present. The defined? is for old Chef.
|
||||
super if defined?(super)
|
||||
end
|
||||
|
||||
# Retreive the DSL name for the resource class. If not set explicitly
|
||||
# via {provides} this will try to auto-detect based on the class name.
|
||||
#
|
||||
# @param auto [Boolean] Try to auto-detect based on class name.
|
||||
# @return [Symbol]
|
||||
def resource_name(auto=true)
|
||||
return @provides_name if @provides_name
|
||||
@provides_name || if name && name.start_with?('Chef::Resource')
|
||||
Chef::Mixin::ConvertToClassName.convert_to_snake_case(name, 'Chef::Resource').to_sym
|
||||
elsif name
|
||||
Chef::Mixin::ConvertToClassName.convert_to_snake_case(name.split('::').last).to_sym
|
||||
end
|
||||
end
|
||||
|
||||
# Used by Resource#to_text to find the human name for the resource.
|
||||
#
|
||||
# @api private
|
||||
def dsl_name
|
||||
resource_name.to_s
|
||||
end
|
||||
|
||||
def included(klass)
|
||||
super
|
||||
klass.extend(ClassMethods)
|
||||
end
|
||||
end
|
||||
|
||||
extend ClassMethods
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,58 @@
|
||||
#
|
||||
# Copyright 2013-2015, Noah Kantrowitz
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
require 'poise/subcontext/resource_collection'
|
||||
|
||||
|
||||
module Poise
|
||||
module Helpers
|
||||
# A provider mixin to help with creating subcontexts. Mostly for internal
|
||||
# use within Poise.
|
||||
#
|
||||
# @since 1.0.0
|
||||
module SubcontextBlock
|
||||
private
|
||||
|
||||
def subcontext_block(parent_context=nil, &block)
|
||||
# Setup a sub-run-context.
|
||||
parent_context ||= @run_context
|
||||
sub_run_context = parent_context.dup
|
||||
sub_run_context.resource_collection = Poise::Subcontext::ResourceCollection.new(parent_context.resource_collection)
|
||||
|
||||
# Declare sub-resources within the sub-run-context. Since they
|
||||
# are declared here, they do not pollute the parent run-context.
|
||||
begin
|
||||
outer_run_context = @run_context
|
||||
@run_context = sub_run_context
|
||||
instance_eval(&block)
|
||||
ensure
|
||||
@run_context = outer_run_context
|
||||
end
|
||||
|
||||
# Return the inner context to do other things with
|
||||
sub_run_context
|
||||
end
|
||||
|
||||
def global_resource_collection
|
||||
collection = @run_context.resource_collection
|
||||
while collection.respond_to?(:parent) && collection.parent
|
||||
collection = collection.parent
|
||||
end
|
||||
collection
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,29 @@
|
||||
#
|
||||
# Copyright 2015, Noah Kantrowitz
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
|
||||
module Poise
|
||||
module Helpers
|
||||
# Mixins and helpers for managing subresources, resources with a
|
||||
# parent/child relationship.
|
||||
#
|
||||
# @since 2.0.0
|
||||
module Subresources
|
||||
autoload :Child, 'poise/helpers/subresources/child'
|
||||
autoload :Container, 'poise/helpers/subresources/container'
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,217 @@
|
||||
#
|
||||
# Copyright 2013-2015, Noah Kantrowitz
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
require 'chef/resource'
|
||||
|
||||
require 'poise/error'
|
||||
require 'poise/helpers/subresources/default_containers'
|
||||
|
||||
|
||||
module Poise
|
||||
module Helpers
|
||||
module Subresources
|
||||
# A resource mixin for child subresources.
|
||||
#
|
||||
# @since 1.0.0
|
||||
module Child
|
||||
# Little class used to fix up the display of subresources in #to_text.
|
||||
# Without this you get the full parent resource shown for @parent et al.
|
||||
# @api private
|
||||
class ParentRef
|
||||
attr_accessor :resource
|
||||
|
||||
def initialize(resource)
|
||||
@resource = resource
|
||||
end
|
||||
|
||||
def to_text
|
||||
if @resource.nil?
|
||||
'nil'
|
||||
else
|
||||
@resource.to_s
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# @overload parent()
|
||||
# Get the parent resource for this child. This may be nil if the
|
||||
# resource is set to parent_optional = true.
|
||||
# @return [Chef::Resource, nil]
|
||||
# @overload parent(val)
|
||||
# Set the parent resource. The parent can be set as resource
|
||||
# object, a string (either a bare resource name or a type[name]
|
||||
# string), or a type:name hash.
|
||||
# @param val [String, Hash, Chef::Resource] Parent resource to set.
|
||||
# @return [Chef::Resource, nil]
|
||||
def parent(val=nil)
|
||||
_parent(:parent, self.class.parent_type, self.class.parent_optional, self.class.parent_auto, val)
|
||||
end
|
||||
|
||||
# Register ourself with parents in case this is not a nested resource.
|
||||
#
|
||||
# @api private
|
||||
def after_created
|
||||
super
|
||||
self.class.parent_attributes.each_key do |name|
|
||||
parent = self.send(name)
|
||||
parent.register_subresource(self) if parent
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Generic form of the parent getter/setter.
|
||||
#
|
||||
# @since 2.0.0
|
||||
# @see #parent
|
||||
def _parent(name, parent_type, parent_optional, parent_auto, val=nil)
|
||||
# Allow using a DSL symbol as the parent type.
|
||||
if parent_type.is_a?(Symbol)
|
||||
parent_type = Chef::Resource.resource_for_node(parent_type, node)
|
||||
end
|
||||
# Grab the ivar for local use.
|
||||
parent_ref = instance_variable_get(:"@#{name}")
|
||||
if val
|
||||
if val.is_a?(String) && !val.include?('[')
|
||||
raise Poise::Error.new('Cannot use a string parent without defining a parent type') if parent_type == Chef::Resource
|
||||
val = "#{parent_type.resource_name}[#{val}]"
|
||||
end
|
||||
if val.is_a?(String) || val.is_a?(Hash)
|
||||
parent = @run_context.resource_collection.find(val)
|
||||
else
|
||||
parent = val
|
||||
end
|
||||
if !parent.is_a?(parent_type)
|
||||
raise Poise::Error.new("Parent resource is not an instance of #{parent_type.name}: #{val.inspect}")
|
||||
end
|
||||
parent_ref = ParentRef.new(parent)
|
||||
elsif !parent_ref
|
||||
if parent_auto
|
||||
# Automatic sibling lookup for sequential composition.
|
||||
# Find the last instance of the parent class as the default parent.
|
||||
# This is super flaky and should only be a last resort.
|
||||
parent = Poise::Helpers::Subresources::DefaultContainers.find(parent_type, run_context)
|
||||
end
|
||||
# Can't find a valid parent, if it wasn't optional raise an error.
|
||||
raise Poise::Error.new("No parent found for #{self}") unless parent || parent_optional
|
||||
parent_ref = ParentRef.new(parent)
|
||||
else
|
||||
parent = parent_ref.resource
|
||||
end
|
||||
# Store the ivar back.
|
||||
instance_variable_set(:"@#{name}", parent_ref)
|
||||
# Return the actual resource.
|
||||
parent
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
# @overload parent_type()
|
||||
# Get the class of the default parent link on this resource.
|
||||
# @return [Class, Symbol]
|
||||
# @overload parent_type(type)
|
||||
# Set the class of the default parent link on this resource.
|
||||
# @param type [Class, Symbol] Class to set.
|
||||
# @return [Class, Symbol]
|
||||
def parent_type(type=nil)
|
||||
if type
|
||||
raise Poise::Error.new("Parent type must be a class, symbol, or true, got #{type.inspect}") unless type.is_a?(Class) || type.is_a?(Symbol) || type == true
|
||||
@parent_type = type
|
||||
end
|
||||
@parent_type || (superclass.respond_to?(:parent_type) ? superclass.parent_type : Chef::Resource)
|
||||
end
|
||||
|
||||
# @overload parent_optional()
|
||||
# Get the optional mode for the default parent link on this resource.
|
||||
# @return [Boolean]
|
||||
# @overload parent_optional(val)
|
||||
# Set the optional mode for the default parent link on this resource.
|
||||
# @param val [Boolean] Mode to set.
|
||||
# @return [Boolean]
|
||||
def parent_optional(val=nil)
|
||||
unless val.nil?
|
||||
@parent_optional = val
|
||||
end
|
||||
if @parent_optional.nil?
|
||||
superclass.respond_to?(:parent_optional) ? superclass.parent_optional : false
|
||||
else
|
||||
@parent_optional
|
||||
end
|
||||
end
|
||||
|
||||
# @overload parent_auto()
|
||||
# Get the auto-detect mode for the default parent link on this resource.
|
||||
# @return [Boolean]
|
||||
# @overload parent_auto(val)
|
||||
# Set the auto-detect mode for the default parent link on this resource.
|
||||
# @param val [Boolean] Mode to set.
|
||||
# @return [Boolean]
|
||||
def parent_auto(val=nil)
|
||||
unless val.nil?
|
||||
@parent_auto = val
|
||||
end
|
||||
if @parent_auto.nil?
|
||||
superclass.respond_to?(:parent_auto) ? superclass.parent_auto : true
|
||||
else
|
||||
@parent_auto
|
||||
end
|
||||
end
|
||||
|
||||
# Create a new kind of parent link.
|
||||
#
|
||||
# @since 2.0.0
|
||||
# @param name [Symbol] Name of the relationship. This becomes a method
|
||||
# name on the resource instance.
|
||||
# @param type [Class] Class of the parent.
|
||||
# @param optional [Boolean] If the parent is optional.
|
||||
# @param auto [Boolean] If the parent is auto-detected.
|
||||
# @return [void]
|
||||
def parent_attribute(name, type: Chef::Resource, optional: false, auto: true)
|
||||
name = :"parent_#{name}"
|
||||
(@parent_attributes ||= {})[name] = type
|
||||
define_method(name) do |val=nil|
|
||||
_parent(name, type, optional, auto, val)
|
||||
end
|
||||
end
|
||||
|
||||
# Return the name of all parent relationships on this class.
|
||||
#
|
||||
# @since 2.0.0
|
||||
# @return [Hash<Symbol, Class>]
|
||||
def parent_attributes
|
||||
{}.tap do |attrs|
|
||||
# Grab superclass's attributes if possible.
|
||||
attrs.update(superclass.parent_attributes) if superclass.respond_to?(:parent_attributes)
|
||||
# Local default parent.
|
||||
attrs[:parent] = parent_type
|
||||
# Extra locally defined parents.
|
||||
attrs.update(@parent_attributes) if @parent_attributes
|
||||
# Remove anything with the type set to true.
|
||||
attrs.reject! {|name, type| type == true }
|
||||
end
|
||||
end
|
||||
|
||||
# @api private
|
||||
def included(klass)
|
||||
super
|
||||
klass.extend(ClassMethods)
|
||||
end
|
||||
end
|
||||
|
||||
extend ClassMethods
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,165 @@
|
||||
#
|
||||
# Copyright 2013-2015, Noah Kantrowitz
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
require 'chef/dsl/recipe'
|
||||
|
||||
require 'poise/helpers/subcontext_block'
|
||||
require 'poise/helpers/subresources/default_containers'
|
||||
|
||||
|
||||
module Poise
|
||||
module Helpers
|
||||
module Subresources
|
||||
# A resource mixin for subresource containers.
|
||||
#
|
||||
# @since 1.0.0
|
||||
module Container
|
||||
# A resource collection that has much more condensed text output. This
|
||||
# is used to show the value of @subresources during Chef's error formatting.
|
||||
# @api private
|
||||
class NoPrintingResourceCollection < Chef::ResourceCollection
|
||||
def to_text
|
||||
"[#{all_resources.map(&:to_s).join(', ')}]"
|
||||
end
|
||||
end
|
||||
|
||||
include SubcontextBlock
|
||||
include Chef::DSL::Recipe
|
||||
|
||||
attr_reader :subresources
|
||||
|
||||
def initialize(*args)
|
||||
super
|
||||
@subresources = NoPrintingResourceCollection.new
|
||||
end
|
||||
|
||||
def after_created
|
||||
super
|
||||
# Register
|
||||
Poise::Helpers::Subresources::DefaultContainers.register!(self, run_context)
|
||||
unless @subresources.empty?
|
||||
Chef::Log.debug("[#{self}] Adding subresources to collection:")
|
||||
# Because after_create is run before adding the container to the resource collection
|
||||
# we need to jump through some hoops to get it swapped into place.
|
||||
self_ = self
|
||||
order_fixer = Chef::Resource::RubyBlock.new('subresource_order_fixer', @run_context)
|
||||
order_fixer.block do
|
||||
collection = self_.run_context.resource_collection
|
||||
# Delete the current container resource from its current position.
|
||||
collection.all_resources.delete(self_)
|
||||
# Replace the order fixer with the container so it runs before all
|
||||
# subresources.
|
||||
collection.all_resources[collection.iterator.position] = self_
|
||||
# Hack for Chef 11 to reset the resources_by_name position too.
|
||||
# @todo Remove this when I drop support for Chef 11.
|
||||
if resources_by_name = collection.instance_variable_get(:@resources_by_name)
|
||||
resources_by_name[self_.to_s] = collection.iterator.position
|
||||
end
|
||||
# Step back so we re-run the "current" resource, which is now the
|
||||
# container.
|
||||
collection.iterator.skip_back
|
||||
end
|
||||
@run_context.resource_collection.insert(order_fixer)
|
||||
@subresources.each do |r|
|
||||
Chef::Log.debug(" * #{r}")
|
||||
@run_context.resource_collection.insert(r)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def declare_resource(type, name, created_at=nil, &block)
|
||||
Chef::Log.debug("[#{self}] Creating subresource from #{type}(#{name})")
|
||||
self_ = self
|
||||
# Used to break block context, non-local return from subcontext_block.
|
||||
resource = []
|
||||
# Grab the caller so we can make the subresource look like it comes from
|
||||
# correct place.
|
||||
created_at ||= caller[0]
|
||||
# Run this inside a subcontext to avoid adding to the current resource collection.
|
||||
# It will end up added later, indirected via @subresources to ensure ordering.
|
||||
subcontext_block do
|
||||
namespace = if self.class.container_namespace == true
|
||||
# If the value is true, use the name of the container resource.
|
||||
self.name
|
||||
elsif self.class.container_namespace.is_a?(Proc)
|
||||
instance_eval(&self.class.container_namespace)
|
||||
else
|
||||
self.class.container_namespace
|
||||
end
|
||||
sub_name = if name && !name.empty?
|
||||
if namespace
|
||||
"#{namespace}::#{name}"
|
||||
else
|
||||
name
|
||||
end
|
||||
else
|
||||
# If you pass in nil or '', you just get the namespace or parent name.
|
||||
namespace || self.name
|
||||
end
|
||||
resource << super(type, sub_name, created_at) do
|
||||
# Apply the correct parent before anything else so it is available
|
||||
# in after_created for the subresource.
|
||||
parent(self_) if respond_to?(:parent)
|
||||
# Run the resource block.
|
||||
instance_exec(&block) if block
|
||||
end
|
||||
end
|
||||
# Try and add to subresources. For normal subresources this is handled
|
||||
# in the after_created.
|
||||
register_subresource(resource.first) if resource.first
|
||||
# Return whatever we have
|
||||
resource.first
|
||||
end
|
||||
|
||||
def register_subresource(resource)
|
||||
subresources.lookup(resource)
|
||||
rescue Chef::Exceptions::ResourceNotFound
|
||||
Chef::Log.debug("[#{self}] Adding #{resource} to subresources")
|
||||
subresources.insert(resource)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Thanks Array.flatten, big help you are. Specifically the
|
||||
# method_missing in the recipe DSL will make a flatten on an array of
|
||||
# resources fail, so make this safe.
|
||||
def to_ary
|
||||
nil
|
||||
end
|
||||
|
||||
# @!classmethods
|
||||
module ClassMethods
|
||||
def container_namespace(val=nil)
|
||||
@container_namespace = val unless val.nil?
|
||||
if @container_namespace.nil?
|
||||
# Not set here, look at the superclass of true by default for backwards compat.
|
||||
superclass.respond_to?(:container_namespace) ? superclass.container_namespace : true
|
||||
else
|
||||
@container_namespace
|
||||
end
|
||||
end
|
||||
|
||||
def included(klass)
|
||||
super
|
||||
klass.extend(ClassMethods)
|
||||
end
|
||||
end
|
||||
|
||||
extend ClassMethods
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,73 @@
|
||||
#
|
||||
# Copyright 2015, Noah Kantrowitz
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
|
||||
module Poise
|
||||
module Helpers
|
||||
module Subresources
|
||||
# Helpers to track default container resources. This is used to find a
|
||||
# default parent for a child with no parent set. It flat out violates
|
||||
# encapsulation to allow for the use of default parents to act as
|
||||
# system-level defaults even when created in a nested scope.
|
||||
#
|
||||
# @api private
|
||||
# @since 2.0.0
|
||||
module DefaultContainers
|
||||
# Mutex to sync access to the containers array.
|
||||
#
|
||||
# @see .containers
|
||||
CONTAINER_MUTEX = Mutex.new
|
||||
|
||||
# Add a resource to the array of default containers.
|
||||
#
|
||||
# @param resource [Chef::Resource] Resource to add.
|
||||
# @param run_context [Chef::RunContext] Context of the current run.
|
||||
# @return [void]
|
||||
def self.register!(resource, run_context)
|
||||
CONTAINER_MUTEX.synchronize do
|
||||
containers(run_context) << resource
|
||||
end
|
||||
end
|
||||
|
||||
# Find a default container for a resource class.
|
||||
#
|
||||
# @param klass [Class] Resource class to search for.
|
||||
# @param run_context [Chef::RunContext] Context of the current run.
|
||||
# @return [Chef::Resource]
|
||||
def self.find(klass, run_context)
|
||||
CONTAINER_MUTEX.synchronize do
|
||||
containers(run_context).reverse_each do |resource|
|
||||
return resource if resource.is_a?(klass)
|
||||
end
|
||||
# Nothing found.
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Get the array of all default container resources.
|
||||
#
|
||||
# @note MUST BE CALLED FROM A LOCKED CONTEXT!
|
||||
# @param run_context [Chef::RunContext] Context of the current run.
|
||||
# @return [Array<Chef::Resource>]
|
||||
def self.containers(run_context)
|
||||
run_context.node.run_state[:poise_default_containers] ||= []
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,165 @@
|
||||
#
|
||||
# Copyright 2013-2015, Noah Kantrowitz
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
require 'chef/provider/template_finder'
|
||||
require 'chef/mixin/template'
|
||||
|
||||
require 'poise/helpers/lazy_default'
|
||||
require 'poise/helpers/lwrp_polyfill'
|
||||
require 'poise/helpers/option_collector'
|
||||
require 'poise/utils'
|
||||
|
||||
|
||||
module Poise
|
||||
module Helpers
|
||||
# A resource mixin to add a new kind of attribute, template content. TODO
|
||||
#
|
||||
# @since 1.0.0
|
||||
module TemplateContent
|
||||
include LazyDefault
|
||||
include LWRPPolyfill
|
||||
include OptionCollector
|
||||
|
||||
# @!classmethods
|
||||
module ClassMethods
|
||||
def attribute(name, options={})
|
||||
if options.delete(:template)
|
||||
name_prefix = name.empty? ? '' : "#{name}_"
|
||||
|
||||
# If you are reading this, I'm so sorry
|
||||
# This is used for computing the default cookbook below
|
||||
parent_filename = caller.first.reverse.split(':', 4).last.reverse
|
||||
|
||||
# If our parent class also declared a template_content attribute on the same name, inherit its options
|
||||
if superclass.respond_to?("_#{name_prefix}_template_content_options")
|
||||
options = superclass.send("_#{name_prefix}_template_content_options").merge(options)
|
||||
end
|
||||
|
||||
# Template source path if using a template
|
||||
attribute("#{name_prefix}source", kind_of: String)
|
||||
define_method("_#{name_prefix}source") do
|
||||
send("#{name_prefix}source") || maybe_eval(options[:default_source])
|
||||
end
|
||||
|
||||
# Template cookbook name if using a template
|
||||
attribute("#{name_prefix}cookbook", kind_of: [String, Symbol], default: lazy do
|
||||
if send("#{name_prefix}source")
|
||||
cookbook_name
|
||||
elsif options[:default_cookbook]
|
||||
maybe_eval(options[:default_cookbook])
|
||||
else
|
||||
Poise::Utils.find_cookbook_name(run_context, parent_filename)
|
||||
end
|
||||
end)
|
||||
|
||||
# Template variables if using a template
|
||||
attribute("#{name_prefix}options", option_collector: true)
|
||||
|
||||
# The big one, get/set content, but if you are getting and no
|
||||
# explicit content was given, try to render the template
|
||||
define_method("#{name_prefix}content") do |arg=nil, no_compute=false|
|
||||
ret = set_or_return("#{name_prefix}content", arg, kind_of: String)
|
||||
if !ret && !arg && !no_compute
|
||||
ret = send("_#{name_prefix}content")
|
||||
# Cache the results for next time
|
||||
set_or_return("#{name_prefix}content", ret, {}) if ret
|
||||
end
|
||||
ret
|
||||
end
|
||||
|
||||
# Validate that arguments work
|
||||
define_method("_#{name_prefix}validate") do
|
||||
if options[:required] && !send("_#{name_prefix}source") && !send("#{name_prefix}content", nil, true)
|
||||
raise Chef::Exceptions::ValidationFailed, "#{self}: One of #{name_prefix}source or #{name_prefix}content is required"
|
||||
end
|
||||
if send("#{name_prefix}source") && send("#{name_prefix}content", nil, true)
|
||||
raise Chef::Exceptions::ValidationFailed, "#{self}: Only one of #{name_prefix}source or #{name_prefix}content can be specified"
|
||||
end
|
||||
end
|
||||
|
||||
# Monkey patch #after_create to run best-effort validation. Arguments
|
||||
# could be changed after creation, but this gives nicer errors for
|
||||
# most cases.
|
||||
unless options[:no_validate_on_create]
|
||||
old_after_created = instance_method(:after_created)
|
||||
define_method(:after_created) do
|
||||
old_after_created.bind(self).call
|
||||
send("_#{name_prefix}validate")
|
||||
end
|
||||
end
|
||||
|
||||
# Compile the needed content
|
||||
define_method("_#{name_prefix}content") do
|
||||
# Run validation again
|
||||
send("_#{name_prefix}validate")
|
||||
# Get all the relevant parameters
|
||||
content = send("#{name_prefix}content", nil, true)
|
||||
source = send("_#{name_prefix}source")
|
||||
if content
|
||||
content # I don't think it can ever hit this branch
|
||||
elsif source
|
||||
cookbook = send("#{name_prefix}cookbook")
|
||||
template_options = send("#{name_prefix}options")
|
||||
send("_#{name_prefix}render_template", source, cookbook, template_options)
|
||||
else
|
||||
maybe_eval(options[:default])
|
||||
end
|
||||
end
|
||||
|
||||
# Actually render a template
|
||||
define_method("_#{name_prefix}render_template") do |source, cookbook, template_options|
|
||||
all_template_options = {}
|
||||
all_template_options.update(maybe_eval(options[:default_options])) if options[:default_options]
|
||||
all_template_options.update(template_options)
|
||||
all_template_options[:new_resource] = self
|
||||
finder = Chef::Provider::TemplateFinder.new(run_context, cookbook, node)
|
||||
context = Chef::Mixin::Template::TemplateContext.new(all_template_options)
|
||||
context[:node] = node
|
||||
context[:template_finder] = finder
|
||||
context.render_template(finder.find(source))
|
||||
end
|
||||
|
||||
# Used to check if a parent class already defined a template_content thing here
|
||||
define_singleton_method("_#{name_prefix}_template_content_options") do
|
||||
options
|
||||
end
|
||||
else
|
||||
super if defined?(super)
|
||||
end
|
||||
end
|
||||
|
||||
def included(klass)
|
||||
super
|
||||
klass.extend(ClassMethods)
|
||||
end
|
||||
end
|
||||
|
||||
extend ClassMethods
|
||||
|
||||
private
|
||||
|
||||
# Evaluate lazy blocks if needed
|
||||
def maybe_eval(val)
|
||||
if val.is_a?(Chef::DelayedEvaluator)
|
||||
instance_eval(&val)
|
||||
else
|
||||
val
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
55
cookbooks/poise/files/halite_gem/poise/provider.rb
Normal file
55
cookbooks/poise/files/halite_gem/poise/provider.rb
Normal file
@@ -0,0 +1,55 @@
|
||||
#
|
||||
# Copyright 2013-2015, Noah Kantrowitz
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
require 'poise/helpers'
|
||||
|
||||
|
||||
module Poise
|
||||
# Master provider mixin for Poise-based providers.
|
||||
#
|
||||
# @since 1.0.0
|
||||
# @example Default helpers.
|
||||
# class MyProvider < Chef::Provider
|
||||
# include Poise::Provider
|
||||
# end
|
||||
# @example With optional helpers.
|
||||
# class MyProvider < Chef::Provider
|
||||
# include Poise::Provider
|
||||
# poise_inversion(MyResource)
|
||||
# end
|
||||
module Provider
|
||||
include Poise::Helpers::DefinedIn
|
||||
include Poise::Helpers::IncludeRecipe
|
||||
include Poise::Helpers::LWRPPolyfill
|
||||
include Poise::Helpers::NotifyingBlock
|
||||
|
||||
# @!classmethods
|
||||
module ClassMethods
|
||||
def poise_inversion(resource, attribute=nil)
|
||||
include Poise::Helpers::Inversion
|
||||
inversion_resource(resource)
|
||||
inversion_attribute(attribute) if attribute
|
||||
end
|
||||
|
||||
def included(klass)
|
||||
super
|
||||
klass.extend(ClassMethods)
|
||||
end
|
||||
end
|
||||
|
||||
extend ClassMethods
|
||||
end
|
||||
end
|
||||
75
cookbooks/poise/files/halite_gem/poise/resource.rb
Normal file
75
cookbooks/poise/files/halite_gem/poise/resource.rb
Normal file
@@ -0,0 +1,75 @@
|
||||
#
|
||||
# Copyright 2013-2015, Noah Kantrowitz
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
require 'poise/helpers'
|
||||
|
||||
|
||||
module Poise
|
||||
# Master resource mixin for Poise-based resources.
|
||||
#
|
||||
# @since 1.0.0
|
||||
# @example Default helpers.
|
||||
# class MyResource < Chef::Resource
|
||||
# include Poise::Resource
|
||||
# end
|
||||
# @example With optional helpers.
|
||||
# class MyResource < Chef::Resource
|
||||
# include Poise::Resource
|
||||
# poise_subresource(MyParent)
|
||||
# poise_fused
|
||||
# end
|
||||
module Resource
|
||||
include Poise::Helpers::ChefspecMatchers
|
||||
include Poise::Helpers::DefinedIn
|
||||
include Poise::Helpers::LazyDefault
|
||||
include Poise::Helpers::LWRPPolyfill
|
||||
include Poise::Helpers::OptionCollector
|
||||
include Poise::Helpers::ResourceName
|
||||
include Poise::Helpers::TemplateContent
|
||||
|
||||
# @!classmethods
|
||||
module ClassMethods
|
||||
def poise_subresource_container(namespace=nil)
|
||||
include Poise::Helpers::Subresources::Container
|
||||
# false is a valid value.
|
||||
container_namespace(namespace) unless namespace.nil?
|
||||
end
|
||||
|
||||
def poise_subresource(parent_type=nil, parent_optional=nil, parent_auto=nil)
|
||||
include Poise::Helpers::Subresources::Child
|
||||
parent_type(parent_type) if parent_type
|
||||
parent_optional(parent_optional) unless parent_optional.nil?
|
||||
parent_auto(parent_auto) unless parent_auto.nil?
|
||||
end
|
||||
|
||||
def poise_fused
|
||||
include Poise::Helpers::Fused
|
||||
end
|
||||
|
||||
def poise_inversion(options_resource=nil)
|
||||
include Poise::Helpers::Inversion
|
||||
inversion_options_resource(true) unless options_resource == false
|
||||
end
|
||||
|
||||
def included(klass)
|
||||
super
|
||||
klass.extend(ClassMethods)
|
||||
end
|
||||
end
|
||||
|
||||
extend ClassMethods
|
||||
end
|
||||
end
|
||||
27
cookbooks/poise/files/halite_gem/poise/subcontext.rb
Normal file
27
cookbooks/poise/files/halite_gem/poise/subcontext.rb
Normal file
@@ -0,0 +1,27 @@
|
||||
#
|
||||
# Copyright 2015, Noah Kantrowitz
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
|
||||
module Poise
|
||||
# Helpers and whatnot for dealing with subcontexts.
|
||||
#
|
||||
# @api private
|
||||
# @since 2.0.0
|
||||
module Subcontext
|
||||
autoload :ResourceCollection, 'poise/subcontext/resource_collection'
|
||||
autoload :Runner, 'poise/subcontext/runner'
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,56 @@
|
||||
#
|
||||
# Copyright 2013-2015, Noah Kantrowitz
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
require 'chef/resource_collection'
|
||||
|
||||
|
||||
module Poise
|
||||
module Subcontext
|
||||
# A subclass of the normal Chef ResourceCollection that creates a partially
|
||||
# isolated set of resources. Notifications and other resources lookups can
|
||||
# propagate out to parent contexts but not back in. This is used to allow
|
||||
# black-box resources that are still aware of things in upper contexts.
|
||||
#
|
||||
# @api private
|
||||
# @since 1.0.0
|
||||
class ResourceCollection < Chef::ResourceCollection
|
||||
attr_accessor :parent
|
||||
|
||||
def initialize(parent)
|
||||
@parent = parent
|
||||
super()
|
||||
end
|
||||
|
||||
def lookup(resource)
|
||||
super
|
||||
rescue Chef::Exceptions::ResourceNotFound
|
||||
@parent.lookup(resource)
|
||||
end
|
||||
|
||||
# Iterate and expand all nested contexts
|
||||
def recursive_each(&block)
|
||||
if @parent
|
||||
if @parent.respond_to?(:recursive_each)
|
||||
@parent.recursive_each(&block)
|
||||
else
|
||||
@parent.each(&block)
|
||||
end
|
||||
end
|
||||
each(&block)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
50
cookbooks/poise/files/halite_gem/poise/subcontext/runner.rb
Normal file
50
cookbooks/poise/files/halite_gem/poise/subcontext/runner.rb
Normal file
@@ -0,0 +1,50 @@
|
||||
#
|
||||
# Copyright 2013-2015, Noah Kantrowitz
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
require 'chef/runner'
|
||||
|
||||
|
||||
module Poise
|
||||
module Subcontext
|
||||
# A subclass of the normal Chef Runner that migrates delayed notifications
|
||||
# to the enclosing run_context instead of running them at the end of the
|
||||
# subcontext convergence.
|
||||
#
|
||||
# @api private
|
||||
# @since 1.0.0
|
||||
class Runner < Chef::Runner
|
||||
def initialize(resource, *args)
|
||||
super(*args)
|
||||
@resource = resource
|
||||
end
|
||||
|
||||
def run_delayed_notifications(error=nil)
|
||||
# If there is an error, just do the normal thing. The return shouldn't
|
||||
# ever fire because the superclass re-raises if there is an error.
|
||||
return super if error
|
||||
delayed_actions.each do |notification|
|
||||
notifications = run_context.delayed_notifications(@resource)
|
||||
if run_context.delayed_notifications(@resource).any? { |existing_notification| existing_notification.duplicates?(notification) }
|
||||
Chef::Log.info( "#{@resource} not queuing delayed action #{notification.action} on #{notification.resource}"\
|
||||
" (delayed), as it's already been queued")
|
||||
else
|
||||
notifications << notification
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
70
cookbooks/poise/files/halite_gem/poise/utils.rb
Normal file
70
cookbooks/poise/files/halite_gem/poise/utils.rb
Normal file
@@ -0,0 +1,70 @@
|
||||
#
|
||||
# Copyright 2015, Noah Kantrowitz
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
require 'poise/error'
|
||||
|
||||
|
||||
module Poise
|
||||
module Utils
|
||||
autoload :ResourceProviderMixin, 'poise/utils/resource_provider_mixin'
|
||||
|
||||
extend self
|
||||
|
||||
# Find the cookbook name for a given filename. The can used to find the
|
||||
# cookbook that corresponds to a caller of a file.
|
||||
#
|
||||
# @param run_context [Chef::RunContext] Context to check.
|
||||
# @param filename [String] Absolute filename to check for.
|
||||
# @return [String]
|
||||
# @example
|
||||
# def my_thing
|
||||
# caller_filename = caller.first.split(':').first
|
||||
# cookbook = Poise::Utils.find_cookbook_name(run_context, caller_filename)
|
||||
# # ...
|
||||
# end
|
||||
def find_cookbook_name(run_context, filename)
|
||||
possibles = {}
|
||||
Chef::Log.debug("[Poise] Checking cookbook for #{filename.inspect}")
|
||||
run_context.cookbook_collection.each do |name, ver|
|
||||
# This special method is added by Halite::Gem#as_cookbook_version.
|
||||
if ver.respond_to?(:halite_root)
|
||||
# The join is there because ../poise-ruby/lib starts with ../poise so
|
||||
# we want a trailing /.
|
||||
Chef::Log.debug("")
|
||||
if filename.start_with?(File.join(ver.halite_root, ''))
|
||||
Chef::Log.debug("[Poise] Found matching halite_root in #{name}: #{ver.halite_root.inspect}")
|
||||
possibles[ver.halite_root] = name
|
||||
end
|
||||
else
|
||||
Chef::CookbookVersion::COOKBOOK_SEGMENTS.each do |seg|
|
||||
ver.segment_filenames(seg).each do |file|
|
||||
# Put this behind an environment variable because it is verbose
|
||||
# even for normal debugging-level output.
|
||||
Chef::Log.debug("[Poise] Checking #{seg} in #{name}: #{file.inspect}") if ENV['POISE_DEBUG']
|
||||
if file == filename
|
||||
Chef::Log.debug("[Poise] Found matching #{seg} in #{name}: #{file.inspect}")
|
||||
possibles[file] = name
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
raise Poise::Error.new("Unable to find cookbook for file #{filename.inspect}") if possibles.empty?
|
||||
# Sort the items by matching path length, pick the name attached to the longest.
|
||||
possibles.sort_by{|key, value| key.length }.last[1]
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,65 @@
|
||||
#
|
||||
# Copyright 2015, Noah Kantrowitz
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
|
||||
module Poise
|
||||
module Utils
|
||||
# A mixin to dispatch other mixins with resource and provider
|
||||
# implementations. The module this is included in must have Resource and
|
||||
# Provider sub-modules.
|
||||
#
|
||||
# @since 2.0.0
|
||||
# @example
|
||||
# module MyHelper
|
||||
# include Poise::Utils::ResourceProviderMixin
|
||||
# module Resource
|
||||
# # ...
|
||||
# end
|
||||
#
|
||||
# module Provider
|
||||
# # ...
|
||||
# end
|
||||
# end
|
||||
module ResourceProviderMixin
|
||||
def self.included(klass)
|
||||
# Warning here be dragons.
|
||||
# Create a new anonymous module, klass will be the module that
|
||||
# actually included ResourceProviderMixin. We want to keep a reference
|
||||
# to that locked down so that we can close over it and use it in the
|
||||
# "real" .included defined below to find the original relative consts.
|
||||
mod = Module.new do
|
||||
# Use define_method instead of def so we can close over klass and mod.
|
||||
define_method(:included) do |inner_klass|
|
||||
# Has to be explicit because super inside define_method.
|
||||
super(inner_klass)
|
||||
# Cargo this .included to things which include us.
|
||||
inner_klass.extend(mod)
|
||||
# Dispatch to submodules, inner_klass is the most recent includer.
|
||||
if inner_klass < Chef::Resource
|
||||
# Use klass::Resource to look up relative to the original module.
|
||||
inner_klass.class_exec { include klass::Resource }
|
||||
elsif inner_klass < Chef::Provider
|
||||
# As above, klass::Provider.
|
||||
inner_klass.class_exec { include klass::Provider }
|
||||
end
|
||||
end
|
||||
end
|
||||
# Add our .included to the original includer.
|
||||
klass.extend(mod)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
20
cookbooks/poise/files/halite_gem/poise/version.rb
Normal file
20
cookbooks/poise/files/halite_gem/poise/version.rb
Normal file
@@ -0,0 +1,20 @@
|
||||
#
|
||||
# Copyright 2013-2015, Noah Kantrowitz
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
|
||||
module Poise
|
||||
VERSION = '2.0.1'
|
||||
end
|
||||
18
cookbooks/poise/libraries/default.rb
Normal file
18
cookbooks/poise/libraries/default.rb
Normal file
@@ -0,0 +1,18 @@
|
||||
#
|
||||
# Copyright 2013-2015, Noah Kantrowitz
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
raise 'Halite is not compatible with no_lazy_load false, please set no_lazy_load true in your Chef configuration file.' unless Chef::Config[:no_lazy_load]
|
||||
$LOAD_PATH << File.expand_path('../../files/halite_gem', __FILE__)
|
||||
1
cookbooks/poise/metadata.json
Normal file
1
cookbooks/poise/metadata.json
Normal file
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user