Replacing TransObject usage with Puppet::Resource

This completely reorganizes how RAL resources are
initialized, and in the process I was able to remove a lot
of code (I removed other apparently obsolete code at
the same time).

Signed-off-by: Luke Kanies <luke@madstop.com>
This commit is contained in:
Luke Kanies 2008-12-11 12:13:41 -06:00
Родитель 60062e4b9a
Коммит 14c3c54ee1
7 изменённых файлов: 221 добавлений и 559 удалений

Просмотреть файл

@ -11,7 +11,7 @@ class Puppet::Resource
# Proxy these methods to the parameters hash. It's likely they'll
# be overridden at some point, but this works for now.
%w{has_key? length delete empty? <<}.each do |method|
%w{has_key? keys length delete empty? <<}.each do |method|
define_method(method) do |*args|
@parameters.send(method, *args)
end
@ -112,19 +112,6 @@ class Puppet::Resource
return result
end
private
# Create an old-style TransBucket instance, for non-builtin resource types.
def to_transbucket
bucket = Puppet::TransBucket.new([])
bucket.type = self.type
bucket.name = self.title
# TransBuckets don't support parameters, which is why they're being deprecated.
return bucket
end
# Create an old-style TransObject instance, for builtin resource types.
def to_transobject
# Now convert to a transobject
@ -157,6 +144,19 @@ class Puppet::Resource
return result
end
private
# Create an old-style TransBucket instance, for non-builtin resource types.
def to_transbucket
bucket = Puppet::TransBucket.new([])
bucket.type = self.type
bucket.name = self.title
# TransBuckets don't support parameters, which is why they're being deprecated.
return bucket
end
# Produce a canonical method name.
def parameter_name(param)
param.to_s.downcase.to_sym

Просмотреть файл

