Fixing type/title resource resolution

This code is impressively difficult, because
sometimes resource types act like resources (classes
and nodes are singletons) and sometimes like resource
types (defined and builtin resources).

So, to get nodes to show as Node[foo] and classes as
Class[Foo::Bar], but defined resources to show up as
Foo::Bar[baz], we have to do some silliness.

Signed-off-by: Luke Kanies <luke@reductivelabs.com>
This commit is contained in:
Luke Kanies 2010-01-31 13:40:33 -08:00 коммит произвёл test branch
Родитель aa659f27aa
Коммит 6e4db8261a
3 изменённых файлов: 242 добавлений и 185 удалений

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

@ -13,8 +13,7 @@ class Puppet::Resource
extend Puppet::Util::Pson
include Enumerable
attr_accessor :file, :line, :catalog, :exported, :virtual, :validate_parameters, :strict
attr_reader :title, :namespaces
attr_writer :relative_type
attr_reader :namespaces
require 'puppet/indirector'
extend Puppet::Indirector
@ -166,6 +165,8 @@ class Puppet::Resource
extract_parameters(params)
end
resolve_type_and_title()
tag(self.type)
tag(self.title) if valid_tag?(self.title)
@ -185,23 +186,38 @@ class Puppet::Resource
end
def title=(value)
if klass = resource_type and klass.respond_to?(:canonicalize_ref)
value = klass.canonicalize_ref(value)
@unresolved_title = value
@title = nil
end
def old_title
if type == "Class" and value == ""
@title = :main
return
end
if klass = resource_type
p klass
if type == "Class"
value = munge_type_name(resource_type.name)
end
if klass.respond_to?(:canonicalize_ref)
value = klass.canonicalize_ref(value)
end
elsif type == "Class"
value = munge_type_name(value)
end
@title = value
end
# Canonize the type so we know it's always consistent.
def relative_type
munge_type_name(@relative_type)
end
def resource_type
case relative_type.to_s.downcase
when "class"; find_hostclass
when "node"; find_node
case type
when "Class"; find_hostclass(title)
when "Node"; find_node(title)
else
find_builtin_resource_type || find_defined_resource_type
find_resource_type(type)
end
end
@ -306,18 +322,26 @@ class Puppet::Resource
self
end
def type
munge_type_name(if r = resource_type
resource_type.name
else
relative_type
end)
# We have to lazy-evaluate this.
def title=(value)
@title = nil
@unresolved_title = value
end
# Only allow people to set the relative type,
# so we force it to be looked up each time.
# We have to lazy-evaluate this.
def type=(value)
@relative_type = value
@type = nil
@unresolved_type = value || "Class"
end
def title
resolve_type_and_title unless @title
@title
end
def type
resolve_type_and_title unless @type
@type
end
def valid_parameter?(name)
@ -330,21 +354,25 @@ class Puppet::Resource
private
def find_node
known_resource_types.node(title)
def find_node(name)
known_resource_types.node(name)
end
def find_hostclass
def find_hostclass(title)
name = title == :main ? "" : title
known_resource_types.find_hostclass(namespaces, name)
end
def find_builtin_resource_type
Puppet::Type.type(relative_type.to_s.downcase.to_sym)
def find_resource_type(type)
find_builtin_resource_type(type) || find_defined_resource_type(type)
end
def find_defined_resource_type
known_resource_types.find_definition(namespaces, relative_type.to_s.downcase)
def find_builtin_resource_type(type)
Puppet::Type.type(type.to_s.downcase.to_sym)
end
def find_defined_resource_type(type)
known_resource_types.find_definition(namespaces, type.to_s.downcase)
end
# Produce a canonical method name.
@ -381,8 +409,6 @@ class Puppet::Resource
return bucket
end
private
def extract_parameters(params)
params.each do |param, value|
validate_parameter(param) if strict?
@ -399,12 +425,72 @@ class Puppet::Resource
end
def munge_type_name(value)
return :main if value == ""
return :main if value == :main
return "Class" if value == "" or value.nil? or value.to_s.downcase == "component"
if value.nil? or value.to_s.downcase == "component"
"Class"
value.to_s.split("::").collect { |s| s.capitalize }.join("::")
end
# This is an annoyingly complicated method for resolving qualified
# types as necessary, and putting them in type or title attributes.
def resolve_type_and_title
if @unresolved_type
@type = resolve_type
@unresolved_type = nil
end
if @unresolved_title
@title = resolve_title
@unresolved_title = nil
end
end
def resolve_type
type = munge_type_name(@unresolved_type)
case type
when "Class", "Node";
return type
else
value.to_s.split("::").collect { |s| s.capitalize }.join("::")
# Otherwise, some kind of builtin or defined resource type
return munge_type_name(if r = find_resource_type(type)
r.name
else
type
end)
end
end
# This method only works if resolve_type was called first
def resolve_title
case @type
when "Node"; return @unresolved_title
when "Class";
resolve_title_for_class(@unresolved_title)
else
resolve_title_for_resource(@unresolved_title)
end
end
def resolve_title_for_class(title)
if title == "" or title == :main
return :main
end
if klass = find_hostclass(title)
result = klass.name
if klass.respond_to?(:canonicalize_ref)
result = klass.canonicalize_ref(result)
end
end
return munge_type_name(result || title)
end
def resolve_title_for_resource(title)
if type = find_resource_type(@type) and type.respond_to?(:canonicalize_ref)
return type.canonicalize_ref(title)
else
return title
end
end
end

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

