diff --git a/lib/puppet/face/ca.rb b/lib/puppet/face/ca.rb index 9b5e8d361..54ed8304e 100644 --- a/lib/puppet/face/ca.rb +++ b/lib/puppet/face/ca.rb @@ -1,242 +1,242 @@ require 'puppet/face' Puppet::Face.define(:ca, '0.1.0') do copyright "Puppet Labs", 2011 license "Apache 2 license; see COPYING" summary "Local Puppet Certificate Authority management." description <<-TEXT This provides local management of the Puppet Certificate Authority. You can use this subcommand to sign outstanding certificate requests, list and manage local certificates, and inspect the state of the CA. TEXT action :list do summary "List certificates and/or certificate requests." description <<-TEXT This will list the current certificates and certificate signing requests in the Puppet CA. You will also get the fingerprint, and any certificate verification failure reported. TEXT option "--[no-]all" do summary "Include all certificates and requests." end option "--[no-]pending" do summary "Include pending certificate signing requests." end option "--[no-]signed" do summary "Include signed certificates." end option "--subject PATTERN" do summary "Only list if the subject matches PATTERN." description <<-TEXT Only include certificates or requests where subject matches PATTERN. PATTERN is interpreted as a regular expression, allowing complex filtering of the content. TEXT end when_invoked do |options| raise "Not a CA" unless Puppet::SSL::CertificateAuthority.ca? unless ca = Puppet::SSL::CertificateAuthority.instance raise "Unable to fetch the CA" end pattern = options[:subject].nil? ? nil : Regexp.new(options[:subject], Regexp::IGNORECASE) pending = options[:pending].nil? ? options[:all] : options[:pending] signed = options[:signed].nil? ? options[:all] : options[:signed] # By default we list pending, so if nothing at all was requested... unless pending or signed then pending = true end hosts = [] pending and hosts += ca.waiting? signed and hosts += ca.list pattern and hosts = hosts.select {|hostname| pattern.match hostname } hosts.sort.map {|host| Puppet::SSL::Host.new(host) } end when_rendering :console do |hosts| unless ca = Puppet::SSL::CertificateAuthority.instance raise "Unable to fetch the CA" end length = hosts.map{|x| x.name.length }.max.to_i + 1 hosts.map do |host| name = host.name.ljust(length) if host.certificate_request then " #{name} (#{host.certificate_request.fingerprint})" else begin - ca.verify(host.certificate) + ca.verify(host.name) "+ #{name} (#{host.certificate.fingerprint})" rescue Puppet::SSL::CertificateAuthority::CertificateVerificationError => e "- #{name} (#{host.certificate.fingerprint}) (#{e.to_s})" end end end.join("\n") end end action :destroy do when_invoked do |host, options| raise "Not a CA" unless Puppet::SSL::CertificateAuthority.ca? unless ca = Puppet::SSL::CertificateAuthority.instance raise "Unable to fetch the CA" end ca.destroy host end end action :revoke do when_invoked do |host, options| raise "Not a CA" unless Puppet::SSL::CertificateAuthority.ca? unless ca = Puppet::SSL::CertificateAuthority.instance raise "Unable to fetch the CA" end begin ca.revoke host rescue ArgumentError => e # This is a bit naff, but it makes the behaviour consistent with the # destroy action. The underlying tools could be nicer for that sort # of thing; they have fairly inconsistent reporting of failures. raise unless e.to_s =~ /Could not find a serial number for / "Nothing was revoked" end end end action :generate do option "--dns-alt-names NAMES" do summary "Additional DNS names to add to the certificate request" description Puppet.settings.setting(:dns_alt_names).desc end when_invoked do |host, options| raise "Not a CA" unless Puppet::SSL::CertificateAuthority.ca? unless ca = Puppet::SSL::CertificateAuthority.instance raise "Unable to fetch the CA" end begin ca.generate(host, :dns_alt_names => options[:dns_alt_names]) rescue RuntimeError => e if e.to_s =~ /already has a requested certificate/ "#{host} already has a certificate request; use sign instead" else raise end rescue ArgumentError => e if e.to_s =~ /A Certificate already exists for / "#{host} already has a certificate" else raise end end end end action :sign do option("--[no-]allow-dns-alt-names") do summary "Whether or not to accept DNS alt names in the certificate request" end when_invoked do |host, options| raise "Not a CA" unless Puppet::SSL::CertificateAuthority.ca? unless ca = Puppet::SSL::CertificateAuthority.instance raise "Unable to fetch the CA" end begin ca.sign(host, options[:allow_dns_alt_names]) rescue ArgumentError => e if e.to_s =~ /Could not find certificate request/ e.to_s else raise end end end end action :print do when_invoked do |host, options| raise "Not a CA" unless Puppet::SSL::CertificateAuthority.ca? unless ca = Puppet::SSL::CertificateAuthority.instance raise "Unable to fetch the CA" end ca.print host end end action :fingerprint do option "--digest ALGORITHM" do summary "The hash algorithm to use when displaying the fingerprint" end when_invoked do |host, options| raise "Not a CA" unless Puppet::SSL::CertificateAuthority.ca? unless ca = Puppet::SSL::CertificateAuthority.instance raise "Unable to fetch the CA" end begin # I want the default from the CA, not to duplicate it, but passing # 'nil' explicitly means that we don't get that. This works... if options.has_key? :digest ca.fingerprint host, options[:digest] else ca.fingerprint host end rescue ArgumentError => e raise unless e.to_s =~ /Could not find a certificate or csr for/ nil end end end action :verify do when_invoked do |host, options| raise "Not a CA" unless Puppet::SSL::CertificateAuthority.ca? unless ca = Puppet::SSL::CertificateAuthority.instance raise "Unable to fetch the CA" end begin ca.verify host { :host => host, :valid => true } rescue ArgumentError => e raise unless e.to_s =~ /Could not find a certificate for/ { :host => host, :valid => false, :error => e.to_s } rescue Puppet::SSL::CertificateAuthority::CertificateVerificationError => e { :host => host, :valid => false, :error => e.to_s } end end when_rendering :console do |value| if value[:valid] nil else "Could not verify #{value[:host]}: #{value[:error]}" end end end end diff --git a/lib/puppet/indirector/indirection.rb b/lib/puppet/indirector/indirection.rb index 65f8526ab..5e9f3ef66 100644 --- a/lib/puppet/indirector/indirection.rb +++ b/lib/puppet/indirector/indirection.rb @@ -1,323 +1,323 @@ require 'puppet/util/docs' require 'puppet/indirector/envelope' require 'puppet/indirector/request' require 'puppet/util/instrumentation/instrumentable' # The class that connects functional classes with their different collection # back-ends. Each indirection has a set of associated terminus classes, # each of which is a subclass of Puppet::Indirector::Terminus. class Puppet::Indirector::Indirection include Puppet::Util::Docs extend Puppet::Util::Instrumentation::Instrumentable probe :find, :label => Proc.new { |parent, key, *args| "find_#{parent.name}_#{parent.terminus_class}" }, :data => Proc.new { |parent, key, *args| { :key => key }} probe :save, :label => Proc.new { |parent, key, *args| "save_#{parent.name}_#{parent.terminus_class}" }, :data => Proc.new { |parent, key, *args| { :key => key }} probe :search, :label => Proc.new { |parent, key, *args| "search_#{parent.name}_#{parent.terminus_class}" }, :data => Proc.new { |parent, key, *args| { :key => key }} probe :destroy, :label => Proc.new { |parent, key, *args| "destroy_#{parent.name}_#{parent.terminus_class}" }, :data => Proc.new { |parent, key, *args| { :key => key }} @@indirections = [] # Find an indirection by name. This is provided so that Terminus classes # can specifically hook up with the indirections they are associated with. def self.instance(name) @@indirections.find { |i| i.name == name } end # Return a list of all known indirections. Used to generate the # reference. def self.instances @@indirections.collect { |i| i.name } end # Find an indirected model by name. This is provided so that Terminus classes # can specifically hook up with the indirections they are associated with. def self.model(name) return nil unless match = @@indirections.find { |i| i.name == name } match.model end attr_accessor :name, :model attr_reader :termini # Create and return our cache terminus. def cache raise(Puppet::DevError, "Tried to cache when no cache class was set") unless cache_class terminus(cache_class) end # Should we use a cache? def cache? cache_class ? true : false end attr_reader :cache_class # Define a terminus class to be used for caching. def cache_class=(class_name) validate_terminus_class(class_name) if class_name @cache_class = class_name end # This is only used for testing. def delete @@indirections.delete(self) if @@indirections.include?(self) end # Set the time-to-live for instances created through this indirection. def ttl=(value) raise ArgumentError, "Indirection TTL must be an integer" unless value.is_a?(Fixnum) @ttl = value end # Default to the runinterval for the ttl. def ttl @ttl ||= Puppet[:runinterval].to_i end # Calculate the expiration date for a returned instance. def expiration Time.now + ttl end # Generate the full doc string. def doc text = "" text += scrub(@doc) + "\n\n" if @doc if s = terminus_setting text += "* **Terminus Setting**: #{terminus_setting}" end text end def initialize(model, name, options = {}) @model = model @name = name @termini = {} @cache_class = nil @terminus_class = nil raise(ArgumentError, "Indirection #{@name} is already defined") if @@indirections.find { |i| i.name == @name } @@indirections << self if mod = options[:extend] extend(mod) options.delete(:extend) end # This is currently only used for cache_class and terminus_class. options.each do |name, value| begin send(name.to_s + "=", value) rescue NoMethodError raise ArgumentError, "#{name} is not a valid Indirection parameter" end end end # Set up our request object. def request(*args) Puppet::Indirector::Request.new(self.name, *args) end # Return the singleton terminus for this indirection. def terminus(terminus_name = nil) # Get the name of the terminus. raise Puppet::DevError, "No terminus specified for #{self.name}; cannot redirect" unless terminus_name ||= terminus_class termini[terminus_name] ||= make_terminus(terminus_name) end # This can be used to select the terminus class. attr_accessor :terminus_setting # Determine the terminus class. def terminus_class unless @terminus_class if setting = self.terminus_setting self.terminus_class = Puppet.settings[setting].to_sym else raise Puppet::DevError, "No terminus class nor terminus setting was provided for indirection #{self.name}" end end @terminus_class end def reset_terminus_class @terminus_class = nil end # Specify the terminus class to use. def terminus_class=(klass) validate_terminus_class(klass) @terminus_class = klass end # This is used by terminus_class= and cache=. def validate_terminus_class(terminus_class) raise ArgumentError, "Invalid terminus name #{terminus_class.inspect}" unless terminus_class and terminus_class.to_s != "" unless Puppet::Indirector::Terminus.terminus_class(self.name, terminus_class) raise ArgumentError, "Could not find terminus #{terminus_class} for indirection #{self.name}" end end # Expire a cached object, if one is cached. Note that we don't actually # remove it, we expire it and write it back out to disk. This way people # can still use the expired object if they want. - def expire(key, *args) - request = request(:expire, key, *args) + def expire(key, options={}) + request = request(:expire, key, nil, options) return nil unless cache? - return nil unless instance = cache.find(request(:find, key, *args)) + return nil unless instance = cache.find(request(:find, key, nil, options)) Puppet.info "Expiring the #{self.name} cache of #{instance.name}" # Set an expiration date in the past instance.expiration = Time.now - 60 - cache.save(request(:save, instance, *args)) + cache.save(request(:save, nil, instance, options)) end # Search for an instance in the appropriate terminus, caching the # results if caching is configured.. - def find(key, *args) - request = request(:find, key, *args) + def find(key, options={}) + request = request(:find, key, nil, options) terminus = prepare(request) if result = find_in_cache(request) return result end # Otherwise, return the result from the terminus, caching if appropriate. if ! request.ignore_terminus? and result = terminus.find(request) result.expiration ||= self.expiration if result.respond_to?(:expiration) if cache? and request.use_cache? Puppet.info "Caching #{self.name} for #{request.key}" - cache.save request(:save, result, *args) + cache.save request(:save, nil, result, options) end return terminus.respond_to?(:filter) ? terminus.filter(result) : result end nil end # Search for an instance in the appropriate terminus, and return a # boolean indicating whether the instance was found. - def head(key, *args) - request = request(:head, key, *args) + def head(key, options={}) + request = request(:head, key, nil, options) terminus = prepare(request) # Look in the cache first, then in the terminus. Force the result # to be a boolean. !!(find_in_cache(request) || terminus.head(request)) end def find_in_cache(request) # See if our instance is in the cache and up to date. return nil unless cache? and ! request.ignore_cache? and cached = cache.find(request) if cached.expired? Puppet.info "Not using expired #{self.name} for #{request.key} from cache; expired at #{cached.expiration}" return nil end Puppet.debug "Using cached #{self.name} for #{request.key}" cached rescue => detail Puppet.log_exception(detail, "Cached #{self.name} for #{request.key} failed: #{detail}") nil end # Remove something via the terminus. - def destroy(key, *args) - request = request(:destroy, key, *args) + def destroy(key, options={}) + request = request(:destroy, key, nil, options) terminus = prepare(request) result = terminus.destroy(request) - if cache? and cached = cache.find(request(:find, key, *args)) + if cache? and cached = cache.find(request(:find, key, nil, options)) # Reuse the existing request, since it's equivalent. cache.destroy(request) end result end # Search for more than one instance. Should always return an array. - def search(key, *args) - request = request(:search, key, *args) + def search(key, options={}) + request = request(:search, key, nil, options) terminus = prepare(request) if result = terminus.search(request) raise Puppet::DevError, "Search results from terminus #{terminus.name} are not an array" unless result.is_a?(Array) result.each do |instance| next unless instance.respond_to? :expiration instance.expiration ||= self.expiration end return result end end # Save the instance in the appropriate terminus. This method is # normally an instance method on the indirected class. - def save(instance, key = nil, *args) - request = request(:save, key, instance, *args) + def save(instance, key = nil, options={}) + request = request(:save, key, instance, options) terminus = prepare(request) result = terminus.save(request) # If caching is enabled, save our document there cache.save(request) if cache? result end private # Check authorization if there's a hook available; fail if there is one # and it returns false. def check_authorization(request, terminus) # At this point, we're assuming authorization makes no sense without # client information. return unless request.node # This is only to authorize via a terminus-specific authorization hook. return unless terminus.respond_to?(:authorized?) unless terminus.authorized?(request) msg = "Not authorized to call #{request.method} on #{request}" msg += " with #{request.options.inspect}" unless request.options.empty? raise ArgumentError, msg end end # Setup a request, pick the appropriate terminus, check the request's authorization, and return it. def prepare(request) # Pick our terminus. if respond_to?(:select_terminus) unless terminus_name = select_terminus(request) raise ArgumentError, "Could not determine appropriate terminus for #{request}" end else terminus_name = terminus_class end dest_terminus = terminus(terminus_name) check_authorization(request, dest_terminus) dest_terminus end # Create a new terminus instance. def make_terminus(terminus_class) # Load our terminus class. unless klass = Puppet::Indirector::Terminus.terminus_class(self.name, terminus_class) raise ArgumentError, "Could not find terminus #{terminus_class} for indirection #{self.name}" end klass.new end end diff --git a/lib/puppet/indirector/request.rb b/lib/puppet/indirector/request.rb index 84d51c1dd..3ee2d118e 100644 --- a/lib/puppet/indirector/request.rb +++ b/lib/puppet/indirector/request.rb @@ -1,277 +1,266 @@ require 'cgi' require 'uri' require 'puppet/indirector' require 'puppet/util/pson' require 'puppet/network/resolver' # This class encapsulates all of the information you need to make an # Indirection call, and as a a result also handles REST calls. It's somewhat # analogous to an HTTP Request object, except tuned for our Indirector. class Puppet::Indirector::Request attr_accessor :key, :method, :options, :instance, :node, :ip, :authenticated, :ignore_cache, :ignore_terminus attr_accessor :server, :port, :uri, :protocol attr_reader :indirection_name OPTION_ATTRIBUTES = [:ip, :node, :authenticated, :ignore_terminus, :ignore_cache, :instance, :environment] # Load json before trying to register. Puppet.features.pson? and ::PSON.register_document_type('IndirectorRequest',self) def self.from_pson(json) raise ArgumentError, "No indirection name provided in json data" unless indirection_name = json['type'] raise ArgumentError, "No method name provided in json data" unless method = json['method'] raise ArgumentError, "No key provided in json data" unless key = json['key'] - request = new(indirection_name, method, key, json['attributes']) + request = new(indirection_name, method, key, nil, json['attributes']) if instance = json['instance'] klass = Puppet::Indirector::Indirection.instance(request.indirection_name).model if instance.is_a?(klass) request.instance = instance else request.instance = klass.from_pson(instance) end end request end def to_pson(*args) result = { 'document_type' => 'IndirectorRequest', 'data' => { 'type' => indirection_name, 'method' => method, 'key' => key } } data = result['data'] attributes = {} OPTION_ATTRIBUTES.each do |key| next unless value = send(key) attributes[key] = value end options.each do |opt, value| attributes[opt] = value end data['attributes'] = attributes unless attributes.empty? data['instance'] = instance if instance result.to_pson(*args) end # Is this an authenticated request? def authenticated? # Double negative, so we just get true or false ! ! authenticated end def environment @environment ||= Puppet::Node::Environment.new end def environment=(env) @environment = if env.is_a?(Puppet::Node::Environment) env else Puppet::Node::Environment.new(env) end end def escaped_key URI.escape(key) end # LAK:NOTE This is a messy interface to the cache, and it's only # used by the Configurer class. I decided it was better to implement # it now and refactor later, when we have a better design, than # to spend another month coming up with a design now that might # not be any better. def ignore_cache? ignore_cache end def ignore_terminus? ignore_terminus end - def initialize(indirection_name, method, key_or_instance, options_or_instance = {}) - if options_or_instance.is_a? Hash - options = options_or_instance - @instance = nil - else - options = {} - @instance = options_or_instance - end + def initialize(indirection_name, method, key, instance, options = {}) + @instance = instance + options ||= {} self.indirection_name = indirection_name self.method = method options = options.inject({}) { |hash, ary| hash[ary[0].to_sym] = ary[1]; hash } set_attributes(options) @options = options - if key_or_instance.is_a?(String) || key_or_instance.is_a?(Symbol) - key = key_or_instance - else - @instance ||= key_or_instance - end - if key # If the request key is a URI, then we need to treat it specially, # because it rewrites the key. We could otherwise strip server/port/etc # info out in the REST class, but it seemed bad design for the REST # class to rewrite the key. if key.to_s =~ /^\w+:\// and not Puppet::Util.absolute_path?(key.to_s) # it's a URI set_uri_key(key) else @key = key end end @key = @instance.name if ! @key and @instance end # Look up the indirection based on the name provided. def indirection Puppet::Indirector::Indirection.instance(indirection_name) end def indirection_name=(name) @indirection_name = name.to_sym end def model raise ArgumentError, "Could not find indirection '#{indirection_name}'" unless i = indirection i.model end # Should we allow use of the cached object? def use_cache? if defined?(@use_cache) ! ! use_cache else true end end # Are we trying to interact with multiple resources, or just one? def plural? method == :search end # Create the query string, if options are present. def query_string return "" unless options and ! options.empty? "?" + options.collect do |key, value| case value when nil; next when true, false; value = value.to_s when Fixnum, Bignum, Float; value = value # nothing when String; value = CGI.escape(value) when Symbol; value = CGI.escape(value.to_s) when Array; value = CGI.escape(YAML.dump(value)) else raise ArgumentError, "HTTP REST queries cannot handle values of type '#{value.class}'" end "#{key}=#{value}" end.join("&") end def to_hash result = options.dup OPTION_ATTRIBUTES.each do |attribute| if value = send(attribute) result[attribute] = value end end result end def to_s return(uri ? uri : "/#{indirection_name}/#{key}") end def do_request(srv_service=:puppet, default_server=Puppet.settings[:server], default_port=Puppet.settings[:masterport], &block) # We were given a specific server to use, so just use that one. # This happens if someone does something like specifying a file # source using a puppet:// URI with a specific server. return yield(self) if !self.server.nil? if Puppet.settings[:use_srv_records] Puppet::Network::Resolver.each_srv_record(Puppet.settings[:srv_domain], srv_service) do |srv_server, srv_port| begin self.server = srv_server self.port = srv_port return yield(self) rescue SystemCallError => e Puppet.warning "Error connecting to #{srv_server}:#{srv_port}: #{e.message}" end end end # ... Fall back onto the default server. Puppet.debug "No more servers left, falling back to #{default_server}:#{default_port}" if Puppet.settings[:use_srv_records] self.server = default_server self.port = default_port return yield(self) end private def set_attributes(options) OPTION_ATTRIBUTES.each do |attribute| if options.include?(attribute.to_sym) send(attribute.to_s + "=", options[attribute]) options.delete(attribute) end end end # Parse the key as a URI, setting attributes appropriately. def set_uri_key(key) @uri = key begin uri = URI.parse(URI.escape(key)) rescue => detail raise ArgumentError, "Could not understand URL #{key}: #{detail}" end # Just short-circuit these to full paths if uri.scheme == "file" @key = Puppet::Util.uri_to_path(uri) return end @server = uri.host if uri.host # If the URI class can look up the scheme, it will provide a port, # otherwise it will default to '0'. if uri.port.to_i == 0 and uri.scheme == "puppet" @port = Puppet.settings[:masterport].to_i else @port = uri.port.to_i end @protocol = uri.scheme if uri.scheme == 'puppet' @key = URI.unescape(uri.path.sub(/^\//, '')) return end env, indirector, @key = URI.unescape(uri.path.sub(/^\//, '')).split('/',3) @key ||= '' self.environment = env unless env == '' end end diff --git a/lib/puppet/ssl/host.rb b/lib/puppet/ssl/host.rb index b73423131..f6b430fe1 100644 --- a/lib/puppet/ssl/host.rb +++ b/lib/puppet/ssl/host.rb @@ -1,337 +1,336 @@ require 'puppet/indirector' require 'puppet/ssl' require 'puppet/ssl/key' require 'puppet/ssl/certificate' require 'puppet/ssl/certificate_request' require 'puppet/ssl/certificate_revocation_list' # The class that manages all aspects of our SSL certificates -- # private keys, public keys, requests, etc. class Puppet::SSL::Host # Yay, ruby's strange constant lookups. Key = Puppet::SSL::Key CA_NAME = Puppet::SSL::CA_NAME Certificate = Puppet::SSL::Certificate CertificateRequest = Puppet::SSL::CertificateRequest CertificateRevocationList = Puppet::SSL::CertificateRevocationList extend Puppet::Indirector indirects :certificate_status, :terminus_class => :file attr_reader :name attr_accessor :ca attr_writer :key, :certificate, :certificate_request # This accessor is used in instances for indirector requests to hold desired state attr_accessor :desired_state def self.localhost return @localhost if @localhost @localhost = new @localhost.generate unless @localhost.certificate @localhost.key @localhost end def self.reset @localhost = nil end # This is the constant that people will use to mark that a given host is # a certificate authority. def self.ca_name CA_NAME end class << self attr_reader :ca_location end # Configure how our various classes interact with their various terminuses. def self.configure_indirection(terminus, cache = nil) Certificate.indirection.terminus_class = terminus CertificateRequest.indirection.terminus_class = terminus CertificateRevocationList.indirection.terminus_class = terminus host_map = {:ca => :file, :file => nil, :rest => :rest} if term = host_map[terminus] self.indirection.terminus_class = term else self.indirection.reset_terminus_class end if cache # This is weird; we don't actually cache our keys, we # use what would otherwise be the cache as our normal # terminus. Key.indirection.terminus_class = cache else Key.indirection.terminus_class = terminus end if cache Certificate.indirection.cache_class = cache CertificateRequest.indirection.cache_class = cache CertificateRevocationList.indirection.cache_class = cache else # Make sure we have no cache configured. puppet master # switches the configurations around a bit, so it's important # that we specify the configs for absolutely everything, every # time. Certificate.indirection.cache_class = nil CertificateRequest.indirection.cache_class = nil CertificateRevocationList.indirection.cache_class = nil end end CA_MODES = { # Our ca is local, so we use it as the ultimate source of information # And we cache files locally. :local => [:ca, :file], # We're a remote CA client. :remote => [:rest, :file], # We are the CA, so we don't have read/write access to the normal certificates. :only => [:ca], # We have no CA, so we just look in the local file store. :none => [:file] } # Specify how we expect to interact with our certificate authority. def self.ca_location=(mode) modes = CA_MODES.collect { |m, vals| m.to_s }.join(", ") raise ArgumentError, "CA Mode can only be one of: #{modes}" unless CA_MODES.include?(mode) @ca_location = mode configure_indirection(*CA_MODES[@ca_location]) end # Puppet::SSL::Host is actually indirected now so the original implementation # has been moved into the certificate_status indirector. This method is in-use # in `puppet cert -c `. def self.destroy(name) indirection.destroy(name) end def self.from_pson(pson) instance = new(pson["name"]) if pson["desired_state"] instance.desired_state = pson["desired_state"] end instance end # Puppet::SSL::Host is actually indirected now so the original implementation # has been moved into the certificate_status indirector. This method does not # appear to be in use in `puppet cert -l`. def self.search(options = {}) indirection.search("*", options) end # Is this a ca host, meaning that all of its files go in the CA location? def ca? ca end def key @key ||= Key.indirection.find(name) end # This is the private key; we can create it from scratch # with no inputs. def generate_key @key = Key.new(name) @key.generate begin Key.indirection.save(@key) rescue @key = nil raise end true end def certificate_request @certificate_request ||= CertificateRequest.indirection.find(name) end def this_csr_is_for_the_current_host name == Puppet[:certname].downcase end def this_csr_is_for_the_current_host name == Puppet[:certname].downcase end # Our certificate request requires the key but that's all. def generate_certificate_request(options = {}) generate_key unless key # If this is for the current machine... if this_csr_is_for_the_current_host # ...add our configured dns_alt_names if Puppet[:dns_alt_names] and Puppet[:dns_alt_names] != '' options[:dns_alt_names] ||= Puppet[:dns_alt_names] elsif Puppet::SSL::CertificateAuthority.ca? and fqdn = Facter.value(:fqdn) and domain = Facter.value(:domain) options[:dns_alt_names] = "puppet, #{fqdn}, puppet.#{domain}" end end @certificate_request = CertificateRequest.new(name) @certificate_request.generate(key.content, options) begin CertificateRequest.indirection.save(@certificate_request) rescue @certificate_request = nil raise end true end def certificate unless @certificate generate_key unless key # get the CA cert first, since it's required for the normal cert # to be of any use. return nil unless Certificate.indirection.find("ca") unless ca? return nil unless @certificate = Certificate.indirection.find(name) validate_certificate_with_key end @certificate end def validate_certificate_with_key raise Puppet::Error, "No certificate to validate." unless certificate raise Puppet::Error, "No private key with which to validate certificate with fingerprint: #{certificate.fingerprint}" unless key unless certificate.content.check_private_key(key.content) raise Puppet::Error, < name } my_state = state pson_hash[:state] = my_state pson_hash[:desired_state] = desired_state if desired_state if my_state == 'requested' pson_hash[:fingerprint] = certificate_request.fingerprint else pson_hash[:fingerprint] = my_cert.fingerprint end pson_hash.to_pson(*args) end # Attempt to retrieve a cert, if we don't already have one. def wait_for_cert(time) begin return if certificate generate return if certificate rescue SystemExit,NoMemoryError raise rescue Exception => detail Puppet.log_exception(detail, "Could not request certificate: #{detail}") if time < 1 puts "Exiting; failed to retrieve certificate and waitforcert is disabled" exit(1) else sleep(time) end retry end if time < 1 puts "Exiting; no certificate found and waitforcert is disabled" exit(1) end while true sleep time begin break if certificate Puppet.notice "Did not receive certificate" rescue StandardError => detail Puppet.log_exception(detail, "Could not request certificate: #{detail}") end end end def state - my_cert = Puppet::SSL::Certificate.indirection.find(name) if certificate_request return 'requested' end begin - Puppet::SSL::CertificateAuthority.new.verify(my_cert) + Puppet::SSL::CertificateAuthority.new.verify(name) return 'signed' rescue Puppet::SSL::CertificateAuthority::CertificateVerificationError return 'revoked' end end end require 'puppet/ssl/certificate_authority' diff --git a/spec/integration/indirector/catalog/queue_spec.rb b/spec/integration/indirector/catalog/queue_spec.rb index 940c8bab8..945c3cc78 100755 --- a/spec/integration/indirector/catalog/queue_spec.rb +++ b/spec/integration/indirector/catalog/queue_spec.rb @@ -1,57 +1,57 @@ #!/usr/bin/env rspec require 'spec_helper' require 'puppet/resource/catalog' describe "Puppet::Resource::Catalog::Queue", :if => Puppet.features.pson? do before do Puppet::Resource::Catalog.indirection.terminus(:queue) @catalog = Puppet::Resource::Catalog.new @one = Puppet::Resource.new(:file, "/one") @two = Puppet::Resource.new(:file, "/two") @catalog.add_resource(@one, @two) @catalog.add_edge(@one, @two) Puppet[:trace] = true end after { Puppet.settings.clear } it "should render catalogs to pson and publish them via the queue client when catalogs are saved" do terminus = Puppet::Resource::Catalog.indirection.terminus(:queue) client = mock 'client' terminus.stubs(:client).returns client client.expects(:publish_message).with(:catalog, @catalog.to_pson) - request = Puppet::Indirector::Request.new(:catalog, :save, "foo", :instance => @catalog) + request = Puppet::Indirector::Request.new(:catalog, :save, "foo", @catalog) terminus.save(request) end it "should intern catalog messages when they are passed via a subscription" do client = mock 'client' Puppet::Resource::Catalog::Queue.stubs(:client).returns client pson = @catalog.to_pson client.expects(:subscribe).with(:catalog).yields(pson) Puppet.expects(:err).never result = [] Puppet::Resource::Catalog::Queue.subscribe do |catalog| result << catalog end catalog = result.shift catalog.should be_instance_of(Puppet::Resource::Catalog) catalog.resource(:file, "/one").should be_instance_of(Puppet::Resource) catalog.resource(:file, "/two").should be_instance_of(Puppet::Resource) catalog.should be_edge(catalog.resource(:file, "/one"), catalog.resource(:file, "/two")) end end diff --git a/spec/integration/indirector/direct_file_server_spec.rb b/spec/integration/indirector/direct_file_server_spec.rb index de656a3e9..1e6e876a4 100755 --- a/spec/integration/indirector/direct_file_server_spec.rb +++ b/spec/integration/indirector/direct_file_server_spec.rb @@ -1,69 +1,69 @@ #!/usr/bin/env rspec require 'spec_helper' require 'puppet/indirector/file_content/file' describe Puppet::Indirector::DirectFileServer, " when interacting with the filesystem and the model" do include PuppetSpec::Files before do # We just test a subclass, since it's close enough. @terminus = Puppet::Indirector::FileContent::File.new @filepath = make_absolute("/path/to/my/file") end it "should return an instance of the model" do pending("porting to Windows", :if => Puppet.features.microsoft_windows?) do FileTest.expects(:exists?).with(@filepath).returns(true) - @terminus.find(@terminus.indirection.request(:find, "file://host#{@filepath}")).should be_instance_of(Puppet::FileServing::Content) + @terminus.find(@terminus.indirection.request(:find, "file://host#{@filepath}", nil)).should be_instance_of(Puppet::FileServing::Content) end end it "should return an instance capable of returning its content" do pending("porting to Windows", :if => Puppet.features.microsoft_windows?) do FileTest.expects(:exists?).with(@filepath).returns(true) File.stubs(:lstat).with(@filepath).returns(stub("stat", :ftype => "file")) IO.expects(:binread).with(@filepath).returns("my content") - instance = @terminus.find(@terminus.indirection.request(:find, "file://host#{@filepath}")) + instance = @terminus.find(@terminus.indirection.request(:find, "file://host#{@filepath}", nil)) instance.content.should == "my content" end end end describe Puppet::Indirector::DirectFileServer, " when interacting with FileServing::Fileset and the model" do include PuppetSpec::Files let(:path) { tmpdir('direct_file_server_testing') } before do @terminus = Puppet::Indirector::FileContent::File.new File.open(File.join(path, "one"), "w") { |f| f.print "one content" } File.open(File.join(path, "two"), "w") { |f| f.print "two content" } - @request = @terminus.indirection.request(:search, "file:///#{path}", :recurse => true) + @request = @terminus.indirection.request(:search, "file:///#{path}", nil, :recurse => true) end it "should return an instance for every file in the fileset" do result = @terminus.search(@request) result.should be_instance_of(Array) result.length.should == 3 result.each { |r| r.should be_instance_of(Puppet::FileServing::Content) } end it "should return instances capable of returning their content" do @terminus.search(@request).each do |instance| case instance.full_path when /one/; instance.content.should == "one content" when /two/; instance.content.should == "two content" when path else raise "No valid key for #{instance.path.inspect}" end end end end diff --git a/spec/integration/node/facts_spec.rb b/spec/integration/node/facts_spec.rb index 78bdabce1..910d72dec 100755 --- a/spec/integration/node/facts_spec.rb +++ b/spec/integration/node/facts_spec.rb @@ -1,41 +1,41 @@ #!/usr/bin/env rspec require 'spec_helper' describe Puppet::Node::Facts do describe "when using the indirector" do it "should expire any cached node instances when it is saved" do Puppet::Node::Facts.indirection.stubs(:terminus_class).returns :yaml Puppet::Node::Facts.indirection.terminus(:yaml).should equal(Puppet::Node::Facts.indirection.terminus(:yaml)) terminus = Puppet::Node::Facts.indirection.terminus(:yaml) terminus.stubs :save - Puppet::Node.indirection.expects(:expire).with("me") + Puppet::Node.indirection.expects(:expire).with("me", optionally(instance_of(Hash))) facts = Puppet::Node::Facts.new("me") Puppet::Node::Facts.indirection.save(facts) end it "should be able to delegate to the :yaml terminus" do Puppet::Node::Facts.indirection.stubs(:terminus_class).returns :yaml # Load now, before we stub the exists? method. terminus = Puppet::Node::Facts.indirection.terminus(:yaml) terminus.expects(:path).with("me").returns "/my/yaml/file" FileTest.expects(:exist?).with("/my/yaml/file").returns false Puppet::Node::Facts.indirection.find("me").should be_nil end it "should be able to delegate to the :facter terminus" do Puppet::Node::Facts.indirection.stubs(:terminus_class).returns :facter Facter.expects(:to_hash).returns "facter_hash" facts = Puppet::Node::Facts.new("me") Puppet::Node::Facts.expects(:new).with("me", "facter_hash").returns facts Puppet::Node::Facts.indirection.find("me").should equal(facts) end end end diff --git a/spec/shared_behaviours/file_server_terminus.rb b/spec/shared_behaviours/file_server_terminus.rb index 33037e551..3b95462f3 100755 --- a/spec/shared_behaviours/file_server_terminus.rb +++ b/spec/shared_behaviours/file_server_terminus.rb @@ -1,41 +1,41 @@ #!/usr/bin/env rspec shared_examples_for "Puppet::Indirector::FileServerTerminus" do # This only works if the shared behaviour is included before # the 'before' block in the including context. before do Puppet::FileServing::Configuration.instance_variable_set(:@configuration, nil) FileTest.stubs(:exists?).returns true FileTest.stubs(:exists?).with(Puppet[:fileserverconfig]).returns(true) @path = Tempfile.new("file_server_testing") path = @path.path @path.close! @path = path Dir.mkdir(@path) File.open(File.join(@path, "myfile"), "w") { |f| f.print "my content" } # Use a real mount, so the integration is a bit deeper. @mount1 = Puppet::FileServing::Configuration::Mount::File.new("one") @mount1.path = @path @parser = stub 'parser', :changed? => false @parser.stubs(:parse).returns("one" => @mount1) Puppet::FileServing::Configuration::Parser.stubs(:new).returns(@parser) # Stub out the modules terminus @modules = mock 'modules terminus' - @request = Puppet::Indirector::Request.new(:indirection, :method, "puppet://myhost/one/myfile") + @request = Puppet::Indirector::Request.new(:indirection, :method, "puppet://myhost/one/myfile", nil) end it "should use the file server configuration to find files" do @modules.stubs(:find).returns(nil) @terminus.indirection.stubs(:terminus).with(:modules).returns(@modules) path = File.join(@path, "myfile") @terminus.find(@request).should be_instance_of(@test_class) end end diff --git a/spec/unit/configurer_spec.rb b/spec/unit/configurer_spec.rb index 793a8f18c..1ea91a002 100755 --- a/spec/unit/configurer_spec.rb +++ b/spec/unit/configurer_spec.rb @@ -1,615 +1,615 @@ #!/usr/bin/env rspec require 'spec_helper' require 'puppet/configurer' describe Puppet::Configurer do before do Puppet.settings.stubs(:use).returns(true) @agent = Puppet::Configurer.new @agent.stubs(:init_storage) Puppet::Util::Storage.stubs(:store) Puppet[:server] = "puppetmaster" Puppet[:report] = true end it "should include the Plugin Handler module" do Puppet::Configurer.ancestors.should be_include(Puppet::Configurer::PluginHandler) end it "should include the Fact Handler module" do Puppet::Configurer.ancestors.should be_include(Puppet::Configurer::FactHandler) end it "should use the puppetdlockfile as its lockfile path" do Puppet.settings.expects(:value).with(:puppetdlockfile).returns("/my/lock") Puppet::Configurer.lockfile_path.should == "/my/lock" end describe "when executing a pre-run hook" do it "should do nothing if the hook is set to an empty string" do Puppet.settings[:prerun_command] = "" Puppet::Util.expects(:exec).never @agent.execute_prerun_command end it "should execute any pre-run command provided via the 'prerun_command' setting" do Puppet.settings[:prerun_command] = "/my/command" Puppet::Util::Execution.expects(:execute).with(["/my/command"]).raises(Puppet::ExecutionFailure, "Failed") @agent.execute_prerun_command end it "should fail if the command fails" do Puppet.settings[:prerun_command] = "/my/command" Puppet::Util::Execution.expects(:execute).with(["/my/command"]).raises(Puppet::ExecutionFailure, "Failed") @agent.execute_prerun_command.should be_false end end describe "when executing a post-run hook" do it "should do nothing if the hook is set to an empty string" do Puppet.settings[:postrun_command] = "" Puppet::Util.expects(:exec).never @agent.execute_postrun_command end it "should execute any post-run command provided via the 'postrun_command' setting" do Puppet.settings[:postrun_command] = "/my/command" Puppet::Util::Execution.expects(:execute).with(["/my/command"]).raises(Puppet::ExecutionFailure, "Failed") @agent.execute_postrun_command end it "should fail if the command fails" do Puppet.settings[:postrun_command] = "/my/command" Puppet::Util::Execution.expects(:execute).with(["/my/command"]).raises(Puppet::ExecutionFailure, "Failed") @agent.execute_postrun_command.should be_false end end describe "when executing a catalog run" do before do Puppet.settings.stubs(:use).returns(true) @agent.stubs(:download_plugins) Puppet::Node::Facts.indirection.terminus_class = :memory @facts = Puppet::Node::Facts.new(Puppet[:node_name_value]) Puppet::Node::Facts.indirection.save(@facts) @catalog = Puppet::Resource::Catalog.new @catalog.stubs(:to_ral).returns(@catalog) Puppet::Resource::Catalog.indirection.terminus_class = :rest Puppet::Resource::Catalog.indirection.stubs(:find).returns(@catalog) @agent.stubs(:send_report) @agent.stubs(:save_last_run_summary) Puppet::Util::Log.stubs(:close_all) end after :all do Puppet::Node::Facts.indirection.reset_terminus_class Puppet::Resource::Catalog.indirection.reset_terminus_class end it "should initialize storage" do Puppet::Util::Storage.expects(:load) @agent.run end it "should download plugins" do @agent.expects(:download_plugins) @agent.run end it "should initialize a transaction report if one is not provided" do report = Puppet::Transaction::Report.new("apply") Puppet::Transaction::Report.expects(:new).returns report @agent.run end it "should respect node_name_fact when setting the host on a report" do Puppet[:node_name_fact] = 'my_name_fact' @facts.values = {'my_name_fact' => 'node_name_from_fact'} report = Puppet::Transaction::Report.new("apply") @agent.run(:report => report) report.host.should == 'node_name_from_fact' end it "should pass the new report to the catalog" do report = Puppet::Transaction::Report.new("apply") Puppet::Transaction::Report.stubs(:new).returns report @catalog.expects(:apply).with{|options| options[:report] == report} @agent.run end it "should use the provided report if it was passed one" do report = Puppet::Transaction::Report.new("apply") Puppet::Transaction::Report.expects(:new).never @catalog.expects(:apply).with{|options| options[:report] == report} @agent.run(:report => report) end it "should set the report as a log destination" do report = Puppet::Transaction::Report.new("apply") Puppet::Transaction::Report.expects(:new).returns report Puppet::Util::Log.expects(:newdestination).with(report) Puppet::Util::Log.expects(:close).with(report) @agent.run end it "should retrieve the catalog" do @agent.expects(:retrieve_catalog) @agent.run end it "should log a failure and do nothing if no catalog can be retrieved" do @agent.expects(:retrieve_catalog).returns nil Puppet.expects(:err).with "Could not retrieve catalog; skipping run" @agent.run end it "should apply the catalog with all options to :run" do @agent.expects(:retrieve_catalog).returns @catalog @catalog.expects(:apply).with { |args| args[:one] == true } @agent.run :one => true end it "should accept a catalog and use it instead of retrieving a different one" do @agent.expects(:retrieve_catalog).never @catalog.expects(:apply) @agent.run :one => true, :catalog => @catalog end it "should benchmark how long it takes to apply the catalog" do @agent.expects(:benchmark).with(:notice, "Finished catalog run") @agent.expects(:retrieve_catalog).returns @catalog @catalog.expects(:apply).never # because we're not yielding @agent.run end it "should execute post-run hooks after the run" do @agent.expects(:execute_postrun_command) @agent.run end it "should send the report" do report = Puppet::Transaction::Report.new("apply") Puppet::Transaction::Report.expects(:new).returns(report) @agent.expects(:send_report).with(report) @agent.run end it "should send the transaction report even if the catalog could not be retrieved" do @agent.expects(:retrieve_catalog).returns nil report = Puppet::Transaction::Report.new("apply") Puppet::Transaction::Report.expects(:new).returns(report) @agent.expects(:send_report) @agent.run end it "should send the transaction report even if there is a failure" do @agent.expects(:retrieve_catalog).raises "whatever" report = Puppet::Transaction::Report.new("apply") Puppet::Transaction::Report.expects(:new).returns(report) @agent.expects(:send_report) @agent.run.should be_nil end it "should remove the report as a log destination when the run is finished" do report = Puppet::Transaction::Report.new("apply") Puppet::Transaction::Report.expects(:new).returns(report) @agent.run Puppet::Util::Log.destinations.should_not include(report) end it "should return the report exit_status as the result of the run" do report = Puppet::Transaction::Report.new("apply") Puppet::Transaction::Report.expects(:new).returns(report) report.expects(:exit_status).returns(1234) @agent.run.should == 1234 end it "should send the transaction report even if the pre-run command fails" do report = Puppet::Transaction::Report.new("apply") Puppet::Transaction::Report.expects(:new).returns(report) Puppet.settings[:prerun_command] = "/my/command" Puppet::Util::Execution.expects(:execute).with(["/my/command"]).raises(Puppet::ExecutionFailure, "Failed") @agent.expects(:send_report) @agent.run.should be_nil end it "should include the pre-run command failure in the report" do report = Puppet::Transaction::Report.new("apply") Puppet::Transaction::Report.expects(:new).returns(report) Puppet.settings[:prerun_command] = "/my/command" Puppet::Util::Execution.expects(:execute).with(["/my/command"]).raises(Puppet::ExecutionFailure, "Failed") @agent.run.should be_nil report.logs.find { |x| x.message =~ /Could not run command from prerun_command/ }.should be end it "should send the transaction report even if the post-run command fails" do report = Puppet::Transaction::Report.new("apply") Puppet::Transaction::Report.expects(:new).returns(report) Puppet.settings[:postrun_command] = "/my/command" Puppet::Util::Execution.expects(:execute).with(["/my/command"]).raises(Puppet::ExecutionFailure, "Failed") @agent.expects(:send_report) @agent.run.should be_nil end it "should include the post-run command failure in the report" do report = Puppet::Transaction::Report.new("apply") Puppet::Transaction::Report.expects(:new).returns(report) Puppet.settings[:postrun_command] = "/my/command" Puppet::Util::Execution.expects(:execute).with(["/my/command"]).raises(Puppet::ExecutionFailure, "Failed") report.expects(:<<).with { |log| log.message.include?("Could not run command from postrun_command") } @agent.run.should be_nil end it "should execute post-run command even if the pre-run command fails" do Puppet.settings[:prerun_command] = "/my/precommand" Puppet.settings[:postrun_command] = "/my/postcommand" Puppet::Util::Execution.expects(:execute).with(["/my/precommand"]).raises(Puppet::ExecutionFailure, "Failed") Puppet::Util::Execution.expects(:execute).with(["/my/postcommand"]) @agent.run.should be_nil end it "should finalize the report" do report = Puppet::Transaction::Report.new("apply") Puppet::Transaction::Report.expects(:new).returns(report) report.expects(:finalize_report) @agent.run end it "should not apply the catalog if the pre-run command fails" do report = Puppet::Transaction::Report.new("apply") Puppet::Transaction::Report.expects(:new).returns(report) Puppet.settings[:prerun_command] = "/my/command" Puppet::Util::Execution.expects(:execute).with(["/my/command"]).raises(Puppet::ExecutionFailure, "Failed") @catalog.expects(:apply).never() @agent.expects(:send_report) @agent.run.should be_nil end it "should apply the catalog, send the report, and return nil if the post-run command fails" do report = Puppet::Transaction::Report.new("apply") Puppet::Transaction::Report.expects(:new).returns(report) Puppet.settings[:postrun_command] = "/my/command" Puppet::Util::Execution.expects(:execute).with(["/my/command"]).raises(Puppet::ExecutionFailure, "Failed") @catalog.expects(:apply) @agent.expects(:send_report) @agent.run.should be_nil end it "should refetch the catalog if the server specifies a new environment in the catalog" do @catalog.stubs(:environment).returns("second_env") @agent.expects(:retrieve_catalog).returns(@catalog).twice @agent.run end it "should change the environment setting if the server specifies a new environment in the catalog" do @catalog.stubs(:environment).returns("second_env") @agent.run @agent.environment.should == "second_env" end describe "when not using a REST terminus for catalogs" do it "should not pass any facts when retrieving the catalog" do Puppet::Resource::Catalog.indirection.terminus_class = :compiler @agent.expects(:facts_for_uploading).never Puppet::Resource::Catalog.indirection.expects(:find).with { |name, options| options[:facts].nil? }.returns @catalog @agent.run end end describe "when using a REST terminus for catalogs" do it "should pass the prepared facts and the facts format as arguments when retrieving the catalog" do Puppet::Resource::Catalog.indirection.terminus_class = :rest @agent.expects(:facts_for_uploading).returns(:facts => "myfacts", :facts_format => :foo) Puppet::Resource::Catalog.indirection.expects(:find).with { |name, options| options[:facts] == "myfacts" and options[:facts_format] == :foo }.returns @catalog @agent.run end end end describe "when sending a report" do include PuppetSpec::Files before do Puppet.settings.stubs(:use).returns(true) @configurer = Puppet::Configurer.new Puppet[:lastrunfile] = tmpfile('last_run_file') @report = Puppet::Transaction::Report.new("apply") end it "should print a report summary if configured to do so" do Puppet.settings[:summarize] = true @report.expects(:summary).returns "stuff" @configurer.expects(:puts).with("stuff") @configurer.send_report(@report) end it "should not print a report summary if not configured to do so" do Puppet.settings[:summarize] = false @configurer.expects(:puts).never @configurer.send_report(@report) end it "should save the report if reporting is enabled" do Puppet.settings[:report] = true - Puppet::Transaction::Report.indirection.expects(:save).with(@report) + Puppet::Transaction::Report.indirection.expects(:save).with(@report, nil, instance_of(Hash)) @configurer.send_report(@report) end it "should not save the report if reporting is disabled" do Puppet.settings[:report] = false - Puppet::Transaction::Report.indirection.expects(:save).with(@report).never + Puppet::Transaction::Report.indirection.expects(:save).with(@report, nil, instance_of(Hash)).never @configurer.send_report(@report) end it "should save the last run summary if reporting is enabled" do Puppet.settings[:report] = true @configurer.expects(:save_last_run_summary).with(@report) @configurer.send_report(@report) end it "should save the last run summary if reporting is disabled" do Puppet.settings[:report] = false @configurer.expects(:save_last_run_summary).with(@report) @configurer.send_report(@report) end it "should log but not fail if saving the report fails" do Puppet.settings[:report] = true Puppet::Transaction::Report.indirection.expects(:save).raises("whatever") Puppet.expects(:err) lambda { @configurer.send_report(@report) }.should_not raise_error end end describe "when saving the summary report file" do include PuppetSpec::Files before do Puppet.settings.stubs(:use).returns(true) @configurer = Puppet::Configurer.new @report = stub 'report', :raw_summary => {} Puppet[:lastrunfile] = tmpfile('last_run_file') end it "should write the last run file" do @configurer.save_last_run_summary(@report) FileTest.exists?(Puppet[:lastrunfile]).should be_true end it "should write the raw summary as yaml" do @report.expects(:raw_summary).returns("summary") @configurer.save_last_run_summary(@report) File.read(Puppet[:lastrunfile]).should == YAML.dump("summary") end it "should log but not fail if saving the last run summary fails" do # The mock will raise an exception on any method used. This should # simulate a nice hard failure from the underlying OS for us. fh = Class.new(Object) do def method_missing(*args) raise "failed to do #{args[0]}" end end.new Puppet::Util.expects(:replace_file).yields(fh) Puppet.expects(:err) expect { @configurer.save_last_run_summary(@report) }.should_not raise_error end end describe "when retrieving a catalog" do before do Puppet.settings.stubs(:use).returns(true) @agent.stubs(:facts_for_uploading).returns({}) @catalog = Puppet::Resource::Catalog.new # this is the default when using a Configurer instance Puppet::Resource::Catalog.indirection.stubs(:terminus_class).returns :rest @agent.stubs(:convert_catalog).returns @catalog end describe "and configured to only retrieve a catalog from the cache" do before do Puppet.settings[:use_cached_catalog] = true end it "should first look in the cache for a catalog" do Puppet::Resource::Catalog.indirection.expects(:find).with { |name, options| options[:ignore_terminus] == true }.returns @catalog Puppet::Resource::Catalog.indirection.expects(:find).with { |name, options| options[:ignore_cache] == true }.never @agent.retrieve_catalog({}).should == @catalog end it "should compile a new catalog if none is found in the cache" do Puppet::Resource::Catalog.indirection.expects(:find).with { |name, options| options[:ignore_terminus] == true }.returns nil Puppet::Resource::Catalog.indirection.expects(:find).with { |name, options| options[:ignore_cache] == true }.returns @catalog @agent.retrieve_catalog({}).should == @catalog end end it "should use the Catalog class to get its catalog" do Puppet::Resource::Catalog.indirection.expects(:find).returns @catalog @agent.retrieve_catalog({}) end it "should use its node_name_value to retrieve the catalog" do Facter.stubs(:value).returns "eh" Puppet.settings[:node_name_value] = "myhost.domain.com" Puppet::Resource::Catalog.indirection.expects(:find).with { |name, options| name == "myhost.domain.com" }.returns @catalog @agent.retrieve_catalog({}) end it "should default to returning a catalog retrieved directly from the server, skipping the cache" do Puppet::Resource::Catalog.indirection.expects(:find).with { |name, options| options[:ignore_cache] == true }.returns @catalog @agent.retrieve_catalog({}).should == @catalog end it "should log and return the cached catalog when no catalog can be retrieved from the server" do Puppet::Resource::Catalog.indirection.expects(:find).with { |name, options| options[:ignore_cache] == true }.returns nil Puppet::Resource::Catalog.indirection.expects(:find).with { |name, options| options[:ignore_terminus] == true }.returns @catalog Puppet.expects(:notice) @agent.retrieve_catalog({}).should == @catalog end it "should not look in the cache for a catalog if one is returned from the server" do Puppet::Resource::Catalog.indirection.expects(:find).with { |name, options| options[:ignore_cache] == true }.returns @catalog Puppet::Resource::Catalog.indirection.expects(:find).with { |name, options| options[:ignore_terminus] == true }.never @agent.retrieve_catalog({}).should == @catalog end it "should return the cached catalog when retrieving the remote catalog throws an exception" do Puppet::Resource::Catalog.indirection.expects(:find).with { |name, options| options[:ignore_cache] == true }.raises "eh" Puppet::Resource::Catalog.indirection.expects(:find).with { |name, options| options[:ignore_terminus] == true }.returns @catalog @agent.retrieve_catalog({}).should == @catalog end it "should log and return nil if no catalog can be retrieved from the server and :usecacheonfailure is disabled" do Puppet.stubs(:[]) Puppet.expects(:[]).with(:usecacheonfailure).returns false Puppet::Resource::Catalog.indirection.expects(:find).with { |name, options| options[:ignore_cache] == true }.returns nil Puppet.expects(:warning) @agent.retrieve_catalog({}).should be_nil end it "should return nil if no cached catalog is available and no catalog can be retrieved from the server" do Puppet::Resource::Catalog.indirection.expects(:find).with { |name, options| options[:ignore_cache] == true }.returns nil Puppet::Resource::Catalog.indirection.expects(:find).with { |name, options| options[:ignore_terminus] == true }.returns nil @agent.retrieve_catalog({}).should be_nil end it "should convert the catalog before returning" do Puppet::Resource::Catalog.indirection.stubs(:find).returns @catalog @agent.expects(:convert_catalog).with { |cat, dur| cat == @catalog }.returns "converted catalog" @agent.retrieve_catalog({}).should == "converted catalog" end it "should return nil if there is an error while retrieving the catalog" do Puppet::Resource::Catalog.indirection.expects(:find).at_least_once.raises "eh" @agent.retrieve_catalog({}).should be_nil end end describe "when converting the catalog" do before do Puppet.settings.stubs(:use).returns(true) @catalog = Puppet::Resource::Catalog.new @oldcatalog = stub 'old_catalog', :to_ral => @catalog end it "should convert the catalog to a RAL-formed catalog" do @oldcatalog.expects(:to_ral).returns @catalog @agent.convert_catalog(@oldcatalog, 10).should equal(@catalog) end it "should finalize the catalog" do @catalog.expects(:finalize) @agent.convert_catalog(@oldcatalog, 10) end it "should record the passed retrieval time with the RAL catalog" do @catalog.expects(:retrieval_duration=).with 10 @agent.convert_catalog(@oldcatalog, 10) end it "should write the RAL catalog's class file" do @catalog.expects(:write_class_file) @agent.convert_catalog(@oldcatalog, 10) end it "should write the RAL catalog's resource file" do @catalog.expects(:write_resource_file) @agent.convert_catalog(@oldcatalog, 10) end end end diff --git a/spec/unit/file_serving/fileset_spec.rb b/spec/unit/file_serving/fileset_spec.rb index 4f9d3c542..858a69d55 100755 --- a/spec/unit/file_serving/fileset_spec.rb +++ b/spec/unit/file_serving/fileset_spec.rb @@ -1,378 +1,378 @@ #!/usr/bin/env rspec require 'spec_helper' require 'puppet/file_serving/fileset' describe Puppet::FileServing::Fileset, " when initializing" do include PuppetSpec::Files + let(:request) { Puppet::Indirector::Request.new(:file_serving, :find, "foo", nil) } + before :each do @somefile = make_absolute("/some/file") end it "should require a path" do proc { Puppet::FileServing::Fileset.new }.should raise_error(ArgumentError) end it "should fail if its path is not fully qualified" do proc { Puppet::FileServing::Fileset.new("some/file") }.should raise_error(ArgumentError) end it "should not fail if the path is fully qualified, with a trailing separator" do path_with_separator = "#{@somefile}#{File::SEPARATOR}" File.stubs(:lstat).with(@somefile).returns stub('stat') fileset = Puppet::FileServing::Fileset.new(path_with_separator) fileset.path.should == @somefile end it "should not fail if the path is just the file separator" do path = File.expand_path(File::SEPARATOR) File.stubs(:lstat).with(path).returns stub('stat') fileset = Puppet::FileServing::Fileset.new(path) fileset.path.should == path end it "should fail if its path does not exist" do File.expects(:lstat).with(@somefile).returns nil proc { Puppet::FileServing::Fileset.new(@somefile) }.should raise_error(ArgumentError) end it "should accept a 'recurse' option" do File.expects(:lstat).with(@somefile).returns stub("stat") set = Puppet::FileServing::Fileset.new(@somefile, :recurse => true) set.recurse.should be_true end it "should accept a 'recurselimit' option" do File.expects(:lstat).with(@somefile).returns stub("stat") set = Puppet::FileServing::Fileset.new(@somefile, :recurselimit => 3) set.recurselimit.should == 3 end it "should accept an 'ignore' option" do File.expects(:lstat).with(@somefile).returns stub("stat") set = Puppet::FileServing::Fileset.new(@somefile, :ignore => ".svn") set.ignore.should == [".svn"] end it "should accept a 'links' option" do File.expects(:lstat).with(@somefile).returns stub("stat") set = Puppet::FileServing::Fileset.new(@somefile, :links => :manage) set.links.should == :manage end it "should accept a 'checksum_type' option" do File.expects(:lstat).with(@somefile).returns stub("stat") set = Puppet::FileServing::Fileset.new(@somefile, :checksum_type => :test) set.checksum_type.should == :test end it "should fail if 'links' is set to anything other than :manage or :follow" do proc { Puppet::FileServing::Fileset.new(@somefile, :links => :whatever) }.should raise_error(ArgumentError) end it "should default to 'false' for recurse" do File.expects(:lstat).with(@somefile).returns stub("stat") Puppet::FileServing::Fileset.new(@somefile).recurse.should == false end it "should default to :infinite for recurselimit" do File.expects(:lstat).with(@somefile).returns stub("stat") Puppet::FileServing::Fileset.new(@somefile).recurselimit.should == :infinite end it "should default to an empty ignore list" do File.expects(:lstat).with(@somefile).returns stub("stat") Puppet::FileServing::Fileset.new(@somefile).ignore.should == [] end it "should default to :manage for links" do File.expects(:lstat).with(@somefile).returns stub("stat") Puppet::FileServing::Fileset.new(@somefile).links.should == :manage end it "should support using an Indirector Request for its options" do File.expects(:lstat).with(@somefile).returns stub("stat") - request = Puppet::Indirector::Request.new(:file_serving, :find, "foo") lambda { Puppet::FileServing::Fileset.new(@somefile, request) }.should_not raise_error end describe "using an indirector request" do before do File.stubs(:lstat).returns stub("stat") @values = {:links => :manage, :ignore => %w{a b}, :recurse => true, :recurselimit => 1234} - @request = Puppet::Indirector::Request.new(:file_serving, :find, "foo") @myfile = make_absolute("/my/file") end [:recurse, :recurselimit, :ignore, :links].each do |option| it "should pass :recurse, :recurselimit, :ignore, and :links settings on to the fileset if present" do - @request.stubs(:options).returns(option => @values[option]) - Puppet::FileServing::Fileset.new(@myfile, @request).send(option).should == @values[option] + request.stubs(:options).returns(option => @values[option]) + Puppet::FileServing::Fileset.new(@myfile, request).send(option).should == @values[option] end it "should pass :recurse, :recurselimit, :ignore, and :links settings on to the fileset if present with the keys stored as strings" do - @request.stubs(:options).returns(option.to_s => @values[option]) - Puppet::FileServing::Fileset.new(@myfile, @request).send(option).should == @values[option] + request.stubs(:options).returns(option.to_s => @values[option]) + Puppet::FileServing::Fileset.new(@myfile, request).send(option).should == @values[option] end end it "should convert the integer as a string to their integer counterpart when setting options" do - @request.stubs(:options).returns(:recurselimit => "1234") - Puppet::FileServing::Fileset.new(@myfile, @request).recurselimit.should == 1234 + request.stubs(:options).returns(:recurselimit => "1234") + Puppet::FileServing::Fileset.new(@myfile, request).recurselimit.should == 1234 end it "should convert the string 'true' to the boolean true when setting options" do - @request.stubs(:options).returns(:recurse => "true") - Puppet::FileServing::Fileset.new(@myfile, @request).recurse.should == true + request.stubs(:options).returns(:recurse => "true") + Puppet::FileServing::Fileset.new(@myfile, request).recurse.should == true end it "should convert the string 'false' to the boolean false when setting options" do - @request.stubs(:options).returns(:recurse => "false") - Puppet::FileServing::Fileset.new(@myfile, @request).recurse.should == false + request.stubs(:options).returns(:recurse => "false") + Puppet::FileServing::Fileset.new(@myfile, request).recurse.should == false end end end describe Puppet::FileServing::Fileset, " when determining whether to recurse" do include PuppetSpec::Files before do @path = make_absolute("/my/path") File.expects(:lstat).with(@path).returns stub("stat") @fileset = Puppet::FileServing::Fileset.new(@path) end it "should always recurse if :recurse is set to 'true' and with infinite recursion" do @fileset.recurse = true @fileset.recurselimit = :infinite @fileset.recurse?(0).should be_true end it "should never recurse if :recurse is set to 'false'" do @fileset.recurse = false @fileset.recurse?(-1).should be_false end it "should recurse if :recurse is set to true, :recurselimit is set to an integer and the current depth is less than that integer" do @fileset.recurse = true @fileset.recurselimit = 1 @fileset.recurse?(0).should be_true end it "should recurse if :recurse is set to true, :recurselimit is set to an integer and the current depth is equal to that integer" do @fileset.recurse = true @fileset.recurselimit = 1 @fileset.recurse?(1).should be_true end it "should not recurse if :recurse is set to true, :recurselimit is set to an integer and the current depth is greater than that integer" do @fileset.recurse = true @fileset.recurselimit = 1 @fileset.recurse?(2).should be_false end end describe Puppet::FileServing::Fileset, " when recursing" do include PuppetSpec::Files before do @path = make_absolute("/my/path") File.expects(:lstat).with(@path).returns stub("stat", :directory? => true) @fileset = Puppet::FileServing::Fileset.new(@path) @dirstat = stub 'dirstat', :directory? => true @filestat = stub 'filestat', :directory? => false end def mock_dir_structure(path, stat_method = :lstat) File.stubs(stat_method).with(path).returns(@dirstat) Dir.stubs(:entries).with(path).returns(%w{one two .svn CVS}) # Keep track of the files we're stubbing. @files = %w{.} %w{one two .svn CVS}.each do |subdir| @files << subdir # relative path subpath = File.join(path, subdir) File.stubs(stat_method).with(subpath).returns(@dirstat) Dir.stubs(:entries).with(subpath).returns(%w{.svn CVS file1 file2}) %w{file1 file2 .svn CVS}.each do |file| @files << File.join(subdir, file) # relative path File.stubs(stat_method).with(File.join(subpath, file)).returns(@filestat) end end end it "should recurse through the whole file tree if :recurse is set to 'true'" do mock_dir_structure(@path) @fileset.stubs(:recurse?).returns(true) @fileset.files.sort.should == @files.sort end it "should not recurse if :recurse is set to 'false'" do mock_dir_structure(@path) @fileset.stubs(:recurse?).returns(false) @fileset.files.should == %w{.} end # It seems like I should stub :recurse? here, or that I shouldn't stub the # examples above, but... it "should recurse to the level set if :recurselimit is set to an integer" do mock_dir_structure(@path) @fileset.recurse = true @fileset.recurselimit = 1 @fileset.files.should == %w{. one two .svn CVS} end it "should ignore the '.' and '..' directories in subdirectories" do mock_dir_structure(@path) @fileset.recurse = true @fileset.files.sort.should == @files.sort end it "should function if the :ignore value provided is nil" do mock_dir_structure(@path) @fileset.recurse = true @fileset.ignore = nil lambda { @fileset.files }.should_not raise_error end it "should ignore files that match a single pattern in the ignore list" do mock_dir_structure(@path) @fileset.recurse = true @fileset.ignore = ".svn" @fileset.files.find { |file| file.include?(".svn") }.should be_nil end it "should ignore files that match any of multiple patterns in the ignore list" do mock_dir_structure(@path) @fileset.recurse = true @fileset.ignore = %w{.svn CVS} @fileset.files.find { |file| file.include?(".svn") or file.include?("CVS") }.should be_nil end it "should use File.stat if :links is set to :follow" do mock_dir_structure(@path, :stat) @fileset.recurse = true @fileset.links = :follow @fileset.files.sort.should == @files.sort end it "should use File.lstat if :links is set to :manage" do mock_dir_structure(@path, :lstat) @fileset.recurse = true @fileset.links = :manage @fileset.files.sort.should == @files.sort end it "should succeed when paths have regexp significant characters" do @path = make_absolute("/my/path/rV1x2DafFr0R6tGG+1bbk++++TM") File.expects(:lstat).with(@path).returns stub("stat", :directory? => true) @fileset = Puppet::FileServing::Fileset.new(@path) mock_dir_structure(@path) @fileset.recurse = true @fileset.files.sort.should == @files.sort end end describe Puppet::FileServing::Fileset, " when following links that point to missing files" do include PuppetSpec::Files before do @path = make_absolute("/my/path") File.expects(:lstat).with(@path).returns stub("stat", :directory? => true) @fileset = Puppet::FileServing::Fileset.new(@path) @fileset.links = :follow @fileset.recurse = true @stat = stub 'stat', :directory? => true File.expects(:stat).with(@path).returns(@stat) File.expects(:stat).with(File.join(@path, "mylink")).raises(Errno::ENOENT) Dir.stubs(:entries).with(@path).returns(["mylink"]) end it "should not fail" do proc { @fileset.files }.should_not raise_error end it "should still manage the link" do @fileset.files.sort.should == %w{. mylink}.sort end end describe Puppet::FileServing::Fileset, " when ignoring" do include PuppetSpec::Files before do @path = make_absolute("/my/path") File.expects(:lstat).with(@path).returns stub("stat", :directory? => true) @fileset = Puppet::FileServing::Fileset.new(@path) end it "should use ruby's globbing to determine what files should be ignored" do @fileset.ignore = ".svn" File.expects(:fnmatch?).with(".svn", "my_file") @fileset.ignore?("my_file") end it "should ignore files whose paths match a single provided ignore value" do @fileset.ignore = ".svn" File.stubs(:fnmatch?).with(".svn", "my_file").returns true @fileset.ignore?("my_file").should be_true end it "should ignore files whose paths match any of multiple provided ignore values" do @fileset.ignore = [".svn", "CVS"] File.stubs(:fnmatch?).with(".svn", "my_file").returns false File.stubs(:fnmatch?).with("CVS", "my_file").returns true @fileset.ignore?("my_file").should be_true end end describe Puppet::FileServing::Fileset, "when merging other filesets" do include PuppetSpec::Files before do @paths = [make_absolute("/first/path"), make_absolute("/second/path"), make_absolute("/third/path")] File.stubs(:lstat).returns stub("stat", :directory? => false) @filesets = @paths.collect do |path| File.stubs(:lstat).with(path).returns stub("stat", :directory? => true) Puppet::FileServing::Fileset.new(path, :recurse => true) end Dir.stubs(:entries).returns [] end it "should return a hash of all files in each fileset with the value being the base path" do Dir.expects(:entries).with(make_absolute("/first/path")).returns(%w{one uno}) Dir.expects(:entries).with(make_absolute("/second/path")).returns(%w{two dos}) Dir.expects(:entries).with(make_absolute("/third/path")).returns(%w{three tres}) Puppet::FileServing::Fileset.merge(*@filesets).should == { "." => make_absolute("/first/path"), "one" => make_absolute("/first/path"), "uno" => make_absolute("/first/path"), "two" => make_absolute("/second/path"), "dos" => make_absolute("/second/path"), "three" => make_absolute("/third/path"), "tres" => make_absolute("/third/path"), } end it "should include the base directory from the first fileset" do Dir.expects(:entries).with(make_absolute("/first/path")).returns(%w{one}) Dir.expects(:entries).with(make_absolute("/second/path")).returns(%w{two}) Puppet::FileServing::Fileset.merge(*@filesets)["."].should == make_absolute("/first/path") end it "should use the base path of the first found file when relative file paths conflict" do Dir.expects(:entries).with(make_absolute("/first/path")).returns(%w{one}) Dir.expects(:entries).with(make_absolute("/second/path")).returns(%w{one}) Puppet::FileServing::Fileset.merge(*@filesets)["one"].should == make_absolute("/first/path") end end diff --git a/spec/unit/indirector/catalog/active_record_spec.rb b/spec/unit/indirector/catalog/active_record_spec.rb index 7e9e93937..030bedc4b 100755 --- a/spec/unit/indirector/catalog/active_record_spec.rb +++ b/spec/unit/indirector/catalog/active_record_spec.rb @@ -1,98 +1,98 @@ #!/usr/bin/env rspec require 'spec_helper' describe "Puppet::Resource::Catalog::ActiveRecord", :if => can_use_scratch_database? do include PuppetSpec::Files require 'puppet/rails' before :each do require 'puppet/indirector/catalog/active_record' setup_scratch_database end let :terminus do Puppet::Resource::Catalog::ActiveRecord.new end it "should be a subclass of the ActiveRecord terminus class" do Puppet::Resource::Catalog::ActiveRecord.ancestors.should be_include(Puppet::Indirector::ActiveRecord) end it "should use Puppet::Rails::Host as its ActiveRecord model" do Puppet::Resource::Catalog::ActiveRecord.ar_model.should equal(Puppet::Rails::Host) end describe "when finding an instance" do it "should return nil" do r = stub 'request', :key => "foo", :options => {:cache_integration_hack => false} terminus.find(r).should be_nil end # This used to make things go to the database, but that is code that is as # dead as a doornail. This just checks we don't blow up unexpectedly, and # can go away after a few releases. --daniel 2012-02-27 it "should always return nil" do r = stub 'request', :key => "foo", :options => {:cache_integration_hack => true} terminus.find(r).should be_nil end end describe "when saving an instance" do let :catalog do Puppet::Resource::Catalog.new("foo") end - let :request do Puppet::Indirector::Request.new(:active_record, :save, catalog) end + let :request do Puppet::Indirector::Request.new(:active_record, :save, nil, catalog) end let :node do Puppet::Node.new("foo", :environment => "environment") end before :each do Puppet::Node.indirection.stubs(:find).with("foo").returns(node) end it "should find the Rails host with the same name" do Puppet::Rails::Host.expects(:find_by_name).with("foo") terminus.save(request) end it "should create a new Rails host if none can be found" do Puppet::Rails::Host.find_by_name('foo').should be_nil terminus.save(request) Puppet::Rails::Host.find_by_name('foo').should be_valid end it "should set the catalog vertices as resources on the Rails host instance" do # We need to stub this so we get the same object, not just the same # content, otherwise the expect can't fire. :( host = Puppet::Rails::Host.create!(:name => "foo") Puppet::Rails::Host.expects(:find_by_name).with("foo").returns(host) catalog.expects(:vertices).returns("foo") host.expects(:merge_resources).with("foo") terminus.save(request) end it "should set host ip if we could find a matching node" do node.merge("ipaddress" => "192.168.0.1") terminus.save(request) Puppet::Rails::Host.find_by_name("foo").ip.should == '192.168.0.1' end it "should set host environment if we could find a matching node" do terminus.save(request) Puppet::Rails::Host.find_by_name("foo").environment.should == "environment" end it "should set the last compile time on the host" do now = Time.now terminus.save(request) Puppet::Rails::Host.find_by_name("foo").last_compile.should be_within(1).of(now) end it "should save the Rails host instance" do host = Puppet::Rails::Host.create!(:name => "foo") Puppet::Rails::Host.expects(:find_by_name).with("foo").returns(host) host.expects(:save) terminus.save(request) end end end diff --git a/spec/unit/indirector/catalog/compiler_spec.rb b/spec/unit/indirector/catalog/compiler_spec.rb index d0535cd82..54378aa99 100755 --- a/spec/unit/indirector/catalog/compiler_spec.rb +++ b/spec/unit/indirector/catalog/compiler_spec.rb @@ -1,261 +1,261 @@ #!/usr/bin/env rspec require 'spec_helper' require 'puppet/indirector/catalog/compiler' require 'puppet/rails' describe Puppet::Resource::Catalog::Compiler do before do require 'puppet/rails' Puppet::Rails.stubs(:init) Facter.stubs(:to_hash).returns({}) Facter.stubs(:value).returns(Facter::Util::Fact.new("something")) end describe "when initializing" do before do Puppet.expects(:version).returns(1) Facter.expects(:value).with('fqdn').returns("my.server.com") Facter.expects(:value).with('ipaddress').returns("my.ip.address") end it "should gather data about itself" do Puppet::Resource::Catalog::Compiler.new end it "should cache the server metadata and reuse it" do compiler = Puppet::Resource::Catalog::Compiler.new node1 = stub 'node1', :merge => nil node2 = stub 'node2', :merge => nil compiler.stubs(:compile) Puppet::Node.indirection.stubs(:find).with('node1', anything).returns(node1) Puppet::Node.indirection.stubs(:find).with('node2', anything).returns(node2) - compiler.find(Puppet::Indirector::Request.new(:catalog, :find, 'node1', :node => 'node1')) - compiler.find(Puppet::Indirector::Request.new(:catalog, :find, 'node2', :node => 'node2')) + compiler.find(Puppet::Indirector::Request.new(:catalog, :find, 'node1', nil, :node => 'node1')) + compiler.find(Puppet::Indirector::Request.new(:catalog, :find, 'node2', nil, :node => 'node2')) end it "should provide a method for determining if the catalog is networked" do compiler = Puppet::Resource::Catalog::Compiler.new compiler.should respond_to(:networked?) end end describe "when finding catalogs" do before do Facter.stubs(:value).returns("whatever") @compiler = Puppet::Resource::Catalog::Compiler.new @name = "me" @node = Puppet::Node.new @name @node.stubs(:merge) Puppet::Node.indirection.stubs(:find).returns @node - @request = Puppet::Indirector::Request.new(:catalog, :find, @name, :node => @name) + @request = Puppet::Indirector::Request.new(:catalog, :find, @name, nil, :node => @name) end it "should directly use provided nodes" do Puppet::Node.indirection.expects(:find).never @compiler.expects(:compile).with(@node) @request.stubs(:options).returns(:use_node => @node) @compiler.find(@request) end it "should use the authenticated node name if no request key is provided" do @request.stubs(:key).returns(nil) Puppet::Node.indirection.expects(:find).with(@name, anything).returns(@node) @compiler.expects(:compile).with(@node) @compiler.find(@request) end it "should use the provided node name by default" do @request.expects(:key).returns "my_node" Puppet::Node.indirection.expects(:find).with("my_node", anything).returns @node @compiler.expects(:compile).with(@node) @compiler.find(@request) end it "should fail if no node is passed and none can be found" do Puppet::Node.indirection.stubs(:find).with(@name, anything).returns(nil) proc { @compiler.find(@request) }.should raise_error(ArgumentError) end it "should fail intelligently when searching for a node raises an exception" do Puppet::Node.indirection.stubs(:find).with(@name, anything).raises "eh" proc { @compiler.find(@request) }.should raise_error(Puppet::Error) end it "should pass the found node to the compiler for compiling" do Puppet::Node.indirection.expects(:find).with(@name, anything).returns(@node) config = mock 'config' Puppet::Parser::Compiler.expects(:compile).with(@node) @compiler.find(@request) end it "should extract and save any facts from the request" do Puppet::Node.indirection.expects(:find).with(@name, anything).returns @node @compiler.expects(:extract_facts_from_request).with(@request) Puppet::Parser::Compiler.stubs(:compile) @compiler.find(@request) end it "should return the results of compiling as the catalog" do Puppet::Node.indirection.stubs(:find).returns(@node) config = mock 'config' result = mock 'result' Puppet::Parser::Compiler.expects(:compile).returns result @compiler.find(@request).should equal(result) end it "should benchmark the compile process" do Puppet::Node.indirection.stubs(:find).returns(@node) @compiler.stubs(:networked?).returns(true) @compiler.expects(:benchmark).with do |level, message| level == :notice and message =~ /^Compiled catalog/ end Puppet::Parser::Compiler.stubs(:compile) @compiler.find(@request) end it "should log the benchmark result" do Puppet::Node.indirection.stubs(:find).returns(@node) @compiler.stubs(:networked?).returns(true) Puppet::Parser::Compiler.stubs(:compile) Puppet.expects(:notice).with { |msg| msg =~ /Compiled catalog/ } @compiler.find(@request) end end describe "when extracting facts from the request" do before do Facter.stubs(:value).returns "something" @compiler = Puppet::Resource::Catalog::Compiler.new @request = stub 'request', :options => {} @facts = Puppet::Node::Facts.new('hostname', "fact" => "value", "architecture" => "i386") Puppet::Node::Facts.indirection.stubs(:save).returns(nil) end it "should do nothing if no facts are provided" do Puppet::Node::Facts.indirection.expects(:convert_from).never @request.options[:facts] = nil @compiler.extract_facts_from_request(@request) end it "should use the Facts class to deserialize the provided facts and update the timestamp" do @request.options[:facts_format] = "foo" @request.options[:facts] = "bar" Puppet::Node::Facts.expects(:convert_from).returns @facts @facts.timestamp = Time.parse('2010-11-01') @now = Time.parse('2010-11-02') Time.expects(:now).returns(@now) @compiler.extract_facts_from_request(@request) @facts.timestamp.should == @now end it "should use the provided fact format" do @request.options[:facts_format] = "foo" @request.options[:facts] = "bar" Puppet::Node::Facts.expects(:convert_from).with { |format, text| format == "foo" }.returns @facts @compiler.extract_facts_from_request(@request) end it "should convert the facts into a fact instance and save it" do @request.options[:facts_format] = "foo" @request.options[:facts] = "bar" Puppet::Node::Facts.expects(:convert_from).returns @facts Puppet::Node::Facts.indirection.expects(:save).with(@facts) @compiler.extract_facts_from_request(@request) end end describe "when finding nodes" do before do Facter.stubs(:value).returns("whatever") @compiler = Puppet::Resource::Catalog::Compiler.new @name = "me" @node = mock 'node' - @request = Puppet::Indirector::Request.new(:catalog, :find, @name) + @request = Puppet::Indirector::Request.new(:catalog, :find, @name, nil) @compiler.stubs(:compile) end it "should look node information up via the Node class with the provided key" do @node.stubs :merge Puppet::Node.indirection.expects(:find).with(@name, anything).returns(@node) @compiler.find(@request) end end describe "after finding nodes" do before do Puppet.expects(:version).returns(1) Facter.expects(:value).with('fqdn').returns("my.server.com") Facter.expects(:value).with('ipaddress').returns("my.ip.address") @compiler = Puppet::Resource::Catalog::Compiler.new @name = "me" @node = mock 'node' - @request = Puppet::Indirector::Request.new(:catalog, :find, @name) + @request = Puppet::Indirector::Request.new(:catalog, :find, @name, nil) @compiler.stubs(:compile) Puppet::Node.indirection.stubs(:find).with(@name, anything).returns(@node) end it "should add the server's Puppet version to the node's parameters as 'serverversion'" do @node.expects(:merge).with { |args| args["serverversion"] == "1" } @compiler.find(@request) end it "should add the server's fqdn to the node's parameters as 'servername'" do @node.expects(:merge).with { |args| args["servername"] == "my.server.com" } @compiler.find(@request) end it "should add the server's IP address to the node's parameters as 'serverip'" do @node.expects(:merge).with { |args| args["serverip"] == "my.ip.address" } @compiler.find(@request) end end describe "when filtering resources" do before :each do Facter.stubs(:value) @compiler = Puppet::Resource::Catalog::Compiler.new @catalog = stub_everything 'catalog' @catalog.stubs(:respond_to?).with(:filter).returns(true) end it "should delegate to the catalog instance filtering" do @catalog.expects(:filter) @compiler.filter(@catalog) end it "should filter out virtual resources" do resource = mock 'resource', :virtual? => true @catalog.stubs(:filter).yields(resource) @compiler.filter(@catalog) end it "should return the same catalog if it doesn't support filtering" do @catalog.stubs(:respond_to?).with(:filter).returns(false) @compiler.filter(@catalog).should == @catalog end it "should return the filtered catalog" do catalog = stub 'filtered catalog' @catalog.stubs(:filter).returns(catalog) @compiler.filter(@catalog).should == catalog end end end diff --git a/spec/unit/indirector/certificate/rest_spec.rb b/spec/unit/indirector/certificate/rest_spec.rb index 4ec475bc2..a409727eb 100755 --- a/spec/unit/indirector/certificate/rest_spec.rb +++ b/spec/unit/indirector/certificate/rest_spec.rb @@ -1,62 +1,62 @@ #!/usr/bin/env rspec require 'spec_helper' require 'puppet/indirector/certificate/rest' describe Puppet::SSL::Certificate::Rest do before do @searcher = Puppet::SSL::Certificate::Rest.new end it "should be a sublcass of Puppet::Indirector::REST" do Puppet::SSL::Certificate::Rest.superclass.should equal(Puppet::Indirector::REST) end it "should set server_setting to :ca_server" do Puppet::SSL::Certificate::Rest.server_setting.should == :ca_server end it "should set port_setting to :ca_port" do Puppet::SSL::Certificate::Rest.port_setting.should == :ca_port end it "should use the :ca SRV service" do Puppet::SSL::Certificate::Rest.srv_service.should == :ca end it "should make sure found certificates have their names set to the search string" do terminus = Puppet::SSL::Certificate::Rest.new # This has 'boo.com' in the CN cert_string = "-----BEGIN CERTIFICATE----- MIICPzCCAaigAwIBAgIBBDANBgkqhkiG9w0BAQUFADAWMRQwEgYDVQQDDAtidWNr eS5sb2NhbDAeFw0wOTA5MTcxNzI1MzJaFw0xNDA5MTYxNzI1MzJaMBIxEDAOBgNV BAMMB2Jvby5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAKG9B+DkTCNh F5xHchNDfnbC9NzWKM600oxrr84pgUVAG6B2wAZcdfoEtXszhsY9Jzpwqkvxk4Mx AbYqo9+TCi4UoiH6e+vAKOOJD3DHrlf+/RW4hGtyaI41DBhf4+B4/oFz5PH9mvKe NSfHFI/yPW+1IXYjxKLQNwF9E7q3JbnzAgMBAAGjgaAwgZ0wOAYJYIZIAYb4QgEN BCsWKVB1cHBldCBSdWJ5L09wZW5TU0wgR2VuZXJhdGVkIENlcnRpZmljYXRlMAwG A1UdEwEB/wQCMAAwHQYDVR0OBBYEFJOxEUeyf4cNOBmf9zIaE1JTuNdLMAsGA1Ud DwQEAwIFoDAnBgNVHSUEIDAeBggrBgEFBQcDAQYIKwYBBQUHAwIGCCsGAQUFBwME MA0GCSqGSIb3DQEBBQUAA4GBAFTJxKprMg6tfhGnvEvURPmlJrINn9c2b5Y4AGYp tO86PFFkWw/EIJvvJzbj3s+Butr+eUo//+f1xxX7UCwwGqGxKqjtVS219oU/wkx8 h7rW4Xk7MrLl0auSS1p4wLcAMm+ZImf94+j8Cj+tkr8eGozZceRV13b8+EkdaE3S rn/G -----END CERTIFICATE----- " network = stub 'network' terminus.stubs(:network).returns network response = stub 'response', :code => "200", :body => cert_string response.stubs(:[]).with('content-type').returns "text/plain" response.stubs(:[]).with('content-encoding') network.stubs(:verify_callback=) network.expects(:get).returns response - request = Puppet::Indirector::Request.new(:certificate, :find, "foo.com") + request = Puppet::Indirector::Request.new(:certificate, :find, "foo.com", nil) result = terminus.find(request) result.should_not be_nil result.name.should == "foo.com" end end diff --git a/spec/unit/indirector/direct_file_server_spec.rb b/spec/unit/indirector/direct_file_server_spec.rb index 14c1fc5e9..8213f5342 100755 --- a/spec/unit/indirector/direct_file_server_spec.rb +++ b/spec/unit/indirector/direct_file_server_spec.rb @@ -1,80 +1,80 @@ #!/usr/bin/env rspec require 'spec_helper' require 'puppet/indirector/direct_file_server' describe Puppet::Indirector::DirectFileServer do before :all do Puppet::Indirector::Terminus.stubs(:register_terminus_class) @model = mock 'model' @indirection = stub 'indirection', :name => :mystuff, :register_terminus_type => nil, :model => @model Puppet::Indirector::Indirection.stubs(:instance).returns(@indirection) module Testing; end @direct_file_class = class Testing::Mytype < Puppet::Indirector::DirectFileServer self end @server = @direct_file_class.new @path = File.expand_path('/my/local') @uri = Puppet::Util.path_to_uri(@path).to_s - @request = Puppet::Indirector::Request.new(:mytype, :find, @uri) + @request = Puppet::Indirector::Request.new(:mytype, :find, @uri, nil) end describe Puppet::Indirector::DirectFileServer, "when finding a single file" do it "should return nil if the file does not exist" do FileTest.expects(:exists?).with(@path).returns false @server.find(@request).should be_nil end it "should return a Content instance created with the full path to the file if the file exists" do FileTest.expects(:exists?).with(@path).returns true @model.expects(:new).returns(:mycontent) @server.find(@request).should == :mycontent end end describe Puppet::Indirector::DirectFileServer, "when creating the instance for a single found file" do before do @data = mock 'content' @data.stubs(:collect) FileTest.expects(:exists?).with(@path).returns true end it "should pass the full path to the instance" do @model.expects(:new).with { |key, options| key == @path }.returns(@data) @server.find(@request) end it "should pass the :links setting on to the created Content instance if the file exists and there is a value for :links" do @model.expects(:new).returns(@data) @data.expects(:links=).with(:manage) @request.stubs(:options).returns(:links => :manage) @server.find(@request) end end describe Puppet::Indirector::DirectFileServer, "when searching for multiple files" do it "should return nil if the file does not exist" do FileTest.expects(:exists?).with(@path).returns false @server.find(@request).should be_nil end it "should use :path2instances from the terminus_helper to return instances if the file exists" do FileTest.expects(:exists?).with(@path).returns true @server.expects(:path2instances) @server.search(@request) end it "should pass the original request to :path2instances" do FileTest.expects(:exists?).with(@path).returns true @server.expects(:path2instances).with(@request, @path) @server.search(@request) end end end diff --git a/spec/unit/indirector/facts/inventory_active_record_spec.rb b/spec/unit/indirector/facts/inventory_active_record_spec.rb index a79f53744..856adfdb4 100755 --- a/spec/unit/indirector/facts/inventory_active_record_spec.rb +++ b/spec/unit/indirector/facts/inventory_active_record_spec.rb @@ -1,166 +1,166 @@ #!/usr/bin/env rspec require 'spec_helper' require 'puppet/rails' describe "Puppet::Node::Facts::InventoryActiveRecord", :if => can_use_scratch_database? do include PuppetSpec::Files let(:terminus) { Puppet::Node::Facts::InventoryActiveRecord.new } before :all do require 'puppet/indirector/facts/inventory_active_record' end after :each do Puppet::Node::Facts.indirection.reset_terminus_class end before :each do Puppet::Node.indirection.reset_terminus_class Puppet::Node.indirection.cache_class = nil Puppet::Node::Facts.indirection.terminus_class = :inventory_active_record setup_scratch_database end describe "#save" do let(:node) { Puppet::Rails::InventoryNode.new(:name => "foo", :timestamp => Time.now) } let(:facts) { Puppet::Node::Facts.new("foo", "uptime_days" => "60", "kernel" => "Darwin") } it "should retry on ActiveRecord error" do Puppet::Rails::InventoryNode.expects(:create).twice.raises(ActiveRecord::StatementInvalid).returns node Puppet::Node::Facts.indirection.save(facts) end it "should use an existing node if possible" do node.save Puppet::Node::Facts.indirection.save(facts) Puppet::Rails::InventoryNode.count.should == 1 Puppet::Rails::InventoryNode.first.should == node end it "should create a new node if one can't be found" do # This test isn't valid if there are nodes to begin with Puppet::Rails::InventoryNode.count.should == 0 Puppet::Node::Facts.indirection.save(facts) Puppet::Rails::InventoryNode.count.should == 1 Puppet::Rails::InventoryNode.first.name.should == "foo" end it "should save the facts" do Puppet::Node::Facts.indirection.save(facts) Puppet::Rails::InventoryFact.all.map{|f| [f.name,f.value]}.should =~ [["uptime_days","60"],["kernel","Darwin"]] end it "should remove the previous facts for an existing node" do facts = Puppet::Node::Facts.new("foo", "uptime_days" => "30", "kernel" => "Darwin") Puppet::Node::Facts.indirection.save(facts) bar_facts = Puppet::Node::Facts.new("bar", "uptime_days" => "35", "kernel" => "Linux") foo_facts = Puppet::Node::Facts.new("foo", "uptime_days" => "60", "is_virtual" => "false") Puppet::Node::Facts.indirection.save(bar_facts) Puppet::Node::Facts.indirection.save(foo_facts) Puppet::Node::Facts.indirection.find("bar").should == bar_facts Puppet::Node::Facts.indirection.find("foo").should == foo_facts Puppet::Rails::InventoryFact.all.map{|f| [f.name,f.value]}.should_not include(["uptime_days", "30"], ["kernel", "Darwin"]) end end describe "#find" do before do @foo_facts = Puppet::Node::Facts.new("foo", "uptime_days" => "60", "kernel" => "Darwin") @bar_facts = Puppet::Node::Facts.new("bar", "uptime_days" => "30", "kernel" => "Linux") Puppet::Node::Facts.indirection.save(@foo_facts) Puppet::Node::Facts.indirection.save(@bar_facts) end it "should identify facts by node name" do Puppet::Node::Facts.indirection.find("foo").should == @foo_facts end it "should return nil if no node instance can be found" do Puppet::Node::Facts.indirection.find("non-existent node").should == nil end end describe "#search" do def search_request(conditions) - Puppet::Indirector::Request.new(:facts, :search, nil, conditions) + Puppet::Indirector::Request.new(:facts, :search, nil, nil, conditions) end before :each do @now = Time.now @foo = Puppet::Node::Facts.new("foo", "fact1" => "value1", "fact2" => "value2", "uptime_days" => "30") @bar = Puppet::Node::Facts.new("bar", "fact1" => "value1", "uptime_days" => "60") @baz = Puppet::Node::Facts.new("baz", "fact1" => "value2", "fact2" => "value1", "uptime_days" => "90") @bat = Puppet::Node::Facts.new("bat") @foo.timestamp = @now - 3600*1 @bar.timestamp = @now - 3600*3 @baz.timestamp = @now - 3600*5 @bat.timestamp = @now - 3600*7 [@foo, @bar, @baz, @bat].each {|facts| Puppet::Node::Facts.indirection.save(facts)} end it "should return node names that match 'equal' constraints" do request = search_request('facts.fact1.eq' => 'value1', 'facts.fact2.eq' => 'value2') terminus.search(request).should == ["foo"] end it "should return node names that match 'not equal' constraints" do request = search_request('facts.fact1.ne' => 'value2') terminus.search(request).should == ["bar","foo"] end it "should return node names that match strict inequality constraints" do request = search_request('facts.uptime_days.gt' => '20', 'facts.uptime_days.lt' => '70') terminus.search(request).should == ["bar","foo"] end it "should return node names that match non-strict inequality constraints" do request = search_request('facts.uptime_days.ge' => '30', 'facts.uptime_days.le' => '60') terminus.search(request).should == ["bar","foo"] end it "should return node names whose facts are within a given timeframe" do request = search_request('meta.timestamp.ge' => @now - 3600*5, 'meta.timestamp.le' => @now - 3600*1) terminus.search(request).should == ["bar","baz","foo"] end it "should return node names whose facts are from a specific time" do request = search_request('meta.timestamp.eq' => @now - 3600*3) terminus.search(request).should == ["bar"] end it "should return node names whose facts are not from a specific time" do request = search_request('meta.timestamp.ne' => @now - 3600*1) terminus.search(request).should == ["bar","bat","baz"] end it "should perform strict searches on nodes by timestamp" do request = search_request('meta.timestamp.gt' => @now - 3600*5, 'meta.timestamp.lt' => @now - 3600*1) terminus.search(request).should == ["bar"] end it "should search nodes based on both facts and timestamp values" do request = search_request('facts.uptime_days.gt' => '45', 'meta.timestamp.lt' => @now - 3600*4) terminus.search(request).should == ["baz"] end end end diff --git a/spec/unit/indirector/facts/inventory_service_spec.rb b/spec/unit/indirector/facts/inventory_service_spec.rb index 8cce8b3f6..6976466f9 100644 --- a/spec/unit/indirector/facts/inventory_service_spec.rb +++ b/spec/unit/indirector/facts/inventory_service_spec.rb @@ -1,21 +1,21 @@ #!/usr/bin/env rspec require 'spec_helper' require 'puppet/indirector/facts/inventory_service' describe Puppet::Node::Facts::InventoryService do it "should suppress failures and warn when saving facts" do facts = Puppet::Node::Facts.new('foo') - request = Puppet::Indirector::Request.new(:facts, :save, facts) + request = Puppet::Indirector::Request.new(:facts, :save, nil, facts) Net::HTTP.any_instance.stubs(:put).raises(Errno::ECONNREFUSED) Puppet.expects(:warning).with do |msg| msg =~ /Could not upload facts for foo to inventory service/ end expect { subject.save(request) }.should_not raise_error end end diff --git a/spec/unit/indirector/facts/yaml_spec.rb b/spec/unit/indirector/facts/yaml_spec.rb index a22d690b8..d6e1af75a 100755 --- a/spec/unit/indirector/facts/yaml_spec.rb +++ b/spec/unit/indirector/facts/yaml_spec.rb @@ -1,239 +1,239 @@ #!/usr/bin/env rspec require 'spec_helper' require 'puppet/node/facts' require 'puppet/indirector/facts/yaml' describe Puppet::Node::Facts::Yaml do it "should be a subclass of the Yaml terminus" do Puppet::Node::Facts::Yaml.superclass.should equal(Puppet::Indirector::Yaml) end it "should have documentation" do Puppet::Node::Facts::Yaml.doc.should_not be_nil Puppet::Node::Facts::Yaml.doc.should_not be_empty end it "should be registered with the facts indirection" do indirection = Puppet::Indirector::Indirection.instance(:facts) Puppet::Node::Facts::Yaml.indirection.should equal(indirection) end it "should have its name set to :yaml" do Puppet::Node::Facts::Yaml.name.should == :yaml end describe "#search" do def assert_search_matches(matching, nonmatching, query) - request = Puppet::Indirector::Request.new(:inventory, :search, nil, query) + request = Puppet::Indirector::Request.new(:inventory, :search, nil, nil, query) Dir.stubs(:glob).returns(matching.keys + nonmatching.keys) [matching, nonmatching].each do |examples| examples.each do |key, value| YAML.stubs(:load_file).with(key).returns value end end Puppet::Node::Facts::Yaml.new.search(request).should =~ matching.values.map {|facts| facts.name} end it "should return node names that match the search query options" do assert_search_matches({ '/path/to/matching.yaml' => Puppet::Node::Facts.new("matchingnode", "architecture" => "i386", 'processor_count' => '4'), '/path/to/matching1.yaml' => Puppet::Node::Facts.new("matchingnode1", "architecture" => "i386", 'processor_count' => '4', 'randomfact' => 'foo') }, { "/path/to/nonmatching.yaml" => Puppet::Node::Facts.new("nonmatchingnode", "architecture" => "powerpc", 'processor_count' => '4'), "/path/to/nonmatching1.yaml" => Puppet::Node::Facts.new("nonmatchingnode1", "architecture" => "powerpc", 'processor_count' => '5'), "/path/to/nonmatching2.yaml" => Puppet::Node::Facts.new("nonmatchingnode2", "architecture" => "i386", 'processor_count' => '5'), "/path/to/nonmatching3.yaml" => Puppet::Node::Facts.new("nonmatchingnode3", 'processor_count' => '4'), }, {'facts.architecture' => 'i386', 'facts.processor_count' => '4'} ) end it "should return empty array when no nodes match the search query options" do assert_search_matches({}, { "/path/to/nonmatching.yaml" => Puppet::Node::Facts.new("nonmatchingnode", "architecture" => "powerpc", 'processor_count' => '10'), "/path/to/nonmatching1.yaml" => Puppet::Node::Facts.new("nonmatchingnode1", "architecture" => "powerpc", 'processor_count' => '5'), "/path/to/nonmatching2.yaml" => Puppet::Node::Facts.new("nonmatchingnode2", "architecture" => "i386", 'processor_count' => '5'), "/path/to/nonmatching3.yaml" => Puppet::Node::Facts.new("nonmatchingnode3", 'processor_count' => '4'), }, {'facts.processor_count.lt' => '4', 'facts.processor_count.gt' => '4'} ) end it "should return node names that match the search query options with the greater than operator" do assert_search_matches({ '/path/to/matching.yaml' => Puppet::Node::Facts.new("matchingnode", "architecture" => "i386", 'processor_count' => '5'), '/path/to/matching1.yaml' => Puppet::Node::Facts.new("matchingnode1", "architecture" => "powerpc", 'processor_count' => '10', 'randomfact' => 'foo') }, { "/path/to/nonmatching.yaml" => Puppet::Node::Facts.new("nonmatchingnode", "architecture" => "powerpc", 'processor_count' => '4'), "/path/to/nonmatching2.yaml" => Puppet::Node::Facts.new("nonmatchingnode2", "architecture" => "i386", 'processor_count' => '3'), "/path/to/nonmatching3.yaml" => Puppet::Node::Facts.new("nonmatchingnode3" ), }, {'facts.processor_count.gt' => '4'} ) end it "should return node names that match the search query options with the less than operator" do assert_search_matches({ '/path/to/matching.yaml' => Puppet::Node::Facts.new("matchingnode", "architecture" => "i386", 'processor_count' => '5'), '/path/to/matching1.yaml' => Puppet::Node::Facts.new("matchingnode1", "architecture" => "powerpc", 'processor_count' => '30', 'randomfact' => 'foo') }, { "/path/to/nonmatching.yaml" => Puppet::Node::Facts.new("nonmatchingnode", "architecture" => "powerpc", 'processor_count' => '50' ), "/path/to/nonmatching2.yaml" => Puppet::Node::Facts.new("nonmatchingnode2", "architecture" => "i386", 'processor_count' => '100'), "/path/to/nonmatching3.yaml" => Puppet::Node::Facts.new("nonmatchingnode3" ), }, {'facts.processor_count.lt' => '50'} ) end it "should return node names that match the search query options with the less than or equal to operator" do assert_search_matches({ '/path/to/matching.yaml' => Puppet::Node::Facts.new("matchingnode", "architecture" => "i386", 'processor_count' => '5'), '/path/to/matching1.yaml' => Puppet::Node::Facts.new("matchingnode1", "architecture" => "powerpc", 'processor_count' => '50', 'randomfact' => 'foo') }, { "/path/to/nonmatching.yaml" => Puppet::Node::Facts.new("nonmatchingnode", "architecture" => "powerpc", 'processor_count' => '100' ), "/path/to/nonmatching2.yaml" => Puppet::Node::Facts.new("nonmatchingnode2", "architecture" => "i386", 'processor_count' => '5000'), "/path/to/nonmatching3.yaml" => Puppet::Node::Facts.new("nonmatchingnode3" ), }, {'facts.processor_count.le' => '50'} ) end it "should return node names that match the search query options with the greater than or equal to operator" do assert_search_matches({ '/path/to/matching.yaml' => Puppet::Node::Facts.new("matchingnode", "architecture" => "i386", 'processor_count' => '100'), '/path/to/matching1.yaml' => Puppet::Node::Facts.new("matchingnode1", "architecture" => "powerpc", 'processor_count' => '50', 'randomfact' => 'foo') }, { "/path/to/nonmatching.yaml" => Puppet::Node::Facts.new("nonmatchingnode", "architecture" => "powerpc", 'processor_count' => '40'), "/path/to/nonmatching2.yaml" => Puppet::Node::Facts.new("nonmatchingnode2", "architecture" => "i386", 'processor_count' => '9' ), "/path/to/nonmatching3.yaml" => Puppet::Node::Facts.new("nonmatchingnode3" ), }, {'facts.processor_count.ge' => '50'} ) end it "should return node names that match the search query options with the not equal operator" do assert_search_matches({ '/path/to/matching.yaml' => Puppet::Node::Facts.new("matchingnode", "architecture" => 'arm' ), '/path/to/matching1.yaml' => Puppet::Node::Facts.new("matchingnode1", "architecture" => 'powerpc', 'randomfact' => 'foo') }, { "/path/to/nonmatching.yaml" => Puppet::Node::Facts.new("nonmatchingnode", "architecture" => "i386" ), "/path/to/nonmatching2.yaml" => Puppet::Node::Facts.new("nonmatchingnode2", "architecture" => "i386", 'processor_count' => '9' ), "/path/to/nonmatching3.yaml" => Puppet::Node::Facts.new("nonmatchingnode3" ), }, {'facts.architecture.ne' => 'i386'} ) end def apply_timestamp(facts, timestamp) facts.timestamp = timestamp facts end it "should be able to query based on meta.timestamp.gt" do assert_search_matches({ '/path/to/2010-11-01.yaml' => apply_timestamp(Puppet::Node::Facts.new("2010-11-01", {}), Time.parse("2010-11-01")), '/path/to/2010-11-10.yaml' => apply_timestamp(Puppet::Node::Facts.new("2010-11-10", {}), Time.parse("2010-11-10")), }, { '/path/to/2010-10-01.yaml' => apply_timestamp(Puppet::Node::Facts.new("2010-10-01", {}), Time.parse("2010-10-01")), '/path/to/2010-10-10.yaml' => apply_timestamp(Puppet::Node::Facts.new("2010-10-10", {}), Time.parse("2010-10-10")), '/path/to/2010-10-15.yaml' => apply_timestamp(Puppet::Node::Facts.new("2010-10-15", {}), Time.parse("2010-10-15")), }, {'meta.timestamp.gt' => '2010-10-15'} ) end it "should be able to query based on meta.timestamp.le" do assert_search_matches({ '/path/to/2010-10-01.yaml' => apply_timestamp(Puppet::Node::Facts.new("2010-10-01", {}), Time.parse("2010-10-01")), '/path/to/2010-10-10.yaml' => apply_timestamp(Puppet::Node::Facts.new("2010-10-10", {}), Time.parse("2010-10-10")), '/path/to/2010-10-15.yaml' => apply_timestamp(Puppet::Node::Facts.new("2010-10-15", {}), Time.parse("2010-10-15")), }, { '/path/to/2010-11-01.yaml' => apply_timestamp(Puppet::Node::Facts.new("2010-11-01", {}), Time.parse("2010-11-01")), '/path/to/2010-11-10.yaml' => apply_timestamp(Puppet::Node::Facts.new("2010-11-10", {}), Time.parse("2010-11-10")), }, {'meta.timestamp.le' => '2010-10-15'} ) end it "should be able to query based on meta.timestamp.lt" do assert_search_matches({ '/path/to/2010-10-01.yaml' => apply_timestamp(Puppet::Node::Facts.new("2010-10-01", {}), Time.parse("2010-10-01")), '/path/to/2010-10-10.yaml' => apply_timestamp(Puppet::Node::Facts.new("2010-10-10", {}), Time.parse("2010-10-10")), }, { '/path/to/2010-11-01.yaml' => apply_timestamp(Puppet::Node::Facts.new("2010-11-01", {}), Time.parse("2010-11-01")), '/path/to/2010-11-10.yaml' => apply_timestamp(Puppet::Node::Facts.new("2010-11-10", {}), Time.parse("2010-11-10")), '/path/to/2010-10-15.yaml' => apply_timestamp(Puppet::Node::Facts.new("2010-10-15", {}), Time.parse("2010-10-15")), }, {'meta.timestamp.lt' => '2010-10-15'} ) end it "should be able to query based on meta.timestamp.ge" do assert_search_matches({ '/path/to/2010-11-01.yaml' => apply_timestamp(Puppet::Node::Facts.new("2010-11-01", {}), Time.parse("2010-11-01")), '/path/to/2010-11-10.yaml' => apply_timestamp(Puppet::Node::Facts.new("2010-11-10", {}), Time.parse("2010-11-10")), '/path/to/2010-10-15.yaml' => apply_timestamp(Puppet::Node::Facts.new("2010-10-15", {}), Time.parse("2010-10-15")), }, { '/path/to/2010-10-01.yaml' => apply_timestamp(Puppet::Node::Facts.new("2010-10-01", {}), Time.parse("2010-10-01")), '/path/to/2010-10-10.yaml' => apply_timestamp(Puppet::Node::Facts.new("2010-10-10", {}), Time.parse("2010-10-10")), }, {'meta.timestamp.ge' => '2010-10-15'} ) end it "should be able to query based on meta.timestamp.eq" do assert_search_matches({ '/path/to/2010-10-15.yaml' => apply_timestamp(Puppet::Node::Facts.new("2010-10-15", {}), Time.parse("2010-10-15")), }, { '/path/to/2010-11-01.yaml' => apply_timestamp(Puppet::Node::Facts.new("2010-11-01", {}), Time.parse("2010-11-01")), '/path/to/2010-11-10.yaml' => apply_timestamp(Puppet::Node::Facts.new("2010-11-10", {}), Time.parse("2010-11-10")), '/path/to/2010-10-01.yaml' => apply_timestamp(Puppet::Node::Facts.new("2010-10-01", {}), Time.parse("2010-10-01")), '/path/to/2010-10-10.yaml' => apply_timestamp(Puppet::Node::Facts.new("2010-10-10", {}), Time.parse("2010-10-10")), }, {'meta.timestamp.eq' => '2010-10-15'} ) end it "should be able to query based on meta.timestamp" do assert_search_matches({ '/path/to/2010-10-15.yaml' => apply_timestamp(Puppet::Node::Facts.new("2010-10-15", {}), Time.parse("2010-10-15")), }, { '/path/to/2010-11-01.yaml' => apply_timestamp(Puppet::Node::Facts.new("2010-11-01", {}), Time.parse("2010-11-01")), '/path/to/2010-11-10.yaml' => apply_timestamp(Puppet::Node::Facts.new("2010-11-10", {}), Time.parse("2010-11-10")), '/path/to/2010-10-01.yaml' => apply_timestamp(Puppet::Node::Facts.new("2010-10-01", {}), Time.parse("2010-10-01")), '/path/to/2010-10-10.yaml' => apply_timestamp(Puppet::Node::Facts.new("2010-10-10", {}), Time.parse("2010-10-10")), }, {'meta.timestamp' => '2010-10-15'} ) end it "should be able to query based on meta.timestamp.ne" do assert_search_matches({ '/path/to/2010-11-01.yaml' => apply_timestamp(Puppet::Node::Facts.new("2010-11-01", {}), Time.parse("2010-11-01")), '/path/to/2010-11-10.yaml' => apply_timestamp(Puppet::Node::Facts.new("2010-11-10", {}), Time.parse("2010-11-10")), '/path/to/2010-10-01.yaml' => apply_timestamp(Puppet::Node::Facts.new("2010-10-01", {}), Time.parse("2010-10-01")), '/path/to/2010-10-10.yaml' => apply_timestamp(Puppet::Node::Facts.new("2010-10-10", {}), Time.parse("2010-10-10")), }, { '/path/to/2010-10-15.yaml' => apply_timestamp(Puppet::Node::Facts.new("2010-10-15", {}), Time.parse("2010-10-15")), }, {'meta.timestamp.ne' => '2010-10-15'} ) end end end diff --git a/spec/unit/indirector/file_bucket_file/file_spec.rb b/spec/unit/indirector/file_bucket_file/file_spec.rb index 9f9754a8f..6f1a6823e 100755 --- a/spec/unit/indirector/file_bucket_file/file_spec.rb +++ b/spec/unit/indirector/file_bucket_file/file_spec.rb @@ -1,270 +1,270 @@ #!/usr/bin/env rspec require 'spec_helper' require 'puppet/indirector/file_bucket_file/file' describe Puppet::FileBucketFile::File do include PuppetSpec::Files it "should be a subclass of the Code terminus class" do Puppet::FileBucketFile::File.superclass.should equal(Puppet::Indirector::Code) end it "should have documentation" do Puppet::FileBucketFile::File.doc.should be_instance_of(String) end describe "non-stubbing tests" do include PuppetSpec::Files before do Puppet[:bucketdir] = tmpdir('bucketdir') end def save_bucket_file(contents, path = "/who_cares") bucket_file = Puppet::FileBucket::File.new(contents) Puppet::FileBucket::File.indirection.save(bucket_file, "md5/#{Digest::MD5.hexdigest(contents)}#{path}") bucket_file.checksum_data end describe "when servicing a save request" do describe "when supplying a path" do it "should store the path if not already stored" do checksum = save_bucket_file("stuff\r\n", "/foo/bar") dir_path = "#{Puppet[:bucketdir]}/f/c/7/7/7/c/0/b/fc777c0bc467e1ab98b4c6915af802ec" IO.binread("#{dir_path}/contents").should == "stuff\r\n" File.read("#{dir_path}/paths").should == "foo/bar\n" end it "should leave the paths file alone if the path is already stored" do checksum = save_bucket_file("stuff", "/foo/bar") checksum = save_bucket_file("stuff", "/foo/bar") dir_path = "#{Puppet[:bucketdir]}/c/1/3/d/8/8/c/b/c13d88cb4cb02003daedb8a84e5d272a" File.read("#{dir_path}/contents").should == "stuff" File.read("#{dir_path}/paths").should == "foo/bar\n" end it "should store an additional path if the new path differs from those already stored" do checksum = save_bucket_file("stuff", "/foo/bar") checksum = save_bucket_file("stuff", "/foo/baz") dir_path = "#{Puppet[:bucketdir]}/c/1/3/d/8/8/c/b/c13d88cb4cb02003daedb8a84e5d272a" File.read("#{dir_path}/contents").should == "stuff" File.read("#{dir_path}/paths").should == "foo/bar\nfoo/baz\n" end end describe "when not supplying a path" do it "should save the file and create an empty paths file" do checksum = save_bucket_file("stuff", "") dir_path = "#{Puppet[:bucketdir]}/c/1/3/d/8/8/c/b/c13d88cb4cb02003daedb8a84e5d272a" File.read("#{dir_path}/contents").should == "stuff" File.read("#{dir_path}/paths").should == "" end end end describe "when servicing a head/find request" do describe "when supplying a path" do it "should return false/nil if the file isn't bucketed" do Puppet::FileBucket::File.indirection.head("md5/0ae2ec1980410229885fe72f7b44fe55/foo/bar").should == false Puppet::FileBucket::File.indirection.find("md5/0ae2ec1980410229885fe72f7b44fe55/foo/bar").should == nil end it "should return false/nil if the file is bucketed but with a different path" do checksum = save_bucket_file("I'm the contents of a file", '/foo/bar') Puppet::FileBucket::File.indirection.head("md5/#{checksum}/foo/baz").should == false Puppet::FileBucket::File.indirection.find("md5/#{checksum}/foo/baz").should == nil end it "should return true/file if the file is already bucketed with the given path" do contents = "I'm the contents of a file" checksum = save_bucket_file(contents, '/foo/bar') Puppet::FileBucket::File.indirection.head("md5/#{checksum}/foo/bar").should == true find_result = Puppet::FileBucket::File.indirection.find("md5/#{checksum}/foo/bar") find_result.should be_a(Puppet::FileBucket::File) find_result.checksum.should == "{md5}#{checksum}" find_result.to_s.should == contents end end describe "when not supplying a path" do [false, true].each do |trailing_slash| describe "#{trailing_slash ? 'with' : 'without'} a trailing slash" do trailing_string = trailing_slash ? '/' : '' it "should return false/nil if the file isn't bucketed" do Puppet::FileBucket::File.indirection.head("md5/0ae2ec1980410229885fe72f7b44fe55#{trailing_string}").should == false Puppet::FileBucket::File.indirection.find("md5/0ae2ec1980410229885fe72f7b44fe55#{trailing_string}").should == nil end it "should return true/file if the file is already bucketed" do contents = "I'm the contents of a file" checksum = save_bucket_file(contents, '/foo/bar') Puppet::FileBucket::File.indirection.head("md5/#{checksum}#{trailing_string}").should == true find_result = Puppet::FileBucket::File.indirection.find("md5/#{checksum}#{trailing_string}") find_result.should be_a(Puppet::FileBucket::File) find_result.checksum.should == "{md5}#{checksum}" find_result.to_s.should == contents end end end end end describe "when diffing files", :unless => Puppet.features.microsoft_windows? do it "should generate an empty string if there is no diff" do checksum = save_bucket_file("I'm the contents of a file") Puppet::FileBucket::File.indirection.find("md5/#{checksum}", :diff_with => checksum).should == '' end it "should generate a proper diff if there is a diff" do checksum1 = save_bucket_file("foo\nbar\nbaz") checksum2 = save_bucket_file("foo\nbiz\nbaz") diff = Puppet::FileBucket::File.indirection.find("md5/#{checksum1}", :diff_with => checksum2) diff.should == < biz HERE end it "should raise an exception if the hash to diff against isn't found" do checksum = save_bucket_file("whatever") bogus_checksum = "d1bf072d0e2c6e20e3fbd23f022089a1" lambda { Puppet::FileBucket::File.indirection.find("md5/#{checksum}", :diff_with => bogus_checksum) }.should raise_error "could not find diff_with #{bogus_checksum}" end it "should return nil if the hash to diff from isn't found" do checksum = save_bucket_file("whatever") bogus_checksum = "d1bf072d0e2c6e20e3fbd23f022089a1" Puppet::FileBucket::File.indirection.find("md5/#{bogus_checksum}", :diff_with => checksum).should == nil end end end describe "when initializing" do it "should use the filebucket settings section" do Puppet.settings.expects(:use).with(:filebucket) Puppet::FileBucketFile::File.new end end [true, false].each do |override_bucket_path| describe "when bucket path #{if override_bucket_path then 'is' else 'is not' end} overridden" do [true, false].each do |supply_path| describe "when #{supply_path ? 'supplying' : 'not supplying'} a path" do before :each do Puppet.settings.stubs(:use) @store = Puppet::FileBucketFile::File.new @contents = "my content" @digest = "f2bfa7fc155c4f42cb91404198dda01f" @digest.should == Digest::MD5.hexdigest(@contents) @bucket_dir = tmpdir("bucket") if override_bucket_path Puppet[:bucketdir] = "/bogus/path" # should not be used else Puppet[:bucketdir] = @bucket_dir end @dir = "#{@bucket_dir}/f/2/b/f/a/7/f/c/f2bfa7fc155c4f42cb91404198dda01f" @contents_path = "#{@dir}/contents" end describe "when retrieving files" do before :each do request_options = {} if override_bucket_path request_options[:bucket_path] = @bucket_dir end key = "md5/#{@digest}" if supply_path key += "/path/to/file" end - @request = Puppet::Indirector::Request.new(:indirection_name, :find, key, request_options) + @request = Puppet::Indirector::Request.new(:indirection_name, :find, key, nil, request_options) end def make_bucketed_file FileUtils.mkdir_p(@dir) File.open(@contents_path, 'w') { |f| f.write @contents } end it "should return an instance of Puppet::FileBucket::File created with the content if the file exists" do make_bucketed_file if supply_path @store.find(@request).should == nil @store.head(@request).should == false # because path didn't match else bucketfile = @store.find(@request) bucketfile.should be_a(Puppet::FileBucket::File) bucketfile.contents.should == @contents @store.head(@request).should == true end end it "should return nil if no file is found" do @store.find(@request).should be_nil @store.head(@request).should == false end end describe "when saving files" do it "should save the contents to the calculated path" do options = {} if override_bucket_path options[:bucket_path] = @bucket_dir end key = "md5/#{@digest}" if supply_path key += "//path/to/file" end file_instance = Puppet::FileBucket::File.new(@contents, options) request = Puppet::Indirector::Request.new(:indirection_name, :save, key, file_instance) @store.save(request) File.read("#{@dir}/contents").should == @contents end end end end end end describe "when verifying identical files" do let(:contents) { "file\r\n contents" } let(:digest) { "8b3702ad1aed1ace7e32bde76ffffb2d" } let(:checksum) { "{md5}#{@digest}" } let(:bucketdir) { tmpdir('file_bucket_file') } let(:destdir) { "#{bucketdir}/8/b/3/7/0/2/a/d/8b3702ad1aed1ace7e32bde76ffffb2d" } let(:bucket) { Puppet::FileBucket::File.new(contents) } before :each do Puppet[:bucketdir] = bucketdir FileUtils.mkdir_p(destdir) end it "should raise an error if the files don't match" do File.open(File.join(destdir, 'contents'), 'wb') { |f| f.print "corrupt contents" } lambda{ Puppet::FileBucketFile::File.new.send(:verify_identical_file!, bucket) }.should raise_error(Puppet::FileBucket::BucketError) end it "should do nothing if the files match" do File.open(File.join(destdir, 'contents'), 'wb') { |f| f.print contents } Puppet::FileBucketFile::File.new.send(:verify_identical_file!, bucket) end end end diff --git a/spec/unit/indirector/file_metadata/file_spec.rb b/spec/unit/indirector/file_metadata/file_spec.rb index b66c7e05f..3a108ca9b 100755 --- a/spec/unit/indirector/file_metadata/file_spec.rb +++ b/spec/unit/indirector/file_metadata/file_spec.rb @@ -1,50 +1,50 @@ #!/usr/bin/env rspec require 'spec_helper' require 'puppet/indirector/file_metadata/file' describe Puppet::Indirector::FileMetadata::File do it "should be registered with the file_metadata indirection" do Puppet::Indirector::Terminus.terminus_class(:file_metadata, :file).should equal(Puppet::Indirector::FileMetadata::File) end it "should be a subclass of the DirectFileServer terminus" do Puppet::Indirector::FileMetadata::File.superclass.should equal(Puppet::Indirector::DirectFileServer) end describe "when creating the instance for a single found file" do before do @metadata = Puppet::Indirector::FileMetadata::File.new @path = File.expand_path('/my/local') @uri = Puppet::Util.path_to_uri(@path).to_s @data = mock 'metadata' @data.stubs(:collect) FileTest.expects(:exists?).with(@path).returns true - @request = Puppet::Indirector::Request.new(:file_metadata, :find, @uri) + @request = Puppet::Indirector::Request.new(:file_metadata, :find, @uri, nil) end it "should collect its attributes when a file is found" do @data.expects(:collect) Puppet::FileServing::Metadata.expects(:new).returns(@data) @metadata.find(@request).should == @data end end describe "when searching for multiple files" do before do @metadata = Puppet::Indirector::FileMetadata::File.new @path = File.expand_path('/my/local') @uri = Puppet::Util.path_to_uri(@path).to_s - @request = Puppet::Indirector::Request.new(:file_metadata, :find, @uri) + @request = Puppet::Indirector::Request.new(:file_metadata, :find, @uri, nil) end it "should collect the attributes of the instances returned" do FileTest.expects(:exists?).with(@path).returns true @metadata.expects(:path2instances).returns( [mock("one", :collect => nil), mock("two", :collect => nil)] ) @metadata.search(@request) end end end diff --git a/spec/unit/indirector/indirection_spec.rb b/spec/unit/indirector/indirection_spec.rb index c33fdf165..72a0b0c57 100755 --- a/spec/unit/indirector/indirection_spec.rb +++ b/spec/unit/indirector/indirection_spec.rb @@ -1,856 +1,856 @@ #!/usr/bin/env rspec require 'spec_helper' require 'puppet/indirector/indirection' shared_examples_for "Indirection Delegator" do it "should create a request object with the appropriate method name and all of the passed arguments" do - request = Puppet::Indirector::Request.new(:indirection, :find, "me") + request = Puppet::Indirector::Request.new(:indirection, :find, "me", nil) - @indirection.expects(:request).with(@method, "mystuff", :one => :two).returns request + @indirection.expects(:request).with(@method, "mystuff", nil, :one => :two).returns request @terminus.stubs(@method) @indirection.send(@method, "mystuff", :one => :two) end it "should let the :select_terminus method choose the terminus using the created request if the :select_terminus method is available" do # Define the method, so our respond_to? hook matches. class << @indirection def select_terminus(request) end end - request = Puppet::Indirector::Request.new(:indirection, :find, "me") + request = Puppet::Indirector::Request.new(:indirection, :find, "me", nil) @indirection.stubs(:request).returns request @indirection.expects(:select_terminus).with(request).returns :test_terminus @indirection.stubs(:check_authorization) @terminus.expects(@method) @indirection.send(@method, "me") end it "should fail if the :select_terminus hook does not return a terminus name" do # Define the method, so our respond_to? hook matches. class << @indirection def select_terminus(request) end end request = stub 'request', :key => "me", :options => {} @indirection.stubs(:request).returns request @indirection.expects(:select_terminus).with(request).returns nil lambda { @indirection.send(@method, "me") }.should raise_error(ArgumentError) end it "should choose the terminus returned by the :terminus_class method if no :select_terminus method is available" do @indirection.expects(:terminus_class).returns :test_terminus @terminus.expects(@method) @indirection.send(@method, "me") end it "should let the appropriate terminus perform the lookup" do @terminus.expects(@method).with { |r| r.is_a?(Puppet::Indirector::Request) } @indirection.send(@method, "me") end end shared_examples_for "Delegation Authorizer" do before do # So the :respond_to? turns out correctly. class << @terminus def authorized? end end end it "should not check authorization if a node name is not provided" do @terminus.expects(:authorized?).never @terminus.stubs(@method) # The quotes are necessary here, else it looks like a block. @request.stubs(:options).returns({}) @indirection.send(@method, "/my/key") end it "should pass the request to the terminus's authorization method" do @terminus.expects(:authorized?).with { |r| r.is_a?(Puppet::Indirector::Request) }.returns(true) @terminus.stubs(@method) @indirection.send(@method, "/my/key", :node => "mynode") end it "should fail if authorization returns false" do @terminus.expects(:authorized?).returns(false) @terminus.stubs(@method) proc { @indirection.send(@method, "/my/key", :node => "mynode") }.should raise_error(ArgumentError) end it "should continue if authorization returns true" do @terminus.expects(:authorized?).returns(true) @terminus.stubs(@method) @indirection.send(@method, "/my/key", :node => "mynode") end end describe Puppet::Indirector::Indirection do describe "when initializing" do # (LAK) I've no idea how to test this, really. it "should store a reference to itself before it consumes its options" do proc { @indirection = Puppet::Indirector::Indirection.new(Object.new, :testingness, :not_valid_option) }.should raise_error Puppet::Indirector::Indirection.instance(:testingness).should be_instance_of(Puppet::Indirector::Indirection) Puppet::Indirector::Indirection.instance(:testingness).delete end it "should keep a reference to the indirecting model" do model = mock 'model' @indirection = Puppet::Indirector::Indirection.new(model, :myind) @indirection.model.should equal(model) end it "should set the name" do @indirection = Puppet::Indirector::Indirection.new(mock('model'), :myind) @indirection.name.should == :myind end it "should require indirections to have unique names" do @indirection = Puppet::Indirector::Indirection.new(mock('model'), :test) proc { Puppet::Indirector::Indirection.new(:test) }.should raise_error(ArgumentError) end it "should extend itself with any specified module" do mod = Module.new @indirection = Puppet::Indirector::Indirection.new(mock('model'), :test, :extend => mod) @indirection.singleton_class.included_modules.should include(mod) end after do @indirection.delete if defined?(@indirection) end end describe "when an instance" do before :each do @terminus_class = mock 'terminus_class' @terminus = mock 'terminus' @terminus_class.stubs(:new).returns(@terminus) @cache = stub 'cache', :name => "mycache" @cache_class = mock 'cache_class' Puppet::Indirector::Terminus.stubs(:terminus_class).with(:test, :cache_terminus).returns(@cache_class) Puppet::Indirector::Terminus.stubs(:terminus_class).with(:test, :test_terminus).returns(@terminus_class) @indirection = Puppet::Indirector::Indirection.new(mock('model'), :test) @indirection.terminus_class = :test_terminus @instance = stub 'instance', :expiration => nil, :expiration= => nil, :name => "whatever" @name = :mything #@request = stub 'instance', :key => "/my/key", :instance => @instance, :options => {} @request = mock 'instance' end it "should allow setting the ttl" do @indirection.ttl = 300 @indirection.ttl.should == 300 end it "should default to the :runinterval setting, converted to an integer, for its ttl" do Puppet.settings.expects(:value).returns "1800" @indirection.ttl.should == 1800 end it "should calculate the current expiration by adding the TTL to the current time" do @indirection.stubs(:ttl).returns(100) now = Time.now Time.stubs(:now).returns now @indirection.expiration.should == (Time.now + 100) end it "should have a method for creating an indirection request instance" do @indirection.should respond_to(:request) end describe "creates a request" do it "should create it with its name as the request's indirection name" do Puppet::Indirector::Request.expects(:new).with { |name, *other| @indirection.name == name } @indirection.request(:funtest, "yayness") end it "should require a method and key" do Puppet::Indirector::Request.expects(:new).with { |name, method, key, *other| method == :funtest and key == "yayness" } @indirection.request(:funtest, "yayness") end it "should support optional arguments" do Puppet::Indirector::Request.expects(:new).with { |name, method, key, other| other == {:one => :two} } @indirection.request(:funtest, "yayness", :one => :two) end it "should not pass options if none are supplied" do Puppet::Indirector::Request.expects(:new).with { |*args| args.length < 4 } @indirection.request(:funtest, "yayness") end it "should return the request" do request = mock 'request' Puppet::Indirector::Request.expects(:new).returns request @indirection.request(:funtest, "yayness").should equal(request) end end describe "and looking for a model instance" do before { @method = :find } it_should_behave_like "Indirection Delegator" it_should_behave_like "Delegation Authorizer" it "should return the results of the delegation" do @terminus.expects(:find).returns(@instance) @indirection.find("me").should equal(@instance) end it "should set the expiration date on any instances without one set" do @terminus.stubs(:find).returns(@instance) @indirection.expects(:expiration).returns :yay @instance.expects(:expiration).returns(nil) @instance.expects(:expiration=).with(:yay) @indirection.find("/my/key") end it "should not override an already-set expiration date on returned instances" do @terminus.stubs(:find).returns(@instance) @indirection.expects(:expiration).never @instance.expects(:expiration).returns(:yay) @instance.expects(:expiration=).never @indirection.find("/my/key") end it "should filter the result instance if the terminus supports it" do @terminus.stubs(:find).returns(@instance) @terminus.stubs(:respond_to?).with(:filter).returns(true) @terminus.expects(:filter).with(@instance) @indirection.find("/my/key") end describe "when caching is enabled" do before do @indirection.cache_class = :cache_terminus @cache_class.stubs(:new).returns(@cache) @instance.stubs(:expired?).returns false end it "should first look in the cache for an instance" do @terminus.stubs(:find).never @cache.expects(:find).returns @instance @indirection.find("/my/key") end it "should not look in the cache if the request specifies not to use the cache" do @terminus.expects(:find).returns @instance @cache.expects(:find).never @cache.stubs(:save) @indirection.find("/my/key", :ignore_cache => true) end it "should still save to the cache even if the cache is being ignored during readin" do @terminus.expects(:find).returns @instance @cache.expects(:save) @indirection.find("/my/key", :ignore_cache => true) end it "should only look in the cache if the request specifies not to use the terminus" do @terminus.expects(:find).never @cache.expects(:find) @indirection.find("/my/key", :ignore_terminus => true) end it "should use a request to look in the cache for cached objects" do @cache.expects(:find).with { |r| r.method == :find and r.key == "/my/key" }.returns @instance @cache.stubs(:save) @indirection.find("/my/key") end it "should return the cached object if it is not expired" do @instance.stubs(:expired?).returns false @cache.stubs(:find).returns @instance @indirection.find("/my/key").should equal(@instance) end it "should not fail if the cache fails" do @terminus.stubs(:find).returns @instance @cache.expects(:find).raises ArgumentError @cache.stubs(:save) lambda { @indirection.find("/my/key") }.should_not raise_error end it "should look in the main terminus if the cache fails" do @terminus.expects(:find).returns @instance @cache.expects(:find).raises ArgumentError @cache.stubs(:save) @indirection.find("/my/key").should equal(@instance) end it "should send a debug log if it is using the cached object" do Puppet.expects(:debug) @cache.stubs(:find).returns @instance @indirection.find("/my/key") end it "should not return the cached object if it is expired" do @instance.stubs(:expired?).returns true @cache.stubs(:find).returns @instance @terminus.stubs(:find).returns nil @indirection.find("/my/key").should be_nil end it "should send an info log if it is using the cached object" do Puppet.expects(:info) @instance.stubs(:expired?).returns true @cache.stubs(:find).returns @instance @terminus.stubs(:find).returns nil @indirection.find("/my/key") end it "should cache any objects not retrieved from the cache" do @cache.expects(:find).returns nil @terminus.expects(:find).returns(@instance) @cache.expects(:save) @indirection.find("/my/key") end it "should use a request to look in the cache for cached objects" do @cache.expects(:find).with { |r| r.method == :find and r.key == "/my/key" }.returns nil @terminus.stubs(:find).returns(@instance) @cache.stubs(:save) @indirection.find("/my/key") end it "should cache the instance using a request with the instance set to the cached object" do @cache.stubs(:find).returns nil @terminus.stubs(:find).returns(@instance) @cache.expects(:save).with { |r| r.method == :save and r.instance == @instance } @indirection.find("/my/key") end it "should send an info log that the object is being cached" do @cache.stubs(:find).returns nil @terminus.stubs(:find).returns(@instance) @cache.stubs(:save) Puppet.expects(:info) @indirection.find("/my/key") end end end describe "and doing a head operation" do before { @method = :head } it_should_behave_like "Indirection Delegator" it_should_behave_like "Delegation Authorizer" it "should return true if the head method returned true" do @terminus.expects(:head).returns(true) @indirection.head("me").should == true end it "should return false if the head method returned false" do @terminus.expects(:head).returns(false) @indirection.head("me").should == false end describe "when caching is enabled" do before do @indirection.cache_class = :cache_terminus @cache_class.stubs(:new).returns(@cache) @instance.stubs(:expired?).returns false end it "should first look in the cache for an instance" do @terminus.stubs(:find).never @terminus.stubs(:head).never @cache.expects(:find).returns @instance @indirection.head("/my/key").should == true end it "should not save to the cache" do @cache.expects(:find).returns nil @cache.expects(:save).never @terminus.expects(:head).returns true @indirection.head("/my/key").should == true end it "should not fail if the cache fails" do @terminus.stubs(:head).returns true @cache.expects(:find).raises ArgumentError lambda { @indirection.head("/my/key") }.should_not raise_error end it "should look in the main terminus if the cache fails" do @terminus.expects(:head).returns true @cache.expects(:find).raises ArgumentError @indirection.head("/my/key").should == true end it "should send a debug log if it is using the cached object" do Puppet.expects(:debug) @cache.stubs(:find).returns @instance @indirection.head("/my/key") end it "should not accept the cached object if it is expired" do @instance.stubs(:expired?).returns true @cache.stubs(:find).returns @instance @terminus.stubs(:head).returns false @indirection.head("/my/key").should == false end end end describe "and storing a model instance" do before { @method = :save } it "should return the result of the save" do @terminus.stubs(:save).returns "foo" @indirection.save(@instance).should == "foo" end describe "when caching is enabled" do before do @indirection.cache_class = :cache_terminus @cache_class.stubs(:new).returns(@cache) @instance.stubs(:expired?).returns false end it "should return the result of saving to the terminus" do request = stub 'request', :instance => @instance, :node => nil @indirection.expects(:request).returns request @cache.stubs(:save) @terminus.stubs(:save).returns @instance @indirection.save(@instance).should equal(@instance) end it "should use a request to save the object to the cache" do request = stub 'request', :instance => @instance, :node => nil @indirection.expects(:request).returns request @cache.expects(:save).with(request) @terminus.stubs(:save) @indirection.save(@instance) end it "should not save to the cache if the normal save fails" do request = stub 'request', :instance => @instance, :node => nil @indirection.expects(:request).returns request @cache.expects(:save).never @terminus.expects(:save).raises "eh" lambda { @indirection.save(@instance) }.should raise_error end end end describe "and removing a model instance" do before { @method = :destroy } it_should_behave_like "Indirection Delegator" it_should_behave_like "Delegation Authorizer" it "should return the result of removing the instance" do @terminus.stubs(:destroy).returns "yayness" @indirection.destroy("/my/key").should == "yayness" end describe "when caching is enabled" do before do @indirection.cache_class = :cache_terminus @cache_class.expects(:new).returns(@cache) @instance.stubs(:expired?).returns false end it "should use a request instance to search in and remove objects from the cache" do destroy = stub 'destroy_request', :key => "/my/key", :node => nil find = stub 'destroy_request', :key => "/my/key", :node => nil - @indirection.expects(:request).with(:destroy, "/my/key").returns destroy - @indirection.expects(:request).with(:find, "/my/key").returns find + @indirection.expects(:request).with(:destroy, "/my/key", nil, optionally(instance_of(Hash))).returns destroy + @indirection.expects(:request).with(:find, "/my/key", nil, optionally(instance_of(Hash))).returns find cached = mock 'cache' @cache.expects(:find).with(find).returns cached @cache.expects(:destroy).with(destroy) @terminus.stubs(:destroy) @indirection.destroy("/my/key") end end end describe "and searching for multiple model instances" do before { @method = :search } it_should_behave_like "Indirection Delegator" it_should_behave_like "Delegation Authorizer" it "should set the expiration date on any instances without one set" do @terminus.stubs(:search).returns([@instance]) @indirection.expects(:expiration).returns :yay @instance.expects(:expiration).returns(nil) @instance.expects(:expiration=).with(:yay) @indirection.search("/my/key") end it "should not override an already-set expiration date on returned instances" do @terminus.stubs(:search).returns([@instance]) @indirection.expects(:expiration).never @instance.expects(:expiration).returns(:yay) @instance.expects(:expiration=).never @indirection.search("/my/key") end it "should return the results of searching in the terminus" do @terminus.expects(:search).returns([@instance]) @indirection.search("/my/key").should == [@instance] end end describe "and expiring a model instance" do describe "when caching is not enabled" do it "should do nothing" do @cache_class.expects(:new).never @indirection.expire("/my/key") end end describe "when caching is enabled" do before do @indirection.cache_class = :cache_terminus @cache_class.expects(:new).returns(@cache) @instance.stubs(:expired?).returns false @cached = stub 'cached', :expiration= => nil, :name => "/my/key" end it "should use a request to find within the cache" do @cache.expects(:find).with { |r| r.is_a?(Puppet::Indirector::Request) and r.method == :find } @indirection.expire("/my/key") end it "should do nothing if no such instance is cached" do @cache.expects(:find).returns nil @indirection.expire("/my/key") end it "should log when expiring a found instance" do @cache.expects(:find).returns @cached @cache.stubs(:save) Puppet.expects(:info) @indirection.expire("/my/key") end it "should set the cached instance's expiration to a time in the past" do @cache.expects(:find).returns @cached @cache.stubs(:save) @cached.expects(:expiration=).with { |t| t < Time.now } @indirection.expire("/my/key") end it "should save the now expired instance back into the cache" do @cache.expects(:find).returns @cached @cached.expects(:expiration=).with { |t| t < Time.now } @cache.expects(:save) @indirection.expire("/my/key") end it "should use a request to save the expired resource to the cache" do @cache.expects(:find).returns @cached @cached.expects(:expiration=).with { |t| t < Time.now } @cache.expects(:save).with { |r| r.is_a?(Puppet::Indirector::Request) and r.instance == @cached and r.method == :save }.returns(@cached) @indirection.expire("/my/key") end end end after :each do @indirection.delete end end describe "when managing indirection instances" do it "should allow an indirection to be retrieved by name" do @indirection = Puppet::Indirector::Indirection.new(mock('model'), :test) Puppet::Indirector::Indirection.instance(:test).should equal(@indirection) end it "should return nil when the named indirection has not been created" do Puppet::Indirector::Indirection.instance(:test).should be_nil end it "should allow an indirection's model to be retrieved by name" do mock_model = mock('model') @indirection = Puppet::Indirector::Indirection.new(mock_model, :test) Puppet::Indirector::Indirection.model(:test).should equal(mock_model) end it "should return nil when no model matches the requested name" do Puppet::Indirector::Indirection.model(:test).should be_nil end after do @indirection.delete if defined?(@indirection) end end describe "when routing to the correct the terminus class" do before do @indirection = Puppet::Indirector::Indirection.new(mock('model'), :test) @terminus = mock 'terminus' @terminus_class = stub 'terminus class', :new => @terminus Puppet::Indirector::Terminus.stubs(:terminus_class).with(:test, :default).returns(@terminus_class) end it "should fail if no terminus class can be picked" do proc { @indirection.terminus_class }.should raise_error(Puppet::DevError) end it "should choose the default terminus class if one is specified" do @indirection.terminus_class = :default @indirection.terminus_class.should equal(:default) end it "should use the provided Puppet setting if told to do so" do Puppet::Indirector::Terminus.stubs(:terminus_class).with(:test, :my_terminus).returns(mock("terminus_class2")) Puppet.settings.expects(:value).with(:my_setting).returns("my_terminus") @indirection.terminus_setting = :my_setting @indirection.terminus_class.should equal(:my_terminus) end it "should fail if the provided terminus class is not valid" do Puppet::Indirector::Terminus.stubs(:terminus_class).with(:test, :nosuchclass).returns(nil) proc { @indirection.terminus_class = :nosuchclass }.should raise_error(ArgumentError) end after do @indirection.delete if defined?(@indirection) end end describe "when specifying the terminus class to use" do before do @indirection = Puppet::Indirector::Indirection.new(mock('model'), :test) @terminus = mock 'terminus' @terminus_class = stub 'terminus class', :new => @terminus end it "should allow specification of a terminus type" do @indirection.should respond_to(:terminus_class=) end it "should fail to redirect if no terminus type has been specified" do proc { @indirection.find("blah") }.should raise_error(Puppet::DevError) end it "should fail when the terminus class name is an empty string" do proc { @indirection.terminus_class = "" }.should raise_error(ArgumentError) end it "should fail when the terminus class name is nil" do proc { @indirection.terminus_class = nil }.should raise_error(ArgumentError) end it "should fail when the specified terminus class cannot be found" do Puppet::Indirector::Terminus.expects(:terminus_class).with(:test, :foo).returns(nil) proc { @indirection.terminus_class = :foo }.should raise_error(ArgumentError) end it "should select the specified terminus class if a terminus class name is provided" do Puppet::Indirector::Terminus.expects(:terminus_class).with(:test, :foo).returns(@terminus_class) @indirection.terminus(:foo).should equal(@terminus) end it "should use the configured terminus class if no terminus name is specified" do Puppet::Indirector::Terminus.stubs(:terminus_class).with(:test, :foo).returns(@terminus_class) @indirection.terminus_class = :foo @indirection.terminus.should equal(@terminus) end after do @indirection.delete if defined?(@indirection) end end describe "when managing terminus instances" do before do @indirection = Puppet::Indirector::Indirection.new(mock('model'), :test) @terminus = mock 'terminus' @terminus_class = mock 'terminus class' Puppet::Indirector::Terminus.stubs(:terminus_class).with(:test, :foo).returns(@terminus_class) end it "should create an instance of the chosen terminus class" do @terminus_class.stubs(:new).returns(@terminus) @indirection.terminus(:foo).should equal(@terminus) end # Make sure it caches the terminus. it "should return the same terminus instance each time for a given name" do @terminus_class.stubs(:new).returns(@terminus) @indirection.terminus(:foo).should equal(@terminus) @indirection.terminus(:foo).should equal(@terminus) end it "should not create a terminus instance until one is actually needed" do Puppet::Indirector.expects(:terminus).never indirection = Puppet::Indirector::Indirection.new(mock('model'), :lazytest) end after do @indirection.delete end end describe "when deciding whether to cache" do before do @indirection = Puppet::Indirector::Indirection.new(mock('model'), :test) @terminus = mock 'terminus' @terminus_class = mock 'terminus class' @terminus_class.stubs(:new).returns(@terminus) Puppet::Indirector::Terminus.stubs(:terminus_class).with(:test, :foo).returns(@terminus_class) @indirection.terminus_class = :foo end it "should provide a method for setting the cache terminus class" do @indirection.should respond_to(:cache_class=) end it "should fail to cache if no cache type has been specified" do proc { @indirection.cache }.should raise_error(Puppet::DevError) end it "should fail to set the cache class when the cache class name is an empty string" do proc { @indirection.cache_class = "" }.should raise_error(ArgumentError) end it "should allow resetting the cache_class to nil" do @indirection.cache_class = nil @indirection.cache_class.should be_nil end it "should fail to set the cache class when the specified cache class cannot be found" do Puppet::Indirector::Terminus.expects(:terminus_class).with(:test, :foo).returns(nil) proc { @indirection.cache_class = :foo }.should raise_error(ArgumentError) end after do @indirection.delete end end describe "when using a cache" do before :each do Puppet.settings.stubs(:value).with("test_terminus").returns("test_terminus") @terminus_class = mock 'terminus_class' @terminus = mock 'terminus' @terminus_class.stubs(:new).returns(@terminus) @cache = mock 'cache' @cache_class = mock 'cache_class' Puppet::Indirector::Terminus.stubs(:terminus_class).with(:test, :cache_terminus).returns(@cache_class) Puppet::Indirector::Terminus.stubs(:terminus_class).with(:test, :test_terminus).returns(@terminus_class) @indirection = Puppet::Indirector::Indirection.new(mock('model'), :test) @indirection.terminus_class = :test_terminus end describe "and managing the cache terminus" do it "should not create a cache terminus at initialization" do # This is weird, because all of the code is in the setup. If we got # new called on the cache class, we'd get an exception here. end it "should reuse the cache terminus" do @cache_class.expects(:new).returns(@cache) Puppet.settings.stubs(:value).with("test_cache").returns("cache_terminus") @indirection.cache_class = :cache_terminus @indirection.cache.should equal(@cache) @indirection.cache.should equal(@cache) end end describe "and saving" do end describe "and finding" do end after :each do @indirection.delete end end end diff --git a/spec/unit/indirector/node/active_record_spec.rb b/spec/unit/indirector/node/active_record_spec.rb index 6fed940b9..d0e693927 100755 --- a/spec/unit/indirector/node/active_record_spec.rb +++ b/spec/unit/indirector/node/active_record_spec.rb @@ -1,37 +1,37 @@ #!/usr/bin/env rspec require 'spec_helper' require 'puppet/node' describe "Puppet::Node::ActiveRecord", :if => Puppet.features.rails? && Puppet.features.sqlite? do include PuppetSpec::Files before do require 'puppet/indirector/node/active_record' end it "should be a subclass of the ActiveRecord terminus class" do Puppet::Node::ActiveRecord.ancestors.should be_include(Puppet::Indirector::ActiveRecord) end it "should use Puppet::Rails::Host as its ActiveRecord model" do Puppet::Node::ActiveRecord.ar_model.should equal(Puppet::Rails::Host) end it "should call fact_merge when a node is found" do db_instance = stub 'db_instance' Puppet::Node::ActiveRecord.ar_model.expects(:find_by_name).returns db_instance node = Puppet::Node.new("foo") db_instance.expects(:to_puppet).returns node Puppet[:statedir] = tmpdir('active_record_tmp') Puppet[:railslog] = '$statedir/rails.log' ar = Puppet::Node::ActiveRecord.new node.expects(:fact_merge) - request = Puppet::Indirector::Request.new(:node, :find, "what.ever") + request = Puppet::Indirector::Request.new(:node, :find, "what.ever", nil) ar.find(request) end end diff --git a/spec/unit/indirector/node/exec_spec.rb b/spec/unit/indirector/node/exec_spec.rb index ccd58bee1..876987af3 100755 --- a/spec/unit/indirector/node/exec_spec.rb +++ b/spec/unit/indirector/node/exec_spec.rb @@ -1,76 +1,76 @@ #!/usr/bin/env rspec require 'spec_helper' require 'puppet/indirector/node/exec' require 'puppet/indirector/request' describe Puppet::Node::Exec do before do @indirection = mock 'indirection' Puppet.settings[:external_nodes] = File.expand_path("/echo") @searcher = Puppet::Node::Exec.new end describe "when constructing the command to run" do it "should use the external_node script as the command" do Puppet[:external_nodes] = "/bin/echo" @searcher.command.should == %w{/bin/echo} end it "should throw an exception if no external node command is set" do Puppet[:external_nodes] = "none" proc { @searcher.find(stub('request', :key => "foo")) }.should raise_error(ArgumentError) end end describe "when handling the results of the command" do before do @name = "yay" @node = Puppet::Node.new(@name) @node.stubs(:fact_merge) Puppet::Node.expects(:new).with(@name).returns(@node) @result = {} # Use a local variable so the reference is usable in the execute definition. result = @result @searcher.meta_def(:execute) do |command, arguments| return YAML.dump(result) end - @request = Puppet::Indirector::Request.new(:node, :find, @name) + @request = Puppet::Indirector::Request.new(:node, :find, @name, nil) end it "should translate the YAML into a Node instance" do # Use an empty hash @searcher.find(@request).should equal(@node) end it "should set the resulting parameters as the node parameters" do @result[:parameters] = {"a" => "b", "c" => "d"} @searcher.find(@request) @node.parameters.should == {"a" => "b", "c" => "d"} end it "should set the resulting classes as the node classes" do @result[:classes] = %w{one two} @searcher.find(@request) @node.classes.should == [ 'one', 'two' ] end it "should merge the node's facts with its parameters" do @node.expects(:fact_merge) @searcher.find(@request) end it "should set the node's environment if one is provided" do @result[:environment] = "yay" @searcher.find(@request) @node.environment.to_s.should == 'yay' end it "should set the node's environment based on the request if not otherwise provided" do @request.environment = "boo" @searcher.find(@request) @node.environment.to_s.should == 'boo' end end end diff --git a/spec/unit/indirector/report/processor_spec.rb b/spec/unit/indirector/report/processor_spec.rb index 6184cb9b5..ebe5e1336 100755 --- a/spec/unit/indirector/report/processor_spec.rb +++ b/spec/unit/indirector/report/processor_spec.rb @@ -1,100 +1,100 @@ #!/usr/bin/env rspec require 'spec_helper' require 'puppet/indirector/report/processor' describe Puppet::Transaction::Report::Processor do before do Puppet.settings.stubs(:use).returns(true) end it "should provide a method for saving reports" do Puppet::Transaction::Report::Processor.new.should respond_to(:save) end it "should provide a method for cleaning reports" do Puppet::Transaction::Report::Processor.new.should respond_to(:destroy) end end describe Puppet::Transaction::Report::Processor, " when processing a report" do before do Puppet.settings.stubs(:use) @reporter = Puppet::Transaction::Report::Processor.new @request = stub 'request', :instance => stub("report", :host => 'hostname'), :key => 'node' end it "should not save the report if reports are set to 'none'" do Puppet::Reports.expects(:report).never Puppet[:reports] = 'none' - request = Puppet::Indirector::Request.new(:indirection_name, :head, "key") + request = Puppet::Indirector::Request.new(:indirection_name, :head, "key", nil) report = Puppet::Transaction::Report.new('apply') request.instance = report @reporter.save(request) end it "should save the report with each configured report type" do Puppet.settings.stubs(:value).with(:reports).returns("one,two") @reporter.send(:reports).should == %w{one two} Puppet::Reports.expects(:report).with('one') Puppet::Reports.expects(:report).with('two') @reporter.save(@request) end it "should destroy reports for each processor that responds to destroy" do Puppet.settings.stubs(:value).with(:reports).returns("http,store") http_report = mock() store_report = mock() store_report.expects(:destroy).with(@request.key) Puppet::Reports.expects(:report).with('http').returns(http_report) Puppet::Reports.expects(:report).with('store').returns(store_report) @reporter.destroy(@request) end end describe Puppet::Transaction::Report::Processor, " when processing a report" do before do Puppet[:reports] = "one" Puppet.settings.stubs(:use) @reporter = Puppet::Transaction::Report::Processor.new @report_type = mock 'one' @dup_report = mock 'dupe report' @dup_report.stubs(:process) @report = Puppet::Transaction::Report.new('apply') @report.expects(:dup).returns(@dup_report) @request = stub 'request', :instance => @report Puppet::Reports.expects(:report).with("one").returns(@report_type) @dup_report.expects(:extend).with(@report_type) end # LAK:NOTE This is stupid, because the code is so short it doesn't # make sense to split it out, which means I just do the same test # three times so the spec looks right. it "should process a duplicate of the report, not the original" do @reporter.save(@request) end it "should extend the report with the report type's module" do @reporter.save(@request) end it "should call the report type's :process method" do @dup_report.expects(:process) @reporter.save(@request) end it "should not raise exceptions" do Puppet[:trace] = false @dup_report.expects(:process).raises(ArgumentError) proc { @reporter.save(@request) }.should_not raise_error end end diff --git a/spec/unit/indirector/request_spec.rb b/spec/unit/indirector/request_spec.rb index bc6b59952..a326e367c 100755 --- a/spec/unit/indirector/request_spec.rb +++ b/spec/unit/indirector/request_spec.rb @@ -1,517 +1,513 @@ #!/usr/bin/env rspec require 'spec_helper' require 'matchers/json' require 'puppet/indirector/request' require 'puppet/util/pson' describe Puppet::Indirector::Request do describe "when registering the document type" do it "should register its document type with JSON" do PSON.registered_document_types["IndirectorRequest"].should equal(Puppet::Indirector::Request) end end describe "when initializing" do - it "should require an indirection name, a key, and a method" do - lambda { Puppet::Indirector::Request.new }.should raise_error(ArgumentError) - end - it "should always convert the indirection name to a symbol" do - Puppet::Indirector::Request.new("ind", :method, "mykey").indirection_name.should == :ind + Puppet::Indirector::Request.new("ind", :method, "mykey", nil).indirection_name.should == :ind end it "should use provided value as the key if it is a string" do - Puppet::Indirector::Request.new(:ind, :method, "mykey").key.should == "mykey" + Puppet::Indirector::Request.new(:ind, :method, "mykey", nil).key.should == "mykey" end it "should use provided value as the key if it is a symbol" do - Puppet::Indirector::Request.new(:ind, :method, :mykey).key.should == :mykey + Puppet::Indirector::Request.new(:ind, :method, :mykey, nil).key.should == :mykey end it "should use the name of the provided instance as its key if an instance is provided as the key instead of a string" do instance = mock 'instance', :name => "mykey" - request = Puppet::Indirector::Request.new(:ind, :method, instance) + request = Puppet::Indirector::Request.new(:ind, :method, nil, instance) request.key.should == "mykey" request.instance.should equal(instance) end it "should support options specified as a hash" do - lambda { Puppet::Indirector::Request.new(:ind, :method, :key, :one => :two) }.should_not raise_error(ArgumentError) + lambda { Puppet::Indirector::Request.new(:ind, :method, :key, nil, :one => :two) }.should_not raise_error(ArgumentError) end it "should support nil options" do - lambda { Puppet::Indirector::Request.new(:ind, :method, :key, nil) }.should_not raise_error(ArgumentError) + lambda { Puppet::Indirector::Request.new(:ind, :method, :key, nil, nil) }.should_not raise_error(ArgumentError) end it "should support unspecified options" do - lambda { Puppet::Indirector::Request.new(:ind, :method, :key) }.should_not raise_error(ArgumentError) + lambda { Puppet::Indirector::Request.new(:ind, :method, :key, nil) }.should_not raise_error(ArgumentError) end it "should use an empty options hash if nil was provided" do - Puppet::Indirector::Request.new(:ind, :method, :key, nil).options.should == {} + Puppet::Indirector::Request.new(:ind, :method, :key, nil, nil).options.should == {} end it "should default to a nil node" do Puppet::Indirector::Request.new(:ind, :method, :key, nil).node.should be_nil end it "should set its node attribute if provided in the options" do - Puppet::Indirector::Request.new(:ind, :method, :key, :node => "foo.com").node.should == "foo.com" + Puppet::Indirector::Request.new(:ind, :method, :key, nil, :node => "foo.com").node.should == "foo.com" end it "should default to a nil ip" do Puppet::Indirector::Request.new(:ind, :method, :key, nil).ip.should be_nil end it "should set its ip attribute if provided in the options" do - Puppet::Indirector::Request.new(:ind, :method, :key, :ip => "192.168.0.1").ip.should == "192.168.0.1" + Puppet::Indirector::Request.new(:ind, :method, :key, nil, :ip => "192.168.0.1").ip.should == "192.168.0.1" end it "should default to being unauthenticated" do Puppet::Indirector::Request.new(:ind, :method, :key, nil).should_not be_authenticated end it "should set be marked authenticated if configured in the options" do - Puppet::Indirector::Request.new(:ind, :method, :key, :authenticated => "eh").should be_authenticated + Puppet::Indirector::Request.new(:ind, :method, :key, nil, :authenticated => "eh").should be_authenticated end it "should keep its options as a hash even if a node is specified" do - Puppet::Indirector::Request.new(:ind, :method, :key, :node => "eh").options.should be_instance_of(Hash) + Puppet::Indirector::Request.new(:ind, :method, :key, nil, :node => "eh").options.should be_instance_of(Hash) end it "should keep its options as a hash even if another option is specified" do - Puppet::Indirector::Request.new(:ind, :method, :key, :foo => "bar").options.should be_instance_of(Hash) + Puppet::Indirector::Request.new(:ind, :method, :key, nil, :foo => "bar").options.should be_instance_of(Hash) end it "should treat options other than :ip, :node, and :authenticated as options rather than attributes" do - Puppet::Indirector::Request.new(:ind, :method, :key, :server => "bar").options[:server].should == "bar" + Puppet::Indirector::Request.new(:ind, :method, :key, nil, :server => "bar").options[:server].should == "bar" end it "should normalize options to use symbols as keys" do - Puppet::Indirector::Request.new(:ind, :method, :key, "foo" => "bar").options[:foo].should == "bar" + Puppet::Indirector::Request.new(:ind, :method, :key, nil, "foo" => "bar").options[:foo].should == "bar" end describe "and the request key is a URI" do let(:file) { File.expand_path("/my/file with spaces") } describe "and the URI is a 'file' URI" do before do - @request = Puppet::Indirector::Request.new(:ind, :method, "#{URI.unescape(Puppet::Util.path_to_uri(file).to_s)}") + @request = Puppet::Indirector::Request.new(:ind, :method, "#{URI.unescape(Puppet::Util.path_to_uri(file).to_s)}", nil) end it "should set the request key to the unescaped full file path" do @request.key.should == file end it "should not set the protocol" do @request.protocol.should be_nil end it "should not set the port" do @request.port.should be_nil end it "should not set the server" do @request.server.should be_nil end end it "should set the protocol to the URI scheme" do - Puppet::Indirector::Request.new(:ind, :method, "http://host/stuff").protocol.should == "http" + Puppet::Indirector::Request.new(:ind, :method, "http://host/stuff", nil).protocol.should == "http" end it "should set the server if a server is provided" do - Puppet::Indirector::Request.new(:ind, :method, "http://host/stuff").server.should == "host" + Puppet::Indirector::Request.new(:ind, :method, "http://host/stuff", nil).server.should == "host" end it "should set the server and port if both are provided" do - Puppet::Indirector::Request.new(:ind, :method, "http://host:543/stuff").port.should == 543 + Puppet::Indirector::Request.new(:ind, :method, "http://host:543/stuff", nil).port.should == 543 end it "should default to the masterport if the URI scheme is 'puppet'" do Puppet.settings.expects(:value).with(:masterport).returns "321" - Puppet::Indirector::Request.new(:ind, :method, "puppet://host/stuff").port.should == 321 + Puppet::Indirector::Request.new(:ind, :method, "puppet://host/stuff", nil).port.should == 321 end it "should use the provided port if the URI scheme is not 'puppet'" do - Puppet::Indirector::Request.new(:ind, :method, "http://host/stuff").port.should == 80 + Puppet::Indirector::Request.new(:ind, :method, "http://host/stuff", nil).port.should == 80 end it "should set the request key to the unescaped key part path from the URI" do - Puppet::Indirector::Request.new(:ind, :method, "http://host/environment/terminus/stuff with spaces").key.should == "stuff with spaces" + Puppet::Indirector::Request.new(:ind, :method, "http://host/environment/terminus/stuff with spaces", nil).key.should == "stuff with spaces" end it "should set the :uri attribute to the full URI" do - Puppet::Indirector::Request.new(:ind, :method, "http:///stu ff").uri.should == 'http:///stu ff' + Puppet::Indirector::Request.new(:ind, :method, "http:///stu ff", nil).uri.should == 'http:///stu ff' end it "should not parse relative URI" do - Puppet::Indirector::Request.new(:ind, :method, "foo/bar").uri.should be_nil + Puppet::Indirector::Request.new(:ind, :method, "foo/bar", nil).uri.should be_nil end it "should not parse opaque URI" do - Puppet::Indirector::Request.new(:ind, :method, "mailto:joe").uri.should be_nil + Puppet::Indirector::Request.new(:ind, :method, "mailto:joe", nil).uri.should be_nil end end it "should allow indication that it should not read a cached instance" do - Puppet::Indirector::Request.new(:ind, :method, :key, :ignore_cache => true).should be_ignore_cache + Puppet::Indirector::Request.new(:ind, :method, :key, nil, :ignore_cache => true).should be_ignore_cache end it "should default to not ignoring the cache" do - Puppet::Indirector::Request.new(:ind, :method, :key).should_not be_ignore_cache + Puppet::Indirector::Request.new(:ind, :method, :key, nil).should_not be_ignore_cache end it "should allow indication that it should not not read an instance from the terminus" do - Puppet::Indirector::Request.new(:ind, :method, :key, :ignore_terminus => true).should be_ignore_terminus + Puppet::Indirector::Request.new(:ind, :method, :key, nil, :ignore_terminus => true).should be_ignore_terminus end it "should default to not ignoring the terminus" do - Puppet::Indirector::Request.new(:ind, :method, :key).should_not be_ignore_terminus + Puppet::Indirector::Request.new(:ind, :method, :key, nil).should_not be_ignore_terminus end end it "should look use the Indirection class to return the appropriate indirection" do ind = mock 'indirection' Puppet::Indirector::Indirection.expects(:instance).with(:myind).returns ind - request = Puppet::Indirector::Request.new(:myind, :method, :key) + request = Puppet::Indirector::Request.new(:myind, :method, :key, nil) request.indirection.should equal(ind) end it "should use its indirection to look up the appropriate model" do ind = mock 'indirection' Puppet::Indirector::Indirection.expects(:instance).with(:myind).returns ind - request = Puppet::Indirector::Request.new(:myind, :method, :key) + request = Puppet::Indirector::Request.new(:myind, :method, :key, nil) ind.expects(:model).returns "mymodel" request.model.should == "mymodel" end it "should fail intelligently when asked to find a model but the indirection cannot be found" do Puppet::Indirector::Indirection.expects(:instance).with(:myind).returns nil - request = Puppet::Indirector::Request.new(:myind, :method, :key) + request = Puppet::Indirector::Request.new(:myind, :method, :key, nil) lambda { request.model }.should raise_error(ArgumentError) end it "should have a method for determining if the request is plural or singular" do - Puppet::Indirector::Request.new(:myind, :method, :key).should respond_to(:plural?) + Puppet::Indirector::Request.new(:myind, :method, :key, nil).should respond_to(:plural?) end it "should be considered plural if the method is 'search'" do - Puppet::Indirector::Request.new(:myind, :search, :key).should be_plural + Puppet::Indirector::Request.new(:myind, :search, :key, nil).should be_plural end it "should not be considered plural if the method is not 'search'" do - Puppet::Indirector::Request.new(:myind, :find, :key).should_not be_plural + Puppet::Indirector::Request.new(:myind, :find, :key, nil).should_not be_plural end it "should use its uri, if it has one, as its string representation" do - Puppet::Indirector::Request.new(:myind, :find, "foo://bar/baz").to_s.should == "foo://bar/baz" + Puppet::Indirector::Request.new(:myind, :find, "foo://bar/baz", nil).to_s.should == "foo://bar/baz" end it "should use its indirection name and key, if it has no uri, as its string representation" do - Puppet::Indirector::Request.new(:myind, :find, "key") == "/myind/key" + Puppet::Indirector::Request.new(:myind, :find, "key", nil) == "/myind/key" end it "should be able to return the URI-escaped key" do - Puppet::Indirector::Request.new(:myind, :find, "my key").escaped_key.should == URI.escape("my key") + Puppet::Indirector::Request.new(:myind, :find, "my key", nil).escaped_key.should == URI.escape("my key") end it "should have an environment accessor" do - Puppet::Indirector::Request.new(:myind, :find, "my key", :environment => "foo").should respond_to(:environment) + Puppet::Indirector::Request.new(:myind, :find, "my key", nil, :environment => "foo").should respond_to(:environment) end it "should set its environment to an environment instance when a string is specified as its environment" do - Puppet::Indirector::Request.new(:myind, :find, "my key", :environment => "foo").environment.should == Puppet::Node::Environment.new("foo") + Puppet::Indirector::Request.new(:myind, :find, "my key", nil, :environment => "foo").environment.should == Puppet::Node::Environment.new("foo") end it "should use any passed in environment instances as its environment" do env = Puppet::Node::Environment.new("foo") - Puppet::Indirector::Request.new(:myind, :find, "my key", :environment => env).environment.should equal(env) + Puppet::Indirector::Request.new(:myind, :find, "my key", nil, :environment => env).environment.should equal(env) end it "should use the default environment when none is provided" do - Puppet::Indirector::Request.new(:myind, :find, "my key" ).environment.should equal(Puppet::Node::Environment.new) + Puppet::Indirector::Request.new(:myind, :find, "my key", nil ).environment.should equal(Puppet::Node::Environment.new) end it "should support converting its options to a hash" do - Puppet::Indirector::Request.new(:myind, :find, "my key" ).should respond_to(:to_hash) + Puppet::Indirector::Request.new(:myind, :find, "my key", nil ).should respond_to(:to_hash) end it "should include all of its attributes when its options are converted to a hash" do - Puppet::Indirector::Request.new(:myind, :find, "my key", :node => 'foo').to_hash[:node].should == 'foo' + Puppet::Indirector::Request.new(:myind, :find, "my key", nil, :node => 'foo').to_hash[:node].should == 'foo' end describe "when building a query string from its options" do before do - @request = Puppet::Indirector::Request.new(:myind, :find, "my key") + @request = Puppet::Indirector::Request.new(:myind, :find, "my key", nil) end it "should return an empty query string if there are no options" do @request.stubs(:options).returns nil @request.query_string.should == "" end it "should return an empty query string if the options are empty" do @request.stubs(:options).returns({}) @request.query_string.should == "" end it "should prefix the query string with '?'" do @request.stubs(:options).returns(:one => "two") @request.query_string.should =~ /^\?/ end it "should include all options in the query string, separated by '&'" do @request.stubs(:options).returns(:one => "two", :three => "four") @request.query_string.sub(/^\?/, '').split("&").sort.should == %w{one=two three=four}.sort end it "should ignore nil options" do @request.stubs(:options).returns(:one => "two", :three => nil) @request.query_string.should_not be_include("three") end it "should convert 'true' option values into strings" do @request.stubs(:options).returns(:one => true) @request.query_string.should == "?one=true" end it "should convert 'false' option values into strings" do @request.stubs(:options).returns(:one => false) @request.query_string.should == "?one=false" end it "should convert to a string all option values that are integers" do @request.stubs(:options).returns(:one => 50) @request.query_string.should == "?one=50" end it "should convert to a string all option values that are floating point numbers" do @request.stubs(:options).returns(:one => 1.2) @request.query_string.should == "?one=1.2" end it "should CGI-escape all option values that are strings" do escaping = CGI.escape("one two") @request.stubs(:options).returns(:one => "one two") @request.query_string.should == "?one=#{escaping}" end it "should YAML-dump and CGI-escape arrays" do escaping = CGI.escape(YAML.dump(%w{one two})) @request.stubs(:options).returns(:one => %w{one two}) @request.query_string.should == "?one=#{escaping}" end it "should convert to a string and CGI-escape all option values that are symbols" do escaping = CGI.escape("sym bol") @request.stubs(:options).returns(:one => :"sym bol") @request.query_string.should == "?one=#{escaping}" end it "should fail if options other than booleans or strings are provided" do @request.stubs(:options).returns(:one => {:one => :two}) lambda { @request.query_string }.should raise_error(ArgumentError) end end describe "when converting to json" do before do - @request = Puppet::Indirector::Request.new(:facts, :find, "foo") + @request = Puppet::Indirector::Request.new(:facts, :find, "foo", nil) end it "should produce a hash with the document_type set to 'request'" do @request.should set_json_document_type_to("IndirectorRequest") end it "should set the 'key'" do @request.should set_json_attribute("key").to("foo") end it "should include an attribute for its indirection name" do @request.should set_json_attribute("type").to("facts") end it "should include a 'method' attribute set to its method" do @request.should set_json_attribute("method").to("find") end it "should add all attributes under the 'attributes' attribute" do @request.ip = "127.0.0.1" @request.should set_json_attribute("attributes", "ip").to("127.0.0.1") end it "should add all options under the 'attributes' attribute" do @request.options["opt"] = "value" PSON.parse(@request.to_pson)["data"]['attributes']['opt'].should == "value" end it "should include the instance if provided" do facts = Puppet::Node::Facts.new("foo") @request.instance = facts PSON.parse(@request.to_pson)["data"]['instance'].should be_instance_of(Hash) end end describe "when converting from json" do before do - @request = Puppet::Indirector::Request.new(:facts, :find, "foo") + @request = Puppet::Indirector::Request.new(:facts, :find, "foo", nil) @klass = Puppet::Indirector::Request @format = Puppet::Network::FormatHandler.format('pson') end def from_json(json) @format.intern(Puppet::Indirector::Request, json) end it "should set the 'key'" do from_json(@request.to_pson).key.should == "foo" end it "should fail if no key is provided" do json = PSON.parse(@request.to_pson) json['data'].delete("key") lambda { from_json(json.to_pson) }.should raise_error(ArgumentError) end it "should set its indirector name" do from_json(@request.to_pson).indirection_name.should == :facts end it "should fail if no type is provided" do json = PSON.parse(@request.to_pson) json['data'].delete("type") lambda { from_json(json.to_pson) }.should raise_error(ArgumentError) end it "should set its method" do from_json(@request.to_pson).method.should == "find" end it "should fail if no method is provided" do json = PSON.parse(@request.to_pson) json['data'].delete("method") lambda { from_json(json.to_pson) }.should raise_error(ArgumentError) end it "should initialize with all attributes and options" do @request.ip = "127.0.0.1" @request.options["opt"] = "value" result = from_json(@request.to_pson) result.options[:opt].should == "value" result.ip.should == "127.0.0.1" end it "should set its instance as an instance if one is provided" do facts = Puppet::Node::Facts.new("foo") @request.instance = facts result = from_json(@request.to_pson) result.instance.should be_instance_of(Puppet::Node::Facts) end end context '#do_request' do before :each do - @request = Puppet::Indirector::Request.new(:myind, :find, "my key") + @request = Puppet::Indirector::Request.new(:myind, :find, "my key", nil) end context 'when not using SRV records' do before :each do Puppet.settings[:use_srv_records] = false end it "yields the request with the default server and port when no server or port were specified on the original request" do count = 0 rval = @request.do_request(:puppet, 'puppet.example.com', '90210') do |got| count += 1 got.server.should == 'puppet.example.com' got.port.should == '90210' 'Block return value' end count.should == 1 rval.should == 'Block return value' end end context 'when using SRV records' do before :each do Puppet.settings[:use_srv_records] = true Puppet.settings[:srv_domain] = 'example.com' end it "yields the request with the original server and port unmodified" do @request.server = 'puppet.example.com' @request.port = '90210' count = 0 rval = @request.do_request do |got| count += 1 got.server.should == 'puppet.example.com' got.port.should == '90210' 'Block return value' end count.should == 1 rval.should == 'Block return value' end context "when SRV returns servers" do before :each do @dns_mock = mock('dns') Resolv::DNS.expects(:new).returns(@dns_mock) @port = 7205 @host = '_x-puppet._tcp.example.com' @srv_records = [Resolv::DNS::Resource::IN::SRV.new(0, 0, @port, @host)] @dns_mock.expects(:getresources). with("_x-puppet._tcp.#{Puppet.settings[:srv_domain]}", Resolv::DNS::Resource::IN::SRV). returns(@srv_records) end it "yields a request using the server and port from the SRV record" do count = 0 rval = @request.do_request do |got| count += 1 got.server.should == '_x-puppet._tcp.example.com' got.port.should == 7205 @block_return end count.should == 1 rval.should == @block_return end it "should fall back to the default server when the block raises a SystemCallError" do count = 0 second_pass = nil rval = @request.do_request(:puppet, 'puppet', 8140) do |got| count += 1 if got.server == '_x-puppet._tcp.example.com' then raise SystemCallError, "example failure" else second_pass = got end @block_return end second_pass.server.should == 'puppet' second_pass.port.should == 8140 count.should == 2 rval.should == @block_return end end end end end diff --git a/spec/unit/indirector/resource/active_record_spec.rb b/spec/unit/indirector/resource/active_record_spec.rb index e9ccea30f..4aca0a443 100755 --- a/spec/unit/indirector/resource/active_record_spec.rb +++ b/spec/unit/indirector/resource/active_record_spec.rb @@ -1,178 +1,178 @@ #!/usr/bin/env rspec require 'spec_helper' require 'puppet/rails' require 'puppet/node/facts' describe "Puppet::Resource::ActiveRecord", :if => can_use_scratch_database? do include PuppetSpec::Files before :each do setup_scratch_database Puppet[:storeconfigs] = true end subject { require 'puppet/indirector/resource/active_record' Puppet::Resource.indirection.terminus(:active_record) } it "should automatically initialize Rails" do # Other tests in the suite may have established the connection, which will # linger; the assertion is just to enforce our assumption about the call, # not because I *really* want to test ActiveRecord works. Better to have # an early failure than wonder why the test overall doesn't DTRT. ActiveRecord::Base.remove_connection ActiveRecord::Base.should_not be_connected subject.should be ActiveRecord::Base.should be_connected end describe "#search" do before :each do Puppet::Rails.init end def search(type, host = 'default.local', filter = nil) args = { :host => host, :filter => filter } - subject.search(Puppet::Resource.indirection.request(:search, type, args)) + subject.search(Puppet::Resource.indirection.request(:search, type, nil, args)) end it "should return an empty array if no resources match" do search("Exec").should == [] end # Assert that this is a case-insensitive rule, too. %w{and or AND OR And Or anD oR}.each do |op| it "should fail if asked to search with #{op.inspect}" do filter = [%w{tag == foo}, op, %w{title == bar}] expect { search("Notify", 'localhost', filter) }. to raise_error Puppet::Error, /not supported/ end end context "with a matching resource" do before :each do host = Puppet::Rails::Host.create!(:name => 'one.local') Puppet::Rails::Resource. create!(:host => host, :restype => 'Exec', :title => 'whammo', :exported => true) end it "should return something responding to `to_resource` if a resource matches" do found = search("Exec") found.length.should == 1 found.map do |item| item.should respond_to :to_resource item.restype.should == "Exec" end end it "should not filter resources that have been found before" do search("Exec").should == search("Exec") end end end describe "#build_active_record_query" do before :each do Puppet::Rails.init end let :type do 'Notify' end def query(type, host, filter = nil) subject.send :build_active_record_query, type, host, filter end it "should exclude all database resources from the host" do host = Puppet::Rails::Host.create! :name => 'one.local' got = query(type, host.name) got.keys.should =~ [:conditions] got[:conditions][0] =~ /\(host_id != \?\)/ got[:conditions].last.should == host.id end it "should join appropriately when filtering on parameters" do filter = %w{propname == propval} got = query(type, 'whatever', filter) got.keys.should =~ [:conditions, :joins] got[:joins].should == { :param_values => :param_name } got[:conditions][0].should =~ /param_names\.name = \?/ got[:conditions][0].should =~ /param_values\.value = \?/ got[:conditions].should be_include filter.first got[:conditions].should be_include filter.last end it "should join appropriately when filtering on tags" do filter = %w{tag == test} got = query(type, 'whatever', filter) got.keys.should =~ [:conditions, :joins] got[:joins].should == {:resource_tags => :puppet_tag} got[:conditions].first.should =~ /puppet_tags/ got[:conditions].should_not be_include filter.first got[:conditions].should be_include filter.last end it "should only search for exported resources with the matching type" do got = query(type, 'whatever') got.keys.should =~ [:conditions] got[:conditions][0].should be_include "(exported=? AND restype=?)" got[:conditions][1].should == true got[:conditions][2].should == type.to_s.capitalize end it "should capitalize the type, since PGSQL is case sensitive" do got = query(type, 'whatever') got[:conditions][2].should == 'Notify' end end describe "#filter_to_active_record" do def filter_to_active_record(input) subject.send :filter_to_active_record, input end [nil, '', 'whatever', 12].each do |input| it "should fail if filter is not an array (with #{input.inspect})" do expect { filter_to_active_record(input) }. to raise_error ArgumentError, /must be arrays/ end end # Not exhaustive, just indicative. ['=', '<>', '=~', '+', '-', '!'].each do |input| it "should fail with unexpected comparison operators (with #{input.inspect})" do expect { filter_to_active_record(["one", input, "two"]) }. to raise_error ArgumentError, /unknown operator/ end end { ["title", "==", "whatever"] => ["title = ?", ["whatever"]], ["title", "!=", "whatever"] => ["title != ?", ["whatever"]], # Technically, these are not supported by Puppet yet, but as we pay # approximately zero cost other than a few minutes writing the tests, # and it would be *harder* to fail on them, nested queries. [["title", "==", "foo"], "or", ["title", "==", "bar"]] => ["(title = ?) OR (title = ?)", ["foo", "bar"]], [["title", "==", "foo"], "or", ["tag", "==", "bar"]] => ["(title = ?) OR (puppet_tags.name = ?)", ["foo", "bar"]], [["title", "==", "foo"], "or", ["param", "==", "bar"]] => ["(title = ?) OR (param_names.name = ? AND param_values.value = ?)", ["foo", "param", "bar"]], [[["title","==","foo"],"or",["tag", "==", "bar"]],"and",["param","!=","baz"]] => ["((title = ?) OR (puppet_tags.name = ?)) AND "+ "(param_names.name = ? AND param_values.value != ?)", ["foo", "bar", "param", "baz"]] }.each do |input, expect| it "should map #{input.inspect} to #{expect.inspect}" do filter_to_active_record(input).should == expect end end end end diff --git a/spec/unit/indirector/resource_type/parser_spec.rb b/spec/unit/indirector/resource_type/parser_spec.rb index fa2aa10b8..a0362496e 100755 --- a/spec/unit/indirector/resource_type/parser_spec.rb +++ b/spec/unit/indirector/resource_type/parser_spec.rb @@ -1,149 +1,149 @@ #!/usr/bin/env rspec require 'spec_helper' require 'puppet/indirector/resource_type/parser' require 'puppet_spec/files' describe Puppet::Indirector::ResourceType::Parser do include PuppetSpec::Files before do @terminus = Puppet::Indirector::ResourceType::Parser.new - @request = Puppet::Indirector::Request.new(:resource_type, :find, "foo") + @request = Puppet::Indirector::Request.new(:resource_type, :find, "foo", nil) @krt = @request.environment.known_resource_types end it "should be registered with the resource_type indirection" do Puppet::Indirector::Terminus.terminus_class(:resource_type, :parser).should equal(Puppet::Indirector::ResourceType::Parser) end describe "when finding" do it "should return any found type from the request's environment" do type = Puppet::Resource::Type.new(:hostclass, "foo") @request.environment.known_resource_types.add(type) @terminus.find(@request).should == type end it "should attempt to load the type if none is found in memory" do dir = tmpdir("find_a_type") FileUtils.mkdir_p(dir) Puppet[:modulepath] = dir # Make a new request, since we've reset the env - @request = Puppet::Indirector::Request.new(:resource_type, :find, "foo::bar") + @request = Puppet::Indirector::Request.new(:resource_type, :find, "foo::bar", nil) manifest_path = File.join(dir, "foo", "manifests") FileUtils.mkdir_p(manifest_path) File.open(File.join(manifest_path, "bar.pp"), "w") { |f| f.puts "class foo::bar {}" } result = @terminus.find(@request) result.should be_instance_of(Puppet::Resource::Type) result.name.should == "foo::bar" end it "should return nil if no type can be found" do @terminus.find(@request).should be_nil end it "should prefer definitions to nodes" do type = @krt.add(Puppet::Resource::Type.new(:hostclass, "foo")) node = @krt.add(Puppet::Resource::Type.new(:node, "foo")) @terminus.find(@request).should == type end end describe "when searching" do before do @request.key = "*" end it "should use the request's environment's list of known resource types" do @request.environment.known_resource_types.expects(:hostclasses).returns({}) @terminus.search(@request) end it "should return all results if '*' is provided as the search string" do @request.key = "*" type = @krt.add(Puppet::Resource::Type.new(:hostclass, "foo")) node = @krt.add(Puppet::Resource::Type.new(:node, "bar")) define = @krt.add(Puppet::Resource::Type.new(:definition, "baz")) result = @terminus.search(@request) result.should be_include(type) result.should be_include(node) result.should be_include(define) end it "should treat any search string not '*' as a regex" do @request.key = "a" foo = @krt.add(Puppet::Resource::Type.new(:hostclass, "foo")) bar = @krt.add(Puppet::Resource::Type.new(:hostclass, "bar")) baz = @krt.add(Puppet::Resource::Type.new(:hostclass, "baz")) result = @terminus.search(@request) result.should be_include(bar) result.should be_include(baz) result.should_not be_include(foo) end it "should fail if a provided search string is not '*' and is not a valid regex" do @request.key = "*foo*" # Add one instance so we don't just get an empty array" @krt.add(Puppet::Resource::Type.new(:hostclass, "foo")) lambda { @terminus.search(@request) }.should raise_error(ArgumentError) end it "should return all known types" do type = @krt.add(Puppet::Resource::Type.new(:hostclass, "foo")) node = @krt.add(Puppet::Resource::Type.new(:node, "bar")) define = @krt.add(Puppet::Resource::Type.new(:definition, "baz")) result = @terminus.search(@request) result.should be_include(type) result.should be_include(node) result.should be_include(define) end it "should not return the 'main' class" do main = @krt.add(Puppet::Resource::Type.new(:hostclass, "")) # So there is a return value foo = @krt.add(Puppet::Resource::Type.new(:hostclass, "foo")) @terminus.search(@request).should_not be_include(main) end it "should return nil if no types can be found" do @terminus.search(@request).should be_nil end it "should load all resource types from all search paths" do dir = tmpdir("searching_in_all") first = File.join(dir, "first") second = File.join(dir, "second") FileUtils.mkdir_p(first) FileUtils.mkdir_p(second) Puppet[:modulepath] = "#{first}#{File::PATH_SEPARATOR}#{second}" # Make a new request, since we've reset the env - @request = Puppet::Indirector::Request.new(:resource_type, :search, "*") + @request = Puppet::Indirector::Request.new(:resource_type, :search, "*", nil) onepath = File.join(first, "one", "manifests") FileUtils.mkdir_p(onepath) twopath = File.join(first, "two", "manifests") FileUtils.mkdir_p(twopath) File.open(File.join(onepath, "oneklass.pp"), "w") { |f| f.puts "class one::oneklass {}" } File.open(File.join(twopath, "twoklass.pp"), "w") { |f| f.puts "class two::twoklass {}" } result = @terminus.search(@request) result.find { |t| t.name == "one::oneklass" }.should be_instance_of(Puppet::Resource::Type) result.find { |t| t.name == "two::twoklass" }.should be_instance_of(Puppet::Resource::Type) end end end diff --git a/spec/unit/indirector/rest_spec.rb b/spec/unit/indirector/rest_spec.rb index e664ef20a..f1f30fa2d 100755 --- a/spec/unit/indirector/rest_spec.rb +++ b/spec/unit/indirector/rest_spec.rb @@ -1,592 +1,592 @@ #!/usr/bin/env rspec require 'spec_helper' require 'puppet/indirector/rest' shared_examples_for "a REST http call" do it "should accept a path" do lambda { @search.send(@method, *@arguments) }.should_not raise_error(ArgumentError) end it "should require a path" do lambda { @searcher.send(@method) }.should raise_error(ArgumentError) end it "should return the results of deserializing the response to the request" do conn = mock 'connection' conn.stubs(:put).returns @response conn.stubs(:delete).returns @response conn.stubs(:get).returns @response Puppet::Network::HttpPool.stubs(:http_instance).returns conn @searcher.expects(:deserialize).with(@response).returns "myobject" @searcher.send(@method, *@arguments).should == 'myobject' end end describe Puppet::Indirector::REST do before :all do Puppet::Indirector::Terminus.stubs(:register_terminus_class) @model = stub('model', :supported_formats => %w{}, :convert_from => nil) @instance = stub('model instance', :name= => nil) @indirection = stub('indirection', :name => :mystuff, :register_terminus_type => nil, :model => @model) Puppet::Indirector::Indirection.expects(:instance).returns(@indirection) module This module Is module A module Test end end end end @rest_class = class This::Is::A::Test::Class < Puppet::Indirector::REST self end end before :each do @response = stub('mock response', :body => 'result', :code => "200") @response.stubs(:[]).with('content-type').returns "text/plain" @response.stubs(:[]).with('content-encoding').returns nil @searcher = @rest_class.new @searcher.stubs(:model).returns @model end it "should include the v1 REST API module" do Puppet::Indirector::REST.ancestors.should be_include(Puppet::Network::HTTP::API::V1) end it "should have a method for specifying what setting a subclass should use to retrieve its server" do @rest_class.should respond_to(:use_server_setting) end it "should use any specified setting to pick the server" do @rest_class.expects(:server_setting).returns :servset Puppet.settings.expects(:value).with(:servset).returns "myserver" @rest_class.server.should == "myserver" end it "should default to :server for the server setting" do @rest_class.expects(:server_setting).returns nil Puppet.settings.expects(:value).with(:server).returns "myserver" @rest_class.server.should == "myserver" end it "should have a method for specifying what setting a subclass should use to retrieve its port" do @rest_class.should respond_to(:use_port_setting) end it "should use any specified setting to pick the port" do @rest_class.expects(:port_setting).returns :servset Puppet.settings.expects(:value).with(:servset).returns "321" @rest_class.port.should == 321 end it "should default to :port for the port setting" do @rest_class.expects(:port_setting).returns nil Puppet.settings.expects(:value).with(:masterport).returns "543" @rest_class.port.should == 543 end describe "when making http requests" do include PuppetSpec::Files it "should provide a suggestive error message when certificate verify failed" do connection = Net::HTTP.new('my_server', 8140) @searcher.stubs(:network).returns(connection) connection.stubs(:get).raises(OpenSSL::SSL::SSLError.new('certificate verify failed')) expect do @searcher.http_request(:get, stub('request')) end.to raise_error(/This is often because the time is out of sync on the server or client/) end it "should provide a helpful error message when hostname was not match with server certificate", :unless => Puppet.features.microsoft_windows? do Puppet[:confdir] = tmpdir('conf') cert = Puppet::SSL::CertificateAuthority.new.generate('not_my_server', :dns_alt_names => 'foo,bar,baz').content connection = Net::HTTP.new('my_server', 8140) @searcher.stubs(:network).returns(connection) ssl_context = OpenSSL::SSL::SSLContext.new ssl_context.stubs(:current_cert).returns(cert) connection.stubs(:get).with do connection.verify_callback.call(true, ssl_context) end.raises(OpenSSL::SSL::SSLError.new('hostname was not match with server certificate')) msg = /Server hostname 'my_server' did not match server certificate; expected one of (.+)/ expect { @searcher.http_request(:get, stub('request')) }.to( raise_error(Puppet::Error, msg) do |error| error.message =~ msg $1.split(', ').should =~ %w[DNS:foo DNS:bar DNS:baz DNS:not_my_server not_my_server] end ) end it "should pass along the error message otherwise" do connection = Net::HTTP.new('my_server', 8140) @searcher.stubs(:network).returns(connection) connection.stubs(:get).raises(OpenSSL::SSL::SSLError.new('some other message')) expect do @searcher.http_request(:get, stub('request')) end.to raise_error(/some other message/) end end it 'should default to :puppet for the srv_service' do Puppet::Indirector::REST.srv_service.should == :puppet end describe "when deserializing responses" do it "should return nil if the response code is 404" do response = mock 'response' response.expects(:code).returns "404" @searcher.deserialize(response).should be_nil end [300,400,403,405,500,501,502,503,504].each { |rc| describe "when the response code is #{rc}" do before :each do @model.expects(:convert_from).never @response = mock 'response' @response.stubs(:code).returns rc.to_s @response.stubs(:[]).with('content-encoding').returns nil @response.stubs(:message).returns "There was a problem (header)" end it "should fail" do @response.stubs(:body).returns nil lambda { @searcher.deserialize(@response) }.should raise_error(Net::HTTPError) end it "should take the error message from the body, if present" do @response.stubs(:body).returns "There was a problem (body)" lambda { @searcher.deserialize(@response) }.should raise_error(Net::HTTPError,"Error #{rc} on SERVER: There was a problem (body)") end it "should take the error message from the response header if the body is empty" do @response.stubs(:body).returns "" lambda { @searcher.deserialize(@response) }.should raise_error(Net::HTTPError,"Error #{rc} on SERVER: There was a problem (header)") end it "should take the error message from the response header if the body is absent" do @response.stubs(:body).returns nil lambda { @searcher.deserialize(@response) }.should raise_error(Net::HTTPError,"Error #{rc} on SERVER: There was a problem (header)") end describe "and with http compression" do it "should uncompress the body" do @response.stubs(:body).returns("compressed body") @searcher.expects(:uncompress_body).with(@response).returns("uncompressed") lambda { @searcher.deserialize(@response) }.should raise_error { |e| e.message =~ /uncompressed/ } end end end } it "should return the results of converting from the format specified by the content-type header if the response code is in the 200s" do @model.expects(:convert_from).with("myformat", "mydata").returns "myobject" response = mock 'response' response.stubs(:[]).with("content-type").returns "myformat" response.stubs(:[]).with("content-encoding").returns nil response.stubs(:body).returns "mydata" response.stubs(:code).returns "200" @searcher.deserialize(response).should == "myobject" end it "should convert and return multiple instances if the return code is in the 200s and 'multiple' is specified" do @model.expects(:convert_from_multiple).with("myformat", "mydata").returns "myobjects" response = mock 'response' response.stubs(:[]).with("content-type").returns "myformat" response.stubs(:[]).with("content-encoding").returns nil response.stubs(:body).returns "mydata" response.stubs(:code).returns "200" @searcher.deserialize(response, true).should == "myobjects" end it "should strip the content-type header to keep only the mime-type" do @model.expects(:convert_from).with("text/plain", "mydata").returns "myobject" response = mock 'response' response.stubs(:[]).with("content-type").returns "text/plain; charset=utf-8" response.stubs(:[]).with("content-encoding").returns nil response.stubs(:body).returns "mydata" response.stubs(:code).returns "200" @searcher.deserialize(response) end it "should uncompress the body" do @model.expects(:convert_from).with("myformat", "uncompressed mydata").returns "myobject" response = mock 'response' response.stubs(:[]).with("content-type").returns "myformat" response.stubs(:body).returns "compressed mydata" response.stubs(:code).returns "200" @searcher.expects(:uncompress_body).with(response).returns("uncompressed mydata") @searcher.deserialize(response).should == "myobject" end end describe "when creating an HTTP client" do before do Puppet.settings.stubs(:value).returns("rest_testing") end it "should use the class's server and port if the indirection request provides neither" do @request = stub 'request', :key => "foo", :server => nil, :port => nil @searcher.class.expects(:port).returns 321 @searcher.class.expects(:server).returns "myserver" Puppet::Network::HttpPool.expects(:http_instance).with("myserver", 321).returns "myconn" @searcher.network(@request).should == "myconn" end it "should use the server from the indirection request if one is present" do @request = stub 'request', :key => "foo", :server => "myserver", :port => nil @searcher.class.stubs(:port).returns 321 Puppet::Network::HttpPool.expects(:http_instance).with("myserver", 321).returns "myconn" @searcher.network(@request).should == "myconn" end it "should use the port from the indirection request if one is present" do @request = stub 'request', :key => "foo", :server => nil, :port => 321 @searcher.class.stubs(:server).returns "myserver" Puppet::Network::HttpPool.expects(:http_instance).with("myserver", 321).returns "myconn" @searcher.network(@request).should == "myconn" end end describe "when doing a find" do before :each do @connection = stub('mock http connection', :get => @response, :verify_callback= => nil) @searcher.stubs(:network).returns(@connection) # neuter the network connection # Use a key with spaces, so we can test escaping - @request = Puppet::Indirector::Request.new(:foo, :find, "foo bar", :environment => "myenv") + @request = Puppet::Indirector::Request.new(:foo, :find, "foo bar", nil, :environment => "myenv") end describe "with a large body" do it "should use the POST http method" do params = {} 'aa'.upto('zz') do |s| params[s] = 'foo' end # The request special-cases this parameter, and it # won't be passed on to the server, so we remove it here # to avoid a failure. params.delete('ip') - @request = Puppet::Indirector::Request.new(:foo, :find, "foo bar", params.merge(:environment => "myenv")) + @request = Puppet::Indirector::Request.new(:foo, :find, "foo bar", nil, params.merge(:environment => "myenv")) @connection.expects(:post).with do |uri, body| uri == "/myenv/foo/foo%20bar" and body.split("&").sort == params.map {|key,value| "#{key}=#{value}"}.sort end.returns(@response) @searcher.find(@request) end end describe "with a small body" do it "should use the GET http method" do @searcher.expects(:network).returns @connection @connection.expects(:get).returns @response @searcher.find(@request) end end it "should deserialize and return the http response, setting name" do @connection.expects(:get).returns @response instance = stub 'object' instance.expects(:name=) @searcher.expects(:deserialize).with(@response).returns instance @searcher.find(@request).should == instance end it "should deserialize and return the http response, and not require name=" do @connection.expects(:get).returns @response instance = stub 'object' @searcher.expects(:deserialize).with(@response).returns instance @searcher.find(@request).should == instance end it "should use the URI generated by the Handler module" do @connection.expects(:get).with { |path, args| path == "/myenv/foo/foo%20bar?" }.returns(@response) @searcher.find(@request) end it "should provide an Accept header containing the list of supported formats joined with commas" do @connection.expects(:get).with { |path, args| args["Accept"] == "supported, formats" }.returns(@response) @searcher.model.expects(:supported_formats).returns %w{supported formats} @searcher.find(@request) end it "should add Accept-Encoding header" do @searcher.expects(:add_accept_encoding).returns({"accept-encoding" => "gzip"}) @connection.expects(:get).with { |path, args| args["accept-encoding"] == "gzip" }.returns(@response) @searcher.find(@request) end it "should deserialize and return the network response" do @searcher.expects(:deserialize).with(@response).returns @instance @searcher.find(@request).should equal(@instance) end it "should set the name of the resulting instance to the asked-for name" do @searcher.expects(:deserialize).with(@response).returns @instance @instance.expects(:name=).with "foo bar" @searcher.find(@request) end it "should generate an error when result data deserializes fails" do @searcher.expects(:deserialize).raises(ArgumentError) lambda { @searcher.find(@request) }.should raise_error(ArgumentError) end end describe "when doing a head" do before :each do @connection = stub('mock http connection', :head => @response, :verify_callback= => nil) @searcher.stubs(:network).returns(@connection) # Use a key with spaces, so we can test escaping - @request = Puppet::Indirector::Request.new(:foo, :head, "foo bar") + @request = Puppet::Indirector::Request.new(:foo, :head, "foo bar", nil) end it "should call the HEAD http method on a network connection" do @searcher.expects(:network).returns @connection @connection.expects(:head).returns @response @searcher.head(@request) end it "should return true if there was a successful http response" do @connection.expects(:head).returns @response @response.stubs(:code).returns "200" @searcher.head(@request).should == true end it "should return false if there was a successful http response" do @connection.expects(:head).returns @response @response.stubs(:code).returns "404" @searcher.head(@request).should == false end it "should use the URI generated by the Handler module" do @searcher.expects(:indirection2uri).with(@request).returns "/my/uri" @connection.expects(:head).with { |path, args| path == "/my/uri" }.returns(@response) @searcher.head(@request) end end describe "when doing a search" do before :each do @connection = stub('mock http connection', :get => @response, :verify_callback= => nil) @searcher.stubs(:network).returns(@connection) # neuter the network connection @model.stubs(:convert_from_multiple) - @request = Puppet::Indirector::Request.new(:foo, :search, "foo bar") + @request = Puppet::Indirector::Request.new(:foo, :search, "foo bar", nil) end it "should call the GET http method on a network connection" do @searcher.expects(:network).returns @connection @connection.expects(:get).returns @response @searcher.search(@request) end it "should deserialize as multiple instances and return the http response" do @connection.expects(:get).returns @response @searcher.expects(:deserialize).with(@response, true).returns "myobject" @searcher.search(@request).should == 'myobject' end it "should use the URI generated by the Handler module" do @searcher.expects(:indirection2uri).with(@request).returns "/mys/uri" @connection.expects(:get).with { |path, args| path == "/mys/uri" }.returns(@response) @searcher.search(@request) end it "should provide an Accept header containing the list of supported formats joined with commas" do @connection.expects(:get).with { |path, args| args["Accept"] == "supported, formats" }.returns(@response) @searcher.model.expects(:supported_formats).returns %w{supported formats} @searcher.search(@request) end it "should return an empty array if serialization returns nil" do @model.stubs(:convert_from_multiple).returns nil @searcher.search(@request).should == [] end it "should generate an error when result data deserializes fails" do @searcher.expects(:deserialize).raises(ArgumentError) lambda { @searcher.search(@request) }.should raise_error(ArgumentError) end end describe "when doing a destroy" do before :each do @connection = stub('mock http connection', :delete => @response, :verify_callback= => nil) @searcher.stubs(:network).returns(@connection) # neuter the network connection - @request = Puppet::Indirector::Request.new(:foo, :destroy, "foo bar") + @request = Puppet::Indirector::Request.new(:foo, :destroy, "foo bar", nil) end it "should call the DELETE http method on a network connection" do @searcher.expects(:network).returns @connection @connection.expects(:delete).returns @response @searcher.destroy(@request) end it "should fail if any options are provided, since DELETE apparently does not support query options" do @request.stubs(:options).returns(:one => "two", :three => "four") lambda { @searcher.destroy(@request) }.should raise_error(ArgumentError) end it "should deserialize and return the http response" do @connection.expects(:delete).returns @response @searcher.expects(:deserialize).with(@response).returns "myobject" @searcher.destroy(@request).should == 'myobject' end it "should use the URI generated by the Handler module" do @searcher.expects(:indirection2uri).with(@request).returns "/my/uri" @connection.expects(:delete).with { |path, args| path == "/my/uri" }.returns(@response) @searcher.destroy(@request) end it "should not include the query string" do @connection.stubs(:delete).returns @response @searcher.destroy(@request) end it "should provide an Accept header containing the list of supported formats joined with commas" do @connection.expects(:delete).with { |path, args| args["Accept"] == "supported, formats" }.returns(@response) @searcher.model.expects(:supported_formats).returns %w{supported formats} @searcher.destroy(@request) end it "should deserialize and return the network response" do @searcher.expects(:deserialize).with(@response).returns @instance @searcher.destroy(@request).should equal(@instance) end it "should generate an error when result data deserializes fails" do @searcher.expects(:deserialize).raises(ArgumentError) lambda { @searcher.destroy(@request) }.should raise_error(ArgumentError) end end describe "when doing a save" do before :each do @connection = stub('mock http connection', :put => @response, :verify_callback= => nil) @searcher.stubs(:network).returns(@connection) # neuter the network connection @instance = stub 'instance', :render => "mydata", :mime => "mime" - @request = Puppet::Indirector::Request.new(:foo, :save, "foo bar") + @request = Puppet::Indirector::Request.new(:foo, :save, "foo bar", nil) @request.instance = @instance end it "should call the PUT http method on a network connection" do @searcher.expects(:network).returns @connection @connection.expects(:put).returns @response @searcher.save(@request) end it "should fail if any options are provided, since DELETE apparently does not support query options" do @request.stubs(:options).returns(:one => "two", :three => "four") lambda { @searcher.save(@request) }.should raise_error(ArgumentError) end it "should use the URI generated by the Handler module" do @searcher.expects(:indirection2uri).with(@request).returns "/my/uri" @connection.expects(:put).with { |path, args| path == "/my/uri" }.returns(@response) @searcher.save(@request) end it "should serialize the instance using the default format and pass the result as the body of the request" do @instance.expects(:render).returns "serial_instance" @connection.expects(:put).with { |path, data, args| data == "serial_instance" }.returns @response @searcher.save(@request) end it "should deserialize and return the http response" do @connection.expects(:put).returns @response @searcher.expects(:deserialize).with(@response).returns "myobject" @searcher.save(@request).should == 'myobject' end it "should provide an Accept header containing the list of supported formats joined with commas" do @connection.expects(:put).with { |path, data, args| args["Accept"] == "supported, formats" }.returns(@response) @searcher.model.expects(:supported_formats).returns %w{supported formats} @searcher.save(@request) end it "should provide a Content-Type header containing the mime-type of the sent object" do @connection.expects(:put).with { |path, data, args| args['Content-Type'] == "mime" }.returns(@response) @instance.expects(:mime).returns "mime" @searcher.save(@request) end it "should deserialize and return the network response" do @searcher.expects(:deserialize).with(@response).returns @instance @searcher.save(@request).should equal(@instance) end it "should generate an error when result data deserializes fails" do @searcher.expects(:deserialize).raises(ArgumentError) lambda { @searcher.save(@request) }.should raise_error(ArgumentError) end end context 'dealing with SRV settings' do [ :destroy, :find, :head, :save, :search ].each do |method| it "##{method} passes the SRV service, and fall-back server & port to the request's do_request method" do - request = Puppet::Indirector::Request.new(:indirection, method, 'key') + request = Puppet::Indirector::Request.new(:indirection, method, 'key', nil) stub_response = stub 'response' stub_response.stubs(:code).returns('200') @searcher.stubs(:deserialize) request.expects(:do_request).with(@searcher.class.srv_service, @searcher.class.server, @searcher.class.port).returns(stub_response) @searcher.send(method, request) end end end end diff --git a/spec/unit/indirector/run/local_spec.rb b/spec/unit/indirector/run/local_spec.rb index 8fb61d962..da4af76d1 100755 --- a/spec/unit/indirector/run/local_spec.rb +++ b/spec/unit/indirector/run/local_spec.rb @@ -1,19 +1,19 @@ #!/usr/bin/env rspec require 'spec_helper' require 'puppet/indirector/run/local' describe Puppet::Run::Local do it "should be a sublcass of Puppet::Indirector::Code" do Puppet::Run::Local.superclass.should equal(Puppet::Indirector::Code) end it "should call runner.run on save and return the runner" do runner = Puppet::Run.new runner.stubs(:run).returns(runner) - request = Puppet::Indirector::Request.new(:indirection, :save, "anything") + request = Puppet::Indirector::Request.new(:indirection, :save, "anything", nil) request.instance = runner = Puppet::Run.new Puppet::Run::Local.new.save(request).should == runner end end diff --git a/spec/unit/network/http/api/v1_spec.rb b/spec/unit/network/http/api/v1_spec.rb index 115f57351..936e7895c 100755 --- a/spec/unit/network/http/api/v1_spec.rb +++ b/spec/unit/network/http/api/v1_spec.rb @@ -1,208 +1,208 @@ #!/usr/bin/env rspec require 'spec_helper' require 'puppet/network/http/api/v1' class V1RestApiTester include Puppet::Network::HTTP::API::V1 end describe Puppet::Network::HTTP::API::V1 do before do @tester = V1RestApiTester.new end it "should be able to convert a URI into a request" do @tester.should respond_to(:uri2indirection) end it "should be able to convert a request into a URI" do @tester.should respond_to(:indirection2uri) end describe "when converting a URI into a request" do before do @tester.stubs(:handler).returns "foo" end it "should require the http method, the URI, and the query parameters" do # Not a terribly useful test, but an important statement for the spec lambda { @tester.uri2indirection("/foo") }.should raise_error(ArgumentError) end it "should use the first field of the URI as the environment" do @tester.uri2indirection("GET", "/env/foo/bar", {})[3][:environment].to_s.should == "env" end it "should fail if the environment is not alphanumeric" do lambda { @tester.uri2indirection("GET", "/env ness/foo/bar", {}) }.should raise_error(ArgumentError) end it "should use the environment from the URI even if one is specified in the parameters" do @tester.uri2indirection("GET", "/env/foo/bar", {:environment => "otherenv"})[3][:environment].to_s.should == "env" end it "should not pass a buck_path parameter through (See Bugs #13553, #13518, #13511)" do @tester.uri2indirection("GET", "/env/foo/bar", { :bucket_path => "/malicious/path" })[3].should_not include({ :bucket_path => "/malicious/path" }) end it "should pass allowed parameters through" do @tester.uri2indirection("GET", "/env/foo/bar", { :allowed_param => "value" })[3].should include({ :allowed_param => "value" }) end it "should return the environment as a Puppet::Node::Environment" do @tester.uri2indirection("GET", "/env/foo/bar", {})[3][:environment].should be_a Puppet::Node::Environment end it "should use the second field of the URI as the indirection name" do @tester.uri2indirection("GET", "/env/foo/bar", {})[0].should == "foo" end it "should fail if the indirection name is not alphanumeric" do lambda { @tester.uri2indirection("GET", "/env/foo ness/bar", {}) }.should raise_error(ArgumentError) end it "should use the remainder of the URI as the indirection key" do @tester.uri2indirection("GET", "/env/foo/bar", {})[2].should == "bar" end it "should support the indirection key being a /-separated file path" do @tester.uri2indirection("GET", "/env/foo/bee/baz/bomb", {})[2].should == "bee/baz/bomb" end it "should fail if no indirection key is specified" do lambda { @tester.uri2indirection("GET", "/env/foo/", {}) }.should raise_error(ArgumentError) lambda { @tester.uri2indirection("GET", "/env/foo", {}) }.should raise_error(ArgumentError) end it "should choose 'find' as the indirection method if the http method is a GET and the indirection name is singular" do @tester.uri2indirection("GET", "/env/foo/bar", {})[1].should == :find end it "should choose 'find' as the indirection method if the http method is a POST and the indirection name is singular" do @tester.uri2indirection("POST", "/env/foo/bar", {})[1].should == :find end it "should choose 'head' as the indirection method if the http method is a HEAD and the indirection name is singular" do @tester.uri2indirection("HEAD", "/env/foo/bar", {})[1].should == :head end it "should choose 'search' as the indirection method if the http method is a GET and the indirection name is plural" do @tester.uri2indirection("GET", "/env/foos/bar", {})[1].should == :search end it "should choose 'find' as the indirection method if the http method is a GET and the indirection name is facts" do @tester.uri2indirection("GET", "/env/facts/bar", {})[1].should == :find end it "should choose 'save' as the indirection method if the http method is a PUT and the indirection name is facts" do @tester.uri2indirection("PUT", "/env/facts/bar", {})[1].should == :save end it "should choose 'search' as the indirection method if the http method is a GET and the indirection name is inventory" do @tester.uri2indirection("GET", "/env/inventory/search", {})[1].should == :search end it "should choose 'find' as the indirection method if the http method is a GET and the indirection name is facts" do @tester.uri2indirection("GET", "/env/facts/bar", {})[1].should == :find end it "should choose 'save' as the indirection method if the http method is a PUT and the indirection name is facts" do @tester.uri2indirection("PUT", "/env/facts/bar", {})[1].should == :save end it "should choose 'search' as the indirection method if the http method is a GET and the indirection name is inventory" do @tester.uri2indirection("GET", "/env/inventory/search", {})[1].should == :search end it "should choose 'search' as the indirection method if the http method is a GET and the indirection name is facts_search" do @tester.uri2indirection("GET", "/env/facts_search/bar", {})[1].should == :search end it "should change indirection name to 'facts' if the http method is a GET and the indirection name is facts_search" do @tester.uri2indirection("GET", "/env/facts_search/bar", {})[0].should == 'facts' end it "should not change indirection name from 'facts' if the http method is a GET and the indirection name is facts" do @tester.uri2indirection("GET", "/env/facts/bar", {})[0].should == 'facts' end it "should change indirection name to 'status' if the http method is a GET and the indirection name is statuses" do @tester.uri2indirection("GET", "/env/statuses/bar", {})[0].should == 'status' end it "should change indirection name to 'probe' if the http method is a GET and the indirection name is probes" do @tester.uri2indirection("GET", "/env/probes/bar", {})[0].should == 'probe' end it "should choose 'delete' as the indirection method if the http method is a DELETE and the indirection name is singular" do @tester.uri2indirection("DELETE", "/env/foo/bar", {})[1].should == :destroy end it "should choose 'save' as the indirection method if the http method is a PUT and the indirection name is singular" do @tester.uri2indirection("PUT", "/env/foo/bar", {})[1].should == :save end it "should fail if an indirection method cannot be picked" do lambda { @tester.uri2indirection("UPDATE", "/env/foo/bar", {}) }.should raise_error(ArgumentError) end it "should URI unescape the indirection key" do escaped = URI.escape("foo bar") indirection_name, method, key, params = @tester.uri2indirection("GET", "/env/foo/#{escaped}", {}) key.should == "foo bar" end end describe "when converting a request into a URI" do before do - @request = Puppet::Indirector::Request.new(:foo, :find, "with spaces", :foo => :bar, :environment => "myenv") + @request = Puppet::Indirector::Request.new(:foo, :find, "with spaces", nil, :foo => :bar, :environment => "myenv") end it "should use the environment as the first field of the URI" do @tester.indirection2uri(@request).split("/")[1].should == "myenv" end it "should use the indirection as the second field of the URI" do @tester.indirection2uri(@request).split("/")[2].should == "foo" end it "should pluralize the indirection name if the method is 'search'" do @request.stubs(:method).returns :search @tester.indirection2uri(@request).split("/")[2].should == "foos" end it "should use the escaped key as the remainder of the URI" do escaped = URI.escape("with spaces") @tester.indirection2uri(@request).split("/")[3].sub(/\?.+/, '').should == escaped end it "should add the query string to the URI" do @request.expects(:query_string).returns "?query" @tester.indirection2uri(@request).should =~ /\?query$/ end end describe "when converting a request into a URI with body" do before :each do - @request = Puppet::Indirector::Request.new(:foo, :find, "with spaces", :foo => :bar, :environment => "myenv") + @request = Puppet::Indirector::Request.new(:foo, :find, "with spaces", nil, :foo => :bar, :environment => "myenv") end it "should use the environment as the first field of the URI" do @tester.request_to_uri_and_body(@request).first.split("/")[1].should == "myenv" end it "should use the indirection as the second field of the URI" do @tester.request_to_uri_and_body(@request).first.split("/")[2].should == "foo" end it "should use the escaped key as the remainder of the URI" do escaped = URI.escape("with spaces") @tester.request_to_uri_and_body(@request).first.split("/")[3].sub(/\?.+/, '').should == escaped end it "should return the URI and body separately" do @tester.request_to_uri_and_body(@request).should == ["/myenv/foo/with%20spaces", "foo=bar"] end end end diff --git a/spec/unit/node_spec.rb b/spec/unit/node_spec.rb index a47c716f4..79eb03c79 100755 --- a/spec/unit/node_spec.rb +++ b/spec/unit/node_spec.rb @@ -1,268 +1,268 @@ #!/usr/bin/env rspec require 'spec_helper' require 'matchers/json' describe Puppet::Node do it "should register its document type as Node" do PSON.registered_document_types["Node"].should equal(Puppet::Node) end describe "when managing its environment" do it "should use any set environment" do Puppet::Node.new("foo", :environment => "bar").environment.name.should == :bar end it "should support providing an actual environment instance" do Puppet::Node.new("foo", :environment => Puppet::Node::Environment.new(:bar)).environment.name.should == :bar end it "should determine its environment from its parameters if no environment is set" do Puppet::Node.new("foo", :parameters => {"environment" => :bar}).environment.name.should == :bar end it "should use the default environment if no environment is provided" do Puppet::Node.new("foo").environment.name.should == Puppet::Node::Environment.new.name end it "should always return an environment instance rather than a string" do Puppet::Node.new("foo").environment.should be_instance_of(Puppet::Node::Environment) end it "should allow the environment to be set after initialization" do node = Puppet::Node.new("foo") node.environment = :bar node.environment.name.should == :bar end it "should allow its environment to be set by parameters after initialization" do node = Puppet::Node.new("foo") node.parameters["environment"] = :bar node.environment.name.should == :bar end end describe "when converting to json" do before do @node = Puppet::Node.new("mynode") end it "should provide its name" do @node.should set_json_attribute('name').to("mynode") end it "should produce a hash with the document_type set to 'Node'" do @node.should set_json_document_type_to("Node") end it "should include the classes if set" do @node.classes = %w{a b c} @node.should set_json_attribute("classes").to(%w{a b c}) end it "should not include the classes if there are none" do @node.should_not set_json_attribute('classes') end it "should include parameters if set" do @node.parameters = {"a" => "b", "c" => "d"} @node.should set_json_attribute('parameters').to({"a" => "b", "c" => "d"}) end it "should not include the parameters if there are none" do @node.should_not set_json_attribute('parameters') end it "should include the environment" do @node.environment = "production" @node.should set_json_attribute('environment').to('production') end end describe "when converting from json" do before do @node = Puppet::Node.new("mynode") @format = Puppet::Network::FormatHandler.format('pson') end def from_json(json) @format.intern(Puppet::Node, json) end it "should set its name" do Puppet::Node.should read_json_attribute('name').from(@node.to_pson).as("mynode") end it "should include the classes if set" do @node.classes = %w{a b c} Puppet::Node.should read_json_attribute('classes').from(@node.to_pson).as(%w{a b c}) end it "should include parameters if set" do @node.parameters = {"a" => "b", "c" => "d"} Puppet::Node.should read_json_attribute('parameters').from(@node.to_pson).as({"a" => "b", "c" => "d"}) end it "should include the environment" do @node.environment = "production" Puppet::Node.should read_json_attribute('environment').from(@node.to_pson).as(Puppet::Node::Environment.new(:production)) end end end describe Puppet::Node, "when initializing" do before do @node = Puppet::Node.new("testnode") end it "should set the node name" do @node.name.should == "testnode" end it "should not allow nil node names" do proc { Puppet::Node.new(nil) }.should raise_error(ArgumentError) end it "should default to an empty parameter hash" do @node.parameters.should == {} end it "should default to an empty class array" do @node.classes.should == [] end it "should note its creation time" do @node.time.should be_instance_of(Time) end it "should accept parameters passed in during initialization" do params = {"a" => "b"} @node = Puppet::Node.new("testing", :parameters => params) @node.parameters.should == params end it "should accept classes passed in during initialization" do classes = %w{one two} @node = Puppet::Node.new("testing", :classes => classes) @node.classes.should == classes end it "should always return classes as an array" do @node = Puppet::Node.new("testing", :classes => "myclass") @node.classes.should == ["myclass"] end end describe Puppet::Node, "when merging facts" do before do @node = Puppet::Node.new("testnode") - Puppet::Node::Facts.indirection.stubs(:find).with(@node.name).returns(Puppet::Node::Facts.new(@node.name, "one" => "c", "two" => "b")) + Puppet::Node::Facts.indirection.stubs(:find).with(@node.name, instance_of(Hash)).returns(Puppet::Node::Facts.new(@node.name, "one" => "c", "two" => "b")) end it "should fail intelligently if it cannot find facts" do - Puppet::Node::Facts.indirection.expects(:find).with(@node.name).raises "foo" + Puppet::Node::Facts.indirection.expects(:find).with(@node.name, instance_of(Hash)).raises "foo" lambda { @node.fact_merge }.should raise_error(Puppet::Error) end it "should prefer parameters already set on the node over facts from the node" do @node = Puppet::Node.new("testnode", :parameters => {"one" => "a"}) @node.fact_merge @node.parameters["one"].should == "a" end it "should add passed parameters to the parameter list" do @node = Puppet::Node.new("testnode", :parameters => {"one" => "a"}) @node.fact_merge @node.parameters["two"].should == "b" end it "should accept arbitrary parameters to merge into its parameters" do @node = Puppet::Node.new("testnode", :parameters => {"one" => "a"}) @node.merge "two" => "three" @node.parameters["two"].should == "three" end it "should add the environment to the list of parameters" do Puppet.settings.stubs(:value).with(:environments).returns("one,two") Puppet.settings.stubs(:value).with(:environment).returns("one") @node = Puppet::Node.new("testnode", :environment => "one") @node.merge "two" => "three" @node.parameters["environment"].should == "one" end it "should not set the environment if it is already set in the parameters" do Puppet.settings.stubs(:value).with(:environments).returns("one,two") Puppet.settings.stubs(:value).with(:environment).returns("one") @node = Puppet::Node.new("testnode", :environment => "one") @node.merge "environment" => "two" @node.parameters["environment"].should == "two" end end describe Puppet::Node, "when indirecting" do it "should default to the 'plain' node terminus" do Puppet::Node.indirection.reset_terminus_class Puppet::Node.indirection.terminus_class.should == :plain end end describe Puppet::Node, "when generating the list of names to search through" do before do @node = Puppet::Node.new("foo.domain.com", :parameters => {"hostname" => "yay", "domain" => "domain.com"}) end it "should return an array of names" do @node.names.should be_instance_of(Array) end describe "and the node name is fully qualified" do it "should contain an entry for each part of the node name" do @node.names.should be_include("foo.domain.com") @node.names.should be_include("foo.domain") @node.names.should be_include("foo") end end it "should include the node's fqdn" do @node.names.should be_include("yay.domain.com") end it "should combine and include the node's hostname and domain if no fqdn is available" do @node.names.should be_include("yay.domain.com") end it "should contain an entry for each name available by stripping a segment of the fqdn" do @node.parameters["fqdn"] = "foo.deep.sub.domain.com" @node.names.should be_include("foo.deep.sub.domain") @node.names.should be_include("foo.deep.sub") end describe "and :node_name is set to 'cert'" do before do Puppet.settings.stubs(:value).with(:strict_hostname_checking).returns false Puppet.settings.stubs(:value).with(:node_name).returns "cert" end it "should use the passed-in key as the first value" do @node.names[0].should == "foo.domain.com" end describe "and strict hostname checking is enabled" do it "should only use the passed-in key" do Puppet.settings.expects(:value).with(:strict_hostname_checking).returns true @node.names.should == ["foo.domain.com"] end end end describe "and :node_name is set to 'facter'" do before do Puppet.settings.stubs(:value).with(:strict_hostname_checking).returns false Puppet.settings.stubs(:value).with(:node_name).returns "facter" end it "should use the node's 'hostname' fact as the first value" do @node.names[0].should == "yay" end end end