summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJohn Keiser <john@johnkeiser.com>2015-06-19 15:30:16 -0700
committerJohn Keiser <john@johnkeiser.com>2015-06-23 11:42:22 -0700
commitc0739eecdd1ca1dbf7e3571325f2759f17966c92 (patch)
treef3c987db6b7641fb7fb2fc1728abd51a19cc00c6
parent2e8773b102eb066b77f5626118c0c0bfa62ad03d (diff)
downloadchef-jk/resource2.0.tar.gz
Add Chef::Experimental::Resource2, with all Resource2 improvementsjk/resource2.0
-rw-r--r--lib/chef/experimental/create_resource.rb54
-rw-r--r--lib/chef/experimental/resource2.rb134
-rw-r--r--lib/chef/resource.rb43
3 files changed, 221 insertions, 10 deletions
diff --git a/lib/chef/experimental/create_resource.rb b/lib/chef/experimental/create_resource.rb
new file mode 100644
index 0000000000..7728e591dd
--- /dev/null
+++ b/lib/chef/experimental/create_resource.rb
@@ -0,0 +1,54 @@
+require 'chef/resource'
+require 'chef/resource_resolver'
+
+class Chef
+ module Experimental
+ module CreateResource
+ # Chef.resource
+ refine (class<<Chef;self;end) do
+ #
+ # Create a resource type.
+ #
+ # @param name [Symbol] The name of the resource, e.g. :my_resource
+ #
+ # @example
+ # Chef.resource :httpd do
+ # property :config_path, default: '/etc/httpd.conf'
+ # property :port, Integer
+ #
+ # converge do
+ # package 'apache2' do
+ # end
+ # file config_path do
+ # content "port #{port}"
+ # end
+ # service 'httpd' do
+ # end
+ # end
+ # end
+ #
+ def resource(name, *properties, &definition)
+ resource_class = Chef::ResourceResolver.resolve(name, canonical: true)
+ resource_class ||= Class.new(Chef::Resource) do
+ resource_name
+ end
+
+ properties.each do |property|
+ case property
+ when Hash
+ property.each do |name, type|
+ resource_class.property(name, type)
+ end
+ when Symbol, String
+ resource_class.property(name)
+ end
+ end
+
+ if definition
+ resource.class_eval(&definition)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/experimental/resource2.rb b/lib/chef/experimental/resource2.rb
new file mode 100644
index 0000000000..2c91f79097
--- /dev/null
+++ b/lib/chef/experimental/resource2.rb
@@ -0,0 +1,134 @@
+class Chef
+ module Experimental
+ class Resource2 < Chef::Resource
+ def current_resource
+ return @current_resource if instance_variable_defined?(:@current_resource)
+ provider = provider_for_action(self.class.default_action)
+ provider.load_current_resource
+ @current_resource = provider.current_resource
+ end
+
+ def self.property_type(type=NOT_PASSED,**options,&block)
+ # Combine the type with "is"
+ if type != NOT_PASSED
+ if options[:is]
+ options[:is] = ([ type ] + [ options[:is] ]).flatten(1)
+ else
+ options[:is] = type
+ end
+ end
+
+ ReadProperty.new(**options)
+ end
+
+ def self.load(&block)
+ new_action_provider_class.load_block = block
+ end
+
+ def self.new_action_provider_class
+ return @action_provider_class if @action_provider_class
+
+ unless self == Resource2
+ base_provider = superclass.action_provider_class
+ end
+
+ base_provider ||= ActionProviderClass
+
+ resource_class = self
+ action_provider_class = Class.new(base_provider) do
+ use_inline_resources
+ include_resource_dsl true
+ end
+ action_provider_class.resource_class = self
+ @action_provider_class = action_provider_class
+ end
+
+ class ActionProviderClass < Chef::Provider
+ def initialize(*args)
+ super
+ remove_instance_variable(:@current_resource)
+ end
+
+ def load_current_resource
+ # Copy over all non-desired-state
+ self.current_resource = self.class.resource_class.new(new_resource.name, run_context)
+ current_resource.instance_eval { @current_resource = nil }
+ self.class.resource_class.state_properties.each do |property|
+ if property.is_set?(new_resource)
+ property.set(current_resource, property.get(new_resource))
+ end
+ end
+ begin
+ current_resource.instance_eval(&self.class.load_block)
+ rescue ResourceDoesNotExistError
+ self.current_resource = nil
+ end
+ end
+
+ def converge(*properties, &converge_block)
+ properties = new_resource.state_properties if properties.empty?
+ modified = properties.map do |property|
+ property = new_resource.properties[property] if !property.is_a?(Property)
+ new_value = property.get(new_resource)
+ if current_resource.nil?
+ " set #{property.name} to #{new_value}"
+ elsif property.is_set?(new_resource)
+ current_value = property.get(current_resource)
+ if new_value != current_value
+ " set #{property.name} to #{new_value} (was #{current_value})"
+ end
+ end
+ end.compact
+
+ if !modified.empty?
+ if current_resource.nil?
+ converge_by([ "create #{current_resource}", **modified ], &converge_block)
+ else
+ converge_by([ "update #{current_resource}", **modified ], &converge_block)
+ end
+ end
+ end
+
+ def resource_does_not_exist!
+ raise ResourceDoesNotExistError, new_resource
+ end
+
+ def self.resource_class
+ @resource_class
+ end
+ def self.resource_class=(value)
+ raise "Cannot set resource_class on #{self} to two different resources! (Setting to #{value}, was #{resource_class})" if resource_class && resource_class != value
+ @resource_class = value
+ end
+
+ def self.load_block
+ return @load_block if @load_block
+ return superclass.load_block if superclass.respond_to?(:load_block)
+ end
+ def self.load_block=(value)
+ @load_block = value
+ end
+
+ def self.to_s
+ "#{resource_class} action provider"
+ end
+ def self.inspect
+ to_s
+ end
+ end
+
+ class ReadProperty < Property
+ def get(resource)
+ if !is_set?(resource) && desired_state? && resource.current_resource
+ super(resource.current_resource)
+ else
+ super(resource)
+ end
+ end
+ end
+ end
+ end
+
+ class ResourceDoesNotExistError < StandardError
+ end
+end
diff --git a/lib/chef/resource.rb b/lib/chef/resource.rb
index 658ba89e47..4a50442dc1 100644
--- a/lib/chef/resource.rb
+++ b/lib/chef/resource.rb
@@ -197,22 +197,13 @@ class Chef
def self.property(name, type=NOT_PASSED, **options)
name = name.to_sym
- # Combine the type with "is"
- if type != NOT_PASSED
- if options[:is]
- options[:is] = ([ type ] + [ options[:is] ]).flatten(1)
- else
- options[:is] = type
- end
- end
-
local_properties = properties(false)
# Inherit from the current / parent property if type is not passed
if type == NOT_PASSED && properties[name]
local_properties[name] = properties[name].specialize(declared_in: self, **options)
else
- local_properties[name] = Property.new(name: name, declared_in: self, **options)
+ local_properties[name] = property_type(name: name, declared_in: self, **options)
end
begin
@@ -235,6 +226,38 @@ class Chef
end
#
+ # Create a property type that can be reused for multiple properties.
+ #
+ # @param type [Object,Array<Object>] The type(s) of this property.
+ # If present, this is prepended to the `is` validation option.
+ # @param options [Hash<Symbol,Object>] Validation options. See #property
+ #
+ # @example Bare property type
+ # property_type
+ #
+ # @example With just a type
+ # property_type String
+ #
+ # @example With just options
+ # property_type default: 'hi'
+ #
+ # @example With type and options
+ # property_type String, default: 'hi'
+ #
+ def self.property_type(type=NOT_PASSED, **options)
+ # Combine the type with "is"
+ if type != NOT_PASSED
+ if options[:is]
+ options[:is] = ([ type ] + [ options[:is] ]).flatten(1)
+ else
+ options[:is] = type
+ end
+ end
+
+ Property.new(**options)
+ end
+
+ #
# The list of properties defined on this resource.
#
# Everything defined with `property` is in this list.