@ -48,7 +48,7 @@ describe Puppet::Resource do
it "should set its type to 'Class' and its title to the passed title if the passed type is :component and the title has no square brackets in it" do
ref = Puppet::Resource.new(:component, "foo")
ref.type.should == "Class"
ref.title.should == "foo"
ref.title.should == "Foo"
end
it "should interpret the title as a reference and assign appropriately if the type is :component and the title contains square brackets" do
@ -60,7 +60,7 @@ describe Puppet::Resource do
it "should set the type to 'Class' if it is nil and the title contains no square brackets" do
ref = Puppet::Resource.new(nil, "yay")
ref.type.should == "Class"
ref.title.should == "yay"
ref.title.should == "Yay"
end
it "should interpret the title as a reference and assign appropriately if the type is nil and the title contains square brackets" do
@ -113,108 +113,159 @@ describe Puppet::Resource do
end
it "should support specifying namespaces" do
Puppet::Resource.new("file", "/my/file", :namespaces => [:foo]).namespaces.should == [:foo]
Puppet::Resource.new("file", "/my/file", :namespaces => ["foo"]).namespaces.should == ["foo"]
end
it "should convert namespaces to an array if not specified as one" do
Puppet::Resource.new("file", "/my/file", :namespaces => :foo).namespaces.should == [:foo]
Puppet::Resource.new("file", "/my/file", :namespaces => "foo").namespaces.should == ["foo"]
end
it "should default to a single amespace of an empty string" do
Puppet::Resource.new("file", "/my/file").namespaces.should == [""]
end
it "should be able to look up its resource type when the type is a builtin resource" do
Puppet::Resource.new("file", "/my/file").resource_type.should equal(Puppet::Type.type(:file))
end
describe "and munging its type and title" do
describe "when modeling a builtin resource" do
it "should be able to find the resource type" do
Puppet::Resource.new("file", "/my/file").resource_type.should equal(Puppet::Type.type(:file))
end
it "should be able to look up its resource type via its environment when the type is a defined resource type" do
resource = Puppet::Resource.new("foobar", "/my/file")
type = Puppet::Resource::Type.new(:definition, "foobar")
resource.environment.known_resource_types.add type
it "should set its type to the capitalized type name" do
Puppet::Resource.new("file", "/my/file").type.should == "File"
end
end
resource.resource_type.should equal(type)
end
describe "when modeling a defined resource" do
describe "that exists" do
before do
@type = Puppet::Resource::Type.new(:definition, "foo::bar")
Puppet::Node::Environment.new.known_resource_types.add @type
end
it "should be able to look up its resource type via its environment when the type is a node" do
resource = Puppet::Resource.new("node", "foobar")
node = Puppet::Resource::Type.new(:node, "foobar")
resource.environment.known_resource_types.add node
it "should set its type to the capitalized type name" do
Puppet::Resource.new("foo::bar", "/my/file").type.should == "Foo::Bar"
end
resource.resource_type.should equal(node)
end
it "should be able to find the resource type" do
Puppet::Resource.new("foo::bar", "/my/file").resource_type.should equal(@type)
end
it "should be able to look up its resource type via its environment when the type is a class" do
resource = Puppet::Resource.new("class", "foobar")
klass = Puppet::Resource::Type.new(:hostclass, "foobar")
resource.environment.known_resource_types.add klass
it "should set its title to the provided title" do
Puppet::Resource.new("foo::bar", "/my/file").title.should == "/my/file"
end
resource.resource_type.should equal(klass)
end
describe "and the resource is unqualified and models a qualified resource type" do
it "should set its type to the fully qualified resource type" do
Puppet::Resource.new("bar", "/my/file", :namespaces => %w{foo}).type.should == "Foo::Bar"
end
it "should use its namespaces when looking up defined resource types" do
resource = Puppet::Resource.new("bar", "/my/file", :namespaces => ["foo"])
type = Puppet::Resource::Type.new(:definition, "foo::bar")
resource.environment.known_resource_types.add type
it "should be able to find the resource type" do
Puppet::Resource.new("bar", "/my/file", :namespaces => %w{foo}).resource_type.should equal(@type)
end
end
end
resource.resource_type.should equal(type)
end
describe "that does not exist" do
it "should set its resource type to the capitalized resource type name" do
Puppet::Resource.new("foo::bar", "/my/file").type.should == "Foo::Bar"
end
end
end
it "should use its namespaces to set its type name when looking up defined resource types" do
type = Puppet::Resource::Type.new(:definition, "foo::bar")
Puppet::Node::Environment.new.known_resource_types.add type
resource = Puppet::Resource.new("bar", "/my/file", :namespaces => ["foo"])
resource.type.should == "Foo::Bar"
end
describe "when modeling a node" do
# Life's easier with nodes, because they can't be qualified.
it "should set its type to 'Node' and its title to the provided title" do
node = Puppet::Resource.new("node", "foo")
node.type.should == "Node"
node.title.should == "foo"
end
end
it "should look up its resource type when set manually" do
type = Puppet::Resource::Type.new(:definition, "foo::bar")
Puppet::Node::Environment.new.known_resource_types.add type
resource = Puppet::Resource.new("foo", "/my/file", :namespaces => ["foo"])
resource.type = "bar"
resource.type.should == "Foo::Bar"
end
describe "when modeling a class" do
it "should set its type to 'Class'" do
Puppet::Resource.new("class", "foo").type.should == "Class"
end
it "should use its namespaces when looking up host classes" do
resource = Puppet::Resource.new("class", "bar", :namespaces => ["foo"])
type = Puppet::Resource::Type.new(:hostclass, "foo::bar")
resource.environment.known_resource_types.add type
describe "that exists" do
before do
@type = Puppet::Resource::Type.new(:hostclass, "foo::bar")
Puppet::Node::Environment.new.known_resource_types.add @type
end
resource.resource_type.should equal(type)
end
it "should set its title to the capitalized, fully qualified resource type" do
Puppet::Resource.new("class", "foo::bar").title.should == "Foo::Bar"
end
it "should consider a class whose name is an empty string to be the main class" do
type = Puppet::Resource::Type.new(:hostclass, "")
Puppet::Node::Environment.new.known_resource_types.add type
it "should be able to find the resource type" do
Puppet::Resource.new("class", "foo::bar").resource_type.should equal(@type)
end
resource = Puppet::Resource.new("class", "").type.should == :main
describe "and the resource is unqualified and models a qualified class" do
it "should set its title to the fully qualified resource type" do
Puppet::Resource.new("class", "bar", :namespaces => %w{foo}).title.should == "Foo::Bar"
end
it "should be able to find the resource type" do
Puppet::Resource.new("class", "bar", :namespaces => %w{foo}).resource_type.should equal(@type)
end
it "should set its type to 'Class'" do
Puppet::Resource.new("class", "bar", :namespaces => %w{foo}).type.should == "Class"
end
end
end
describe "that does not exist" do
it "should set its type to 'Class' and its title to the capitalized provided name" do
klass = Puppet::Resource.new("class", "foo::bar")
klass.type.should == "Class"
klass.title.should == "Foo::Bar"
end
end
describe "and its name is set to the empty string" do
it "should set its title to :main" do
Puppet::Resource.new("class", "").title.should == :main
end
describe "and a class exists whose name is the empty string" do # this was a bit tough to track down
it "should set its title to :main" do
@type = Puppet::Resource::Type.new(:hostclass, "")
Puppet::Node::Environment.new.known_resource_types.add @type
Puppet::Resource.new("class", "").title.should == :main
end
end
end
describe "and its name is set to :main" do
it "should set its title to :main" do
Puppet::Resource.new("class", :main).title.should == :main
end
describe "and a class exists whose name is the empty string" do # this was a bit tough to track down
it "should set its title to :main" do
@type = Puppet::Resource::Type.new(:hostclass, "")
Puppet::Node::Environment.new.known_resource_types.add @type
Puppet::Resource.new("class", :main).title.should == :main
end
end
end
end
end
it "should return nil when looking up resource types that don't exist" do
Puppet::Resource.new("foobar", "bar").resource_type.should be_nil
end
it "should fail when an invalid parameter is used and parameter validation is enabled" do
type = Puppet::Resource::Type.new(:definition, "foobar")
Puppet::Node::Environment.new.known_resource_types.add type
resource = Puppet::Resource.new("foobar", "/my/file", :validate_parameters => true)
lambda { resource[:yay] = true }.should raise_error(ArgumentError)
end
it "should not fail when an invalid parameter is used and parameter validation is disabled" do
it "should not fail when an invalid parameter is used and strict mode is disabled" do
type = Puppet::Resource::Type.new(:definition, "foobar")
Puppet::Node::Environment.new.known_resource_types.add type
resource = Puppet::Resource.new("foobar", "/my/file")
resource[:yay] = true
end
it "should not fail when a valid parameter is used and parameter validation is enabled" do
type = Puppet::Resource::Type.new(:definition, "foobar", :arguments => {"yay" => nil})
Puppet::Node::Environment.new.known_resource_types.add type
resource = Puppet::Resource.new("foobar", "/my/file", :validate_parameters => true)
resource[:yay] = true
end
it "should be considered equivalent to another resource if their type and title match and no parameters are set" do
Puppet::Resource.new("file", "/f").should == Puppet::Resource.new("file", "/f")
end
@ -239,10 +290,7 @@ describe Puppet::Resource do
Puppet::Resource.new("file", "/foo").should_not == Puppet::Resource.new("file", "/f")
end
describe "when refering to a resource with name canonicalization" do
before do
end
describe "when referring to a resource with name canonicalization" do
it "should canonicalize its own name" do
res = Puppet::Resource.new("file", "/path/")
res.title.should == "/path"

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

