diff --git a/lib/puppet/indirector/catalog/active_record.rb b/lib/puppet/indirector/catalog/active_record.rb index a76606535..ad2dc742b 100644 --- a/lib/puppet/indirector/catalog/active_record.rb +++ b/lib/puppet/indirector/catalog/active_record.rb @@ -1,34 +1,34 @@ require 'puppet/rails/host' require 'puppet/indirector/active_record' -require 'puppet/node/catalog' +require 'puppet/resource/catalog' -class Puppet::Node::Catalog::ActiveRecord < Puppet::Indirector::ActiveRecord +class Puppet::Resource::Catalog::ActiveRecord < Puppet::Indirector::ActiveRecord use_ar_model Puppet::Rails::Host # If we can find the host, then return a catalog with the host's resources # as the vertices. def find(request) return nil unless request.options[:cache_integration_hack] return nil unless host = ar_model.find_by_name(request.key) - catalog = Puppet::Node::Catalog.new(host.name) + catalog = Puppet::Resource::Catalog.new(host.name) host.resources.each do |resource| catalog.add_resource resource.to_transportable end catalog end # Save the values from a Facts instance as the facts on a Rails Host instance. def save(request) catalog = request.instance host = ar_model.find_by_name(catalog.name) || ar_model.create(:name => catalog.name) host.merge_resources(catalog.vertices) host.last_compile = Time.now host.save end end diff --git a/lib/puppet/indirector/catalog/queue.rb b/lib/puppet/indirector/catalog/queue.rb index 85d2f8df5..581382e9e 100644 --- a/lib/puppet/indirector/catalog/queue.rb +++ b/lib/puppet/indirector/catalog/queue.rb @@ -1,5 +1,5 @@ -require 'puppet/node/catalog' +require 'puppet/resource/catalog' require 'puppet/indirector/queue' -class Puppet::Node::Catalog::Queue < Puppet::Indirector::Queue +class Puppet::Resource::Catalog::Queue < Puppet::Indirector::Queue end diff --git a/lib/puppet/parser/resource.rb b/lib/puppet/parser/resource.rb index 23df1c624..8d5e5a359 100644 --- a/lib/puppet/parser/resource.rb +++ b/lib/puppet/parser/resource.rb @@ -1,379 +1,381 @@ # A resource that we're managing. This handles making sure that only subclasses # can set parameters. class Puppet::Parser::Resource require 'puppet/parser/resource/param' require 'puppet/parser/resource/reference' require 'puppet/util/tagging' require 'puppet/file_collection/lookup' + require 'puppet/parser/yaml_trimmer' include Puppet::FileCollection::Lookup include Puppet::Util include Puppet::Util::MethodHelper include Puppet::Util::Errors include Puppet::Util::Logging include Puppet::Util::Tagging + include Puppet::Parser::YamlTrimmer attr_accessor :source, :scope, :rails_id attr_accessor :virtual, :override, :translated attr_reader :exported, :evaluated, :params # Determine whether the provided parameter name is a relationship parameter. def self.relationship_parameter?(name) unless defined?(@relationship_names) @relationship_names = Puppet::Type.relationship_params.collect { |p| p.name } end @relationship_names.include?(name) end # Proxy a few methods to our @ref object. [:builtin?, :type, :title].each do |method| define_method(method) do @ref.send(method) end end # Set up some boolean test methods [:exported, :translated, :override, :virtual, :evaluated].each do |method| newmeth = (method.to_s + "?").intern define_method(newmeth) do self.send(method) end end def [](param) param = symbolize(param) if param == :title return self.title end if @params.has_key?(param) @params[param].value else nil end end def builtin=(bool) @ref.builtin = bool end def eachparam @params.each do |name, param| yield param end end # Retrieve the associated definition and evaluate it. def evaluate if klass = @ref.definedtype finish() return klass.evaluate_code(self) elsif builtin? devfail "Cannot evaluate a builtin type" else self.fail "Cannot find definition %s" % self.type end ensure @evaluated = true end # Mark this resource as both exported and virtual, # or remove the exported mark. def exported=(value) if value @virtual = true @exported = value else @exported = value end end # Do any finishing work on this object, called before evaluation or # before storage/translation. def finish return if finished? @finished = true add_defaults() add_metaparams() add_scope_tags() validate() end # Has this resource already been finished? def finished? defined?(@finished) and @finished end def initialize(options) # Set all of the options we can. options.each do |option, value| if respond_to?(option.to_s + "=") send(option.to_s + "=", value) options.delete(option) end end unless self.scope raise ArgumentError, "Resources require a scope" end @source ||= scope.source options = symbolize_options(options) # Set up our reference. if type = options[:type] and title = options[:title] options.delete(:type) options.delete(:title) else raise ArgumentError, "Resources require a type and title" end @ref = Reference.new(:type => type, :title => title, :scope => self.scope) @params = {} # Define all of the parameters if params = options[:params] options.delete(:params) params.each do |param| set_parameter(param) end end # Throw an exception if we've got any arguments left to set. unless options.empty? raise ArgumentError, "Resources do not accept %s" % options.keys.collect { |k| k.to_s }.join(", ") end tag(@ref.type) tag(@ref.title) if valid_tag?(@ref.title.to_s) end # Is this resource modeling an isomorphic resource type? def isomorphic? if builtin? return @ref.builtintype.isomorphic? else return true end end # Merge an override resource in. This will throw exceptions if # any overrides aren't allowed. def merge(resource) # Test the resource scope, to make sure the resource is even allowed # to override. unless self.source.object_id == resource.source.object_id || resource.source.child_of?(self.source) raise Puppet::ParseError.new("Only subclasses can override parameters", resource.line, resource.file) end # Some of these might fail, but they'll fail in the way we want. resource.params.each do |name, param| override_parameter(param) end end # Return the resource name, or the title if no name # was specified. def name unless defined? @name @name = self[:name] || self.title end @name end # This *significantly* reduces the number of calls to Puppet.[]. def paramcheck? unless defined? @@paramcheck @@paramcheck = Puppet[:paramcheck] end @@paramcheck end # A temporary occasion, until I get paths in the scopes figured out. def path to_s end # Return the short version of our name. def ref @ref.to_s end # Define a parameter in our resource. def set_parameter(param, value = nil) if value param = Puppet::Parser::Resource::Param.new( :name => param, :value => value, :source => self.source ) elsif ! param.is_a?(Puppet::Parser::Resource::Param) raise ArgumentError, "Must pass a parameter or all necessary values" end # And store it in our parameter hash. @params[param.name] = param end def to_hash @params.inject({}) do |hash, ary| param = ary[1] # Skip "undef" values. if param.value != :undef hash[param.name] = param.value end hash end end # Create a Puppet::Resource instance from this parser resource. # We plan, at some point, on not needing to do this conversion, but # it's sufficient for now. def to_resource result = Puppet::Resource.new(type, title) to_hash.each do |p, v| if v.is_a?(Puppet::Parser::Resource::Reference) v = Puppet::Resource::Reference.new(v.type, v.title) elsif v.is_a?(Array) # flatten resource references arrays if v.flatten.find { |av| av.is_a?(Puppet::Parser::Resource::Reference) } v = v.flatten end v = v.collect do |av| if av.is_a?(Puppet::Parser::Resource::Reference) av = Puppet::Resource::Reference.new(av.type, av.title) end av end end # If the value is an array with only one value, then # convert it to a single value. This is largely so that # the database interaction doesn't have to worry about # whether it returns an array or a string. result[p] = if v.is_a?(Array) and v.length == 1 v[0] else v end end result.file = self.file result.line = self.line result.tag(*self.tags) return result end def to_s self.ref end # Translate our object to a transportable object. def to_trans return nil if virtual? return to_resource.to_trans end # Convert this resource to a RAL resource. We hackishly go via the # transportable stuff. def to_ral to_resource.to_ral end private # Add default values from our definition. def add_defaults scope.lookupdefaults(self.type).each do |name, param| unless @params.include?(name) self.debug "Adding default for %s" % name @params[name] = param.dup end end end # Add any metaparams defined in our scope. This actually adds any metaparams # from any parent scope, and there's currently no way to turn that off. def add_metaparams Puppet::Type.eachmetaparam do |name| next if self.class.relationship_parameter?(name) # Skip metaparams that we already have defined, unless they're relationship metaparams. # LAK:NOTE Relationship metaparams get treated specially -- we stack them, instead of # overriding. next if @params[name] # Skip metaparams for which we get no value. next unless val = scope.lookupvar(name.to_s, false) and val != :undefined set_parameter(name, val) and next unless @params[name] end end def add_scope_tags if scope_resource = scope.resource tag(*scope_resource.tags) end end # Accept a parameter from an override. def override_parameter(param) # This can happen if the override is defining a new parameter, rather # than replacing an existing one. (@params[param.name] = param and return) unless current = @params[param.name] # The parameter is already set. Fail if they're not allowed to override it. unless param.source.child_of?(current.source) puts caller if Puppet[:trace] msg = "Parameter '%s' is already set on %s" % [param.name, self.to_s] if current.source.to_s != "" msg += " by %s" % current.source end if current.file or current.line fields = [] fields << current.file if current.file fields << current.line.to_s if current.line msg += " at %s" % fields.join(":") end msg += "; cannot redefine" raise Puppet::ParseError.new(msg, param.line, param.file) end # If we've gotten this far, we're allowed to override. # Merge with previous value, if the parameter was generated with the +> syntax. # It's important that we use the new param instance here, not the old one, # so that the source is registered correctly for later overrides. param.value = [current.value, param.value].flatten if param.add @params[param.name] = param end # Verify that all passed parameters are valid. This throws an error if # there's a problem, so we don't have to worry about the return value. def paramcheck(param) param = param.to_s # Now make sure it's a valid argument to our class. These checks # are organized in order of commonhood -- most types, it's a valid # argument and paramcheck is enabled. if @ref.typeclass.validattr?(param) true elsif %w{name title}.include?(param) # always allow these true elsif paramcheck? self.fail Puppet::ParseError, "Invalid parameter '%s' for type '%s'" % [param, @ref.type] end end # Make sure the resource's parameters are all valid for the type. def validate @params.each do |name, param| # Make sure it's a valid parameter. paramcheck(name) end end end diff --git a/lib/puppet/parser/resource/param.rb b/lib/puppet/parser/resource/param.rb index 6e22d3e17..0f791ecc5 100644 --- a/lib/puppet/parser/resource/param.rb +++ b/lib/puppet/parser/resource/param.rb @@ -1,29 +1,31 @@ require 'puppet/file_collection/lookup' +require 'puppet/parser/yaml_trimmer' # The parameters we stick in Resources. class Puppet::Parser::Resource::Param attr_accessor :name, :value, :source, :add include Puppet::Util include Puppet::Util::Errors include Puppet::Util::MethodHelper include Puppet::FileCollection::Lookup + include Puppet::Parser::YamlTrimmer def initialize(hash) set_options(hash) requiredopts(:name, :value, :source) @name = symbolize(@name) end def inspect - "#<#{self.class} @name => #{name}, @value => #{value}, @source => #{source.name}, @line => #{line}>" + "#<#{self.class} @name => #{name}, @value => #{value}, @source => #{source.name if source}, @line => #{line}>" end def line_to_i return line ? Integer(line) : nil end def to_s "%s => %s" % [self.name, self.value] end end diff --git a/lib/puppet/parser/resource/reference.rb b/lib/puppet/parser/resource/reference.rb index 0c28cf0df..cacd0707e 100644 --- a/lib/puppet/parser/resource/reference.rb +++ b/lib/puppet/parser/resource/reference.rb @@ -1,93 +1,99 @@ # A reference to a resource. Mostly just the type and title. require 'puppet/resource/reference' require 'puppet/file_collection/lookup' +require 'puppet/parser/yaml_trimmer' # A reference to a resource. Mostly just the type and title. class Puppet::Parser::Resource::Reference < Puppet::Resource::Reference + include Puppet::Parser::YamlTrimmer include Puppet::FileCollection::Lookup include Puppet::Util::MethodHelper include Puppet::Util::Errors attr_accessor :builtin, :file, :line, :scope # Are we a builtin type? def builtin? unless defined? @builtin if builtintype() @builtin = true else @builtin = false end end @builtin end def builtintype if t = Puppet::Type.type(self.type.downcase) and t.name != :component t else nil end end # Return the defined type for our obj. This can return classes, # definitions or nodes. def definedtype unless defined? @definedtype case self.type when "Class" # look for host classes if self.title == :main tmp = @scope.findclass("") else unless tmp = @scope.parser.classes[self.title] fail Puppet::ParseError, "Could not find class '%s'" % self.title end end when "Node" # look for node definitions unless tmp = @scope.parser.nodes[self.title] fail Puppet::ParseError, "Could not find node '%s'" % self.title end else # normal definitions # The resource type is capitalized, so we have to downcase. Really, # we should have a better interface for finding these, but eh. tmp = @scope.parser.definitions[self.type.downcase] end if tmp @definedtype = tmp else fail Puppet::ParseError, "Could not find resource type '%s'" % self.type end end @definedtype end def initialize(hash) set_options(hash) requiredopts(:type, :title) end + def skip_for_yaml + %w{@typeclass @definedtype} + end + def to_ref # We have to return different cases to provide backward compatibility # from 0.24.x to 0.23.x. if builtin? return [type.to_s.downcase, title.to_s] else return [type.to_s, title.to_s] end end def typeclass unless defined? @typeclass if tmp = builtintype || definedtype @typeclass = tmp else fail Puppet::ParseError, "Could not find type %s" % self.type end end @typeclass end end diff --git a/lib/puppet/parser/yaml_trimmer.rb b/lib/puppet/parser/yaml_trimmer.rb new file mode 100644 index 000000000..14064c8e6 --- /dev/null +++ b/lib/puppet/parser/yaml_trimmer.rb @@ -0,0 +1,11 @@ +module Puppet::Parser::YamlTrimmer + REMOVE = %w{@scope @source} + + def to_yaml_properties + r = instance_variables - REMOVE + if respond_to?(:skip_for_yaml) + r -= skip_for_yaml() + end + r + end +end diff --git a/spec/unit/indirector/catalog/active_record.rb b/spec/unit/indirector/catalog/active_record.rb index 948b811d3..cf7484110 100755 --- a/spec/unit/indirector/catalog/active_record.rb +++ b/spec/unit/indirector/catalog/active_record.rb @@ -1,119 +1,120 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../../spec_helper' require 'puppet/indirector/catalog/active_record' -describe Puppet::Node::Catalog::ActiveRecord do +describe Puppet::Resource::Catalog::ActiveRecord do confine "Missing Rails" => Puppet.features.rails? before do Puppet.features.stubs(:rails?).returns true - @terminus = Puppet::Node::Catalog::ActiveRecord.new + Puppet::Rails.stubs(:init) + @terminus = Puppet::Resource::Catalog::ActiveRecord.new end it "should be a subclass of the ActiveRecord terminus class" do - Puppet::Node::Catalog::ActiveRecord.ancestors.should be_include(Puppet::Indirector::ActiveRecord) + Puppet::Resource::Catalog::ActiveRecord.ancestors.should be_include(Puppet::Indirector::ActiveRecord) end it "should use Puppet::Rails::Host as its ActiveRecord model" do - Puppet::Node::Catalog::ActiveRecord.ar_model.should equal(Puppet::Rails::Host) + Puppet::Resource::Catalog::ActiveRecord.ar_model.should equal(Puppet::Rails::Host) end describe "when finding an instance" do before do @request = stub 'request', :key => "foo", :options => {:cache_integration_hack => true} end # This hack is here because we don't want to look in the db unless we actually want # to look in the db, but our indirection architecture in 0.24.x isn't flexible # enough to tune that via configuration. it "should return nil unless ':cache_integration_hack' is set to true" do @request.options[:cache_integration_hack] = false Puppet::Rails::Host.expects(:find_by_name).never @terminus.find(@request).should be_nil end it "should use the Hosts ActiveRecord class to find the host" do Puppet::Rails::Host.expects(:find_by_name).with { |key, args| key == "foo" } @terminus.find(@request) end it "should return nil if no host instance can be found" do Puppet::Rails::Host.expects(:find_by_name).returns nil @terminus.find(@request).should be_nil end it "should return a catalog with the same name as the host if the host can be found" do host = stub 'host', :name => "foo", :resources => [] Puppet::Rails::Host.expects(:find_by_name).returns host result = @terminus.find(@request) - result.should be_instance_of(Puppet::Node::Catalog) + result.should be_instance_of(Puppet::Resource::Catalog) result.name.should == "foo" end it "should set each of the host's resources as a transportable resource within the catalog" do host = stub 'host', :name => "foo" Puppet::Rails::Host.expects(:find_by_name).returns host res1 = mock 'res1', :to_transportable => "trans_res1" res2 = mock 'res2', :to_transportable => "trans_res2" host.expects(:resources).returns [res1, res2] catalog = stub 'catalog' - Puppet::Node::Catalog.expects(:new).returns catalog + Puppet::Resource::Catalog.expects(:new).returns catalog catalog.expects(:add_resource).with "trans_res1" catalog.expects(:add_resource).with "trans_res2" @terminus.find(@request) end end describe "when saving an instance" do before do @host = stub 'host', :name => "foo", :save => nil, :merge_resources => nil, :last_compile= => nil Puppet::Rails::Host.stubs(:find_by_name).returns @host - @catalog = Puppet::Node::Catalog.new("foo") + @catalog = Puppet::Resource::Catalog.new("foo") @request = stub 'request', :key => "foo", :instance => @catalog end it "should find the Rails host with the same name" do Puppet::Rails::Host.expects(:find_by_name).with("foo").returns @host @terminus.save(@request) end it "should create a new Rails host if none can be found" do Puppet::Rails::Host.expects(:find_by_name).with("foo").returns nil Puppet::Rails::Host.expects(:create).with(:name => "foo").returns @host @terminus.save(@request) end it "should set the catalog vertices as resources on the Rails host instance" do @catalog.expects(:vertices).returns "foo" @host.expects(:merge_resources).with("foo") @terminus.save(@request) end it "should set the last compile time on the host" do now = Time.now Time.expects(:now).returns now @host.expects(:last_compile=).with now @terminus.save(@request) end it "should save the Rails host instance" do @host.expects(:save) @terminus.save(@request) end end end diff --git a/spec/unit/indirector/catalog/queue.rb b/spec/unit/indirector/catalog/queue.rb index e47af3126..66a30c0d6 100755 --- a/spec/unit/indirector/catalog/queue.rb +++ b/spec/unit/indirector/catalog/queue.rb @@ -1,20 +1,20 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../../spec_helper' require 'puppet/indirector/catalog/queue' -describe Puppet::Node::Catalog::Queue do +describe Puppet::Resource::Catalog::Queue do it 'should be a subclass of the Queue terminus' do - Puppet::Node::Catalog::Queue.superclass.should equal(Puppet::Indirector::Queue) + Puppet::Resource::Catalog::Queue.superclass.should equal(Puppet::Indirector::Queue) end it 'should be registered with the catalog store indirection' do indirection = Puppet::Indirector::Indirection.instance(:catalog) - Puppet::Node::Catalog::Queue.indirection.should equal(indirection) + Puppet::Resource::Catalog::Queue.indirection.should equal(indirection) end it 'shall be dubbed ":queue"' do - Puppet::Node::Catalog::Queue.name.should == :queue + Puppet::Resource::Catalog::Queue.name.should == :queue end end diff --git a/spec/unit/indirector/facts/active_record.rb b/spec/unit/indirector/facts/active_record.rb index fc35f1a45..37a8f9d6b 100755 --- a/spec/unit/indirector/facts/active_record.rb +++ b/spec/unit/indirector/facts/active_record.rb @@ -1,103 +1,104 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../../spec_helper' require 'puppet/node/facts' require 'puppet/indirector/facts/active_record' describe Puppet::Node::Facts::ActiveRecord do confine "Missing Rails" => Puppet.features.rails? before do Puppet.features.stubs(:rails?).returns true + Puppet::Rails.stubs(:init) @terminus = Puppet::Node::Facts::ActiveRecord.new end it "should be a subclass of the ActiveRecord terminus class" do Puppet::Node::Facts::ActiveRecord.ancestors.should be_include(Puppet::Indirector::ActiveRecord) end it "should use Puppet::Rails::Host as its ActiveRecord model" do Puppet::Node::Facts::ActiveRecord.ar_model.should equal(Puppet::Rails::Host) end describe "when finding an instance" do before do @request = stub 'request', :key => "foo" end it "should use the Hosts ActiveRecord class to find the host" do Puppet::Rails::Host.expects(:find_by_name).with { |key, args| key == "foo" } @terminus.find(@request) end it "should include the fact names and values when finding the host" do Puppet::Rails::Host.expects(:find_by_name).with { |key, args| args[:include] == {:fact_values => :fact_name} } @terminus.find(@request) end it "should return nil if no host instance can be found" do Puppet::Rails::Host.expects(:find_by_name).returns nil @terminus.find(@request).should be_nil end it "should convert the node's parameters into a Facts instance if a host instance is found" do host = stub 'host', :name => "foo" host.expects(:get_facts_hash).returns("one" => [mock("two_value", :value => "two")], "three" => [mock("three_value", :value => "four")]) Puppet::Rails::Host.expects(:find_by_name).returns host result = @terminus.find(@request) result.should be_instance_of(Puppet::Node::Facts) result.name.should == "foo" result.values.should == {"one" => "two", "three" => "four"} end it "should convert all single-member arrays into non-arrays" do host = stub 'host', :name => "foo" host.expects(:get_facts_hash).returns("one" => [mock("two_value", :value => "two")]) Puppet::Rails::Host.expects(:find_by_name).returns host @terminus.find(@request).values["one"].should == "two" end end describe "when saving an instance" do before do @host = stub 'host', :name => "foo", :save => nil, :merge_facts => nil Puppet::Rails::Host.stubs(:find_by_name).returns @host @facts = Puppet::Node::Facts.new("foo", "one" => "two", "three" => "four") @request = stub 'request', :key => "foo", :instance => @facts end it "should find the Rails host with the same name" do Puppet::Rails::Host.expects(:find_by_name).with("foo").returns @host @terminus.save(@request) end it "should create a new Rails host if none can be found" do Puppet::Rails::Host.expects(:find_by_name).with("foo").returns nil Puppet::Rails::Host.expects(:create).with(:name => "foo").returns @host @terminus.save(@request) end it "should set the facts as facts on the Rails host instance" do # There is other stuff added to the hash. @host.expects(:merge_facts).with { |args| args["one"] == "two" and args["three"] == "four" } @terminus.save(@request) end it "should save the Rails host instance" do @host.expects(:save) @terminus.save(@request) end end end