# An object that collects stored objects from the central cache and returns # them to the current host, yo. class Puppet::Parser::Collector attr_accessor :type, :scope, :vquery, :equery, :form, :resources, :overrides, :collected # Call the collection method, mark all of the returned objects as non-virtual, # optionally applying parameter overrides. The collector can also delete himself # from the compiler if there is no more resources to collect (valid only for resource fixed-set collector # which get their resources from +collect_resources+ and not from the catalog) def evaluate # Shortcut if we're not using storeconfigs and they're trying to collect # exported resources. if form == :exported and Puppet[:storeconfigs] != true Puppet.warning "Not collecting exported resources without storeconfigs" return false end if self.resources unless objects = collect_resources and ! objects.empty? return false end else method = "collect_#{@form.to_s}" objects = send(method).each do |obj| obj.virtual = false end return false if objects.empty? end # we have an override for the collected resources if @overrides and !objects.empty? # force the resource to be always child of any other resource overrides[:source].meta_def(:child_of?) do true end # tell the compiler we have some override for him unless we already # overrided those resources objects.each do |res| unless @collected.include?(res.ref) newres = Puppet::Parser::Resource.new( res.type, res.title, :parameters => overrides[:parameters], :file => overrides[:file], :line => overrides[:line], :source => overrides[:source], :scope => overrides[:scope] ) scope.compiler.add_override(newres) end end end # filter out object that already have been collected by ourself objects.reject! { |o| @collected.include?(o.ref) } return false if objects.empty? # keep an eye on the resources we have collected objects.inject(@collected) { |c,o| c[o.ref]=o; c } # return our newly collected resources objects end def initialize(scope, type, equery, vquery, form) @scope = scope # initialisation @collected = {} # Canonize the type @type = Puppet::Resource.new(type, "whatever").type @equery = equery @vquery = vquery raise(ArgumentError, "Invalid query form #{form}") unless [:exported, :virtual].include?(form) @form = form end # add a resource override to the soon to be exported/realized resources def add_override(hash) raise ArgumentError, "Exported resource try to override without parameters" unless hash[:parameters] # schedule an override for an upcoming collection @overrides = hash end private # Create our active record query. def build_active_record_query Puppet::Rails.init unless ActiveRecord::Base.connected? raise Puppet::DevError, "Cannot collect resources for a nil host" unless @scope.host host = Puppet::Rails::Host.find_by_name(@scope.host) search = "(exported=? AND restype=?)" values = [true, @type] search += " AND (#{@equery})" if @equery # note: # we're not eagerly including any relations here because # it can creates so much objects we'll throw out later. # We used to eagerly include param_names/values but the way # the search filter is built ruined those efforts and we # were eagerly loading only the searched parameter and not # the other ones. query = {} case search when /puppet_tags/ query = {:joins => {:resource_tags => :puppet_tag}} when /param_name/ query = {:joins => {:param_values => :param_name}} end # We're going to collect objects from rails, but we don't want any # objects from this host. search = ("host_id != ? AND #{search}") and values.unshift(host.id) if host query[:conditions] = [search, *values] query end # Collect exported objects. def collect_exported # First get everything from the export table. Just reuse our # collect_virtual method but tell it to use 'exported? for the test. resources = collect_virtual(true).reject { |r| ! r.virtual? } count = resources.length query = build_active_record_query # Now look them up in the rails db. When we support attribute comparison # and such, we'll need to vary the conditions, but this works with no # attributes, anyway. time = Puppet::Util.thinmark do Puppet::Rails::Resource.find(:all, query).each do |obj| if resource = exported_resource(obj) count += 1 resources << resource end end end scope.debug("Collected %s %s resource%s in %.2f seconds" % [count, @type, count == 1 ? "" : "s", time]) resources end def collect_resources @resources = [@resources] unless @resources.is_a?(Array) method = "collect_#{form.to_s}_resources" send(method) end def collect_exported_resources raise Puppet::ParseError, "realize() is not yet implemented for exported resources" end # Collect resources directly; this is the result of using 'realize', # which specifies resources, rather than using a normal collection. def collect_virtual_resources return [] unless defined?(@resources) and ! @resources.empty? result = @resources.dup.collect do |ref| if res = @scope.findresource(ref.to_s) @resources.delete(ref) res end end.reject { |r| r.nil? }.each do |res| res.virtual = false end # If there are no more resources to find, delete this from the list # of collections. @scope.compiler.delete_collection(self) if @resources.empty? result end # Collect just virtual objects, from our local compiler. def collect_virtual(exported = false) scope.compiler.resources.find_all do |resource| resource.type == @type and (exported ? resource.exported? : true) and match?(resource) end end # Seek a specific exported resource. def exported_resource(obj) if existing = @scope.findresource(obj.restype, obj.title) # Next see if we've already collected this resource return nil if existing.rails_id == obj.id # This is the one we've already collected raise Puppet::ParseError, "Exported resource #{obj.ref} cannot override local resource" end resource = obj.to_resource(self.scope) resource.exported = false scope.compiler.add_resource(scope, resource) resource end # Does the resource match our tests? We don't yet support tests, # so it's always true at the moment. def match?(resource) if self.vquery return self.vquery.call(resource) else return true end end end