@ -1,77 +0,0 @@
#!/usr/bin/env ruby
#
# Created by Luke A. Kanies on 2007-07-8.
# Copyright (c) 2007. All rights reserved.
require File.dirname(__FILE__) + '/../../lib/puppettest'
require 'puppettest'
require 'puppettest/parsertesting'
class TestASTResourceReference < Test::Unit::TestCase
include PuppetTest
include PuppetTest::ParserTesting
AST = Puppet::Parser::AST
def newref(type, title)
AST::ResourceReference.new(:type => type, :title => AST::String.new(:value => title))
end
def setup
super
@scope = mkscope
@parser = Puppet::Parser::Parser.new(Puppet::Node::Environment.new)
end
# Related to #706, make sure resource references correctly translate to qualified types.
def test_scoped_references
@parser.newdefine "one"
@parser.newdefine "one::two"
@parser.newdefine "three"
twoscope = @scope.newscope(:namespace => "one")
assert(twoscope.find_definition("two"), "Could not find 'two' definition")
title = "title"
# First try a qualified type
assert_equal("One::Two", newref("two", title).evaluate(twoscope).type,
"Defined type was not made fully qualified")
# Then try a type that does not need to be qualified
assert_equal("One", newref("one", title).evaluate(twoscope).type,
"Unqualified defined type was not handled correctly")
# Then an unqualified type from within the one namespace
assert_equal("Three", newref("three", title).evaluate(twoscope).type,
"Defined type was not made fully qualified")
# Then a builtin type
assert_equal("File", newref("file", title).evaluate(twoscope).type,
"Builtin type was not handled correctly")
# Now try a type that does not exist, which should throw an error.
assert_raise(Puppet::ParseError, "Did not fail on a missing type in a resource reference") do
newref("nosuchtype", title).evaluate(twoscope)
end
# Now run the same tests, but with the classes
@parser.newclass "four"
@parser.newclass "one::five"
# First try an unqualified type
assert_equal("four", newref("class", "four").evaluate(twoscope).title,
"Unqualified class was not found")
# Then a qualified class
assert_equal("one::five", newref("class", "five").evaluate(twoscope).title,
"Class was not made fully qualified")
# Then try a type that does not need to be qualified
assert_equal("four", newref("class", "four").evaluate(twoscope).title,
"Unqualified class was not handled correctly")
# Now try a type that does not exist, which should throw an error.
assert_raise(Puppet::ParseError, "Did not fail on a missing type in a resource reference") do
newref("class", "nosuchclass").evaluate(twoscope)
end
end
end