@ -35,12 +35,11 @@ class Type
properties()
end
# All parameters, in the appropriate order. The namevar comes first,
# then the properties, then the params and metaparams in the order they
# were specified in the files.
# All parameters, in the appropriate order. The namevar comes first, then
# the provider, then the properties, and finally the params and metaparams
# in the order they were specified in the files.
def self.allattrs
# now get all of the arguments, in a specific order
# Cache this, since it gets called so many times
# Cache this, since it gets called multiple times
namevar = self.namevar
order = [namevar]
@ -52,7 +51,7 @@ class Type
self.metaparams].flatten.reject { |param|
# we don't want our namevar in there multiple times
param == namevar
}
}
order.flatten!
@ -276,15 +275,6 @@ class Type
param.isnamevar if options[:namevar]
# These might be enabled later.
# define_method(name) do
# @parameters[name].value
# end
#
# define_method(name.to_s + "=") do |value|
# newparam(param, value)
# end
if param.isnamevar?
@namevar = param.name
end
@ -349,14 +339,6 @@ class Type
@properties << prop
end
# define_method(name) do
# @parameters[name].should
# end
#
# define_method(name.to_s + "=") do |value|
# newproperty(name, :should => value)
# end
return prop
end
@ -425,38 +407,6 @@ class Type
end
end
# fix any namevar => param translations
def argclean(oldhash)
# This duplication is here because it might be a transobject.
hash = oldhash.dup.to_hash
if hash.include?(:resource)
hash.delete(:resource)
end
namevar = self.class.namevar
# Do a simple translation for those cases where they've passed :name
# but that's not our namevar
if hash.include? :name and namevar != :name
if hash.include? namevar
raise ArgumentError, "Cannot provide both name and %s" % namevar
end
hash[namevar] = hash[:name]
hash.delete(:name)
end
# Make sure we have a name, one way or another
unless hash.include? namevar
if defined? @title and @title
hash[namevar] = @title
else
raise Puppet::Error, "Was not passed a namevar or title"
end
end
return hash
end
# Return either the attribute alias or the attribute.
def attr_alias(name)
name = symbolize(name)
@ -491,7 +441,7 @@ class Type
name = attr_alias(name)
unless self.class.validattr?(name)
raise TypeError.new("Invalid parameter %s(%s)" % [name, name.inspect])
fail("Invalid parameter %s(%s)" % [name, name.inspect])
end
if name == :name
@ -514,7 +464,7 @@ class Type
name = attr_alias(name)
unless self.class.validattr?(name)
raise TypeError.new("Invalid parameter %s" % [name])
fail("Invalid parameter %s" % [name])
end
if name == :name
@ -638,19 +588,10 @@ class Type
end
end
# def set(name, value)
# send(name.to_s + "=", value)
# end
#
# def get(name)
# send(name)
# end
# For any parameters or properties that have defaults and have not yet been
# set, set them now. This method can be handed a list of attributes,
# and if so it will only set defaults for those attributes.
def setdefaults(*ary)
#self.class.eachattr(*ary) { |klass, type|
self.class.eachattr(*ary) { |klass, type|
# not many attributes will have defaults defined, so we short-circuit
# those away
@ -784,19 +725,6 @@ class Type
self.class.depthfirst?
end
# Add a hook for testing for recursion.
def parentof?(child)
if (self == child)
debug "parent is equal to child"
return true
elsif defined? @parent and @parent.parentof?(child)
debug "My parent is parent of child"
return true
else
return false
end
end
# Remove an object. The argument determines whether the object's
# subscriptions get eliminated, too.
def remove(rmdeps = true)
@ -996,11 +924,6 @@ class Type
# Code related to managing resource instances.
require 'puppet/transportable'
# Make 'new' private, so people have to use create instead.
class << self
private :new
end
# retrieve a named instance of the current type
def self.[](name)
raise "Global resource access is deprecated"
@ -1080,69 +1003,7 @@ class Type
# Force users to call this, so that we can merge objects if
# necessary.
def self.create(args)
# Don't modify the original hash; instead, create a duplicate and modify it.
# We have to dup and use the ! so that it stays a TransObject if it is
# one.
hash = args.dup
symbolizehash!(hash)
# If we're the base class, then pass the info on appropriately
if self == Puppet::Type
type = nil
if hash.is_a? Puppet::TransObject
type = hash.type
else
# If we're using the type to determine object type, then delete it
if type = hash[:type]
hash.delete(:type)
end
end
# If they've specified a type and called on the base, then
# delegate to the subclass.
if type
if typeklass = self.type(type)
return typeklass.create(hash)
else
raise Puppet::Error, "Unknown type %s" % type
end
else
raise Puppet::Error, "No type found for %s" % hash.inspect
end
end
# Handle this new object being implicit
implicit = hash[:implicit] || false
if hash.include?(:implicit)
hash.delete(:implicit)
end
name = nil
unless hash.is_a? Puppet::TransObject
hash = self.hash2trans(hash)
end
# XXX This will have to change when transobjects change to using titles
title = hash.name
# create it anew
# if there's a failure, destroy the object if it got that far, but raise
# the error.
begin
obj = new(hash)
rescue => detail
Puppet.err "Could not create %s: %s" % [title, detail.to_s]
if obj
obj.remove(true)
end
raise
end
if implicit
obj.implicit = true
end
return obj
new(args)
end
# remove a specified object
@ -1181,52 +1042,6 @@ class Type
return @objects.has_key?(name)
end
# Convert a hash to a TransObject.
def self.hash2trans(hash)
title = nil
if hash.include? :title
title = hash[:title]
hash.delete(:title)
elsif hash.include? self.namevar
title = hash[self.namevar]
hash.delete(self.namevar)
if hash.include? :name
raise ArgumentError, "Cannot provide both name and %s to %s" %
[self.namevar, self.name]
end
elsif hash[:name]
title = hash[:name]
hash.delete :name
end
if catalog = hash[:catalog]
hash.delete(:catalog)
end
raise(Puppet::Error, "You must specify a title for objects of type %s" % self.to_s) unless title
if hash.include? :type
unless self.validattr? :type
hash.delete :type
end
end
# okay, now make a transobject out of hash
begin
trans = Puppet::TransObject.new(title, self.name.to_s)
trans.catalog = catalog if catalog
hash.each { |param, value|
trans[param] = value
}
rescue => detail
raise Puppet::Error, "Could not create %s: %s" %
[name, detail]
end
return trans
end
# Retrieve all known instances. Either requires providers or must be overridden.
def self.instances
unless defined?(@providers) and ! @providers.empty?
@ -1263,6 +1078,44 @@ class Type
end.compact
end
# Convert a simple hash into a Resource instance. This is a convenience method,
# so people can create RAL resources with a hash and get the same behaviour
# as we get internally when we use Resource instances.
# This should only be used directly from Ruby -- it's not used when going through
# normal Puppet usage.
def self.hash2resource(hash)
hash = hash.inject({}) { |result, ary| result[ary[0].to_sym] = ary[1]; hash }
if title = hash[:title]
hash.delete(:title)
else
if self.namevar != :name
if hash.include?(:name) and hash.include?(self.namevar)
raise ArgumentError, "Cannot provide both name and %s to resources of type %s" % [self.namevar, self.name]
end
if title = hash[self.namevar]
hash.delete(self.namevar)
end
end
unless title ||= hash[:name]
raise Puppet::Error, "You must specify a name or title for resources"
end
end
if catalog = hash[:catalog]
hash.delete(:catalog)
end
# Now create our resource.
resource = Puppet::Resource.new(self.name, title)
resource.catalog = catalog if catalog
hash.each do |param, value|
resource[param] = value
end
return resource
end
# Create the path for logging and such.
def pathbuilder
if p = parent
@ -1732,47 +1585,6 @@ class Type
return @defaultprovider
end
# Convert a hash, as provided by, um, a provider, into an instance of self.
def self.hash2obj(hash)
obj = nil
namevar = self.namevar
unless hash.include?(namevar) and hash[namevar]
raise Puppet::DevError, "Hash was not passed with namevar"
end
# if the obj already exists with that name...
if obj = self[hash[namevar]]
# We're assuming here that objects with the same name
# are the same object, which *should* be the case, assuming
# we've set up our naming stuff correctly everywhere.
# Mark found objects as present
hash.each { |param, value|
if property = obj.property(param)
elsif val = obj[param]
obj[param] = val
else
# There is a value on disk, but it should go away
obj[param] = :absent
end
}
else
# create a new obj, since no existing one seems to
# match
obj = self.create(namevar => hash[namevar])
# We can't just pass the hash in at object creation time,
# because it sets the should value, not the is value.
hash.delete(namevar)
hash.each { |param, value|
obj[param] = value unless obj.add_property_parameter(param)
}
end
return obj
end
# Retrieve a provider by name.
def self.provider(name)
name = Puppet::Util.symbolize(name)
@ -2009,47 +1821,6 @@ class Type
return false
end
# we've received an event
# we only support local events right now, so we can pass actual
# objects around, including the transaction object
# the assumption here is that container objects will pass received
# methods on to contained objects
# i.e., we don't trigger our children, our refresh() method calls
# refresh() on our children
def trigger(event, source)
trans = event.transaction
if @callbacks.include?(source)
[:ALL_EVENTS, event.event].each { |eventname|
if method = @callbacks[source][eventname]
if trans.triggered?(self, method) > 0
next
end
if self.respond_to?(method)
self.send(method)
end
trans.triggered(self, method)
end
}
end
end
# Unsubscribe from a given object, possibly with a specific event.
def unsubscribe(object, event = nil)
# First look through our own relationship params
[:require, :subscribe].each do |param|
if values = self[param]
newvals = values.reject { |d|
d == [object.class.name, object.title]
}
if newvals.length != values.length
self.delete(param)
self[param] = newvals
end
end
end
end
###############################
# All of the scheduling code.
@ -2227,145 +1998,59 @@ class Type
public
def initvars
@evalcount = 0
@tags = []
# callbacks are per object and event
@callbacks = Hash.new { |chash, key|
chash[key] = {}
}
# properties and parameters are treated equivalently from the outside:
# as name-value pairs (using [] and []=)
# internally, however, parameters are merely a hash, while properties
# point to Property objects
# further, the lists of valid properties and parameters are defined
# at the class level
unless defined? @parameters
@parameters = {}
end
# keeping stats for the total number of changes, and how many were
# completely sync'ed
# this isn't really sufficient either, because it adds lots of special
# cases such as failed changes
# it also doesn't distinguish between changes from the current transaction
# vs. changes over the process lifetime
@totalchanges = 0
@syncedchanges = 0
@failedchanges = 0
@inited = true
end
# initialize the type instance
def initialize(hash)
unless defined? @inited
self.initvars
def initialize(resource)
if resource.is_a?(Puppet::TransObject)
raise Puppet::DevError, "Got TransObject instead of Resource or hash"
end
namevar = self.class.namevar
orighash = hash
unless resource.is_a?(Puppet::Resource)
resource = self.class.hash2resource(resource)
end
# If we got passed a transportable object, we just pull a bunch of info
# directly from it. This is the main object instantiation mechanism.
if hash.is_a?(Puppet::TransObject)
# XXX This will need to change when transobjects change to titles.
self.title = hash.name
# The list of parameter/property instances.
@parameters = {}
#self[:name] = hash[:name]
[:file, :line, :tags, :catalog].each { |getter|
if hash.respond_to?(getter)
setter = getter.to_s + "="
if val = hash.send(getter)
self.send(setter, val)
end
end
}
hash = hash.to_hash
# Set the title first, so any failures print correctly.
if resource.type.to_s.downcase.to_sym == self.class.name
self.title = resource.title
else
if hash[:title]
@title = hash[:title]
hash.delete(:title)
# This should only ever happen for components
self.title = resource.ref
end
[:file, :line, :catalog].each do |getter|
setter = getter.to_s + "="
if val = resource.send(getter)
self.send(setter, val)
end
end
# Before anything else, set our parent if it was included
if hash.include?(:parent)
@parent = hash[:parent]
hash.delete(:parent)
@tags = resource.tags
# If they've provided a title but no name, then set the name now.
unless name = resource[:name] || resource[self.class.namevar]
self[:name] = resource.title
end
# Munge up the namevar stuff so we only have one value.
hash = self.argclean(hash)
# Let's do the name first, because some things need to happen once
# we have the name but before anything else
attrs = self.class.allattrs
if hash.include?(namevar)
#self.send(namevar.to_s + "=", hash[namevar])
self[namevar] = hash[namevar]
hash.delete(namevar)
if attrs.include?(namevar)
attrs.delete(namevar)
else
self.devfail "My namevar isn't a valid attribute...?"
end
else
self.devfail "I was not passed a namevar"
end
# If the name and title differ, set up an alias
if self.name != self.title and self.catalog
if obj = catalog.resource(self.class.name, self.name)
if self.class.isomorphic?
raise Puppet::Error, "%s already exists with name %s" %
[obj.title, self.name]
end
else
catalog.alias(self, self.name)
found = []
(self.class.allattrs + resource.keys).uniq.each do |attr|
next unless resource.has_key?(attr)
begin
self[attr] = resource[attr]
rescue ArgumentError, Puppet::Error, TypeError
raise
rescue => detail
error = Puppet::DevError.new( "Could not set %s on %s: %s" % [attr, self.class.name, detail])
error.set_backtrace(detail.backtrace)
raise error
end
end
if hash.include?(:provider)
self[:provider] = hash[:provider]
hash.delete(:provider)
else
setdefaults(:provider)
end
# This is all of our attributes except the namevar.
attrs.each { |attr|
if hash.include?(attr)
begin
self[attr] = hash[attr]
rescue ArgumentError, Puppet::Error, TypeError
raise
rescue => detail
error = Puppet::DevError.new( "Could not set %s on %s: %s" % [attr, self.class.name, detail])
error.set_backtrace(detail.backtrace)
raise error
end
hash.delete attr
end
}
# Set all default values.
self.setdefaults
if hash.length > 0
self.debug hash.inspect
self.fail("Class %s does not accept argument(s) %s" %
[self.class.name, hash.keys.join(" ")])
end
if self.respond_to?(:validate)
self.validate
end
self.validate if self.respond_to?(:validate)
end
# Set up all of our autorequires.
@ -2395,14 +2080,6 @@ class Type
#@cache[name] = value
end
# def set(name, value)
# send(name.to_s + "=", value)
# end
#
# def get(name)
# send(name)
# end
# For now, leave the 'name' method functioning like it used to. Once 'title'
# works everywhere, I'll switch it.
def name

Просмотреть файл

@ -1,14 +1,10 @@
# the object allowing us to build complex structures
# this thing contains everything else, including itself
require 'puppet'
require 'puppet/type'
require 'puppet/transaction'
Puppet::Type.newtype(:component) do
include Enumerable
attr_accessor :children
newparam(:name) do
desc "The name of the component. Generally optional."
@ -22,67 +18,8 @@ Puppet::Type.newtype(:component) do
defaultto "component"
end
# Remove a child from the component.
def delete(child)
if @children.include?(child)
@children.delete(child)
return true
else
return super
end
end
# Recurse deeply through the tree, but only yield types, not properties.
def delve(&block)
self.each do |obj|
if obj.is_a?(self.class)
obj.delve(&block)
end
end
block.call(self)
end
# Return each child in turn.
def each
@children.each { |child| yield child }
end
# Do all of the polishing off, mostly doing autorequires and making
# dependencies. This will get run once on the top-level component,
# and it will do everything necessary.
def finalize
started = {}
finished = {}
# First do all of the finish work, which mostly involves
self.delve do |object|
# Make sure we don't get into loops
if started.has_key?(object)
debug "Already finished %s" % object.title
next
else
started[object] = true
end
unless finished.has_key?(object)
object.finish
finished[object] = true
end
end
@finalized = true
end
def finalized?
if defined? @finalized
return @finalized
else
return false
end
end
# Initialize a new component
def initialize(*args)
@children = []
super
@reference = Puppet::Resource::Reference.new(:component, @title)
@ -91,23 +28,6 @@ Puppet::Type.newtype(:component) do
catalog.alias(self, @reference.to_s)
end
end
def initvars
super
@children = []
end
# Add a hook for testing for recursion.
def parentof?(child)
if super(child)
return true
elsif @children.include?(child)
debug "child is already in children array"
return true
else
return false
end
end
# Component paths are special because they function as containers.
def pathbuilder

Просмотреть файл

@ -306,28 +306,6 @@ module Puppet
@provider.get(:ensure) != :absent
end
def initialize(params)
self.initvars
provider = nil
[:provider, "use"].each { |label|
if params.include?(label)
provider = params[label]
params.delete(label)
end
}
if provider
self[:provider] = provider
else
self.setdefaults(:provider)
end
super(params)
unless @parameters.include?(:provider)
raise Puppet::DevError, "No package provider set"
end
end
def retrieve
@provider.properties.inject({}) do |props, ary|
name, value = ary

Просмотреть файл

@ -108,6 +108,14 @@ describe Puppet::Resource do
@resource.should_not be_has_key(:eh)
end
it "should have a method for providing the list of parameters" do
@resource[:foo] = "bar"
@resource[:bar] = "foo"
keys = @resource.keys
keys.should be_include(:foo)
keys.should be_include(:bar)
end
it "should have a method for providing the number of parameters" do
@resource[:foo] = "bar"
@resource.length.should == 1

Просмотреть файл

@ -35,6 +35,122 @@ describe Puppet::Type do
end
describe "when initializing" do
it "should fail when passed a TransObject" do
trans = Puppet::TransObject.new("/foo", :mount)
lambda { Puppet::Type.type(:mount).new(trans) }.should raise_error(Puppet::DevError)
end
it "should create a Resource instance and use it to configure the RAL resource if passed a hash" do
resource = Puppet::Resource.new(:mount, "/foo")
Puppet::Resource.expects(:new).with(:mount, "/foo").returns resource
Puppet::Type.type(:mount).new(:name => "/foo")
end
it "should set its title to the title of the resource if the resource type is equal to the current type" do
resource = Puppet::Resource.new(:mount, "/foo", :name => "/other")
Puppet::Type.type(:mount).new(resource).title.should == "/foo"
end
it "should set its title to the resource reference if the resource type is not equal to the current type" do
resource = Puppet::Resource.new(:file, "/foo")
Puppet::Type.type(:mount).new(resource).title.should == "File[/foo]"
end
[:line, :file, :catalog].each do |param|
it "should copy #{param} from the resource if present" do
resource = Puppet::Resource.new(:mount, "/foo")
resource.send(param.to_s + "=", "foo")
Puppet::Type.type(:mount).new(resource).send(param).should == "foo"
end
end
it "should copy any tags from the resource" do
resource = Puppet::Resource.new(:mount, "/foo")
resource.tag "one", "two"
tags = Puppet::Type.type(:mount).new(resource).tags
tags.should be_include("one")
tags.should be_include("two")
end
it "should fail if any invalid attributes have been provided" do
resource = Puppet::Resource.new(:mount, "/foo", :nosuchattr => "foo")
lambda { Puppet::Type.type(:mount).new(resource) }.should raise_error(Puppet::Error)
end
it "should set its name to the resource's title if the resource does not have a :name or namevar parameter set" do
resource = Puppet::Resource.new(:mount, "/foo")
Puppet::Type.type(:mount).new(resource).name.should == "/foo"
end
it "should set the attributes in the order returned by the class's :allattrs method" do
Puppet::Type.type(:mount).stubs(:allattrs).returns([:name, :atboot, :noop])
resource = Puppet::Resource.new(:mount, "/foo", :name => "myname", :atboot => "myboot", :noop => "whatever")
set = []
Puppet::Type.type(:mount).any_instance.stubs(:newattr).with do |param, hash|
set << param
true
end
Puppet::Type.type(:mount).new(resource)
set.should == [:name, :atboot, :noop]
end
end
it "should have a class method for converting a hash into a Puppet::Resource instance" do
Puppet::Type.type(:mount).must respond_to(:hash2resource)
end
describe "when converting a hash to a Puppet::Resource instance" do
before do
@type = Puppet::Type.type(:mount)
end
it "should treat a :title key as the title of the resource" do
@type.hash2resource(:name => "/foo", :title => "foo").title.should == "foo"
end
it "should use the name from the hash as the title if no explicit title is provided" do
@type.hash2resource(:name => "foo").title.should == "foo"
end
it "should use the Resource Type's namevar to determine how to find the name in the hash" do
@type.stubs(:namevar).returns :myname
@type.hash2resource(:myname => "foo").title.should == "foo"
end
it "should fail if the namevar is not equal to :name and both :name and the namevar are provided" do
@type.stubs(:namevar).returns :myname
lambda { @type.hash2resource(:myname => "foo", :name => 'bar') }.should raise_error(ArgumentError)
end
it "should use any provided catalog" do
@type.hash2resource(:name => "foo", :catalog => "eh").catalog.should == "eh"
end
it "should set all provided parameters on the resource" do
@type.hash2resource(:name => "foo", :fstype => "boo", :boot => "fee").to_hash.should == {:name => "foo", :fstype => "boo", :boot => "fee"}
end
it "should not set the title as a parameter on the resource" do
@type.hash2resource(:name => "foo", :title => "eh")[:title].should be_nil
end
it "should not set the catalog as a parameter on the resource" do
@type.hash2resource(:name => "foo", :catalog => "eh")[:catalog].should be_nil
end
it "should treat hash keys equivalently whether provided as strings or symbols" do
resource = @type.hash2resource("name" => "foo", "title" => "eh", "fstype" => "boo")
resource.title.should == "eh"
resource[:name].should == "foo"
resource[:fstype].should == "boo"
end
end
describe "when retrieving current property values" do
@ -74,6 +190,7 @@ describe Puppet::Type do
@container = Puppet::Type.type(:component).create(:name => "container")
@one = Puppet::Type.type(:file).create(:path => "/file/one")
@two = Puppet::Type.type(:file).create(:path => "/file/two")
@catalog.add_resource @container
@catalog.add_resource @one
@catalog.add_resource @two

Просмотреть файл

@ -137,13 +137,6 @@ class TestType < Test::Unit::TestCase
}
end
def test_catalogs_are_set_during_initialization_if_present_on_the_transobject
trans = Puppet::TransObject.new("/path/to/some/file", :file)
trans.catalog = stub 'catalog', :resource => nil
resource = trans.to_ral
assert_equal(resource.catalog, trans.catalog, "Did not set catalog on initialization")
end
# Verify that requirements don't depend on file order
def test_prereqorder
one = tempfile()
@ -312,37 +305,6 @@ class TestType < Test::Unit::TestCase
assert_equal(:yayness, ret)
end
def test_name_vs_title
path = tempfile()
trans = nil
assert_nothing_raised {
trans = Puppet::TransObject.new(path, :file)
}
file = nil
assert_nothing_raised {
file = Puppet::Type.newfile(trans)
}
assert(file.respond_to?(:title),
"No 'title' method")
assert(file.respond_to?(:name),
"No 'name' method")
assert_equal(file.title, file.name,
"Name and title were not marked equal")
assert_nothing_raised {
file.title = "My file"
}
assert_equal("My file", file.title)
assert_equal(path, file.name)
end
# Make sure the title is sufficiently differentiated from the namevar.
def test_title_at_creation_with_hash
file = nil