diff --git a/lib/puppet/metatype/relationships.rb b/lib/puppet/metatype/relationships.rb index 332702a25..2a5341e5e 100644 --- a/lib/puppet/metatype/relationships.rb +++ b/lib/puppet/metatype/relationships.rb @@ -1,113 +1,113 @@ class Puppet::Type # Specify a block for generating a list of objects to autorequire. This # makes it so that you don't have to manually specify things that you clearly # require. def self.autorequire(name, &block) @autorequires ||= {} @autorequires[name] = block end # Yield each of those autorequires in turn, yo. def self.eachautorequire @autorequires ||= {} @autorequires.each { |type, block| yield(type, block) } end # Figure out of there are any objects we can automatically add as # dependencies. def autorequire raise(ArgumentError, "Cannot autorequire resources without a configuration") unless configuration reqs = [] self.class.eachautorequire { |type, block| # Retrieve the list of names from the block. next unless list = self.instance_eval(&block) unless list.is_a?(Array) list = [list] end # Collect the current prereqs list.each { |dep| - obj = nil # Support them passing objects directly, to save some effort. - unless dep.is_a? Puppet::Type + if dep.is_a?(Puppet::Type) + next unless configuration.resource(type, dep.title) + resource = dep + else # Skip autorequires that we aren't managing - unless dep = configuration.resource(type, dep) - next - end + next unless resource = configuration.resource(type, dep) end - reqs << Puppet::Relationship.new(dep, self) + reqs << Puppet::Relationship.new(resource, self) } } return reqs end # Build the dependencies associated with an individual object. def builddepends # Handle the requires self.class.relationship_params.collect do |klass| if param = @parameters[klass.name] param.to_edges end end.flatten.reject { |r| r.nil? } end # Does this resource have a relationship with the other? We have to # check each object for both directions of relationship. def requires?(other) them = [other.class.name, other.title] me = [self.class.name, self.title] self.class.relationship_params.each do |param| case param.direction when :in: return true if v = self[param.name] and v.include?(them) when :out: return true if v = other[param.name] and v.include?(me) end end return false end # we've received an event # we only support local events right now, so we can pass actual # objects around, including the transaction object # the assumption here is that container objects will pass received # methods on to contained objects # i.e., we don't trigger our children, our refresh() method calls # refresh() on our children def trigger(event, source) trans = event.transaction if @callbacks.include?(source) [:ALL_EVENTS, event.event].each { |eventname| if method = @callbacks[source][eventname] if trans.triggered?(self, method) > 0 next end if self.respond_to?(method) self.send(method) end trans.triggered(self, method) end } end end # Unsubscribe from a given object, possibly with a specific event. def unsubscribe(object, event = nil) # First look through our own relationship params [:require, :subscribe].each do |param| if values = self[param] newvals = values.reject { |d| d == [object.class.name, object.title] } if newvals.length != values.length self.delete(param) self[param] = newvals end end end end end diff --git a/lib/puppet/metatype/schedules.rb b/lib/puppet/metatype/schedules.rb index 96ebce0ab..4d4f93764 100644 --- a/lib/puppet/metatype/schedules.rb +++ b/lib/puppet/metatype/schedules.rb @@ -1,33 +1,33 @@ class Puppet::Type # Look up the schedule and set it appropriately. This is done after # the instantiation phase, so that the schedule can be anywhere in the # file. def schedule unless defined? @schedule - if name = self[:schedule] - if sched = Puppet.type(:schedule)[name] + if name = self[:schedule] and self.configuration + if sched = configuration.resource(:schedule, name) @schedule = sched else self.fail "Could not find schedule %s" % name end else @schedule = nil end end @schedule end # Check whether we are scheduled to run right now or not. def scheduled? return true if Puppet[:ignoreschedules] return true unless schedule = self.schedule # We use 'checked' here instead of 'synced' because otherwise we'll # end up checking most resources most times, because they will generally # have been synced a long time ago (e.g., a file only gets updated # once a month on the server and its schedule is daily; the last sync time # will have been a month ago, so we'd end up checking every run). return schedule.match?(self.cached(:checked).to_i) end end diff --git a/lib/puppet/network/handler/fileserver.rb b/lib/puppet/network/handler/fileserver.rb index dd00450be..7a5a1fe9a 100755 --- a/lib/puppet/network/handler/fileserver.rb +++ b/lib/puppet/network/handler/fileserver.rb @@ -1,678 +1,681 @@ require 'puppet' require 'puppet/network/authstore' require 'webrick/httpstatus' require 'cgi' require 'delegate' require 'sync' class Puppet::Network::Handler AuthStoreError = Puppet::AuthStoreError class FileServerError < Puppet::Error; end class FileServer < Handler desc "The interface to Puppet's fileserving abilities." attr_accessor :local CHECKPARAMS = [:mode, :type, :owner, :group, :checksum] # Special filserver module for puppet's module system MODULES = "modules" @interface = XMLRPC::Service::Interface.new("fileserver") { |iface| iface.add_method("string describe(string, string)") iface.add_method("string list(string, string, boolean, array)") iface.add_method("string retrieve(string, string)") } def self.params CHECKPARAMS.dup end # Describe a given file. This returns all of the manageable aspects # of that file. def describe(url, links = :ignore, client = nil, clientip = nil) links = links.intern if links.is_a? String if links == :manage raise Puppet::Network::Handler::FileServerError, "Cannot currently copy links" end mount, path = convert(url, client, clientip) if client mount.debug "Describing %s for %s" % [url, client] end obj = nil unless obj = mount.getfileobject(path, links) return "" end currentvalues = mount.check(obj) desc = [] CHECKPARAMS.each { |check| if value = currentvalues[check] desc << value else if check == "checksum" and currentvalues[:type] == "file" mount.notice "File %s does not have data for %s" % [obj.name, check] end desc << nil end } return desc.join("\t") end # Create a new fileserving module. def initialize(hash = {}) @mounts = {} @files = {} if hash[:Local] @local = hash[:Local] else @local = false end if hash[:Config] == false @noreadconfig = true else @config = Puppet::Util::LoadedFile.new( hash[:Config] || Puppet[:fileserverconfig] ) @noreadconfig = false end if hash.include?(:Mount) @passedconfig = true unless hash[:Mount].is_a?(Hash) raise Puppet::DevError, "Invalid mount hash %s" % hash[:Mount].inspect end hash[:Mount].each { |dir, name| if FileTest.exists?(dir) self.mount(dir, name) end } self.mount(nil, MODULES) else @passedconfig = false readconfig(false) # don't check the file the first time. end end # List a specific directory's contents. def list(url, links = :ignore, recurse = false, ignore = false, client = nil, clientip = nil) mount, path = convert(url, client, clientip) if client mount.debug "Listing %s for %s" % [url, client] end obj = nil unless FileTest.exists?(path) return "" end # We pass two paths here, but reclist internally changes one # of the arguments when called internally. desc = reclist(mount, path, path, recurse, ignore) if desc.length == 0 mount.notice "Got no information on //%s/%s" % [mount, path] return "" end desc.collect { |sub| sub.join("\t") }.join("\n") end def local? self.local end # Mount a new directory with a name. def mount(path, name) if @mounts.include?(name) if @mounts[name] != path raise FileServerError, "%s is already mounted at %s" % [@mounts[name].path, name] else # it's already mounted; no problem return end end # Let the mounts do their own error-checking. @mounts[name] = Mount.new(name, path) @mounts[name].info "Mounted %s" % path return @mounts[name] end # Retrieve a file from the local disk and pass it to the remote # client. def retrieve(url, links = :ignore, client = nil, clientip = nil) links = links.intern if links.is_a? String mount, path = convert(url, client, clientip) if client mount.info "Sending %s to %s" % [url, client] end unless FileTest.exists?(path) return "" end links = links.intern if links.is_a? String if links == :ignore and FileTest.symlink?(path) return "" end str = nil if links == :manage raise Puppet::Error, "Cannot copy links yet." else str = File.read(path) end if @local return str else return CGI.escape(str) end end def umount(name) @mounts.delete(name) if @mounts.include? name end private def authcheck(file, mount, client, clientip) # If we're local, don't bother passing in information. if local? client = nil clientip = nil end unless mount.allowed?(client, clientip) mount.warning "%s cannot access %s" % [client, file] raise Puppet::AuthorizationError, "Cannot access %s" % mount end end def convert(url, client, clientip) readconfig url = URI.unescape(url) mount, stub = splitpath(url, client) authcheck(url, mount, client, clientip) path = nil unless path = mount.subdir(stub, client) mount.notice "Could not find subdirectory %s" % "//%s/%s" % [mount, stub] return "" end return mount, path end # Deal with ignore parameters. def handleignore(children, path, ignore) ignore.each { |ignore| Dir.glob(File.join(path,ignore), File::FNM_DOTMATCH) { |match| children.delete(File.basename(match)) } } return children end # Return the mount for the Puppet modules; allows file copying from # the modules. def modules_mount(module_name, client) # Find our environment, if we have one. unless hostname = (client || Facter.value("hostname")) raise ArgumentError, "Could not find hostname" end if node = Puppet::Node.find(hostname) env = node.environment else env = nil end # And use the environment to look up the module. mod = Puppet::Module::find(module_name, env) if mod return @mounts[MODULES].copy(mod.name, mod.files) else return nil end end # Read the configuration file. def readconfig(check = true) return if @noreadconfig if check and ! @config.changed? return end newmounts = {} begin File.open(@config.file) { |f| mount = nil count = 1 f.each { |line| case line when /^\s*#/: next # skip comments when /^\s*$/: next # skip blank lines when /\[([-\w]+)\]/: name = $1 if newmounts.include?(name) raise FileServerError, "%s is already mounted at %s" % [newmounts[name], name], count, @config.file end mount = Mount.new(name) newmounts[name] = mount when /^\s*(\w+)\s+(.+)$/: var = $1 value = $2 case var when "path": if mount.name == MODULES Puppet.warning "The '#{MODULES}' module can not have a path. Ignoring attempt to set it" else begin mount.path = value rescue FileServerError => detail Puppet.err "Removing mount %s: %s" % [mount.name, detail] newmounts.delete(mount.name) end end when "allow": value.split(/\s*,\s*/).each { |val| begin mount.info "allowing %s access" % val mount.allow(val) rescue AuthStoreError => detail raise FileServerError.new(detail.to_s, count, @config.file) end } when "deny": value.split(/\s*,\s*/).each { |val| begin mount.info "denying %s access" % val mount.deny(val) rescue AuthStoreError => detail raise FileServerError.new(detail.to_s, count, @config.file) end } else raise FileServerError.new("Invalid argument '%s'" % var, count, @config.file) end else raise FileServerError.new("Invalid line '%s'" % line.chomp, count, @config.file) end count += 1 } } rescue Errno::EACCES => detail Puppet.err "FileServer error: Cannot read %s; cannot serve" % @config #raise Puppet::Error, "Cannot read %s" % @config rescue Errno::ENOENT => detail Puppet.err "FileServer error: '%s' does not exist; cannot serve" % @config #raise Puppet::Error, "%s does not exit" % @config #rescue FileServerError => detail # Puppet.err "FileServer error: %s" % detail end unless newmounts[MODULES] mount = Mount.new(MODULES) mount.allow("*") newmounts[MODULES] = mount end # Verify each of the mounts are valid. # We let the check raise an error, so that it can raise an error # pointing to the specific problem. newmounts.each { |name, mount| unless mount.valid? raise FileServerError, "No path specified for mount %s" % name end } @mounts = newmounts end # Recursively list the directory. FIXME This should be using # puppet objects, not directly listing. def reclist(mount, root, path, recurse, ignore) # Take out the root of the path. name = path.sub(root, '') if name == "" name = "/" end if name == path raise FileServerError, "Could not match %s in %s" % [root, path] end desc = [name] ftype = File.stat(path).ftype desc << ftype if recurse.is_a?(Integer) recurse -= 1 end ary = [desc] if recurse == true or (recurse.is_a?(Integer) and recurse > -1) if ftype == "directory" children = Dir.entries(path) if ignore children = handleignore(children, path, ignore) end children.each { |child| next if child =~ /^\.\.?$/ reclist(mount, root, File.join(path, child), recurse, ignore).each { |cobj| ary << cobj } } end end return ary.reject { |c| c.nil? } end # Split the path into the separate mount point and path. def splitpath(dir, client) # the dir is based on one of the mounts # so first retrieve the mount path mount = nil path = nil if dir =~ %r{/([-\w]+)/?} # Strip off the mount name. mount_name, path = dir.sub(%r{^/}, '').split(File::Separator, 2) unless mount = modules_mount(mount_name, client) unless mount = @mounts[mount_name] raise FileServerError, "Fileserver module '%s' not mounted" % mount_name end end else raise FileServerError, "Fileserver error: Invalid path '%s'" % dir end if path == "" path = nil elsif path # Remove any double slashes that might have occurred path = URI.unescape(path.gsub(/\/\//, "/")) end return mount, path end def to_s "fileserver" end # A simple class for wrapping mount points. Instances of this class # don't know about the enclosing object; they're mainly just used for # authorization. class Mount < Puppet::Network::AuthStore attr_reader :name @@syncs = {} @@files = {} Puppet::Util.logmethods(self, true) def getfileobject(dir, links) unless FileTest.exists?(dir) self.debug "File source %s does not exist" % dir return nil end return fileobj(dir, links) end # Run 'retrieve' on a file. This gets the actual parameters, so # we can pass them to the client. def check(obj) # Retrieval is enough here, because we don't want to cache # any information in the state file, and we don't want to generate # any state changes or anything. We don't even need to sync # the checksum, because we're always going to hit the disk # directly. # We're now caching file data, using the LoadedFile to check the # disk no more frequently than the :filetimeout. path = obj[:path] sync = sync(path) unless data = @@files[path] data = {} sync.synchronize(Sync::EX) do @@files[path] = data data[:loaded_obj] = Puppet::Util::LoadedFile.new(path) data[:values] = properties(obj) return data[:values] end end changed = nil sync.synchronize(Sync::SH) do changed = data[:loaded_obj].changed? end if changed sync.synchronize(Sync::EX) do data[:values] = properties(obj) return data[:values] end else sync.synchronize(Sync::SH) do return data[:values] end end end # Create a map for a specific client. def clientmap(client) { "h" => client.sub(/\..*$/, ""), "H" => client, "d" => client.sub(/[^.]+\./, "") # domain name } end # Replace % patterns as appropriate. def expand(path, client = nil) # This map should probably be moved into a method. map = nil if client map = clientmap(client) else Puppet.notice "No client; expanding '%s' with local host" % path # Else, use the local information map = localmap() end path.gsub(/%(.)/) do |v| key = $1 if key == "%" "%" else map[key] || v end end end # Do we have any patterns in our path, yo? def expandable? if defined? @expandable @expandable else false end end # Create out object. It must have a name. def initialize(name, path = nil) unless name =~ %r{^[-\w]+$} raise FileServerError, "Invalid name format '%s'" % name end @name = name if path self.path = path else @path = nil end + @cache = {} + super() end def fileobj(path, links) obj = nil - if obj = Puppet.type(:file)[path] + if obj = @cache[path] # This can only happen in local fileserving, but it's an # important one. It'd be nice if we didn't just set # the check params every time, but I'm not sure it's worth # the effort. obj[:check] = CHECKPARAMS else - obj = Puppet.type(:file).create( + obj = Puppet::Type.type(:file).create( :name => path, :check => CHECKPARAMS ) + @cache[path] = obj end if links == :manage links = :follow end # This, ah, might be completely redundant unless obj[:links] == links obj[:links] = links end return obj end # Cache this manufactured map, since if it's used it's likely # to get used a lot. def localmap unless defined? @@localmap @@localmap = { "h" => Facter.value("hostname"), "H" => [Facter.value("hostname"), Facter.value("domain")].join("."), "d" => Facter.value("domain") } end @@localmap end # Return the path as appropriate, expanding as necessary. def path(client = nil) if expandable? return expand(@path, client) else return @path end end # Set the path. def path=(path) # FIXME: For now, just don't validate paths with replacement # patterns in them. if path =~ /%./ # Mark that we're expandable. @expandable = true else unless FileTest.exists?(path) raise FileServerError, "%s does not exist" % path end unless FileTest.directory?(path) raise FileServerError, "%s is not a directory" % path end unless FileTest.readable?(path) raise FileServerError, "%s is not readable" % path end @expandable = false end @path = path end # Return the current values for the object. def properties(obj) obj.retrieve.inject({}) { |props, ary| props[ary[0].name] = ary[1]; props } end # Retrieve a specific directory relative to a mount point. # If they pass in a client, then expand as necessary. def subdir(dir = nil, client = nil) basedir = self.path(client) dirname = if dir File.join(basedir, dir.split("/").join(File::SEPARATOR)) else basedir end dirname end def sync(path) @@syncs[path] ||= Sync.new @@syncs[path] end def to_s "mount[%s]" % @name end # Verify our configuration is valid. This should really check to # make sure at least someone will be allowed, but, eh. def valid? if name == MODULES return @path.nil? else return ! @path.nil? end end # Return a new mount with the same properties as +self+, except # with a different name and path. def copy(name, path) result = self.clone result.path = path result.instance_variable_set(:@name, name) return result end end end end diff --git a/lib/puppet/network/handler/resource.rb b/lib/puppet/network/handler/resource.rb index 0fcd694fb..c96bdf6a6 100755 --- a/lib/puppet/network/handler/resource.rb +++ b/lib/puppet/network/handler/resource.rb @@ -1,194 +1,187 @@ require 'puppet' require 'puppet/network/handler' # Serve Puppet elements. Useful for querying, copying, and, um, other stuff. class Puppet::Network::Handler class Resource < Handler desc "An interface for interacting with client-based resources that can be used for querying or managing remote machines without using Puppet's central server tools. The ``describe`` and ``list`` methods return TransBuckets containing TransObject instances (``describe`` returns a single TransBucket), and the ``apply`` method accepts a TransBucket of TransObjects and applies them locally. " attr_accessor :local @interface = XMLRPC::Service::Interface.new("resource") { |iface| iface.add_method("string apply(string, string)") iface.add_method("string describe(string, string, array, array)") iface.add_method("string list(string, array, string)") } side :client # Apply a TransBucket as a transaction. def apply(bucket, format = "yaml", client = nil, clientip = nil) unless local? begin case format when "yaml": bucket = YAML::load(Base64.decode64(bucket)) else raise Puppet::Error, "Unsupported format '%s'" % format end rescue => detail raise Puppet::Error, "Could not load YAML TransBucket: %s" % detail end end config = bucket.to_configuration # And then apply the configuration. This way we're reusing all # the code in there. It should probably just be separated out, though. transaction = config.apply # And then clean up config.clear(true) # It'd be nice to return some kind of report, but... at this point # we have no such facility. return "success" end - # Describe a given object. This returns the 'is' values for every property - # available on the object type. + # Describe a given resource. This returns the 'is' values for every property + # available on the resource type. def describe(type, name, retrieve = nil, ignore = [], format = "yaml", client = nil, clientip = nil) Puppet.info "Describing %s[%s]" % [type.to_s.capitalize, name] @local = true unless client typeklass = nil unless typeklass = Puppet.type(type) raise Puppet::Error, "Puppet type %s is unsupported" % type end - obj = nil - retrieve ||= :all ignore ||= [] - if obj = typeklass[name] - obj[:check] = retrieve - else - begin - obj = typeklass.create(:name => name, :check => retrieve) - rescue Puppet::Error => detail - raise Puppet::Error, "%s[%s] could not be created: %s" % - [type, name, detail] - end + begin + resource = typeklass.create(:name => name, :check => retrieve) + rescue Puppet::Error => detail + raise Puppet::Error, "%s[%s] could not be created: %s" % + [type, name, detail] end - unless obj + unless resource raise XMLRPC::FaultException.new( 1, "Could not create %s[%s]" % [type, name] ) end - trans = obj.to_trans + trans = resource.to_trans # Now get rid of any attributes they specifically don't want ignore.each do |st| if trans.include? st trans.delete(st) end end # And get rid of any attributes that are nil trans.each do |attr, value| if value.nil? trans.delete(attr) end end unless @local case format when "yaml": trans = Base64.encode64(YAML::dump(trans)) else raise XMLRPC::FaultException.new( 1, "Unavailable config format %s" % format ) end end return trans end # Create a new fileserving module. def initialize(hash = {}) if hash[:Local] @local = hash[:Local] else @local = false end end # List all of the elements of a given type. def list(type, ignore = [], base = nil, format = "yaml", client = nil, clientip = nil) @local = true unless client typeklass = nil unless typeklass = Puppet.type(type) raise Puppet::Error, "Puppet type %s is unsupported" % type end # They can pass in false ignore ||= [] ignore = [ignore] unless ignore.is_a? Array bucket = Puppet::TransBucket.new bucket.type = typeklass.name - typeklass.instances.each do |obj| - next if ignore.include? obj.name + typeklass.instances.each do |resource| + next if ignore.include? resource.name - #object = Puppet::TransObject.new(obj.name, typeklass.name) - bucket << obj.to_trans + bucket << resource.to_trans end unless @local case format when "yaml": begin bucket = Base64.encode64(YAML::dump(bucket)) rescue => detail Puppet.err detail raise XMLRPC::FaultException.new( 1, detail.to_s ) end else raise XMLRPC::FaultException.new( 1, "Unavailable config format %s" % format ) end end return bucket end private def authcheck(file, mount, client, clientip) unless mount.allowed?(client, clientip) mount.warning "%s cannot access %s" % [client, file] raise Puppet::AuthorizationError, "Cannot access %s" % mount end end # Deal with ignore parameters. def handleignore(children, path, ignore) ignore.each { |ignore| Dir.glob(File.join(path,ignore), File::FNM_DOTMATCH) { |match| children.delete(File.basename(match)) } } return children end def to_s "resource" end end end diff --git a/lib/puppet/node/configuration.rb b/lib/puppet/node/configuration.rb index 061e83f4b..e131839df 100644 --- a/lib/puppet/node/configuration.rb +++ b/lib/puppet/node/configuration.rb @@ -1,478 +1,494 @@ require 'puppet/indirector' require 'puppet/external/gratr/digraph' # This class models a node configuration. It is the thing # meant to be passed from server to client, and it contains all # of the information in the configuration, including the resources # and the relationships between them. class Puppet::Node::Configuration < Puppet::PGraph extend Puppet::Indirector indirects :configuration, :terminus_class => :compiler # The host name this is a configuration for. attr_accessor :name # The configuration version. Used for testing whether a configuration # is up to date. attr_accessor :version # How long this configuration took to retrieve. Used for reporting stats. attr_accessor :retrieval_duration # How we should extract the configuration for sending to the client. attr_reader :extraction_format # We need the ability to set this externally, so we can yaml-dump the # configuration. attr_accessor :edgelist_class # Whether this is a host configuration, which behaves very differently. # In particular, reports are sent, graphs are made, and state is # stored in the state database. If this is set incorrectly, then you often # end up in infinite loops, because configurations are used to make things # that the host configuration needs. attr_accessor :host_config # Whether this graph is another configuration's relationship graph. # We don't want to accidentally create a relationship graph for another # relationship graph. attr_accessor :is_relationship_graph # Whether this configuration was retrieved from the cache, which affects # whether it is written back out again. attr_accessor :from_cache # Add classes to our class list. def add_class(*classes) classes.each do |klass| @classes << klass end # Add the class names as tags, too. tag(*classes) end # Add one or more resources to our graph and to our resource table. def add_resource(*resources) resources.each do |resource| unless resource.respond_to?(:ref) raise ArgumentError, "Can only add objects that respond to :ref" end ref = resource.ref + if @resource_table.include?(ref) raise ArgumentError, "Resource %s is already defined" % ref else @resource_table[ref] = resource end + + # If the name and title differ, set up an alias + if ! resource.is_a?(Puppet::Type::Component) and resource.respond_to?(:title) and resource.name != resource.title + if obj = resource(resource.class.name, resource.name) + raise Puppet::Error, "%s already exists with name %s" % [obj.title, self.name] if resource.class.isomorphic? + else + self.alias(resource, resource.name) + end + end + resource.configuration = self unless is_relationship_graph add_vertex!(resource) end end # Create an alias for a resource. def alias(resource, name) resource.ref =~ /^(.+)\[/ newref = "%s[%s]" % [$1 || resource.class.name, name] raise(ArgumentError, "Cannot alias %s to %s; resource %s already exists" % [resource.ref, name, newref]) if @resource_table[newref] @resource_table[newref] = resource @aliases[resource.ref] << newref end # Apply our configuration to the local host. Valid options # are: # :tags - set the tags that restrict what resources run # during the transaction # :ignoreschedules - tell the transaction to ignore schedules # when determining the resources to run def apply(options = {}) @applying = true Puppet::Util::Storage.load if host_config? transaction = Puppet::Transaction.new(self) transaction.tags = options[:tags] if options[:tags] transaction.ignoreschedules = true if options[:ignoreschedules] transaction.addtimes :config_retrieval => @retrieval_duration begin transaction.evaluate rescue Puppet::Error => detail Puppet.err "Could not apply complete configuration: %s" % detail rescue => detail if Puppet[:trace] puts detail.backtrace end Puppet.err "Got an uncaught exception of type %s: %s" % [detail.class, detail] ensure # Don't try to store state unless we're a host config # too recursive. Puppet::Util::Storage.store if host_config? end if block_given? yield transaction end if host_config and (Puppet[:report] or Puppet[:summarize]) transaction.send_report end return transaction ensure @applying = false cleanup() if defined? transaction and transaction transaction.cleanup end end # Are we in the middle of applying the configuration? def applying? @applying end def clear(remove_resources = true) super() # We have to do this so that the resources clean themselves up. @resource_table.values.each { |resource| resource.remove } if remove_resources @resource_table.clear if defined?(@relationship_graph) and @relationship_graph @relationship_graph.clear(false) @relationship_graph = nil end end def classes @classes.dup end # Create an implicit resource, meaning that it will lose out # to any explicitly defined resources. This method often returns # nil. # The quirk of this method is that it's not possible to create # an implicit resource before an explicit resource of the same name, # because all explicit resources are created before any generate() # methods are called on the individual resources. Thus, this # method can safely just check if an explicit resource already exists # and toss this implicit resource if so. def create_implicit_resource(type, options) unless options.include?(:implicit) options[:implicit] = true end # This will return nil if an equivalent explicit resource already exists. # When resource classes no longer retain references to resource instances, # this will need to be modified to catch that conflict and discard # implicit resources. if resource = create_resource(type, options) resource.implicit = true return resource else return nil end end # Create a new resource and register it in the configuration. def create_resource(type, options) unless klass = Puppet::Type.type(type) raise ArgumentError, "Unknown resource type %s" % type end + if options.is_a?(Puppet::TransObject) + options.configuration = self + else + options[:configuration] = self + end return unless resource = klass.create(options) @transient_resources << resource if applying? add_resource(resource) if @relationship_graph @relationship_graph.add_resource(resource) unless @relationship_graph.resource(resource.ref) end resource end # Make sure we support the requested extraction format. def extraction_format=(value) unless respond_to?("extract_to_%s" % value) raise ArgumentError, "Invalid extraction format %s" % value end @extraction_format = value end # Turn our configuration graph into whatever the client is expecting. def extract send("extract_to_%s" % extraction_format) end # Create the traditional TransBuckets and TransObjects from our configuration # graph. This will hopefully be deprecated soon. def extract_to_transportable top = nil current = nil buckets = {} unless main = vertices.find { |res| res.type == "class" and res.title == :main } raise Puppet::DevError, "Could not find 'main' class; cannot generate configuration" end # Create a proc for examining edges, which we'll use to build our tree # of TransBuckets and TransObjects. bucket = nil edges = proc do |edge| # The sources are always non-builtins. source, target = edge.source, edge.target unless tmp = buckets[source.to_s] if tmp = buckets[source.to_s] = source.to_trans bucket = tmp else # This is because virtual resources return nil. If a virtual # container resource contains realized resources, we still need to get # to them. So, we keep a reference to the last valid bucket # we returned and use that if the container resource is virtual. end end bucket = tmp || bucket if child = target.to_trans unless bucket raise "No bucket created for %s" % source end bucket.push child # It's important that we keep a reference to any TransBuckets we've created, so # we don't create multiple buckets for children. unless target.builtin? buckets[target.to_s] = child end end end dfs(:start => main, :examine_edge => edges) unless main raise Puppet::DevError, "Could not find 'main' class; cannot generate configuration" end # Retrieve the bucket for the top-level scope and set the appropriate metadata. unless result = buckets[main.to_s] # This only happens when the configuration is entirely empty. result = buckets[main.to_s] = main.to_trans end result.classes = classes # Clear the cache to encourage the GC buckets.clear return result end # Make sure all of our resources are "finished". def finalize @resource_table.values.each { |resource| resource.finish } write_graph(:resources) end def host_config? host_config || false end def initialize(name = nil) super() @name = name if name @extraction_format ||= :transportable @tags = [] @classes = [] @resource_table = {} @transient_resources = [] @applying = false @relationship_graph = nil @aliases = Hash.new { |hash, key| hash[key] = [] } if block_given? yield(self) finalize() end end # Create a graph of all of the relationships in our configuration. def relationship_graph raise(Puppet::DevError, "Tried get a relationship graph for a relationship graph") if self.is_relationship_graph unless defined? @relationship_graph and @relationship_graph # It's important that we assign the graph immediately, because # the debug messages below use the relationships in the # relationship graph to determine the path to the resources # spitting out the messages. If this is not set, # then we get into an infinite loop. @relationship_graph = Puppet::Node::Configuration.new @relationship_graph.host_config = host_config? @relationship_graph.is_relationship_graph = true # First create the dependency graph self.vertices.each do |vertex| @relationship_graph.add_vertex! vertex vertex.builddepends.each do |edge| @relationship_graph.add_edge!(edge) end end # Lastly, add in any autorequires @relationship_graph.vertices.each do |vertex| vertex.autorequire.each do |edge| unless @relationship_graph.edge?(edge.source, edge.target) # don't let automatic relationships conflict with manual ones. unless @relationship_graph.edge?(edge.target, edge.source) vertex.debug "Autorequiring %s" % [edge.source] @relationship_graph.add_edge!(edge) else vertex.debug "Skipping automatic relationship with %s" % (edge.source == vertex ? edge.target : edge.source) end end end end @relationship_graph.write_graph(:relationships) # Then splice in the container information @relationship_graph.splice!(self, Puppet::Type::Component) @relationship_graph.write_graph(:expanded_relationships) end @relationship_graph end # Remove the resource from our configuration. Notice that we also call # 'remove' on the resource, at least until resource classes no longer maintain # references to the resource instances. def remove_resource(*resources) resources.each do |resource| @resource_table.delete(resource.ref) @aliases[resource.ref].each { |res_alias| @resource_table.delete(res_alias) } @aliases[resource.ref].clear remove_vertex!(resource) if vertex?(resource) @relationship_graph.remove_vertex!(resource) if @relationship_graph and @relationship_graph.vertex?(resource) resource.remove end end # Look a resource up by its reference (e.g., File[/etc/passwd]). def resource(type, title = nil) if title ref = "%s[%s]" % [type.to_s.capitalize, title] else ref = type end if resource = @resource_table[ref] return resource elsif defined?(@relationship_graph) and @relationship_graph @relationship_graph.resource(ref) end end # Return an array of the currently-defined resources. def resources @resource_table.keys end # Add a tag. def tag(*names) names.each do |name| name = name.to_s @tags << name unless @tags.include?(name) if name.include?("::") name.split("::").each do |sub| @tags << sub unless @tags.include?(sub) end end end nil end # Return the list of tags. def tags @tags.dup end # Convert our configuration into a RAL configuration. def to_ral to_configuration :to_type end # Turn our parser configuration into a transportable configuration. def to_transportable to_configuration :to_transobject end # Produce the graph files if requested. def write_graph(name) # We only want to graph the main host configuration. return unless host_config? return unless Puppet[:graph] Puppet.settings.use(:graphing) file = File.join(Puppet[:graphdir], "%s.dot" % name.to_s) File.open(file, "w") { |f| f.puts to_dot("name" => name.to_s.capitalize) } end # LAK:NOTE We cannot yaml-dump the class in the edgelist_class, because classes cannot be # dumped by default, nor does yaml-dumping # the edge-labels work at this point (I don't # know why). # Neither of these matters right now, but I suppose it could at some point. # We also have to have the vertex_dict dumped after the resource table, because yaml can't # seem to handle the output of yaml-dumping the vertex_dict. def to_yaml_properties props = instance_variables.reject { |v| %w{@edgelist_class @edge_labels @vertex_dict}.include?(v) } props << "@vertex_dict" props end private def cleanup unless @transient_resources.empty? remove_resource(*@transient_resources) @transient_resources.clear @relationship_graph = nil end end # An abstracted method for converting one configuration into another type of configuration. # This pretty much just converts all of the resources from one class to another, using # a conversion method. def to_configuration(convert) result = self.class.new(self.name) vertices.each do |resource| next if resource.respond_to?(:virtual?) and resource.virtual? result.add_resource resource.send(convert) end message = convert.to_s.gsub "_", " " edges.each do |edge| # Skip edges between virtual resources. next if edge.source.respond_to?(:virtual?) and edge.source.virtual? next if edge.target.respond_to?(:virtual?) and edge.target.virtual? unless source = result.resource(edge.source.ref) raise Puppet::DevError, "Could not find vertex for %s when converting %s" % [edge.source.ref, message] end unless target = result.resource(edge.target.ref) raise Puppet::DevError, "Could not find vertex for %s when converting %s" % [edge.target.ref, message] end result.add_edge!(source, target, edge.label) end result.add_class *self.classes result.tag(*self.tags) return result end end diff --git a/lib/puppet/type.rb b/lib/puppet/type.rb index 02e04653c..3f69bc8b1 100644 --- a/lib/puppet/type.rb +++ b/lib/puppet/type.rb @@ -1,429 +1,417 @@ require 'puppet' require 'puppet/util/log' require 'puppet/event' require 'puppet/util/metric' require 'puppet/property' require 'puppet/parameter' require 'puppet/util' require 'puppet/util/autoload' require 'puppet/metatype/manager' require 'puppet/util/errors' require 'puppet/util/log_paths' require 'puppet/util/logging' # see the bottom of the file for the rest of the inclusions module Puppet class Type include Puppet::Util include Puppet::Util::Errors include Puppet::Util::LogPaths include Puppet::Util::Logging # Nearly all of the code in this class is stored in files in the # metatype/ directory. This is a temporary measure until I get a chance # to refactor this class entirely. There's still more simplification to # do, but this works for now. require 'puppet/metatype/attributes' require 'puppet/metatype/closure' require 'puppet/metatype/container' require 'puppet/metatype/evaluation' require 'puppet/metatype/instances' require 'puppet/metatype/metaparams' require 'puppet/metatype/providers' require 'puppet/metatype/relationships' require 'puppet/metatype/schedules' require 'puppet/metatype/tags' # Types (which map to resources in the languages) are entirely composed of # attribute value pairs. Generally, Puppet calls any of these things an # 'attribute', but these attributes always take one of three specific # forms: parameters, metaparams, or properties. # In naming methods, I have tried to consistently name the method so # that it is clear whether it operates on all attributes (thus has 'attr' in # the method name, or whether it operates on a specific type of attributes. attr_accessor :file, :line # The configuration that this resource is stored in. attr_accessor :configuration attr_writer :title attr_writer :noop include Enumerable # class methods dealing with Type management public # the Type class attribute accessors class << self attr_reader :name attr_accessor :self_refresh include Enumerable, Puppet::Util::ClassGen include Puppet::MetaType::Manager include Puppet::Util include Puppet::Util::Logging end # all of the variables that must be initialized for each subclass def self.initvars # all of the instances of this class @objects = Hash.new @aliases = Hash.new @providers = Hash.new @defaults = {} unless defined? @parameters @parameters = [] end @validproperties = {} @properties = [] @parameters = [] @paramhash = {} @attr_aliases = {} @paramdoc = Hash.new { |hash,key| if key.is_a?(String) key = key.intern end if hash.include?(key) hash[key] else "Param Documentation for %s not found" % key end } unless defined? @doc @doc = "" end end def self.to_s if defined? @name "Puppet::Type::" + @name.to_s.capitalize else super end end # Create a block to validate that our object is set up entirely. This will # be run before the object is operated on. def self.validate(&block) define_method(:validate, &block) #@validate = block end # create a log at specified level def log(msg) Puppet::Util::Log.create( :level => @parameters[:loglevel].value, :message => msg, :source => self ) end # instance methods related to instance intrinsics # e.g., initialize() and name() public def initvars @evalcount = 0 @tags = [] # callbacks are per object and event @callbacks = Hash.new { |chash, key| chash[key] = {} } # properties and parameters are treated equivalently from the outside: # as name-value pairs (using [] and []=) # internally, however, parameters are merely a hash, while properties # point to Property objects # further, the lists of valid properties and parameters are defined # at the class level unless defined? @parameters @parameters = {} end # set defalts @noop = false # keeping stats for the total number of changes, and how many were # completely sync'ed # this isn't really sufficient either, because it adds lots of special # cases such as failed changes # it also doesn't distinguish between changes from the current transaction # vs. changes over the process lifetime @totalchanges = 0 @syncedchanges = 0 @failedchanges = 0 @inited = true end # initialize the type instance def initialize(hash) unless defined? @inited self.initvars end namevar = self.class.namevar orighash = hash # If we got passed a transportable object, we just pull a bunch of info # directly from it. This is the main object instantiation mechanism. if hash.is_a?(Puppet::TransObject) #self[:name] = hash[:name] [:file, :line, :tags, :configuration].each { |getter| if hash.respond_to?(getter) setter = getter.to_s + "=" if val = hash.send(getter) self.send(setter, val) end end } # XXX This will need to change when transobjects change to titles. @title = hash.name hash = hash.to_hash else if hash[:title] @title = hash[:title] hash.delete(:title) end end # Before anything else, set our parent if it was included if hash.include?(:parent) @parent = hash[:parent] hash.delete(:parent) end # Munge up the namevar stuff so we only have one value. hash = self.argclean(hash) # Let's do the name first, because some things need to happen once # we have the name but before anything else attrs = self.class.allattrs if hash.include?(namevar) #self.send(namevar.to_s + "=", hash[namevar]) self[namevar] = hash[namevar] hash.delete(namevar) if attrs.include?(namevar) attrs.delete(namevar) else self.devfail "My namevar isn't a valid attribute...?" end else self.devfail "I was not passed a namevar" end - # If the name and title differ, set up an alias - if self.configuration and (self.name != self.title) - if obj = self.configuration.resource(self.class.name, self.name) - if self.class.isomorphic? - raise Puppet::Error, "%s already exists with name %s" % - [obj.title, self.name] - end - else - self.configuration.alias(self, self.name) - end - end - if hash.include?(:provider) self[:provider] = hash[:provider] hash.delete(:provider) else setdefaults(:provider) end # This is all of our attributes except the namevar. attrs.each { |attr| if hash.include?(attr) begin self[attr] = hash[attr] rescue ArgumentError, Puppet::Error, TypeError raise rescue => detail error = Puppet::DevError.new( "Could not set %s on %s: %s" % [attr, self.class.name, detail] ) error.set_backtrace(detail.backtrace) raise error end hash.delete attr end } # Set all default values. self.setdefaults if hash.length > 0 self.debug hash.inspect self.fail("Class %s does not accept argument(s) %s" % [self.class.name, hash.keys.join(" ")]) end if self.respond_to?(:validate) self.validate end end # Set up all of our autorequires. def finish # Scheduling has to be done when the whole config is instantiated, so # that file order doesn't matter in finding them. self.schedule # Make sure all of our relationships are valid. Again, must be done # when the entire configuration is instantiated. self.class.relationship_params.collect do |klass| if param = @parameters[klass.name] param.validate_relationship end end.flatten.reject { |r| r.nil? } end # Return a cached value def cached(name) Puppet::Util::Storage.cache(self)[name] #@cache[name] ||= nil end # Cache a value def cache(name, value) Puppet::Util::Storage.cache(self)[name] = value #@cache[name] = value end # def set(name, value) # send(name.to_s + "=", value) # end # # def get(name) # send(name) # end # For now, leave the 'name' method functioning like it used to. Once 'title' # works everywhere, I'll switch it. def name return self[:name] end # Look up our parent in the configuration, if we have one. def parent return nil unless configuration unless defined?(@parent) # This is kinda weird. if implicit? parents = configuration.relationship_graph.adjacent(self, :direction => :in) else parents = configuration.adjacent(self, :direction => :in) end if parents # We should never have more than one parent, so let's just ignore # it if we happen to. @parent = parents.shift else @parent = nil end end @parent end # Return the "type[name]" style reference. def ref "%s[%s]" % [self.class.name.to_s.capitalize, self.title] end def self_refresh? self.class.self_refresh end # Mark that we're purging. def purging @purging = true end # Is this resource being purged? Used by transactions to forbid # deletion when there are dependencies. def purging? if defined? @purging @purging else false end end # Retrieve the title of an object. If no title was set separately, # then use the object's name. def title unless defined? @title and @title namevar = self.class.namevar if self.class.validparameter?(namevar) @title = self[:name] elsif self.class.validproperty?(namevar) @title = self.should(namevar) else self.devfail "Could not find namevar %s for %s" % [namevar, self.class.name] end end return @title end # convert to a string def to_s self.ref end # Convert to a transportable object def to_trans(ret = true) trans = TransObject.new(self.title, self.class.name) values = retrieve() values.each do |name, value| trans[name.name] = value end @parameters.each do |name, param| # Avoid adding each instance name as both the name and the namevar next if param.class.isnamevar? and param.value == self.title # We've already got property values next if param.is_a?(Puppet::Property) trans[name] = param.value end trans.tags = self.tags # FIXME I'm currently ignoring 'parent' and 'path' return trans end end # Puppet::Type end require 'puppet/propertychange' require 'puppet/provider' require 'puppet/type/component' require 'puppet/type/pfile' require 'puppet/type/pfilebucket' require 'puppet/type/tidy' diff --git a/lib/puppet/type/pfile.rb b/lib/puppet/type/pfile.rb index 723fecde2..d6dfd86e0 100644 --- a/lib/puppet/type/pfile.rb +++ b/lib/puppet/type/pfile.rb @@ -1,1177 +1,1171 @@ require 'digest/md5' require 'cgi' require 'etc' require 'uri' require 'fileutils' require 'puppet/network/handler' require 'puppet/util/diff' module Puppet newtype(:file) do include Puppet::Util::MethodHelper @doc = "Manages local files, including setting ownership and permissions, creation of both files and directories, and retrieving entire files from remote servers. As Puppet matures, it expected that the ``file`` resource will be used less and less to manage content, and instead native resources will be used to do so. If you find that you are often copying files in from a central location, rather than using native resources, please contact Reductive Labs and we can hopefully work with you to develop a native resource to support what you are doing." newparam(:path) do desc "The path to the file to manage. Must be fully qualified." isnamevar validate do |value| unless value =~ /^#{File::SEPARATOR}/ raise Puppet::Error, "File paths must be fully qualified" end end end newparam(:backup) do desc "Whether files should be backed up before being replaced. The preferred method of backing files up is via a ``filebucket``, which stores files by their MD5 sums and allows easy retrieval without littering directories with backups. You can specify a local filebucket or a network-accessible server-based filebucket by setting ``backup => bucket-name``. Alternatively, if you specify any value that begins with a ``.`` (e.g., ``.puppet-bak``), then Puppet will use copy the file in the same directory with that value as the extension of the backup. Setting ``backup => false`` disables all backups of the file in question. Puppet automatically creates a local filebucket named ``puppet`` and defaults to backing up there. To use a server-based filebucket, you must specify one in your configuration:: filebucket { main: server => puppet } The ``puppetmasterd`` daemon creates a filebucket by default, so you can usually back up to your main server with this configuration. Once you've described the bucket in your configuration, you can use it in any file:: file { \"/my/file\": source => \"/path/in/nfs/or/something\", backup => main } This will back the file up to the central server. At this point, the benefits of using a filebucket are that you do not have backup files lying around on each of your machines, a given version of a file is only backed up once, and you can restore any given file manually, no matter how old. Eventually, transactional support will be able to automatically restore filebucketed files. " defaultto do - if resource.configuration - # Make sure the default file bucket exists. - obj = resource.configuration.resource(:filebucket, "puppet") || resource.configuration.create_resource(:filebucket, :name => "puppet") - obj.bucket + if resource.configuration and bucket_resource = resource.configuration.resource(:filebucket, "puppet") + bucket_resource.bucket else nil end end munge do |value| # I don't really know how this is happening. if value.is_a?(Array) value = value.shift end case value when false, "false", :false: false when true, "true", ".puppet-bak", :true: ".puppet-bak" when /^\./ value when String: # We can't depend on looking this up right now, # we have to do it after all of the objects # have been instantiated. - if bucketobj = Puppet::Type.type(:filebucket)[value] + if @resource.configuration and bucketobj = @resource.configuration.resource(:filebucket, value) @resource.bucket = bucketobj.bucket bucketobj.title else # Set it to the string; finish() turns it into a # filebucket. @resource.bucket = value value end when Puppet::Network::Client.client(:Dipper): @resource.bucket = value value.name else self.fail "Invalid backup type %s" % value.inspect end end end newparam(:recurse) do desc "Whether and how deeply to do recursive management." newvalues(:true, :false, :inf, /^[0-9]+$/) # Replace the validation so that we allow numbers in # addition to string representations of them. validate { |arg| } munge do |value| newval = super(value) case newval when :true, :inf: true when :false: false when Integer, Fixnum, Bignum: value when /^\d+$/: Integer(value) else raise ArgumentError, "Invalid recurse value %s" % value.inspect end end end newparam(:replace, :boolean => true) do desc "Whether or not to replace a file that is sourced but exists. This is useful for using file sources purely for initialization." newvalues(:true, :false) aliasvalue(:yes, :true) aliasvalue(:no, :false) defaultto :true end newparam(:force, :boolean => true) do desc "Force the file operation. Currently only used when replacing directories with links." newvalues(:true, :false) defaultto false end newparam(:ignore) do desc "A parameter which omits action on files matching specified patterns during recursion. Uses Ruby's builtin globbing engine, so shell metacharacters are fully supported, e.g. ``[a-z]*``. Matches that would descend into the directory structure are ignored, e.g., ``*/*``." defaultto false validate do |value| unless value.is_a?(Array) or value.is_a?(String) or value == false self.devfail "Ignore must be a string or an Array" end end end newparam(:links) do desc "How to handle links during file actions. During file copying, ``follow`` will copy the target file instead of the link, ``manage`` will copy the link itself, and ``ignore`` will just pass it by. When not copying, ``manage`` and ``ignore`` behave equivalently (because you cannot really ignore links entirely during local recursion), and ``follow`` will manage the file to which the link points." newvalues(:follow, :manage, :ignore) # :ignore and :manage behave equivalently on local files, # but don't copy remote links defaultto :ignore end newparam(:purge, :boolean => true) do desc "Whether unmanaged files should be purged. If you have a filebucket configured the purged files will be uploaded, but if you do not, this will destroy data. Only use this option for generated files unless you really know what you are doing. This option only makes sense when recursively managing directories. Note that when using ``purge`` with ``source``, Puppet will purge any files that are not on the remote system." defaultto :false newvalues(:true, :false) end newparam(:sourceselect) do desc "Whether to copy all valid sources, or just the first one. This parameter is only used in recursive copies; by default, the first valid source is the only one used as a recursive source, but if this parameter is set to ``all``, then all valid sources will have all of their contents copied to the local host, and for sources that have the same file, the source earlier in the list will be used." defaultto :first newvalues(:first, :all) end attr_accessor :bucket # Autorequire any parent directories. autorequire(:file) do if self[:path] File.dirname(self[:path]) else Puppet.err "no path for %s, somehow; cannot setup autorequires" % self.ref nil end end # Autorequire the owner and group of the file. {:user => :owner, :group => :group}.each do |type, property| autorequire(type) do if @parameters.include?(property) # The user/group property automatically converts to IDs next unless should = @parameters[property].shouldorig val = should[0] if val.is_a?(Integer) or val =~ /^\d+$/ nil else val end end end end CREATORS = [:content, :source, :target] validate do count = 0 CREATORS.each do |param| count += 1 if self.should(param) end if count > 1 self.fail "You cannot specify more than one of %s" % CREATORS.collect { |p| p.to_s}.join(", ") end end - def self.[](path) - return nil unless path - super(path.gsub(/\/+/, '/').sub(/\/$/, '')) - end - # List files, but only one level deep. def self.instances(base = "/") unless FileTest.directory?(base) return [] end files = [] Dir.entries(base).reject { |e| e == "." or e == ".." }.each do |name| path = File.join(base, name) if obj = self[path] obj[:check] = :all files << obj else files << self.create( :name => path, :check => :all ) end end files end @depthfirst = false def argument?(arg) @arghash.include?(arg) end # Determine the user to write files as. def asuser if self.should(:owner) and ! self.should(:owner).is_a?(Symbol) writeable = Puppet::Util::SUIDManager.asuser(self.should(:owner)) { FileTest.writable?(File.dirname(self[:path])) } # If the parent directory is writeable, then we execute # as the user in question. Otherwise we'll rely on # the 'owner' property to do things. if writeable asuser = self.should(:owner) end end return asuser end # We have to do some extra finishing, to retrieve our bucket if # there is one. def finish # Let's cache these values, since there should really only be # a couple of these buckets @@filebuckets ||= {} + super + # Look up our bucket, if there is one - if bucket = self.bucket - case bucket - when String: - if obj = @@filebuckets[bucket] - # This sets the @value on :backup, too - self.bucket = obj - elsif bucket == "puppet" - obj = Puppet::Network::Client.client(:Dipper).new( - :Path => Puppet[:clientbucketdir] - ) - self.bucket = obj - @@filebuckets[bucket] = obj - elsif obj = Puppet::Type.type(:filebucket).bucket(bucket) - @@filebuckets[bucket] = obj - self.bucket = obj - else - self.fail "Could not find filebucket %s" % bucket - end - when Puppet::Network::Client.client(:Dipper): # things are hunky-dorey - else - self.fail "Invalid bucket type %s" % bucket.class - end + return unless bucket_name = self.bucket + + return if bucket_name.is_a?(Puppet::Network::Client.dipper) + + self.fail("Invalid bucket type %s" % bucket_name.class) unless bucket_name.is_a?(String) + + return self.bucket = bucket if bucket = @@filebuckets[bucket_name] + + if configuration and bucket_resource = configuration.resource(:filebucket, bucket_name) + @@filebuckets[bucket_name] = bucket_resource.bucket + self.bucket = bucket + return + end + + if bucket_name == "puppet" + puts "Creating default bucket" + bucket_resource = Puppet::Type.type(:filebucket).create_default_resources + self.bucket = bucket_resource.bucket + configuration.add_resource(bucket_resource) if configuration + @@filebuckets[bucket_name] = bucket + else + self.fail "Could not find filebucket '%s'" % bucket_name end - super end # Create any children via recursion or whatever. def eval_generate recurse() end # Deal with backups. def handlebackup(file = nil) # let the path be specified file ||= self[:path] # if they specifically don't want a backup, then just say # we're good unless FileTest.exists?(file) return true end unless self[:backup] return true end case File.stat(file).ftype when "directory": if self[:recurse] # we don't need to backup directories when recurse is on return true else backup = self.bucket || self[:backup] case backup when Puppet::Network::Client.client(:Dipper): notice "Recursively backing up to filebucket" require 'find' Find.find(self[:path]) do |f| if File.file?(f) sum = backup.backup(f) self.info "Filebucketed %s to %s with sum %s" % [f, backup.name, sum] end end return true when String: newfile = file + backup # Just move it, since it's a directory. if FileTest.exists?(newfile) remove_backup(newfile) end begin bfile = file + backup # Ruby 1.8.1 requires the 'preserve' addition, but # later versions do not appear to require it. FileUtils.cp_r(file, bfile, :preserve => true) return true rescue => detail # since they said they want a backup, let's error out # if we couldn't make one self.fail "Could not back %s up: %s" % [file, detail.message] end else self.err "Invalid backup type %s" % backup.inspect return false end end when "file": backup = self.bucket || self[:backup] case backup when Puppet::Network::Client.client(:Dipper): sum = backup.backup(file) self.info "Filebucketed to %s with sum %s" % [backup.name, sum] return true when String: newfile = file + backup if FileTest.exists?(newfile) remove_backup(newfile) end begin # FIXME Shouldn't this just use a Puppet object with # 'source' specified? bfile = file + backup # Ruby 1.8.1 requires the 'preserve' addition, but # later versions do not appear to require it. FileUtils.cp(file, bfile, :preserve => true) return true rescue => detail # since they said they want a backup, let's error out # if we couldn't make one self.fail "Could not back %s up: %s" % [file, detail.message] end else self.err "Invalid backup type %s" % backup.inspect return false end when "link": return true else self.notice "Cannot backup files of type %s" % File.stat(file).ftype return false end end def handleignore(children) return children unless self[:ignore] self[:ignore].each { |ignore| ignored = [] Dir.glob(File.join(self[:path],ignore), File::FNM_DOTMATCH) { |match| ignored.push(File.basename(match)) } children = children - ignored } return children end def initialize(hash) # Store a copy of the arguments for later. tmphash = hash.to_hash # Used for caching clients @clients = {} super # Get rid of any duplicate slashes, and remove any trailing slashes. @title = @title.gsub(/\/+/, "/").sub(/\/$/, "") # Clean out as many references to any file paths as possible. # This was the source of many, many bugs. @arghash = tmphash @arghash.delete(self.class.namevar) [:source, :parent].each do |param| if @arghash.include?(param) @arghash.delete(param) end end @stat = nil end # Build a recursive map of a link source def linkrecurse(recurse) target = @parameters[:target].should method = :lstat if self[:links] == :follow method = :stat end targetstat = nil unless FileTest.exist?(target) return end # Now stat our target targetstat = File.send(method, target) unless targetstat.ftype == "directory" return end # Now that we know our corresponding target is a directory, # change our type self[:ensure] = :directory unless FileTest.readable? target self.notice "Cannot manage %s: permission denied" % self.name return end children = Dir.entries(target).reject { |d| d =~ /^\.+$/ } # Get rid of ignored children if @parameters.include?(:ignore) children = handleignore(children) end added = [] children.each do |file| Dir.chdir(target) do longname = File.join(target, file) # Files know to create directories when recursion # is enabled and we're making links args = { :recurse => recurse, :ensure => longname } if child = self.newchild(file, true, args) added << child end end end added end # Build up a recursive map of what's around right now def localrecurse(recurse) unless FileTest.exist?(self[:path]) and self.stat.directory? #self.info "%s is not a directory; not recursing" % # self[:path] return end unless FileTest.readable? self[:path] self.notice "Cannot manage %s: permission denied" % self.name return end children = Dir.entries(self[:path]) #Get rid of ignored children if @parameters.include?(:ignore) children = handleignore(children) end added = [] children.each { |file| file = File.basename(file) next if file =~ /^\.\.?$/ # skip . and .. options = {:recurse => recurse} if child = self.newchild(file, true, options) added << child end } added end # Create a new file or directory object as a child to the current # object. def newchild(path, local, hash = {}) raise(Puppet::DevError, "File recursion cannot happen without a configuration") unless configuration # make local copy of arguments args = symbolize_options(@arghash) # There's probably a better way to do this, but we don't want # to pass this info on. if v = args[:ensure] v = symbolize(v) args.delete(:ensure) end if path =~ %r{^#{File::SEPARATOR}} self.devfail( "Must pass relative paths to PFile#newchild()" ) else path = File.join(self[:path], path) end args[:path] = path unless hash.include?(:recurse) if args.include?(:recurse) if args[:recurse].is_a?(Integer) args[:recurse] -= 1 # reduce the level of recursion end end end hash.each { |key,value| args[key] = value } child = nil # The child might already exist because 'localrecurse' runs # before 'sourcerecurse'. I could push the override stuff into # a separate method or something, but the work is the same other # than this last bit, so it doesn't really make sense. if child = configuration.resource(:file, path) unless child.parent.object_id == self.object_id self.debug "Not managing more explicit file %s" % path return nil end # This is only necessary for sourcerecurse, because we might have # created the object with different 'should' values than are # set remotely. unless local args.each { |var,value| next if var == :path next if var == :name # behave idempotently unless child.should(var) == value child[var] = value end } end return nil else # create it anew #notice "Creating new file with args %s" % args.inspect args[:parent] = self begin # This method is used by subclasses of :file, so use the class name rather than hard-coding # :file. return nil unless child = configuration.create_implicit_resource(self.class.name, args) rescue => detail puts detail.backtrace self.notice "Cannot manage: %s" % [detail] return nil end end # LAK:FIXME This shouldn't be necessary, but as long as we're # modeling the relationship graph specifically, it is. configuration.relationship_graph.add_edge! self, child return child end # Files handle paths specially, because they just lengthen their # path names, rather than including the full parent's title each # time. def pathbuilder # We specifically need to call the method here, so it looks # up our parent in the configuration graph. if parent = parent() # We only need to behave specially when our parent is also # a file if parent.is_a?(self.class) # Remove the parent file name list = parent.pathbuilder list.pop # remove the parent's path info return list << self.ref else return super end else return [self.ref] end end # Should we be purging? def purge? @parameters.include?(:purge) and (self[:purge] == :true or self[:purge] == "true") end # Recurse into the directory. This basically just calls 'localrecurse' # and maybe 'sourcerecurse', returning the collection of generated # files. def recurse # are we at the end of the recursion? return unless self.recurse? recurse = self[:recurse] # we might have a string, rather than a number if recurse.is_a?(String) if recurse =~ /^[0-9]+$/ recurse = Integer(recurse) else # anything else is infinite recursion recurse = true end end if recurse.is_a?(Integer) recurse -= 1 end children = [] # We want to do link-recursing before normal recursion so that all # of the target stuff gets copied over correctly. if @parameters.include? :target and ret = self.linkrecurse(recurse) children += ret end if ret = self.localrecurse(recurse) children += ret end # These will be files pulled in by the file source sourced = false if @parameters.include?(:source) ret, sourced = self.sourcerecurse(recurse) if ret children += ret end end # The purge check needs to happen after all of the other recursion. if self.purge? children.each do |child| if (sourced and ! sourced.include?(child[:path])) or ! child.managed? child[:ensure] = :absent end end end children end # A simple method for determining whether we should be recursing. def recurse? return false unless @parameters.include?(:recurse) val = @parameters[:recurse].value if val and (val == true or val > 0) return true else return false end end # Remove the old backup. def remove_backup(newfile) if self.class.name == :file and self[:links] != :follow method = :lstat else method = :stat end old = File.send(method, newfile).ftype if old == "directory" raise Puppet::Error, "Will not remove directory backup %s; use a filebucket" % newfile end info "Removing old backup of type %s" % File.send(method, newfile).ftype begin File.unlink(newfile) rescue => detail if Puppet[:trace] puts detail.backtrace end self.err "Could not remove old backup: %s" % detail return false end end # Remove any existing data. This is only used when dealing with # links or directories. def remove_existing(should) return unless s = stat(true) unless handlebackup self.fail "Could not back up; will not replace" end unless should.to_s == "link" return if s.ftype.to_s == should.to_s end case s.ftype when "directory": if self[:force] == :true debug "Removing existing directory for replacement with %s" % should FileUtils.rmtree(self[:path]) else notice "Not removing directory; use 'force' to override" end when "link", "file": debug "Removing existing %s for replacement with %s" % [s.ftype, should] File.unlink(self[:path]) else self.fail "Could not back up files of type %s" % s.ftype end end # a wrapper method to make sure the file exists before doing anything def retrieve unless stat = self.stat(true) self.debug "File does not exist" # If the file doesn't exist but we have a source, then call # retrieve on that property propertyvalues = properties().inject({}) { |hash, property| hash[property] = :absent hash } if @parameters.include?(:source) propertyvalues[:source] = @parameters[:source].retrieve end return propertyvalues end return currentpropvalues() end # This recurses against the remote source and makes sure the local # and remote structures match. It's run after 'localrecurse'. This # method only does anything when its corresponding remote entry is # a directory; in that case, this method creates file objects that # correspond to any contained remote files. def sourcerecurse(recurse) # we'll set this manually as necessary if @arghash.include?(:ensure) @arghash.delete(:ensure) end r = false if recurse unless recurse == 0 r = 1 end end ignore = self[:ignore] result = [] found = [] # Keep track of all the files we found in the source, so we can purge # appropriately. sourced = [] @parameters[:source].should.each do |source| sourceobj, path = uri2obj(source) # okay, we've got our source object; now we need to # build up a local file structure to match the remote # one server = sourceobj.server desc = server.list(path, self[:links], r, ignore) if desc == "" next end # Now create a new child for every file returned in the list. result += desc.split("\n").collect { |line| file, type = line.split("\t") next if file == "/" # skip the listing object name = file.sub(/^\//, '') # This makes sure that the first source *always* wins # for conflicting files. next if found.include?(name) # For directories, keep all of the sources, so that # sourceselect still works as planned. if type == "directory" newsource = @parameters[:source].should.collect do |tmpsource| tmpsource + file end else newsource = source + file end args = {:source => newsource} if type == file args[:recurse] = nil end found << name sourced << File.join(self[:path], name) self.newchild(name, false, args) }.reject {|c| c.nil? } if self[:sourceselect] == :first return [result, sourced] end end return [result, sourced] end # Set the checksum, from another property. There are multiple # properties that modify the contents of a file, and they need the # ability to make sure that the checksum value is in sync. def setchecksum(sum = nil) if @parameters.include? :checksum if sum @parameters[:checksum].checksum = sum else # If they didn't pass in a sum, then tell checksum to # figure it out. currentvalue = @parameters[:checksum].retrieve @parameters[:checksum].checksum = currentvalue end end end # Stat our file. Depending on the value of the 'links' attribute, we # use either 'stat' or 'lstat', and we expect the properties to use the # resulting stat object accordingly (mostly by testing the 'ftype' # value). def stat(refresh = false) method = :stat # Files are the only types that support links if (self.class.name == :file and self[:links] != :follow) or self.class.name == :tidy method = :lstat end path = self[:path] # Just skip them when they don't exist at all. unless FileTest.exists?(path) or FileTest.symlink?(path) @stat = nil return @stat end if @stat.nil? or refresh == true begin @stat = File.send(method, self[:path]) rescue Errno::ENOENT => error @stat = nil rescue Errno::EACCES => error self.warning "Could not stat; permission denied" @stat = nil end end return @stat end # We have to hack this just a little bit, because otherwise we'll get # an error when the target and the contents are created as properties on # the far side. def to_trans(retrieve = true) obj = super if obj[:target] == :notlink obj.delete(:target) end obj end def localfileserver unless defined? @@localfileserver args = { :Local => true, :Mount => { "/" => "localhost" }, :Config => false } @@localfileserver = Puppet::Network::Handler.handler(:fileserver).new(args) end @@localfileserver end def uri2obj(source) sourceobj = FileSource.new path = nil unless source devfail "Got a nil source" end if source =~ /^\// source = "file://localhost/%s" % URI.escape(source) sourceobj.mount = "localhost" sourceobj.local = true end begin uri = URI.parse(URI.escape(source)) rescue => detail self.fail "Could not understand source %s: %s" % [source, detail.to_s] end case uri.scheme when "file": sourceobj.server = localfileserver path = "/localhost" + uri.path when "puppet": # FIXME: We should cache clients by uri.host + uri.port # not by the full source path unless @clients.include?(source) host = uri.host host ||= Puppet[:server] unless Puppet[:name] == "puppet" if host.nil? server = localfileserver else args = { :Server => host } if uri.port args[:Port] = uri.port end server = Puppet::Network::Client.file.new(args) end @clients[source] = server end sourceobj.server = @clients[source] tmp = uri.path if tmp =~ %r{^/(\w+)} sourceobj.mount = $1 path = tmp #path = tmp.sub(%r{^/\w+},'') || "/" else self.fail "Invalid source path %s" % tmp end else self.fail "Got other URL type '%s' from %s" % [uri.scheme, source] end return [sourceobj, path.sub(/\/\//, '/')] end # Write out the file. We open the file correctly, with all of the # uid and mode and such, and then yield the file handle for actual # writing. def write(property, usetmp = true) mode = self.should(:mode) remove_existing(:file) # The temporary file path = nil if usetmp path = self[:path] + ".puppettmp" else path = self[:path] end # As the correct user and group write_if_writable(File.dirname(path)) do f = nil # Open our file with the correct modes if mode Puppet::Util.withumask(000) do f = File.open(path, File::CREAT|File::WRONLY|File::TRUNC, mode) end else f = File.open(path, File::CREAT|File::WRONLY|File::TRUNC) end # Yield it yield f f.flush f.close end # And put our new file in place if usetmp begin File.rename(path, self[:path]) rescue => detail self.err "Could not rename tmp %s for replacing: %s" % [self[:path], detail] ensure # Make sure the created file gets removed if FileTest.exists?(path) File.unlink(path) end end end # make sure all of the modes are actually correct property_fix # And then update our checksum, so the next run doesn't find it. # FIXME This is extra work, because it's going to read the whole # file back in again. self.setchecksum end # Run the block as the specified user if the dir is writeable, else # run it as root (or the current user). def write_if_writable(dir) yield # We're getting different behaviors from different versions of ruby, so... # asroot = true # Puppet::Util::SUIDManager.asuser(asuser(), self.should(:group)) do # if FileTest.writable?(dir) # asroot = false # yield # end # end # # if asroot # yield # end end private # Override the parent method, because we don't want to generate changes # when the file is missing and there is no 'ensure' state. def propertychanges(currentvalues) unless self.stat found = false ([:ensure] + CREATORS).each do |prop| if @parameters.include?(prop) found = true break end end unless found return [] end end super end # There are some cases where all of the work does not get done on # file creation/modification, so we have to do some extra checking. def property_fix properties.each do |thing| next unless [:mode, :owner, :group].include?(thing.name) # Make sure we get a new stat objct self.stat(true) currentvalue = thing.retrieve unless thing.insync?(currentvalue) thing.sync end end end end # Puppet.type(:pfile) # the filesource class can't include the path, because the path # changes for every file instance class FileSource attr_accessor :mount, :root, :server, :local end # We put all of the properties in separate files, because there are so many # of them. The order these are loaded is important, because it determines # the order they are in the property lit. require 'puppet/type/pfile/checksum' require 'puppet/type/pfile/content' # can create the file require 'puppet/type/pfile/source' # can create the file require 'puppet/type/pfile/target' # creates a different type of file require 'puppet/type/pfile/ensure' # can create the file require 'puppet/type/pfile/owner' require 'puppet/type/pfile/group' require 'puppet/type/pfile/mode' require 'puppet/type/pfile/type' end diff --git a/lib/puppet/type/pfilebucket.rb b/lib/puppet/type/pfilebucket.rb index 5ce81858b..27fea6ac8 100755 --- a/lib/puppet/type/pfilebucket.rb +++ b/lib/puppet/type/pfilebucket.rb @@ -1,115 +1,106 @@ module Puppet require 'puppet/network/client' newtype(:filebucket) do @doc = "A repository for backing up files. If no filebucket is defined, then files will be backed up in their current directory, but the filebucket can be either a host- or site-global repository for backing up. It stores files and returns the MD5 sum, which can later be used to retrieve the file if restoration becomes necessary. A filebucket does not do any work itself; instead, it can be specified as the value of *backup* in a **file** object. Currently, filebuckets are only useful for manual retrieval of accidentally removed files (e.g., you look in the log for the md5 sum and retrieve the file with that sum from the filebucket), but when transactions are fully supported filebuckets will be used to undo transactions. You will normally want to define a single filebucket for your whole network and then use that as the default backup location:: # Define the bucket filebucket { main: server => puppet } # Specify it as the default target File { backup => main } Puppetmaster servers create a filebucket by default, so this will work in a default configuration." newparam(:name) do desc "The name of the filebucket." isnamevar end newparam(:server) do desc "The server providing the filebucket. If this is not specified, then the bucket is local and *path* must be specified." end newparam(:port) do desc "The port on which the remote server is listening. Defaults to the normal Puppet port, %s." % Puppet[:masterport] defaultto Puppet[:masterport] end newparam(:path) do desc "The path to the local filebucket. If this is not specified, then the bucket is remote and *server* must be specified." defaultto { Puppet[:clientbucketdir] } end - - # get the actual filebucket object - def self.bucket(name) - if object = self[name] - return object.bucket - else - return nil - end - end # Create a default filebucket. def self.create_default_resources Puppet.debug "Creating default local filebucket" - self.create :name => "puppet", :path => Puppet[:clientbucketdir] + [self.create(:name => "puppet", :path => Puppet[:clientbucketdir])] end def self.instances [] end def bucket unless defined? @bucket mkbucket() end @bucket end def mkbucket if self[:server] begin @bucket = Puppet::Network::Client.client(:Dipper).new( :Server => self[:server], :Port => self[:port] ) rescue => detail self.fail( "Could not create remote filebucket: %s" % detail ) end else begin @bucket = Puppet::Network::Client.client(:Dipper).new( :Path => self[:path] ) rescue => detail if Puppet[:trace] puts detail.backtrace end self.fail( "Could not create local filebucket: %s" % detail ) end end @bucket.name = self.name end end end diff --git a/lib/puppet/type/schedule.rb b/lib/puppet/type/schedule.rb index 72d649584..3f4014cd8 100755 --- a/lib/puppet/type/schedule.rb +++ b/lib/puppet/type/schedule.rb @@ -1,357 +1,357 @@ module Puppet newtype(:schedule) do @doc = "Defined schedules for Puppet. The important thing to understand about how schedules are currently implemented in Puppet is that they can only be used to stop a resource from being applied, they never guarantee that it is applied. Every time Puppet applies its configuration, it will collect the list of resources whose schedule does not eliminate them from running right then, but there is currently no system in place to guarantee that a given resource runs at a given time. If you specify a very restrictive schedule and Puppet happens to run at a time within that schedule, then the resources will get applied; otherwise, that work may never get done. Thus, it behooves you to use wider scheduling (e.g., over a couple of hours) combined with periods and repetitions. For instance, if you wanted to restrict certain resources to only running once, between the hours of two and 4 AM, then you would use this schedule:: schedule { maint: range => \"2 - 4\", period => daily, repeat => 1 } With this schedule, the first time that Puppet runs between 2 and 4 AM, all resources with this schedule will get applied, but they won't get applied again between 2 and 4 because they will have already run once that day, and they won't get applied outside that schedule because they will be outside the scheduled range. Puppet automatically creates a schedule for each valid period with the same name as that period (e.g., hourly and daily). Additionally, a schedule named *puppet* is created and used as the default, with the following attributes:: schedule { puppet: period => hourly, repeat => 2 } This will cause resources to be applied every 30 minutes by default. " newparam(:name) do desc "The name of the schedule. This name is used to retrieve the schedule when assigning it to an object:: schedule { daily: period => daily, range => [2, 4] } exec { \"/usr/bin/apt-get update\": schedule => daily } " isnamevar end newparam(:range) do desc "The earliest and latest that a resource can be applied. This is always a range within a 24 hour period, and hours must be specified in numbers between 0 and 23, inclusive. Minutes and seconds can be provided, using the normal colon as a separator. For instance:: schedule { maintenance: range => \"1:30 - 4:30\" } This is mostly useful for restricting certain resources to being applied in maintenance windows or during off-peak hours." # This is lame; properties all use arrays as values, but parameters don't. # That's going to hurt eventually. validate do |values| values = [values] unless values.is_a?(Array) values.each { |value| unless value.is_a?(String) and value =~ /\d+(:\d+){0,2}\s*-\s*\d+(:\d+){0,2}/ self.fail "Invalid range value '%s'" % value end } end munge do |values| values = [values] unless values.is_a?(Array) ret = [] values.each { |value| range = [] # Split each range value into a hour, minute, second triad value.split(/\s*-\s*/).each { |val| # Add the values as an array. range << val.split(":").collect { |n| n.to_i } } if range.length != 2 self.fail "Invalid range %s" % value end # Make sure the hours are valid [range[0][0], range[1][0]].each do |n| if n < 0 or n > 23 raise ArgumentError, "Invalid hour '%s'" % n end end [range[0][1], range[1][1]].each do |n| if n and (n < 0 or n > 59) raise ArgumentError, "Invalid minute '%s'" % n end end if range[0][0] > range[1][0] self.fail(("Invalid range %s; " % value) + "ranges cannot span days." ) end ret << range } # Now our array of arrays ret end def match?(previous, now) # The lowest-level array is of the hour, minute, second triad # then it's an array of two of those, to present the limits # then it's array of those ranges unless @value[0][0].is_a?(Array) @value = [@value] end @value.each do |value| limits = value.collect do |range| ary = [now.year, now.month, now.day, range[0]] if range[1] ary << range[1] else ary << now.min end if range[2] ary << range[2] else ary << now.sec end time = Time.local(*ary) unless time.hour == range[0] self.devfail( "Incorrectly converted time: %s: %s vs %s" % [time, time.hour, range[0]] ) end time end unless limits[0] < limits[1] self.info( "Assuming upper limit should be that time the next day" ) ary = limits[1].to_a ary[3] += 1 limits[1] = Time.local(*ary) #self.devfail("Lower limit is above higher limit: %s" % # limits.inspect #) end #self.info limits.inspect #self.notice now return now.between?(*limits) end # Else, return false, since our current time isn't between # any valid times return false end end newparam(:periodmatch) do desc "Whether periods should be matched by number (e.g., the two times are in the same hour) or by distance (e.g., the two times are 60 minutes apart)." newvalues(:number, :distance) defaultto :distance end newparam(:period) do desc "The period of repetition for a resource. Choose from among a fixed list of *hourly*, *daily*, *weekly*, and *monthly*. The default is for a resource to get applied every time that Puppet runs, whatever that period is. Note that the period defines how often a given resource will get applied but not when; if you would like to restrict the hours that a given resource can be applied (e.g., only at night during a maintenance window) then use the ``range`` attribute. If the provided periods are not sufficient, you can provide a value to the *repeat* attribute, which will cause Puppet to schedule the affected resources evenly in the period the specified number of times. Take this schedule:: schedule { veryoften: period => hourly, repeat => 6 } This can cause Puppet to apply that resource up to every 10 minutes. At the moment, Puppet cannot guarantee that level of repetition; that is, it can run up to every 10 minutes, but internal factors might prevent it from actually running that often (e.g., long-running Puppet runs will squash conflictingly scheduled runs). See the ``periodmatch`` attribute for tuning whether to match times by their distance apart or by their specific value." newvalues(:hourly, :daily, :weekly, :monthly, :never) @@scale = { :hourly => 3600, :daily => 86400, :weekly => 604800, :monthly => 2592000 } @@methods = { :hourly => :hour, :daily => :day, :monthly => :month, :weekly => proc do |prev, now| prev.strftime("%U") == now.strftime("%U") end } def match?(previous, now) return false if value == :never value = self.value case @resource[:periodmatch] when :number method = @@methods[value] if method.is_a?(Proc) return method.call(previous, now) else # We negate it, because if they're equal we don't run val = now.send(method) != previous.send(method) return val end when :distance scale = @@scale[value] # If the number of seconds between the two times is greater # than the unit of time, we match. We divide the scale # by the repeat, so that we'll repeat that often within # the scale. return (now.to_i - previous.to_i) >= (scale / @resource[:repeat]) end end end newparam(:repeat) do desc "How often the application gets repeated in a given period. Defaults to 1. Must be an integer." defaultto 1 validate do |value| unless value.is_a?(Integer) or value =~ /^\d+$/ raise Puppet::Error, "Repeat must be a number" end # This implicitly assumes that 'periodmatch' is distance -- that # is, if there's no value, we assume it's a valid value. return unless @resource[:periodmatch] if value != 1 and @resource[:periodmatch] != :distance raise Puppet::Error, "Repeat must be 1 unless periodmatch is 'distance', not '%s'" % @resource[:periodmatch] end end munge do |value| unless value.is_a?(Integer) value = Integer(value) end value end def match?(previous, now) true end end def self.instances [] end - def self.create_default_schedules + def self.create_default_resources Puppet.debug "Creating default schedules" resources = [] # Create our default schedule resources << self.create( :name => "puppet", :period => :hourly, :repeat => "2" ) # And then one for every period @parameters.find { |p| p.name == :period }.values.each { |value| - resources << self.create(:schedule, + resources << self.create( :name => value.to_s, :period => value ) } resources end def match?(previous = nil, now = nil) # If we've got a value, then convert it to a Time instance if previous previous = Time.at(previous) end now ||= Time.now # Pull them in order self.class.allattrs.each { |param| if @parameters.include?(param) and @parameters[param].respond_to?(:match?) #self.notice "Trying to match %s" % param return false unless @parameters[param].match?(previous, now) end } # If we haven't returned false, then return true; in other words, # any provided schedules need to all match return true end end end diff --git a/lib/puppet/util/filetype.rb b/lib/puppet/util/filetype.rb index 2f1dabe62..b1b2c1d96 100755 --- a/lib/puppet/util/filetype.rb +++ b/lib/puppet/util/filetype.rb @@ -1,311 +1,311 @@ # Basic classes for reading, writing, and emptying files. Not much # to see here. class Puppet::Util::FileType attr_accessor :loaded, :path, :synced class << self attr_accessor :name include Puppet::Util::ClassGen end # Create a new filetype. def self.newfiletype(name, &block) @filetypes ||= {} klass = genclass(name, :block => block, :prefix => "FileType", :hash => @filetypes ) # Rename the read and write methods, so that we're sure they # maintain the stats. klass.class_eval do # Rename the read method define_method(:real_read, instance_method(:read)) define_method(:read) do begin val = real_read() @loaded = Time.now if val return val.gsub(/# HEADER.*\n/,'') else return "" end rescue Puppet::Error => detail raise rescue => detail if Puppet[:trace] puts detail.backtrace end raise Puppet::Error, "%s could not read %s: %s" % [self.class, @path, detail] end end # And then the write method define_method(:real_write, instance_method(:write)) define_method(:write) do |text| begin val = real_write(text) @synced = Time.now return val rescue Puppet::Error => detail raise rescue => detail if Puppet[:debug] puts detail.backtrace end raise Puppet::Error, "%s could not write %s: %s" % [self.class, @path, detail] end end end end def self.filetype(type) @filetypes[type] end # Back the file up before replacing it. def backup bucket.backup(@path) if FileTest.exists?(@path) end # Pick or create a filebucket to use. def bucket - Puppet::Type.type(:filebucket).mkdefaultbucket.bucket + Puppet::Type.type(:filebucket).create_default_resources[0].bucket end def initialize(path) raise ArgumentError.new("Path is nil") if path.nil? @path = path end # Operate on plain files. newfiletype(:flat) do # Read the file. def read if File.exists?(@path) File.read(@path) else return nil end end # Remove the file. def remove if File.exists?(@path) File.unlink(@path) end end # Overwrite the file. def write(text) backup() File.open(@path, "w") { |f| f.print text; f.flush } end end # Operate on plain files. newfiletype(:ram) do @@tabs = {} def self.clear @@tabs.clear end def initialize(path) super @@tabs[@path] ||= "" end # Read the file. def read Puppet.info "Reading %s from RAM" % @path @@tabs[@path] end # Remove the file. def remove Puppet.info "Removing %s from RAM" % @path @@tabs[@path] = "" end # Overwrite the file. def write(text) Puppet.info "Writing %s to RAM" % @path @@tabs[@path] = text end end # Handle Linux-style cron tabs. newfiletype(:crontab) do def initialize(user) self.path = user end def path=(user) begin @uid = Puppet::Util.uid(user) rescue Puppet::Error => detail raise Puppet::Error, "Could not retrieve user %s" % user end # XXX We have to have the user name, not the uid, because some # systems *cough*linux*cough* require it that way @path = user end # Read a specific @path's cron tab. def read %x{#{cmdbase()} -l 2>/dev/null} end # Remove a specific @path's cron tab. def remove if Facter.value("operatingsystem") == "FreeBSD" %x{/bin/echo yes | #{cmdbase()} -r 2>/dev/null} else %x{#{cmdbase()} -r 2>/dev/null} end end # Overwrite a specific @path's cron tab; must be passed the @path name # and the text with which to create the cron tab. def write(text) IO.popen("#{cmdbase()} -", "w") { |p| p.print text } end private # Only add the -u flag when the @path is different. Fedora apparently # does not think I should be allowed to set the @path to my own user name def cmdbase cmd = nil if @uid == Puppet::Util::SUIDManager.uid return "crontab" else return "crontab -u #{@path}" end end end # SunOS has completely different cron commands; this class implements # its versions. newfiletype(:suntab) do # Read a specific @path's cron tab. def read Puppet::Util::SUIDManager.asuser(@path) { %x{crontab -l 2>/dev/null} } end # Remove a specific @path's cron tab. def remove Puppet::Util::SUIDManager.asuser(@path) { %x{crontab -r 2>/dev/null} } end # Overwrite a specific @path's cron tab; must be passed the @path name # and the text with which to create the cron tab. def write(text) Puppet::Util::SUIDManager.asuser(@path) { IO.popen("crontab", "w") { |p| p.print text } } end end # Treat netinfo tables as a single file, just for simplicity of certain # types newfiletype(:netinfo) do class << self attr_accessor :format end def read %x{nidump -r /#{@path} /} end # This really only makes sense for cron tabs. def remove %x{nireport / /#{@path} name}.split("\n").each do |name| newname = name.gsub(/\//, '\/').sub(/\s+$/, '') output = %x{niutil -destroy / '/#{@path}/#{newname}'} unless $? == 0 raise Puppet::Error, "Could not remove %s from %s" % [name, @path] end end end # Convert our table to an array of hashes. This only works for # handling one table at a time. def to_array(text = nil) unless text text = read end hash = nil # Initialize it with the first record records = [] text.split("\n").each do |line| next if line =~ /^[{}]$/ # Skip the wrapping lines next if line =~ /"name" = \( "#{@path}" \)/ # Skip the table name next if line =~ /CHILDREN = \(/ # Skip this header next if line =~ /^ \)/ # and its closer # Now we should have nothing but records, wrapped in braces case line when /^\s+\{/: hash = {} when /^\s+\}/: records << hash when /\s+"(\w+)" = \( (.+) \)/ field = $1 values = $2 # Always use an array hash[field] = [] values.split(/, /).each do |value| if value =~ /^"(.*)"$/ hash[field] << $1 else raise ArgumentError, "Could not match value %s" % value end end else raise ArgumentError, "Could not match line %s" % line end end records end def write(text) text.gsub!(/^#.*\n/,'') text.gsub!(/^$/,'') if text == "" or text == "\n" self.remove return end unless format = self.class.format raise Puppe::DevError, "You must define the NetInfo format to inport" end IO.popen("niload -d #{format} . 1>/dev/null 2>/dev/null", "w") { |p| p.print text } unless $? == 0 raise ArgumentError, "Failed to write %s" % @path end end end end diff --git a/spec/unit/network/client/master.rb b/spec/unit/network/client/master.rb index 8354b5521..fda729cae 100755 --- a/spec/unit/network/client/master.rb +++ b/spec/unit/network/client/master.rb @@ -1,455 +1,476 @@ #!/usr/bin/env ruby # # Created by Luke Kanies on 2007-11-12. # Copyright (c) 2007. All rights reserved. require File.dirname(__FILE__) + '/../../../spec_helper' require 'puppet/network/client/master' describe Puppet::Network::Client::Master, " when retrieving the configuration" do before do @master = mock 'master' @client = Puppet::Network::Client.master.new( :Master => @master ) @facts = {"one" => "two", "three" => "four"} end it "should initialize the metadata store" do @client.class.stubs(:facts).returns(@facts) @client.expects(:dostorage) @master.stubs(:getconfig).returns(nil) @client.getconfig end it "should collect facts to use for configuration retrieval" do @client.stubs(:dostorage) @client.class.expects(:facts).returns(@facts) @master.stubs(:getconfig).returns(nil) @client.getconfig end it "should fail if no facts could be collected" do @client.stubs(:dostorage) @client.class.expects(:facts).returns({}) @master.stubs(:getconfig).returns(nil) proc { @client.getconfig }.should raise_error(Puppet::Network::ClientError) end it "should use the cached configuration if it is up to date" do file = "/path/to/cachefile" @client.stubs(:cachefile).returns(file) FileTest.expects(:exist?).with(file).returns(true) @client.expects(:fresh?).with(@facts).returns true @client.class.stubs(:facts).returns(@facts) @client.expects(:use_cached_config).returns(true) Puppet.stubs(:info) @client.getconfig end it "should log that the configuration does not need a recompile" do file = "/path/to/cachefile" @client.stubs(:cachefile).returns(file) FileTest.stubs(:exist?).with(file).returns(true) @client.stubs(:fresh?).with(@facts).returns true @client.stubs(:use_cached_config).returns(true) @client.class.stubs(:facts).returns(@facts) Puppet.expects(:info).with { |m| m.include?("up to date") } @client.getconfig end it "should retrieve plugins if :pluginsync is enabled" do file = "/path/to/cachefile" @client.stubs(:cachefile).returns(file) @client.stubs(:dostorage) @client.class.stubs(:facts).returns(@facts) Puppet.settings.expects(:value).with(:pluginsync).returns(true) @client.expects(:getplugins) @client.stubs(:get_actual_config).returns(nil) FileTest.stubs(:exist?).with(file).returns(true) @client.stubs(:fresh?).with(@facts).returns true @client.stubs(:use_cached_config).returns(true) @client.class.stubs(:facts).returns(@facts) @client.stubs(:add_default_resources) @client.getconfig end it "should use the cached configuration if no configuration could be retrieved" do @client.stubs(:dostorage) @client.class.stubs(:facts).returns(@facts) @master.stubs(:getconfig).raises(ArgumentError.new("whev")) @client.expects(:use_cached_config).with(true) @client.getconfig end it "should load the retrieved configuration using YAML" do @client.stubs(:dostorage) @client.class.stubs(:facts).returns(@facts) @master.stubs(:getconfig).returns("myconfig") config = mock 'config' YAML.expects(:load).with("myconfig").returns(config) @client.stubs(:setclasses) config.stubs(:classes) config.stubs(:to_configuration).returns(config) config.stubs(:host_config=) config.stubs(:from_cache).returns(true) @client.stubs(:add_default_resources) @client.getconfig end it "should use the cached configuration if the retrieved configuration cannot be converted from YAML" do @client.stubs(:dostorage) @client.class.stubs(:facts).returns(@facts) @master.stubs(:getconfig).returns("myconfig") YAML.expects(:load).with("myconfig").raises(ArgumentError) @client.expects(:use_cached_config).with(true) @client.getconfig end it "should set the classes.txt file with the classes listed in the retrieved configuration" do @client.stubs(:dostorage) @client.class.stubs(:facts).returns(@facts) @master.stubs(:getconfig).returns("myconfig") config = mock 'config' YAML.expects(:load).with("myconfig").returns(config) config.expects(:classes).returns(:myclasses) @client.expects(:setclasses).with(:myclasses) config.stubs(:to_configuration).returns(config) config.stubs(:host_config=) config.stubs(:from_cache).returns(true) @client.stubs(:add_default_resources) @client.getconfig end it "should convert the retrieved configuration to a RAL configuration" do @client.stubs(:dostorage) @client.class.stubs(:facts).returns(@facts) @master.stubs(:getconfig).returns("myconfig") yamlconfig = mock 'yaml config' YAML.stubs(:load).returns(yamlconfig) @client.stubs(:setclasses) config = mock 'config' yamlconfig.stubs(:classes) yamlconfig.expects(:to_configuration).returns(config) config.stubs(:host_config=) config.stubs(:from_cache).returns(true) @client.stubs(:add_default_resources) @client.getconfig end it "should use the cached configuration if the retrieved configuration cannot be converted to a RAL configuration" do @client.stubs(:dostorage) @client.class.stubs(:facts).returns(@facts) @master.stubs(:getconfig).returns("myconfig") yamlconfig = mock 'yaml config' YAML.stubs(:load).returns(yamlconfig) @client.stubs(:setclasses) config = mock 'config' yamlconfig.stubs(:classes) yamlconfig.expects(:to_configuration).raises(ArgumentError) @client.expects(:use_cached_config).with(true) @client.getconfig end it "should clear the failed configuration if using the cached configuration after failing to instantiate the retrieved configuration" do @client.stubs(:dostorage) @client.class.stubs(:facts).returns(@facts) @master.stubs(:getconfig).returns("myconfig") yamlconfig = mock 'yaml config' YAML.stubs(:load).returns(yamlconfig) @client.stubs(:setclasses) config = mock 'config' yamlconfig.stubs(:classes) yamlconfig.stubs(:to_configuration).raises(ArgumentError) @client.stubs(:use_cached_config).with(true) @client.expects(:clear) @client.getconfig end it "should cache the retrieved yaml configuration if it is not from the cache and is valid" do @client.stubs(:dostorage) @client.class.stubs(:facts).returns(@facts) @master.stubs(:getconfig).returns("myconfig") yamlconfig = mock 'yaml config' YAML.stubs(:load).returns(yamlconfig) @client.stubs(:setclasses) config = mock 'config' yamlconfig.stubs(:classes) yamlconfig.expects(:to_configuration).returns(config) config.stubs(:host_config=) @client.stubs(:add_default_resources) config.expects(:from_cache).returns(false) @client.expects(:cache).with("myconfig") @client.getconfig end it "should mark the configuration as a host configuration" do @client.stubs(:dostorage) @client.class.stubs(:facts).returns(@facts) @master.stubs(:getconfig).returns("myconfig") yamlconfig = mock 'yaml config' YAML.stubs(:load).returns(yamlconfig) @client.stubs(:setclasses) config = mock 'config' yamlconfig.stubs(:classes) yamlconfig.expects(:to_configuration).returns(config) config.stubs(:from_cache).returns(true) config.expects(:host_config=).with(true) @client.stubs(:add_default_resources) @client.getconfig end it "should add the default resources to the configuration" do @client.stubs(:dostorage) @client.class.stubs(:facts).returns(@facts) @master.stubs(:getconfig).returns("myconfig") yamlconfig = mock 'yaml config' YAML.stubs(:load).returns(yamlconfig) @client.stubs(:setclasses) config = mock 'config' yamlconfig.stubs(:classes) yamlconfig.stubs(:to_configuration).returns(config) config.stubs(:from_cache).returns(true) config.stubs(:host_config=).with(true) @client.expects(:add_default_resources).with(config) @client.getconfig end end describe Puppet::Network::Client::Master, " when using the cached configuration" do before do @master = mock 'master' @client = Puppet::Network::Client.master.new( :Master => @master ) @facts = {"one" => "two", "three" => "four"} end it "should return do nothing and true if there is already an in-memory configuration" do @client.configuration = :whatever Puppet::Network::Client::Master.publicize_methods :use_cached_config do @client.use_cached_config.should be_true end end it "should return do nothing and false if it has been told there is a failure and :nocacheonfailure is enabled" do Puppet.settings.expects(:value).with(:usecacheonfailure).returns(false) Puppet::Network::Client::Master.publicize_methods :use_cached_config do @client.use_cached_config(true).should be_false end end it "should return false if no cached configuration can be found" do @client.expects(:retrievecache).returns(nil) Puppet::Network::Client::Master.publicize_methods :use_cached_config do @client.use_cached_config().should be_false end end it "should return false if the cached configuration cannot be instantiated" do YAML.expects(:load).raises(ArgumentError) @client.expects(:retrievecache).returns("whatever") Puppet::Network::Client::Master.publicize_methods :use_cached_config do @client.use_cached_config().should be_false end end it "should warn if the cached configuration cannot be instantiated" do YAML.stubs(:load).raises(ArgumentError) @client.stubs(:retrievecache).returns("whatever") Puppet.expects(:warning).with { |m| m.include?("Could not load cache") } Puppet::Network::Client::Master.publicize_methods :use_cached_config do @client.use_cached_config().should be_false end end it "should clear the client if the cached configuration cannot be instantiated" do YAML.stubs(:load).raises(ArgumentError) @client.stubs(:retrievecache).returns("whatever") @client.expects(:clear) Puppet::Network::Client::Master.publicize_methods :use_cached_config do @client.use_cached_config().should be_false end end it "should return true if the cached configuration can be instantiated" do config = mock 'config' YAML.stubs(:load).returns(config) ral_config = mock 'ral config' ral_config.stubs(:from_cache=) ral_config.stubs(:host_config=) config.expects(:to_configuration).returns(ral_config) @client.stubs(:retrievecache).returns("whatever") @client.stubs(:add_default_resources) Puppet::Network::Client::Master.publicize_methods :use_cached_config do @client.use_cached_config().should be_true end end it "should set the configuration instance variable if the cached configuration can be instantiated" do config = mock 'config' YAML.stubs(:load).returns(config) ral_config = mock 'ral config' ral_config.stubs(:from_cache=) ral_config.stubs(:host_config=) config.expects(:to_configuration).returns(ral_config) @client.stubs(:retrievecache).returns("whatever") @client.stubs(:add_default_resources) Puppet::Network::Client::Master.publicize_methods :use_cached_config do @client.use_cached_config() end @client.configuration.should equal(ral_config) end it "should mark the configuration as a host_config if valid" do config = mock 'config' YAML.stubs(:load).returns(config) ral_config = mock 'ral config' ral_config.stubs(:from_cache=) ral_config.expects(:host_config=).with(true) config.expects(:to_configuration).returns(ral_config) @client.stubs(:retrievecache).returns("whatever") @client.stubs(:add_default_resources) Puppet::Network::Client::Master.publicize_methods :use_cached_config do @client.use_cached_config() end @client.configuration.should equal(ral_config) end it "should mark the configuration as from the cache if valid" do config = mock 'config' YAML.stubs(:load).returns(config) ral_config = mock 'ral config' ral_config.expects(:from_cache=).with(true) ral_config.stubs(:host_config=) config.expects(:to_configuration).returns(ral_config) @client.stubs(:retrievecache).returns("whatever") @client.stubs(:add_default_resources) Puppet::Network::Client::Master.publicize_methods :use_cached_config do @client.use_cached_config() end @client.configuration.should equal(ral_config) end it "should add the default resources to the configuration" do config = mock 'config' YAML.stubs(:load).returns(config) ral_config = mock 'ral config' ral_config.expects(:from_cache=).with(true) ral_config.stubs(:host_config=) config.stubs(:to_configuration).returns(ral_config) @client.stubs(:retrievecache).returns("whatever") @client.expects(:add_default_resources).with(ral_config) Puppet::Network::Client::Master.publicize_methods :use_cached_config do @client.use_cached_config() end end end describe Puppet::Network::Client::Master, " when adding default resources" do before do @master = mock 'master' @client = Puppet::Network::Client.master.new( :Master => @master ) @facts = {"one" => "two", "three" => "four"} end it "should add the default schedules" do config = mock 'config' one = stub 'one', :title => "one" two = stub 'two', :title => "two" Puppet::Type.type(:schedule).expects(:create_default_resources).with().returns([one, two]) config.expects(:add_resource).with(one) config.expects(:add_resource).with(two) config.stubs(:resource).returns(false) Puppet::Type.type(:filebucket).stubs(:create_default_resources).returns([]) Puppet::Network::Client::Master.publicize_methods :add_default_resources do @client.add_default_resources(config) end end it "should add the default filebucket" do config = mock 'config' Puppet::Type.type(:schedule).stubs(:create_default_resources).returns([]) one = stub 'one', :title => "one" two = stub 'two', :title => "two" Puppet::Type.type(:filebucket).expects(:create_default_resources).with().returns([one, two]) config.expects(:add_resource).with(one) config.expects(:add_resource).with(two) config.stubs(:resource).returns(false) Puppet::Network::Client::Master.publicize_methods :add_default_resources do @client.add_default_resources(config) end end - it "should only add default resources if no similarly named resource does not exist" do + it "should only add a default filebucket if no similarly named bucket already exists" do + config = mock 'config' + Puppet::Type.type(:schedule).stubs(:create_default_resources).returns([]) + one = stub 'one', :title => "one" + Puppet::Type.type(:filebucket).expects(:create_default_resources).with().returns([one]) + config.expects(:resource).with(:filebucket, "one").returns(true) + config.expects(:add_resource).with(one).never + Puppet::Network::Client::Master.publicize_methods :add_default_resources do + @client.add_default_resources(config) + end + end + + it "should only add default schedules if no similarly named schedule already exists" do + config = mock 'config' + one = stub 'one', :title => "one" + Puppet::Type.type(:schedule).stubs(:create_default_resources).returns([one]) + Puppet::Type.type(:filebucket).stubs(:create_default_resources).with().returns([]) + config.expects(:resource).with(:schedule, "one").returns(true) + config.expects(:add_resource).with(one).never + Puppet::Network::Client::Master.publicize_methods :add_default_resources do + @client.add_default_resources(config) + end end end diff --git a/spec/unit/node/configuration.rb b/spec/unit/node/configuration.rb index 7fda4e9a8..44b98dddb 100755 --- a/spec/unit/node/configuration.rb +++ b/spec/unit/node/configuration.rb @@ -1,750 +1,768 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../spec_helper' describe Puppet::Node::Configuration, " when compiling" do it "should accept tags" do config = Puppet::Node::Configuration.new("mynode") config.tag("one") config.tags.should == %w{one} end it "should accept multiple tags at once" do config = Puppet::Node::Configuration.new("mynode") config.tag("one", "two") config.tags.should == %w{one two} end it "should convert all tags to strings" do config = Puppet::Node::Configuration.new("mynode") config.tag("one", :two) config.tags.should == %w{one two} end it "should tag with both the qualified name and the split name" do config = Puppet::Node::Configuration.new("mynode") config.tag("one::two") config.tags.include?("one").should be_true config.tags.include?("one::two").should be_true end it "should accept classes" do config = Puppet::Node::Configuration.new("mynode") config.add_class("one") config.classes.should == %w{one} config.add_class("two", "three") config.classes.should == %w{one two three} end it "should tag itself with passed class names" do config = Puppet::Node::Configuration.new("mynode") config.add_class("one") config.tags.should == %w{one} end end describe Puppet::Node::Configuration, " when extracting" do it "should return extraction result as the method result" do config = Puppet::Node::Configuration.new("mynode") config.expects(:extraction_format).returns(:whatever) config.expects(:extract_to_whatever).returns(:result) config.extract.should == :result end end describe Puppet::Node::Configuration, " when extracting transobjects" do def mkscope @parser = Puppet::Parser::Parser.new :Code => "" @node = Puppet::Node.new("mynode") @compile = Puppet::Parser::Compile.new(@node, @parser) # XXX This is ridiculous. @compile.send(:evaluate_main) @scope = @compile.topscope end def mkresource(type, name) Puppet::Parser::Resource.new(:type => type, :title => name, :source => @source, :scope => @scope) end it "should always create a TransBucket for the 'main' class" do config = Puppet::Node::Configuration.new("mynode") @scope = mkscope @source = mock 'source' main = mkresource("class", :main) config.add_vertex!(main) bucket = mock 'bucket' bucket.expects(:classes=).with(config.classes) main.stubs(:builtin?).returns(false) main.expects(:to_transbucket).returns(bucket) config.extract_to_transportable.should equal(bucket) end # This isn't really a spec-style test, but I don't know how better to do it. it "should transform the resource graph into a tree of TransBuckets and TransObjects" do config = Puppet::Node::Configuration.new("mynode") @scope = mkscope @source = mock 'source' defined = mkresource("class", :main) builtin = mkresource("file", "/yay") config.add_edge!(defined, builtin) bucket = [] bucket.expects(:classes=).with(config.classes) defined.stubs(:builtin?).returns(false) defined.expects(:to_transbucket).returns(bucket) builtin.expects(:to_transobject).returns(:builtin) config.extract_to_transportable.should == [:builtin] end # Now try it with a more complicated graph -- a three tier graph, each tier it "should transform arbitrarily deep graphs into isomorphic trees" do config = Puppet::Node::Configuration.new("mynode") @scope = mkscope @scope.stubs(:tags).returns([]) @source = mock 'source' # Create our scopes. top = mkresource "class", :main topbucket = [] topbucket.expects(:classes=).with([]) top.expects(:to_trans).returns(topbucket) topres = mkresource "file", "/top" topres.expects(:to_trans).returns(:topres) config.add_edge! top, topres middle = mkresource "class", "middle" middle.expects(:to_trans).returns([]) config.add_edge! top, middle midres = mkresource "file", "/mid" midres.expects(:to_trans).returns(:midres) config.add_edge! middle, midres bottom = mkresource "class", "bottom" bottom.expects(:to_trans).returns([]) config.add_edge! middle, bottom botres = mkresource "file", "/bot" botres.expects(:to_trans).returns(:botres) config.add_edge! bottom, botres toparray = config.extract_to_transportable # This is annoying; it should look like: # [[[:botres], :midres], :topres] # but we can't guarantee sort order. toparray.include?(:topres).should be_true midarray = toparray.find { |t| t.is_a?(Array) } midarray.include?(:midres).should be_true botarray = midarray.find { |t| t.is_a?(Array) } botarray.include?(:botres).should be_true end end describe Puppet::Node::Configuration, " when converting to a transobject configuration" do class TestResource attr_accessor :name, :virtual, :builtin def initialize(name, options = {}) @name = name options.each { |p,v| send(p.to_s + "=", v) } end def ref if builtin? "File[%s]" % name else "Class[%s]" % name end end def virtual? virtual end def builtin? builtin end def to_transobject Puppet::TransObject.new(name, builtin? ? "file" : "class") end end before do @original = Puppet::Node::Configuration.new("mynode") @original.tag(*%w{one two three}) @original.add_class *%w{four five six} @top = TestResource.new 'top' @topobject = TestResource.new 'topobject', :builtin => true @virtual = TestResource.new 'virtual', :virtual => true @virtualobject = TestResource.new 'virtualobject', :builtin => true, :virtual => true @middle = TestResource.new 'middle' @middleobject = TestResource.new 'middleobject', :builtin => true @bottom = TestResource.new 'bottom' @bottomobject = TestResource.new 'bottomobject', :builtin => true @resources = [@top, @topobject, @middle, @middleobject, @bottom, @bottomobject] @original.add_edge!(@top, @topobject) @original.add_edge!(@top, @virtual) @original.add_edge!(@virtual, @virtualobject) @original.add_edge!(@top, @middle) @original.add_edge!(@middle, @middleobject) @original.add_edge!(@middle, @bottom) @original.add_edge!(@bottom, @bottomobject) @config = @original.to_transportable end it "should add all resources as TransObjects" do @resources.each { |resource| @config.resource(resource.ref).should be_instance_of(Puppet::TransObject) } end it "should not extract defined virtual resources" do @config.vertices.find { |v| v.name == "virtual" }.should be_nil end it "should not extract builtin virtual resources" do @config.vertices.find { |v| v.name == "virtualobject" }.should be_nil end it "should copy the tag list to the new configuration" do @config.tags.sort.should == @original.tags.sort end it "should copy the class list to the new configuration" do @config.classes.should == @original.classes end it "should duplicate the original edges" do @original.edges.each do |edge| next if edge.source.virtual? or edge.target.virtual? source = @config.resource(edge.source.ref) target = @config.resource(edge.target.ref) source.should_not be_nil target.should_not be_nil @config.edge?(source, target).should be_true end end it "should set itself as the configuration for each converted resource" do @config.vertices.each { |v| v.configuration.object_id.should equal(@config.object_id) } end end describe Puppet::Node::Configuration, " when converting to a RAL configuration" do before do @original = Puppet::Node::Configuration.new("mynode") @original.tag(*%w{one two three}) @original.add_class *%w{four five six} @top = Puppet::TransObject.new 'top', "class" @topobject = Puppet::TransObject.new '/topobject', "file" @middle = Puppet::TransObject.new 'middle', "class" @middleobject = Puppet::TransObject.new '/middleobject', "file" @bottom = Puppet::TransObject.new 'bottom', "class" @bottomobject = Puppet::TransObject.new '/bottomobject', "file" @resources = [@top, @topobject, @middle, @middleobject, @bottom, @bottomobject] @original.add_resource(*@resources) @original.add_edge!(@top, @topobject) @original.add_edge!(@top, @middle) @original.add_edge!(@middle, @middleobject) @original.add_edge!(@middle, @bottom) @original.add_edge!(@bottom, @bottomobject) @config = @original.to_ral end it "should add all resources as RAL instances" do @resources.each { |resource| @config.resource(resource.ref).should be_instance_of(Puppet::Type) } end it "should copy the tag list to the new configuration" do @config.tags.sort.should == @original.tags.sort end it "should copy the class list to the new configuration" do @config.classes.should == @original.classes end it "should duplicate the original edges" do @original.edges.each do |edge| @config.edge?(@config.resource(edge.source.ref), @config.resource(edge.target.ref)).should be_true end end it "should set itself as the configuration for each converted resource" do @config.vertices.each { |v| v.configuration.object_id.should equal(@config.object_id) } end after do # Remove all resource instances. @config.clear(true) end end describe Puppet::Node::Configuration, " when functioning as a resource container" do before do @config = Puppet::Node::Configuration.new("host") - @one = stub 'resource1', :ref => "Me[one]", :configuration= => nil - @two = stub 'resource2', :ref => "Me[two]", :configuration= => nil - @dupe = stub 'resource3', :ref => "Me[one]", :configuration= => nil + @one = stub 'resource1', :ref => "Me[one]", :configuration= => nil, :title => "one", :name => "one" + @two = stub 'resource2', :ref => "Me[two]", :configuration= => nil, :title => "two", :name => "two" + @dupe = stub 'resource3', :ref => "Me[one]", :configuration= => nil, :title => "one", :name => "one" end it "should provide a method to add one or more resources" do @config.add_resource @one, @two @config.resource(@one.ref).should equal(@one) @config.resource(@two.ref).should equal(@two) end it "should set itself as the resource's configuration if it is not a relationship graph" do @one.expects(:configuration=).with(@config) @config.add_resource @one end it "should not set itself as the resource's configuration if it is a relationship graph" do @one.expects(:configuration=).never @config.is_relationship_graph = true @config.add_resource @one end it "should make all vertices available by resource reference" do @config.add_resource(@one) @config.resource(@one.ref).should equal(@one) @config.vertices.find { |r| r.ref == @one.ref }.should equal(@one) end it "should not allow two resources with the same resource reference" do @config.add_resource(@one) proc { @config.add_resource(@dupe) }.should raise_error(ArgumentError) end it "should not store objects that do not respond to :ref" do proc { @config.add_resource("thing") }.should raise_error(ArgumentError) end it "should remove all resources when asked" do @config.add_resource @one @config.add_resource @two @one.expects :remove @two.expects :remove @config.clear(true) end it "should support a mechanism for finishing resources" do @one.expects :finish @two.expects :finish @config.add_resource @one @config.add_resource @two @config.finalize end it "should optionally support an initialization block and should finalize after such blocks" do @one.expects :finish @two.expects :finish config = Puppet::Node::Configuration.new("host") do |conf| conf.add_resource @one conf.add_resource @two end end it "should inform the resource that it is the resource's configuration" do @one.expects(:configuration=).with(@config) @config.add_resource @one end it "should be able to find resources by reference" do @config.add_resource @one @config.resource(@one.ref).should equal(@one) end it "should be able to find resources by reference or by type/title tuple" do @config.add_resource @one @config.resource("me", "one").should equal(@one) end it "should have a mechanism for removing resources" do @config.add_resource @one @one.expects :remove @config.remove_resource(@one) @config.resource(@one.ref).should be_nil @config.vertex?(@one).should be_false end it "should have a method for creating aliases for resources" do @config.add_resource @one @config.alias(@one, "other") @config.resource("me", "other").should equal(@one) end # This test is the same as the previous, but the behaviour should be explicit. it "should alias using the class name from the resource reference, not the resource class name" do @config.add_resource @one @config.alias(@one, "other") @config.resource("me", "other").should equal(@one) end it "should fail to add an alias if the aliased name already exists" do @config.add_resource @one proc { @config.alias @two, "one" }.should raise_error(ArgumentError) end it "should remove resource aliases when the target resource is removed" do @config.add_resource @one @config.alias(@one, "other") @one.expects :remove @config.remove_resource(@one) @config.resource("me", "other").should be_nil end + + it "should alias resources whose names are not equal to their titles" do + resource = stub("resource", :name => "one", :title => "two", :ref => "Me[two]", :configuration= => nil) + @config.expects(:alias).with(resource, "one") + @config.add_resource resource + end + + it "should fail to add resources whose names conflict with an existing resource even when the title does not conflict" do + conflict = stub("resource", :name => "one", :ref => "Me[one]", :configuration= => nil) + resource = stub("resource", :name => "one", :title => "other", :ref => "Me[other]", :configuration= => nil) + @config.expects(:alias).with(resource, "one") + @config.add_resource resource + end + + it "should not alias components whose names do not match their titles" do + comp = Puppet::Type::Component.create :name => "one", :title => "two" + @config.expects(:alias).never + @config.add_resource comp + end end module ApplyingConfigurations def setup @config = Puppet::Node::Configuration.new("host") @config.retrieval_duration = Time.now @transaction = mock 'transaction' Puppet::Transaction.stubs(:new).returns(@transaction) @transaction.stubs(:evaluate) @transaction.stubs(:cleanup) @transaction.stubs(:addtimes) end end describe Puppet::Node::Configuration, " when applying" do include ApplyingConfigurations it "should create and evaluate a transaction" do @transaction.expects(:evaluate) @config.apply end it "should provide the configuration time to the transaction" do @transaction.expects(:addtimes).with do |arg| arg[:config_retrieval].should be_instance_of(Time) true end @config.apply end it "should clean up the transaction" do @transaction.expects :cleanup @config.apply end it "should return the transaction" do @config.apply.should equal(@transaction) end it "should yield the transaction if a block is provided" do @config.apply do |trans| trans.should equal(@transaction) end end it "should default to not being a host configuration" do @config.host_config.should be_nil end it "should pass supplied tags on to the transaction" do @transaction.expects(:tags=).with(%w{one two}) @config.apply(:tags => %w{one two}) end it "should set ignoreschedules on the transaction if specified in apply()" do @transaction.expects(:ignoreschedules=).with(true) @config.apply(:ignoreschedules => true) end end describe Puppet::Node::Configuration, " when applying host configurations" do include ApplyingConfigurations # super() doesn't work in the setup method for some reason before do @config.host_config = true end it "should send a report if reporting is enabled" do Puppet[:report] = true @transaction.expects :send_report @transaction.stubs :any_failed? => false @config.apply end it "should send a report if report summaries are enabled" do Puppet[:summarize] = true @transaction.expects :send_report @transaction.stubs :any_failed? => false @config.apply end it "should initialize the state database before applying a configuration" do Puppet::Util::Storage.expects(:load) # Short-circuit the apply, so we know we're loading before the transaction Puppet::Transaction.expects(:new).raises ArgumentError proc { @config.apply }.should raise_error(ArgumentError) end it "should sync the state database after applying" do Puppet::Util::Storage.expects(:store) @transaction.stubs :any_failed? => false @config.apply end after { Puppet.settings.clear } end describe Puppet::Node::Configuration, " when applying non-host configurations" do include ApplyingConfigurations before do @config.host_config = false end it "should never send reports" do Puppet[:report] = true Puppet[:summarize] = true @transaction.expects(:send_report).never @config.apply end it "should never modify the state database" do Puppet::Util::Storage.expects(:load).never Puppet::Util::Storage.expects(:store).never @config.apply end after { Puppet.settings.clear } end describe Puppet::Node::Configuration, " when creating a relationship graph" do before do @config = Puppet::Node::Configuration.new("host") - @compone = Puppet::Type::Component.create :name => "one" - @comptwo = Puppet::Type::Component.create :name => "two", :require => ["class", "one"] + @compone = @config.create_resource :component, :name => "one" + @comptwo = @config.create_resource :component, :name => "two", :require => ["class", "one"] @file = Puppet::Type.type(:file) - @one = @file.create :path => "/one" - @two = @file.create :path => "/two" + @one = @config.create_resource :file, :path => "/one" + @two = @config.create_resource :file, :path => "/two" @config.add_edge! @compone, @one @config.add_edge! @comptwo, @two - @three = @file.create :path => "/three" - @four = @file.create :path => "/four", :require => ["file", "/three"] - @five = @file.create :path => "/five" - @config.add_resource @compone, @comptwo, @one, @two, @three, @four, @five + @three = @config.create_resource :file, :path => "/three" + @four = @config.create_resource :file, :path => "/four", :require => ["file", "/three"] + @five = @config.create_resource :file, :path => "/five" @relationships = @config.relationship_graph end it "should fail when trying to create a relationship graph for a relationship graph" do proc { @relationships.relationship_graph }.should raise_error(Puppet::DevError) end it "should be able to create a relationship graph" do @relationships.should be_instance_of(Puppet::Node::Configuration) end it "should copy its host_config setting to the relationship graph" do config = Puppet::Node::Configuration.new config.host_config = true config.relationship_graph.host_config.should be_true end it "should not have any components" do @relationships.vertices.find { |r| r.instance_of?(Puppet::Type::Component) }.should be_nil end it "should have all non-component resources from the configuration" do # The failures print out too much info, so i just do a class comparison @relationships.vertex?(@five).should be_true end it "should have all resource relationships set as edges" do @relationships.edge?(@three, @four).should be_true end it "should copy component relationships to all contained resources" do @relationships.edge?(@one, @two).should be_true end it "should get removed when the configuration is cleaned up" do @relationships.expects(:clear).with(false) @config.clear @config.instance_variable_get("@relationship_graph").should be_nil end it "should create a new relationship graph after clearing the old one" do @relationships.expects(:clear).with(false) @config.clear @config.relationship_graph.should be_instance_of(Puppet::Node::Configuration) end it "should look up resources in the relationship graph if not found in the main configuration" do five = stub 'five', :ref => "File[five]", :configuration= => nil @relationships.add_resource five @config.resource(five.ref).should equal(five) end it "should provide a method to create additional resources that also registers the resource" do args = {:name => "/yay", :ensure => :file} resource = stub 'file', :ref => "File[/yay]", :configuration= => @config Puppet::Type.type(:file).expects(:create).with(args).returns(resource) @config.create_resource :file, args @config.resource("File[/yay]").should equal(resource) end it "should provide a mechanism for creating implicit resources" do args = {:name => "/yay", :ensure => :file} resource = stub 'file', :ref => "File[/yay]", :configuration= => @config Puppet::Type.type(:file).expects(:create).with(args).returns(resource) resource.expects(:implicit=).with(true) @config.create_implicit_resource :file, args @config.resource("File[/yay]").should equal(resource) end it "should add implicit resources to the relationship graph if there is one" do args = {:name => "/yay", :ensure => :file} resource = stub 'file', :ref => "File[/yay]", :configuration= => @config resource.expects(:implicit=).with(true) Puppet::Type.type(:file).expects(:create).with(args).returns(resource) # build the graph relgraph = @config.relationship_graph @config.create_implicit_resource :file, args relgraph.resource("File[/yay]").should equal(resource) end it "should remove resources created mid-transaction" do args = {:name => "/yay", :ensure => :file} resource = stub 'file', :ref => "File[/yay]", :configuration= => @config @transaction = mock 'transaction' Puppet::Transaction.stubs(:new).returns(@transaction) @transaction.stubs(:evaluate) @transaction.stubs(:cleanup) @transaction.stubs(:addtimes) Puppet::Type.type(:file).expects(:create).with(args).returns(resource) resource.expects :remove @config.apply do |trans| @config.create_resource :file, args @config.resource("File[/yay]").should equal(resource) end @config.resource("File[/yay]").should be_nil end it "should remove resources from the relationship graph if it exists" do @config.remove_resource(@one) @config.relationship_graph.vertex?(@one).should be_false end after do Puppet::Type.allclear end end describe Puppet::Node::Configuration, " when writing dot files" do before do @config = Puppet::Node::Configuration.new("host") @name = :test @file = File.join(Puppet[:graphdir], @name.to_s + ".dot") end it "should only write when it is a host configuration" do File.expects(:open).with(@file).never @config.host_config = false Puppet[:graph] = true @config.write_graph(@name) end it "should only write when graphing is enabled" do File.expects(:open).with(@file).never @config.host_config = true Puppet[:graph] = false @config.write_graph(@name) end it "should write a dot file based on the passed name" do File.expects(:open).with(@file, "w").yields(stub("file", :puts => nil)) @config.expects(:to_dot).with("name" => @name.to_s.capitalize) @config.host_config = true Puppet[:graph] = true @config.write_graph(@name) end after do Puppet.settings.clear end end describe Puppet::Node::Configuration, " when indirecting" do before do @indirection = mock 'indirection' Puppet::Indirector::Indirection.clear_cache end it "should redirect to the indirection for retrieval" do Puppet::Node::Configuration.stubs(:indirection).returns(@indirection) @indirection.expects(:find).with(:myconfig) Puppet::Node::Configuration.find(:myconfig) end it "should default to the 'compiler' terminus" do Puppet::Node::Configuration.indirection.terminus_class.should == :compiler end after do mocha_verify Puppet::Indirector::Indirection.clear_cache end end describe Puppet::Node::Configuration, " when converting to yaml" do before do @configuration = Puppet::Node::Configuration.new("me") @configuration.add_edge!("one", "two") end it "should be able to be dumped to yaml" do YAML.dump(@configuration).should be_instance_of(String) end end describe Puppet::Node::Configuration, " when converting from yaml" do before do @configuration = Puppet::Node::Configuration.new("me") @configuration.add_edge!("one", "two") text = YAML.dump(@configuration) @newconfig = YAML.load(text) end it "should get converted back to a configuration" do @newconfig.should be_instance_of(Puppet::Node::Configuration) end it "should have all vertices" do @newconfig.vertex?("one").should be_true @newconfig.vertex?("two").should be_true end it "should have all edges" do @newconfig.edge?("one", "two").should be_true end end diff --git a/test/language/snippets.rb b/test/language/snippets.rb index 0806a40b4..05597cdec 100755 --- a/test/language/snippets.rb +++ b/test/language/snippets.rb @@ -1,490 +1,489 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../lib/puppettest' require 'puppet' require 'puppet/parser/interpreter' require 'puppet/parser/parser' require 'puppet/network/client' require 'puppet/network/handler' require 'puppettest' class TestSnippets < Test::Unit::TestCase include PuppetTest def setup super @file = Puppet::Type.type(:file) end def self.snippetdir PuppetTest.datadir "snippets" end def assert_file(path, msg = nil) - unless file = @file[path] + unless file = @configuration.resource(:file, path) msg ||= "Could not find file %s" % path raise msg end end def assert_mode_equal(mode, path) - unless file = @file[path] + unless file = @configuration.resource(:file, path) raise "Could not find file %s" % path end unless mode == file.should(:mode) raise "Mode for %s is incorrect: %o vs %o" % [path, mode, file.should(:mode)] end end def snippet(name) File.join(self.class.snippetdir, name) end def file2ast(file) parser = Puppet::Parser::Parser.new() parser.file = file ast = parser.parse return ast end def snippet2ast(text) parser = Puppet::Parser::Parser.new() parser.string = text ast = parser.parse return ast end def client args = { :Listen => false } Puppet::Network::Client.new(args) end def ast2scope(ast) interp = Puppet::Parser::Interpreter.new( :ast => ast, :client => client() ) scope = Puppet::Parser::Scope.new() ast.evaluate(scope) return scope end def scope2objs(scope) objs = scope.to_trans end def snippet2scope(snippet) ast = snippet2ast(snippet) scope = ast2scope(ast) end def snippet2objs(snippet) ast = snippet2ast(snippet) scope = ast2scope(ast) objs = scope2objs(scope) end def properties(type) properties = type.validproperties end def metaparams(type) mparams = [] Puppet::Type.eachmetaparam { |param| mparams.push param } mparams end def params(type) params = [] type.parameters.each { |name,property| params.push name } params end def randthing(thing,type) list = self.send(thing,type) list[rand(list.length)] end def randeach(type) [:properties, :metaparams, :params].collect { |thing| randthing(thing,type) } end @@snippets = { true => [ %{File { mode => 755 }} ], } def disabled_test_defaults Puppet::Type.eachtype { |type| next if type.name == :puppet or type.name == :component rands = randeach(type) name = type.name.to_s.capitalize [0..1, 0..2].each { |range| params = rands[range] paramstr = params.collect { |param| "%s => fake" % param }.join(", ") str = "%s { %s }" % [name, paramstr] scope = nil assert_nothing_raised { scope = snippet2scope(str) } defaults = nil assert_nothing_raised { defaults = scope.lookupdefaults(name) } p defaults params.each { |param| puts "%s => '%s'" % [name,param] assert(defaults.include?(param)) } } } end # this is here in case no tests get defined; otherwise we get a warning def test_nothing end def snippet_filecreate %w{a b c d}.each { |letter| path = "/tmp/create%stest" % letter assert_file(path) if %w{a b}.include?(letter) assert_mode_equal(0755, path) end } end def snippet_simpledefaults path = "/tmp/defaulttest" assert_file(path) assert_mode_equal(0755, path) end def snippet_simpleselector files = %w{a b c d}.collect { |letter| path = "/tmp/snippetselect%stest" % letter assert_file(path) assert_mode_equal(0755, path) } end def snippet_classpathtest path = "/tmp/classtest" file = @configuration.resource(:file, path) assert(file, "did not create file %s" % path) assert_nothing_raised { assert_equal( "//testing/Mytype[componentname]/File[/tmp/classtest]", file.path) } end def snippet_argumentdefaults path1 = "/tmp/argumenttest1" path2 = "/tmp/argumenttest2" - file1 = @file[path1] - file2 = @file[path2] + file1 = @configuration.resource(:file, path1) + file2 = @configuration.resource(:file, path2) assert_file(path1) assert_mode_equal(0755, path1) assert_file(path2) assert_mode_equal(0644, path2) end def snippet_casestatement paths = %w{ /tmp/existsfile /tmp/existsfile2 /tmp/existsfile3 /tmp/existsfile4 /tmp/existsfile5 } paths.each { |path| - file = @file[path] + file = @configuration.resource(:file, path) assert(file, "File %s is missing" % path) assert_mode_equal(0755, path) } end def snippet_implicititeration paths = %w{a b c d e f g h}.collect { |l| "/tmp/iteration%stest" % l } paths.each { |path| - file = @file[path] + file = @configuration.resource(:file, path) assert_file(path) assert_mode_equal(0755, path) } end def snippet_multipleinstances paths = %w{a b c}.collect { |l| "/tmp/multipleinstances%s" % l } paths.each { |path| assert_file(path) assert_mode_equal(0755, path) } end def snippet_namevartest file = "/tmp/testfiletest" dir = "/tmp/testdirtest" assert_file(file) assert_file(dir) - assert_equal(:directory, @file[dir].should(:ensure), "Directory is not set to be a directory") + assert_equal(:directory, @configuration.resource(:file, dir).should(:ensure), "Directory is not set to be a directory") end def snippet_scopetest file = "/tmp/scopetest" assert_file(file) assert_mode_equal(0755, file) end def snippet_selectorvalues nums = %w{1 2 3 4 5} files = nums.collect { |n| "/tmp/selectorvalues%s" % n } files.each { |f| assert_file(f) assert_mode_equal(0755, f) } end def snippet_singleselector nums = %w{1 2 3} files = nums.collect { |n| "/tmp/singleselector%s" % n } files.each { |f| assert_file(f) assert_mode_equal(0755, f) } end def snippet_falsevalues file = "/tmp/falsevaluesfalse" assert_file(file) end def disabled_snippet_classargtest [1,2].each { |num| file = "/tmp/classargtest%s" % num assert_file(file) assert_mode_equal(0755, file) } end def snippet_classheirarchy [1,2,3].each { |num| file = "/tmp/classheir%s" % num assert_file(file) assert_mode_equal(0755, file) } end def snippet_singleary [1,2,3,4].each { |num| file = "/tmp/singleary%s" % num assert_file(file) } end def snippet_classincludes [1,2,3].each { |num| file = "/tmp/classincludes%s" % num assert_file(file) assert_mode_equal(0755, file) } end def snippet_componentmetaparams ["/tmp/component1", "/tmp/component2"].each { |file| assert_file(file) } end def snippet_aliastest %w{/tmp/aliastest /tmp/aliastest2 /tmp/aliastest3}.each { |file| assert_file(file) } end def snippet_singlequote { 1 => 'a $quote', 2 => 'some "\yayness\"' }.each { |count, str| path = "/tmp/singlequote%s" % count assert_file(path) - assert_equal(str, @file[path].should(:content)) + assert_equal(str, @configuration.resource(:file, path).should(:content)) } end # There's no way to actually retrieve the list of classes from the # transaction. def snippet_tag end # Make sure that set tags are correctly in place, yo. def snippet_tagged tags = {"testing" => true, "yayness" => false, "both" => false, "bothtrue" => true, "define" => true} tags.each do |tag, retval| assert_file("/tmp/tagged#{tag}#{retval.to_s}") end end def snippet_defineoverrides file = "/tmp/defineoverrides1" assert_file(file) assert_mode_equal(0755, file) end def snippet_deepclassheirarchy 5.times { |i| i += 1 file = "/tmp/deepclassheir%s" % i assert_file(file) } end def snippet_emptyclass # There's nothing to check other than that it works end def snippet_emptyexec - assert(Puppet::Type.type(:exec)["touch /tmp/emptyexectest"], - "Did not create exec") + assert(@configuration.resource(:exec, "touch /tmp/emptyexectest"), "Did not create exec") end def snippet_multisubs path = "/tmp/multisubtest" assert_file(path) - file = @file[path] + file = @configuration.resource(:file, path) assert_equal("sub2", file.should(:content), "sub2 did not override content") assert_mode_equal(0755, path) end def snippet_collection assert_file("/tmp/colltest1") - assert_nil(@file["/tmp/colltest2"], "Incorrectly collected file") + assert_nil(@configuration.resource(:file, "/tmp/colltest2"), "Incorrectly collected file") end def snippet_virtualresources %w{1 2 3 4}.each do |num| assert_file("/tmp/virtualtest#{num}") end end def snippet_componentrequire %w{1 2}.each do |num| assert_file("/tmp/testing_component_requires#{num}", "#{num} does not exist") end end def snippet_realize_defined_types assert_file("/tmp/realize_defined_test1") assert_file("/tmp/realize_defined_test2") end def snippet_fqparents assert_file("/tmp/fqparent1", "Did not make file from parent class") assert_file("/tmp/fqparent2", "Did not make file from subclass") end def snippet_fqdefinition assert_file("/tmp/fqdefinition", "Did not make file from fully-qualified definition") end def snippet_subclass_name_duplication assert_file("/tmp/subclass_name_duplication1", "Did not make first file from duplicate subclass names") assert_file("/tmp/subclass_name_duplication2", "Did not make second file from duplicate subclass names") end # Iterate across each of the snippets and create a test. Dir.entries(snippetdir).sort.each { |file| next if file =~ /^\./ mname = "snippet_" + file.sub(/\.pp$/, '') if self.method_defined?(mname) #eval("alias %s %s" % [testname, mname]) testname = ("test_" + mname).intern self.send(:define_method, testname) { Puppet[:manifest] = snippet(file) facts = { "hostname" => "testhost", "domain" => "domain.com", "ipaddress" => "127.0.0.1", "fqdn" => "testhost.domain.com" } node = Puppet::Node.new("testhost") node.merge(facts) config = nil assert_nothing_raised("Could not compile configuration") { config = Puppet::Node::Configuration.find(node) } assert_nothing_raised("Could not convert configuration") { config = config.to_ral } Puppet::Type.eachtype { |type| type.each { |obj| # don't worry about this for now #unless obj.name == "puppet[top]" or # obj.is_a?(Puppet.type(:schedule)) # assert(obj.parent, "%s has no parent" % obj.name) #end assert(obj.name) } } @configuration = config assert_nothing_raised { self.send(mname) } } mname = mname.intern end } end diff --git a/test/lib/puppettest/support/utils.rb b/test/lib/puppettest/support/utils.rb index d9bd6b2b6..83509733e 100644 --- a/test/lib/puppettest/support/utils.rb +++ b/test/lib/puppettest/support/utils.rb @@ -1,178 +1,178 @@ require 'puppettest' module PuppetTest::Support end module PuppetTest::Support::Utils def gcdebug(type) Puppet.warning "%s: %s" % [type, ObjectSpace.each_object(type) { |o| }] end # # TODO: I think this method needs to be renamed to something a little more # explanatory. # def newobj(type, name, hash) transport = Puppet::TransObject.new(name, "file") transport[:path] = path transport[:ensure] = "file" assert_nothing_raised { file = transport.to_type } end # Turn a list of resources, or possibly a configuration and some resources, # into a configuration object. def resources2config(*resources) if resources[0].is_a?(Puppet::Node::Configuration) config = resources.shift unless resources.empty? - resources.each { |r| config.add_resource r } + resources.each { |r| config.add_resource(r) unless config.resource(r.class.name, r.title) } end elsif resources[0].is_a?(Puppet.type(:component)) raise ArgumentError, "resource2config() no longer accpts components" comp = resources.shift comp.delve else config = Puppet::Node::Configuration.new resources.each { |res| config.add_resource res } end return config end # stop any services that might be hanging around def stopservices if stype = Puppet::Type.type(:service) stype.each { |service| service[:ensure] = :stopped service.evaluate } end end # TODO: rewrite this to use the 'etc' module. # Define a variable that contains the name of my user. def setme # retrieve the user name id = %x{id}.chomp if id =~ /uid=\d+\(([^\)]+)\)/ @me = $1 else puts id end unless defined? @me raise "Could not retrieve user name; 'id' did not work" end end # Define a variable that contains a group I'm in. def set_mygroup # retrieve the user name group = %x{groups}.chomp.split(/ /)[0] unless group raise "Could not find group to set in @mygroup" end @mygroup = group end def run_events(type, trans, events, msg) case type when :evaluate, :rollback: # things are hunky-dory else raise Puppet::DevError, "Incorrect run_events type" end method = type newevents = nil assert_nothing_raised("Transaction %s %s failed" % [type, msg]) { newevents = trans.send(method).reject { |e| e.nil? }.collect { |e| e.event } } assert_equal(events, newevents, "Incorrect %s %s events" % [type, msg]) return trans end # If there are any fake data files, retrieve them def fakedata(dir) ary = [basedir, "test"] ary += dir.split("/") dir = File.join(ary) unless FileTest.exists?(dir) raise Puppet::DevError, "No fakedata dir %s" % dir end files = Dir.entries(dir).reject { |f| f =~ /^\./ }.collect { |f| File.join(dir, f) } return files end def fakefile(name) ary = [PuppetTest.basedir, "test"] ary += name.split("/") file = File.join(ary) unless FileTest.exists?(file) raise Puppet::DevError, "No fakedata file %s" % file end return file end # wrap how to retrieve the masked mode def filemode(file) File.stat(file).mode & 007777 end def memory Puppet::Util.memory end # a list of files that we can parse for testing def textfiles textdir = datadir "snippets" Dir.entries(textdir).reject { |f| f =~ /^\./ or f =~ /fail/ }.each { |f| yield File.join(textdir, f) } end def failers textdir = datadir "failers" # only parse this one file now files = Dir.entries(textdir).reject { |file| file =~ %r{\.swp} }.reject { |file| file =~ %r{\.disabled} }.collect { |file| File.join(textdir,file) }.find_all { |file| FileTest.file?(file) }.sort.each { |file| Puppet.debug "Processing %s" % file yield file } end def mk_configuration(*resources) if resources[0].is_a?(String) name = resources.shift else name = :testing end config = Puppet::Node::Configuration.new :testing do |conf| resources.each { |resource| conf.add_resource resource } end return config end end module PuppetTest include PuppetTest::Support::Utils end diff --git a/test/network/client/client.rb b/test/network/client/client.rb index 4a7e9cdb6..8d08bd3d7 100755 --- a/test/network/client/client.rb +++ b/test/network/client/client.rb @@ -1,254 +1,225 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../lib/puppettest' require 'puppettest' require 'mocha' require 'puppet/network/client' class TestClient < Test::Unit::TestCase include PuppetTest::ServerTest class FakeClient < Puppet::Network::Client @drivername = :Test end class FakeDriver end # a single run through of connect, auth, etc. def disabled_test_sslInitWithAutosigningLocalServer # autosign everything, for simplicity Puppet[:autosign] = true # create a server to which to connect mkserver() # create our client client = nil assert_nothing_raised { client = Puppet::Network::Client.master.new( :Server => "localhost", :Port => @@port ) } # get our certs assert_nothing_raised { client.initcerts } # make sure all of our cert files exist certfile = File.join(Puppet[:certdir], [client.fqdn, "pem"].join(".")) keyfile = File.join(Puppet[:privatekeydir], [client.fqdn, "pem"].join(".")) publickeyfile = File.join(Puppet[:publickeydir], [client.fqdn, "pem"].join(".")) assert(File.exists?(keyfile)) assert(File.exists?(certfile)) assert(File.exists?(publickeyfile)) # verify we can retrieve the configuration assert_nothing_raised("Client could not retrieve configuration") { client.getconfig } # and apply it assert_nothing_raised("Client could not apply configuration") { client.apply } # and verify that it did what it was supposed to assert(FileTest.exists?(@createdfile), "Applied file does not exist") end # here we create two servers; we def disabled_test_failureWithUntrustedCerts Puppet[:autosign] = true # create a pair of clients with no certs nonemaster = nil assert_nothing_raised { nonemaster = Puppet::Network::Client.master.new( :Server => "localhost", :Port => @@port ) } nonebucket = nil assert_nothing_raised { nonebucket = Puppet::Network::Client.dipper.new( :Server => "localhost", :Port => @@port ) } # create a ca so we can create a set of certs # make a new ssldir for it ca = nil assert_nothing_raised { ca = Puppet::Network::Client.ca.new( :CA => true, :Local => true ) ca.requestcert } # initialize our clients with this set of certs certmaster = nil assert_nothing_raised { certmaster = Puppet::Network::Client.master.new( :Server => "localhost", :Port => @@port ) } certbucket = nil assert_nothing_raised { certbucket = Puppet::Network::Client.dipper.new( :Server => "localhost", :Port => @@port ) } # Create a new ssl root. confdir = tempfile() Puppet[:ssldir] = confdir Puppet.settings.mkdir(:ssldir) Puppet.settings.clearused Puppet.settings.use(:ssl, :ca) mkserver # now verify that our client cannot do non-cert operations # because its certs are signed by a different CA assert_raise(Puppet::Error, "Client was allowed to call getconfig with no certs") { nonemaster.getconfig } assert_raise(Puppet::Error, "Client was allowed to call getconfig with untrusted certs") { certmaster.getconfig } assert_raise(Puppet::Network::XMLRPCClientError, "Client was allowed to call backup with no certs") { nonebucket.backup("/etc/passwd") } assert_raise(Puppet::Network::XMLRPCClientError, "Client was allowed to call backup with untrusted certs") { certbucket.backup("/etc/passwd") } end - def test_classfile - Puppet[:code] = "class yaytest {}\n class bootest {}\n include yaytest, bootest" - - Puppet::Node::Facts.indirection.stubs(:save) - - master = client = nil - assert_nothing_raised() { - master = Puppet::Network::Handler.master.new( - :Local => false - ) - } - assert_nothing_raised() { - client = Puppet::Network::Client.master.new( - :Master => master - ) - } - - # Fake that it's local, so it creates the class file - client.local = false - - # We can't guarantee class ordering - client.expects(:setclasses).with do |array| - array.length == 2 and array.include?("yaytest") and array.include?("bootest") - end - assert_nothing_raised { - client.getconfig - } - end - def test_client_loading # Make sure we don't get a failure but that we also get nothing back assert_nothing_raised do assert_nil(Puppet::Network::Client.client(:fake), "Got something back from a missing client") assert_nil(Puppet::Network::Client.fake, "Got something back from missing client method") end # Make a fake client dir = tempfile() libdir = File.join([dir, %w{puppet network client}].flatten) FileUtils.mkdir_p(libdir) file = File.join(libdir, "faker.rb") File.open(file, "w") do |f| f.puts %{class Puppet::Network::Client class Faker < Client end end } end $: << dir cleanup { $:.delete(dir) if $:.include?(dir) } client = nil assert_nothing_raised do client = Puppet::Network::Client.client(:faker) end assert(client, "did not load client") assert_nothing_raised do assert_equal(client, Puppet::Network::Client.faker, "Did not get client back from client method") end # Now make sure the client behaves correctly assert_equal(:Faker, client.name, "name was not calculated correctly") end # Make sure we get a client class for each handler type. def test_loading_all_clients %w{ca dipper file master report resource runner status}.each do |name| client = nil assert_nothing_raised do client = Puppet::Network::Client.client(name) end assert(client, "did not get client for %s" % name) [:name, :handler, :drivername].each do |thing| assert(client.send(thing), "did not get %s for %s" % [thing, name]) end end end # Make sure that reading the cert in also sets up the cert stuff for the driver def test_read_cert Puppet::Util::SUIDManager.stubs(:asuser).yields ca = Puppet::Network::Handler.ca.new caclient = Puppet::Network::Client.ca.new :CA => ca caclient.request_cert # First make sure it doesn't get called when the driver doesn't support :cert_setup client = FakeClient.new :Test => FakeDriver.new driver = client.driver assert_nothing_raised("Could not read cert") do client.read_cert end # And then that it does when the driver supports it client = FakeClient.new :Test => FakeDriver.new driver = client.driver driver.meta_def(:cert_setup) { |c| } driver.expects(:cert_setup).with(client) assert_nothing_raised("Could not read cert") do client.read_cert end end end diff --git a/test/network/client/master.rb b/test/network/client/master.rb index 7216af936..0caba4bd2 100755 --- a/test/network/client/master.rb +++ b/test/network/client/master.rb @@ -1,598 +1,547 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../lib/puppettest' require 'puppettest' require 'mocha' class TestMasterClient < Test::Unit::TestCase include PuppetTest::ServerTest def setup super @master = Puppet::Network::Client.master end def mkmaster(options = {}) options[:UseNodes] = false options[:Local] = true if code = options[:Code] Puppet[:code] = code else Puppet[:manifest] = options[:Manifest] || mktestmanifest end # create our master # this is the default server setup master = Puppet::Network::Handler.master.new(options) return master end def mkclient(master = nil) master ||= mkmaster() client = Puppet::Network::Client.master.new( :Master => master ) return client end def test_disable FileUtils.mkdir_p(Puppet[:statedir]) manifest = mktestmanifest master = mkmaster(:Manifest => manifest) client = mkclient(master) assert_nothing_raised("Could not disable client") { client.disable } client.expects(:getconfig).never client.run client = mkclient(master) client.expects(:getconfig) assert_nothing_raised("Could not enable client") { client.enable } client.run end # Make sure we're getting the client version in our list of facts def test_clientversionfact facts = nil assert_nothing_raised { facts = Puppet::Network::Client.master.facts } assert_equal(Puppet.version.to_s, facts["clientversion"]) end # Make sure non-string facts don't make things go kablooie def test_nonstring_facts FileUtils.mkdir_p(Puppet[:statedir]) # Add a nonstring fact Facter.add("nonstring") do setcode { 1 } end assert_equal(1, Facter.nonstring, "Fact was a string from facter") client = mkclient() assert(! FileTest.exists?(@createdfile)) assert_nothing_raised { client.run } end # This method downloads files, and yields each file object if a block is given. def test_download source = tempfile() dest = tempfile() sfile = File.join(source, "file") dfile = File.join(dest, "file") Dir.mkdir(source) File.open(sfile, "w") {|f| f.puts "yay"} files = [] assert_nothing_raised do files = Puppet::Network::Client.master.download(:dest => dest, :source => source, :name => "testing") end assert(FileTest.directory?(dest), "dest dir was not created") assert(FileTest.file?(dfile), "dest file was not created") assert_equal(File.read(sfile), File.read(dfile), "Dest file had incorrect contents") assert_equal([dest, dfile].sort, files.sort, "Changed files were not returned correctly") end def test_getplugins Puppet[:filetimeout] = -1 Puppet[:pluginsource] = tempfile() Dir.mkdir(Puppet[:pluginsource]) Dir.mkdir(File.join(Puppet[:pluginsource], "testing")) $loaded = [] loader = Puppet::Util::Autoload.new(self, "testing") myplugin = File.join(Puppet[:pluginsource], "testing", "myplugin.rb") File.open(myplugin, "w") do |f| f.puts %{$loaded << :myplugin} end assert_nothing_raised("Could not get plugins") { Puppet::Network::Client.master.getplugins } destfile = File.join(Puppet[:plugindest], "testing", "myplugin.rb") assert(File.exists?(destfile), "Did not get plugin") assert(loader.load(:myplugin), "Did not load downloaded plugin") assert($loaded.include?(:myplugin), "Downloaded code was not evaluated") # Now modify the file and make sure the type is replaced File.open(myplugin, "w") do |f| f.puts %{$loaded << :changed} end assert_nothing_raised("Could not get plugin changes") { Puppet::Network::Client.master.getplugins } assert($loaded.include?(:changed), "Changed code was not evaluated") # Now try it again, to make sure we don't have any objects lying around assert_nothing_raised { Puppet::Network::Client.master.getplugins } end def test_getfacts Puppet[:filetimeout] = -1 Puppet[:factsource] = tempfile() Dir.mkdir(Puppet[:factsource]) hostname = Facter.value(:hostname) myfact = File.join(Puppet[:factsource], "myfact.rb") File.open(myfact, "w") do |f| f.puts %{Facter.add("myfact") do setcode { "yayness" } end } end assert_nothing_raised { Puppet::Network::Client.master.getfacts } destfile = File.join(Puppet[:factdest], "myfact.rb") assert(File.exists?(destfile), "Did not get fact") assert_equal(hostname, Facter.value(:hostname), "Lost value to hostname") assert_equal("yayness", Facter.value(:myfact), "Did not get correct fact value") # Now modify the file and make sure the type is replaced File.open(myfact, "w") do |f| f.puts %{Facter.add("myfact") do setcode { "funtest" } end } end assert_nothing_raised { Puppet::Network::Client.master.getfacts } assert_equal("funtest", Facter.value(:myfact), "Did not reload fact") assert_equal(hostname, Facter.value(:hostname), "Lost value to hostname") # Now run it again and make sure the fact still loads assert_nothing_raised { Puppet::Network::Client.master.getfacts } assert_equal("funtest", Facter.value(:myfact), "Did not reload fact") assert_equal(hostname, Facter.value(:hostname), "Lost value to hostname") end # Make sure we load all facts on startup. def test_loadfacts dirs = [tempfile(), tempfile()] count = 0 names = [] dirs.each do |dir| Dir.mkdir(dir) name = "fact%s" % count names << name file = File.join(dir, "%s.rb" % name) # Write out a plugin file File.open(file, "w") do |f| f.puts %{Facter.add("#{name}") do setcode { "#{name}" } end } end count += 1 end Puppet[:factpath] = dirs.join(":") names.each do |name| assert_nil(Facter.value(name), "Somehow retrieved invalid fact") end assert_nothing_raised { Puppet::Network::Client.master.loadfacts } names.each do |name| assert_equal(name, Facter.value(name), "Did not retrieve facts") end end if Process.uid == 0 # Testing #283. Make sure plugins et al are downloaded as the running user. def test_download_ownership dir = tstdir() dest = tstdir() file = File.join(dir, "file") File.open(file, "w") { |f| f.puts "funtest" } user = nonrootuser() group = nonrootgroup() chowner = Puppet::Type.type(:file).create :path => dir, :owner => user.name, :group => group.name, :recurse => true assert_apply(chowner) chowner.remove assert_equal(user.uid, File.stat(file).uid) assert_equal(group.gid, File.stat(file).gid) assert_nothing_raised { Puppet::Network::Client.master.download(:dest => dest, :source => dir, :name => "testing" ) {} } destfile = File.join(dest, "file") assert(FileTest.exists?(destfile), "Did not create destfile") assert_equal(Process.uid, File.stat(destfile).uid) end end # Test retrieving all of the facts. def test_facts facts = nil assert_nothing_raised do facts = Puppet::Network::Client.master.facts end Facter.to_hash.each do |fact, value| assert_equal(facts[fact.downcase], value.to_s, "%s is not equal" % fact.inspect) end # Make sure the puppet version got added assert_equal(Puppet::PUPPETVERSION, facts["clientversion"], "client version did not get added") # And make sure the ruby version is in there assert_equal(RUBY_VERSION, facts["rubyversion"], "ruby version did not get added") end # #424 def test_caching_of_compile_time file = tempfile() manifest = tempfile() File.open(manifest, "w") { |f| f.puts "file { '#{file}': content => yay }" } Puppet::Node::Facts.indirection.stubs(:save) driver = mkmaster(:Manifest => manifest) driver.local = false master = mkclient(driver) # We have to make everything thinks it's remote, because there's no local caching info master.local = false assert(! master.fresh?(master.class.facts), "Considered fresh with no compile at all") assert_nothing_raised { master.run } assert(master.fresh?(master.class.facts), "not considered fresh after compile") # Now make sure the config time is cached assert(master.compile_time, "No stored config time") assert_equal(master.compile_time, Puppet::Util::Storage.cache(:configuration)[:compile_time], "times did not match") time = master.compile_time master.clear File.unlink(file) Puppet::Util::Storage.store # Now make a new master Puppet::Util::Storage.clear master = mkclient(driver) master.run assert_equal(time, master.compile_time, "time was not retrieved from cache") assert(FileTest.exists?(file), "file was not created on second run") end - def test_default_objects - # Make sure they start out missing - assert_nil(Puppet::Type.type(:filebucket)["puppet"], - "default filebucket already exists") - assert_nil(Puppet::Type.type(:schedule)["daily"], - "default schedules already exists") - - master = mkclient() - - # Now make sure they got created - assert(Puppet::Type.type(:filebucket)["puppet"], - "default filebucket not found") - assert(Puppet::Type.type(:schedule)["daily"], - "default schedules not found") - - # clear everything, and make sure we can recreate them - Puppet::Type.allclear - assert_nil(Puppet::Type.type(:filebucket)["puppet"], - "default filebucket not removed") - assert_nil(Puppet::Type.type(:schedule)["daily"], - "default schedules not removed") - assert_nothing_raised { master.mkdefault_objects } - assert(Puppet::Type.type(:filebucket)["puppet"], - "default filebucket not found") - assert(Puppet::Type.type(:schedule)["daily"], - "default schedules not found") - - - # Make sure we've got schedules - assert(Puppet::Type.type(:schedule)["hourly"], "Could not retrieve hourly schedule") - assert(Puppet::Type.type(:filebucket)["puppet"], "Could not retrieve default bucket") - end - # #540 - make sure downloads aren't affected by noop def test_download_in_noop source = tempfile File.open(source, "w") { |f| f.puts "something" } dest = tempfile Puppet[:noop] = true assert_nothing_raised("Could not download in noop") do @master.download(:dest => dest, :source => source, :tag => "yay") end assert(FileTest.exists?(dest), "did not download in noop mode") assert(Puppet[:noop], "noop got disabled in run") end # #491 - make sure a missing config doesn't kill us def test_missing_localconfig master = mkclient master.local = false driver = master.send(:instance_variable_get, "@driver") driver.local = false Puppet::Node::Facts.indirection.stubs(:save) # Retrieve the configuration master.getconfig # Now the config is up to date, so get rid of the @objects var and # the cached config master.clear File.unlink(master.cachefile) assert_nothing_raised("Missing cache file threw error") do master.getconfig end assert(! @logs.detect { |l| l.message =~ /Could not load/}, "Tried to load cache when it is non-existent") end # #519 - cache the facts so that we notice if they change. def test_factchanges_cause_recompile $value = "one" Facter.add(:testfact) do setcode { $value } end assert_equal("one", Facter.value(:testfact), "fact was not set correctly") master = mkclient master.local = false driver = master.send(:instance_variable_get, "@driver") driver.local = false Puppet::Node::Facts.indirection.stubs(:save) assert_nothing_raised("Could not compile config") do master.getconfig end $value = "two" Facter.clear Facter.loadfacts Facter.add(:testfact) do setcode { $value } end facts = master.class.facts assert_equal("two", Facter.value(:testfact), "fact did not change") assert(master.send(:facts_changed?, facts), "master does not think facts changed") assert(! master.fresh?(facts), "master is considered fresh after facts changed") assert_nothing_raised("Could not recompile when facts changed") do master.getconfig end end def test_locking master = mkclient class << master def getconfig raise ArgumentError, "Just testing" end end master.run assert(! master.send(:lockfile).locked?, "Master is still locked after failure") end # Make sure we get a value for timeout def test_config_timeout master = Puppet::Network::Client.client(:master) time = Integer(Puppet[:configtimeout]) assert_equal(time, master.timeout, "Did not get default value for timeout") assert_equal(time, master.timeout, "Did not get default value for timeout on second run") # Reset it Puppet[:configtimeout] = "50" assert_equal(50, master.timeout, "Did not get changed default value for timeout") assert_equal(50, master.timeout, "Did not get changed default value for timeout on second run") # Now try an integer Puppet[:configtimeout] = 100 assert_equal(100, master.timeout, "Did not get changed integer default value for timeout") assert_equal(100, master.timeout, "Did not get changed integer default value for timeout on second run") end # #569 -- Make sure we can ignore dynamic facts. def test_dynamic_facts client = mkclient assert_equal(%w{memorysize memoryfree swapsize swapfree}, client.class.dynamic_facts, "Did not get correct defaults for dynamic facts") # Cache some values for comparison cached = {"one" => "yep", "two" => "nope"} Puppet::Util::Storage.cache(:configuration)[:facts] = cached assert(! client.send(:facts_changed?, cached), "Facts incorrectly considered to be changed") # Now add some values to the passed result and make sure we get a positive newfacts = cached.dup newfacts["changed"] = "something" assert(client.send(:facts_changed?, newfacts), "Did not catch changed fact") # Now add a dynamic fact and make sure it's ignored newfacts = cached.dup newfacts["memorysize"] = "something" assert(! client.send(:facts_changed?, newfacts), "Dynamic facts resulted in a false positive") # And try it with both cached["memorysize"] = "something else" assert(! client.send(:facts_changed?, newfacts), "Dynamic facts resulted in a false positive") # And finally, with only in the cache newfacts.delete("memorysize") assert(! client.send(:facts_changed?, newfacts), "Dynamic facts resulted in a false positive") end def test_splay client = mkclient # Make sure we default to no splay client.expects(:sleep).never assert_nothing_raised("Failed to call splay") do client.send(:splay) end # Now set it to true and make sure we get the right value client = mkclient client.expects(:sleep) Puppet[:splay] = true assert_nothing_raised("Failed to call sleep when splay is true") do client.send(:splay) end time = Puppet::Util::Storage.cache(:configuration)[:splay_time] assert(time, "Splay time was not cached") # Now try it again client = mkclient client.expects(:sleep).with(time) assert_nothing_raised("Failed to call sleep when splay is true with a cached value") do client.send(:splay) end end def test_environment_is_added_to_facts facts = Puppet::Network::Client::Master.facts assert_equal(facts["environment"], Puppet[:environment], "Did not add environment to client facts") # Now set it to a real value Puppet[:environment] = "something" facts = Puppet::Network::Client::Master.facts assert_equal(facts["environment"], Puppet[:environment], "Did not add environment to client facts") end - # This is partially to fix #532, but also to save on memory. - def test_remove_objects_after_every_run - client = mkclient - - ftype = Puppet::Type.type(:file) - file = ftype.create :title => "/what/ever", :ensure => :present - config = Puppet::Node::Configuration.new - config.add_resource(file) - - config.expects :apply - - client.configuration = config - client.expects(:getconfig) - client.run - - assert_nil(ftype[@createdfile], "file object was not removed from memory") - end - # #685 def test_http_failures_do_not_kill_puppetd client = mkclient client.meta_def(:getconfig) { raise "A failure" } assert_nothing_raised("Failure in getconfig threw an error") do client.run end end def test_invalid_configurations_do_not_get_cached master = mkmaster :Code => "notify { one: require => File[yaytest] }" master.local = false # so it gets cached client = mkclient(master) client.stubs(:facts).returns({}) client.local = false Puppet::Node::Facts.indirection.stubs(:terminus_class).returns(:memory) # Make sure the config is not cached. client.expects(:cache).never client.getconfig # Doesn't throw an exception, but definitely fails. client.run end end diff --git a/test/network/handler/fileserver.rb b/test/network/handler/fileserver.rb index 3539169dc..8703700ea 100755 --- a/test/network/handler/fileserver.rb +++ b/test/network/handler/fileserver.rb @@ -1,1153 +1,1158 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../lib/puppettest' require 'puppettest' require 'puppet/network/handler/fileserver' class TestFileServer < Test::Unit::TestCase include PuppetTest + def setup + super + Facter.stubs(:to_hash).returns({}) + end + def mkmount(path = nil) mount = nil name = "yaytest" base = path || tempfile() unless FileTest.exists?(base) Dir.mkdir(base) end # Create a test file File.open(File.join(base, "file"), "w") { |f| f.puts "bazoo" } assert_nothing_raised { mount = Puppet::Network::Handler.fileserver::Mount.new(name, base) } return mount end # make a simple file source def mktestdir testdir = File.join(tmpdir(), "remotefilecopytesting") @@tmpfiles << testdir # create a tmpfile pattern = "tmpfile" tmpfile = File.join(testdir, pattern) assert_nothing_raised { Dir.mkdir(testdir) File.open(tmpfile, "w") { |f| 3.times { f.puts rand(100) } } } return [testdir, %r{#{pattern}}, tmpfile] end # make a bunch of random test files def mktestfiles(testdir) @@tmpfiles << testdir assert_nothing_raised { files = %w{a b c d e}.collect { |l| name = File.join(testdir, "file%s" % l) File.open(name, "w") { |f| f.puts rand(100) } name } return files } end def assert_describe(base, file, server) file = File.basename(file) assert_nothing_raised { desc = server.describe(base + file) assert(desc, "Got no description for %s" % file) assert(desc != "", "Got no description for %s" % file) assert_match(/^\d+/, desc, "Got invalid description %s" % desc) } end # test for invalid names def test_namefailures server = nil assert_nothing_raised { server = Puppet::Network::Handler.fileserver.new( :Local => true, :Config => false ) } [" ", "=" "+", "&", "#", "*"].each do |char| assert_raise(Puppet::Network::Handler::FileServerError, "'%s' did not throw a failure in fileserver module names" % char) { server.mount("/tmp", "invalid%sname" % char) } end end # verify that listing the root behaves as expected def test_listroot server = nil testdir, pattern, tmpfile = mktestdir() file = nil checks = Puppet::Network::Handler.fileserver::CHECKPARAMS # and make our fileserver assert_nothing_raised { server = Puppet::Network::Handler.fileserver.new( :Local => true, :Config => false ) } # mount the testdir assert_nothing_raised { server.mount(testdir, "test") } # and verify different iterations of 'root' return the same value list = nil assert_nothing_raised { list = server.list("/test/", :ignore, true, false) } assert(list =~ pattern) assert_nothing_raised { list = server.list("/test", :ignore, true, false) } assert(list =~ pattern) end # test listing individual files def test_getfilelist server = nil testdir, pattern, tmpfile = mktestdir() file = nil assert_nothing_raised { server = Puppet::Network::Handler.fileserver.new( :Local => true, :Config => false ) } assert_nothing_raised { server.mount(testdir, "test") } # get our listing list = nil sfile = "/test/tmpfile" assert_nothing_raised { list = server.list(sfile, :ignore, true, false) } - assert_nothing_raised { - file = Puppet.type(:file)[tmpfile] - } - output = "/\tfile" # verify it got listed as a file assert_equal(output, list) # verify we got all fields assert(list !~ /\t\t/) # verify that we didn't get the directory itself list.split("\n").each { |line| assert(line !~ %r{remotefile}) } # and then verify that the contents match contents = File.read(tmpfile) ret = nil assert_nothing_raised { ret = server.retrieve(sfile) } assert_equal(contents, ret) end # check that the fileserver is seeing newly created files def test_seenewfiles server = nil testdir, pattern, tmpfile = mktestdir() newfile = File.join(testdir, "newfile") # go through the whole schtick again... file = nil checks = Puppet::Network::Handler.fileserver::CHECKPARAMS assert_nothing_raised { server = Puppet::Network::Handler.fileserver.new( :Local => true, :Config => false ) } assert_nothing_raised { server.mount(testdir, "test") } list = nil sfile = "/test/" assert_nothing_raised { list = server.list(sfile, :ignore, true, false) } # create the new file File.open(newfile, "w") { |f| 3.times { f.puts rand(100) } } newlist = nil assert_nothing_raised { newlist = server.list(sfile, :ignore, true, false) } # verify the list has changed assert(list != newlist) # and verify that we are specifically seeing the new file assert(newlist =~ /newfile/) end # verify we can mount /, which is what local file servers will # normally do def test_mountroot server = nil assert_nothing_raised { server = Puppet::Network::Handler.fileserver.new( :Local => true, :Config => false ) } assert_nothing_raised { server.mount("/", "root") } testdir, pattern, tmpfile = mktestdir() list = nil assert_nothing_raised { list = server.list("/root/" + testdir, :ignore, true, false) } assert(list =~ pattern) assert_nothing_raised { list = server.list("/root" + testdir, :ignore, true, false) } assert(list =~ pattern) end # verify that we're correctly recursing the right number of levels def test_recursionlevels server = nil assert_nothing_raised { server = Puppet::Network::Handler.fileserver.new( :Local => true, :Config => false ) } # make our deep recursion basedir = File.join(tmpdir(), "recurseremotetesting") testdir = "%s/with/some/sub/directories/for/the/purposes/of/testing" % basedir oldfile = File.join(testdir, "oldfile") assert_nothing_raised { system("mkdir -p %s" % testdir) File.open(oldfile, "w") { |f| 3.times { f.puts rand(100) } } @@tmpfiles << basedir } assert_nothing_raised { server.mount(basedir, "test") } # get our list list = nil assert_nothing_raised { list = server.list("/test/with", :ignore, false, false) } # make sure we only got one line, since we're not recursing assert(list !~ /\n/) # for each level of recursion, make sure we get the right list [0, 1, 2].each { |num| assert_nothing_raised { list = server.list("/test/with", :ignore, num, false) } count = 0 while list =~ /\n/ list.sub!(/\n/, '') count += 1 end assert_equal(num, count) } end # verify that we're not seeing the dir we ask for; i.e., that our # list is relative to that dir, not it's parent dir def test_listedpath server = nil assert_nothing_raised { server = Puppet::Network::Handler.fileserver.new( :Local => true, :Config => false ) } # create a deep dir basedir = tempfile() testdir = "%s/with/some/sub/directories/for/testing" % basedir oldfile = File.join(testdir, "oldfile") assert_nothing_raised { system("mkdir -p %s" % testdir) File.open(oldfile, "w") { |f| 3.times { f.puts rand(100) } } @@tmpfiles << basedir } # mounty mounty assert_nothing_raised { server.mount(basedir, "localhost") } list = nil # and then check a few dirs assert_nothing_raised { list = server.list("/localhost/with", :ignore, false, false) } assert(list !~ /with/) assert_nothing_raised { list = server.list("/localhost/with/some/sub", :ignore, true, false) } assert(list !~ /sub/) end # test many dirs, not necessarily very deep def test_widelists server = nil assert_nothing_raised { server = Puppet::Network::Handler.fileserver.new( :Local => true, :Config => false ) } basedir = tempfile() dirs = %w{a set of directories} assert_nothing_raised { Dir.mkdir(basedir) dirs.each { |dir| Dir.mkdir(File.join(basedir, dir)) } @@tmpfiles << basedir } assert_nothing_raised { server.mount(basedir, "localhost") } list = nil assert_nothing_raised { list = server.list("/localhost/", :ignore, 1, false) } assert_instance_of(String, list, "Server returned %s instead of string") list = list.split("\n") assert_equal(dirs.length + 1, list.length) end # verify that 'describe' works as advertised def test_describe server = nil testdir = tstdir() files = mktestfiles(testdir) file = nil checks = Puppet::Network::Handler.fileserver::CHECKPARAMS assert_nothing_raised { server = Puppet::Network::Handler.fileserver.new( :Local => true, :Config => false ) } assert_nothing_raised { server.mount(testdir, "test") } # get our list list = nil sfile = "/test/" assert_nothing_raised { list = server.list(sfile, :ignore, true, false) } # and describe each file in the list assert_nothing_raised { list.split("\n").each { |line| file, type = line.split("\t") desc = server.describe(sfile + file) } } # and then make sure we can describe everything that we know is there files.each { |file| assert_describe(sfile, file, server) } # And then describe some files that we know aren't there retval = nil assert_nothing_raised("Describing non-existent files raised an error") { retval = server.describe(sfile + "noexisties") } assert_equal("", retval, "Description of non-existent files returned a value") # Now try to describe some sources that don't even exist retval = nil assert_raise(Puppet::Network::Handler::FileServerError, "Describing non-existent mount did not raise an error") { retval = server.describe("/notmounted/" + "noexisties") } assert_nil(retval, "Description of non-existent mounts returned a value") end # test that our config file is parsing and working as planned def test_configfile server = nil basedir = File.join(tmpdir, "fileserverconfigfiletesting") @@tmpfiles << basedir # make some dirs for mounting Dir.mkdir(basedir) mounts = {} %w{thing thus the-se those}.each { |dir| path = File.join(basedir, dir) Dir.mkdir(path) mounts[dir] = mktestfiles(path) } # create an example file with each of them conffile = tempfile @@tmpfiles << conffile File.open(conffile, "w") { |f| f.print "# a test config file [thing] path #{basedir}/thing allow 192.168.0.* [thus] path #{basedir}/thus allow *.madstop.com, *.kanies.com deny *.sub.madstop.com [the-se] path #{basedir}/the-se [those] path #{basedir}/those " } # create a server with the file assert_nothing_raised { server = Puppet::Network::Handler.fileserver.new( :Local => false, :Config => conffile ) } list = nil # run through once with no host/ip info, to verify everything is working mounts.each { |mount, files| mount = "/#{mount}/" assert_nothing_raised { list = server.list(mount, :ignore, true, false) } assert_nothing_raised { list.split("\n").each { |line| file, type = line.split("\t") desc = server.describe(mount + file) } } files.each { |f| assert_describe(mount, f, server) } } # now let's check that things are being correctly forbidden # this is just a map of names and expected results { "thing" => { :deny => [ ["hostname.com", "192.168.1.0"], ["hostname.com", "192.158.0.0"] ], :allow => [ ["hostname.com", "192.168.0.0"], ["hostname.com", "192.168.0.245"], ] }, "thus" => { :deny => [ ["hostname.com", "192.168.1.0"], ["name.sub.madstop.com", "192.158.0.0"] ], :allow => [ ["luke.kanies.com", "192.168.0.0"], ["luke.madstop.com", "192.168.0.245"], ] } }.each { |mount, hash| mount = "/#{mount}/" # run through the map hash.each { |type, ary| ary.each { |sub| host, ip = sub case type when :deny: assert_raise(Puppet::AuthorizationError, "Host %s, ip %s, allowed %s" % [host, ip, mount]) { list = server.list(mount, :ignore, true, false, host, ip) } when :allow: assert_nothing_raised("Host %s, ip %s, denied %s" % [host, ip, mount]) { list = server.list(mount, :ignore, true, false, host, ip) } end } } } end # Test that we smoothly handle invalid config files def test_configfailures # create an example file with each of them conffile = tempfile() invalidmounts = { "noexist" => "[noexist] path /this/path/does/not/exist allow 192.168.0.* " } invalidconfigs = [ "[not valid] path /this/path/does/not/exist allow 192.168.0.* ", "[valid] invalidstatement path /etc allow 192.168.0.* ", "[valid] allow 192.168.0.* " ] invalidmounts.each { |mount, text| File.open(conffile, "w") { |f| f.print text } # create a server with the file server = nil assert_nothing_raised { server = Puppet::Network::Handler.fileserver.new( :Local => true, :Config => conffile ) } assert_raise(Puppet::Network::Handler::FileServerError, "Invalid mount was mounted") { server.list(mount, :ignore) } } invalidconfigs.each_with_index { |text, i| File.open(conffile, "w") { |f| f.print text } # create a server with the file server = nil assert_raise(Puppet::Network::Handler::FileServerError, "Invalid config %s did not raise error" % i) { server = Puppet::Network::Handler.fileserver.new( :Local => true, :Config => conffile ) } } end # verify we reread the config file when it changes def test_filereread server = nil conffile = tempfile() dir = tstdir() files = mktestfiles(dir) File.open(conffile, "w") { |f| f.print "# a test config file [thing] path #{dir} allow test1.domain.com " } # Reset the timeout, so we reload faster Puppet[:filetimeout] = 0.5 # start our server with a fast timeout assert_nothing_raised { server = Puppet::Network::Handler.fileserver.new( :Local => false, :Config => conffile ) } list = nil assert_nothing_raised { list = server.list("/thing/", :ignore, false, false, "test1.domain.com", "127.0.0.1") } assert(list != "", "List returned nothing in rereard test") assert_raise(Puppet::AuthorizationError, "List allowed invalid host") { list = server.list("/thing/", :ignore, false, false, "test2.domain.com", "127.0.0.1") } sleep 1 File.open(conffile, "w") { |f| f.print "# a test config file [thing] path #{dir} allow test2.domain.com " } assert_raise(Puppet::AuthorizationError, "List allowed invalid host") { list = server.list("/thing/", :ignore, false, false, "test1.domain.com", "127.0.0.1") } assert_nothing_raised { list = server.list("/thing/", :ignore, false, false, "test2.domain.com", "127.0.0.1") } assert(list != "", "List returned nothing in rereard test") list = nil end # Verify that we get converted to the right kind of string def test_mountstring mount = nil name = "yaytest" path = tmpdir() assert_nothing_raised { mount = Puppet::Network::Handler.fileserver::Mount.new(name, path) } assert_equal("mount[#{name}]", mount.to_s) end def test_servinglinks # Disable the checking, so changes propagate immediately. Puppet[:filetimeout] = -5 server = nil source = tempfile() file = File.join(source, "file") link = File.join(source, "link") Dir.mkdir(source) File.open(file, "w") { |f| f.puts "yay" } File.symlink(file, link) assert_nothing_raised { server = Puppet::Network::Handler.fileserver.new( :Local => true, :Config => false ) } assert_nothing_raised { server.mount(source, "mount") } # First describe the link when following results = {} assert_nothing_raised { server.describe("/mount/link", :follow).split("\t").zip( Puppet::Network::Handler.fileserver::CHECKPARAMS ).each { |v,p| results[p] = v } } assert_equal("file", results[:type]) # Then not results = {} assert_nothing_raised { server.describe("/mount/link", :ignore).split("\t").zip( Puppet::Network::Handler.fileserver::CHECKPARAMS ).each { |v,p| results[p] = v } } assert_equal("link", results[:type]) results.each { |p,v| assert(v, "%s has no value" % p) assert(v != "", "%s has no value" % p) } end # Test that substitution patterns in the path are exapanded # properly. Disabled, because it was testing too much of the process # and in a non-portable way. This is a thorough enough test that it should # be kept, but it should be done in a way that is clearly portable (e.g., # no md5 sums of file paths). def test_host_specific client1 = "client1.example.com" client2 = "client2.example.com" ip = "127.0.0.1" # Setup a directory hierarchy for the tests fsdir = File.join(tmpdir(), "host-specific") @@tmpfiles << fsdir hostdir = File.join(fsdir, "host") fqdndir = File.join(fsdir, "fqdn") client1_hostdir = File.join(hostdir, "client1") client2_fqdndir = File.join(fqdndir, client2) contents = { client1_hostdir => "client1\n", client2_fqdndir => client2 + "\n" } [fsdir, hostdir, fqdndir, client1_hostdir, client2_fqdndir].each { |d| Dir.mkdir(d) } [client1_hostdir, client2_fqdndir].each do |d| File.open(File.join(d, "file.txt"), "w") do |f| f.print contents[d] end end conffile = tempfile() File.open(conffile, "w") do |f| f.print(" [host] path #{hostdir}/%h allow * [fqdn] path #{fqdndir}/%H allow * ") end server = nil assert_nothing_raised { server = Puppet::Network::Handler.fileserver.new( :Local => true, :Config => conffile ) } # check that list returns the correct thing for the two clients list = nil sfile = "/host/file.txt" assert_nothing_raised { list = server.list(sfile, :ignore, true, false, client1, ip) } assert_equal("/\tfile", list) assert_nothing_raised { list = server.list(sfile, :ignore, true, false, client2, ip) } assert_equal("", list) sfile = "/fqdn/file.txt" assert_nothing_raised { list = server.list(sfile, :ignore, true, false, client1, ip) } assert_equal("", list) assert_nothing_raised { list = server.list(sfile, :ignore, true, false, client2, ip) } assert_equal("/\tfile", list) # check describe sfile = "/host/file.txt" assert_nothing_raised { list = server.describe(sfile, :ignore, client1, ip).split("\t") } assert_equal(5, list.size) assert_equal("file", list[1]) md5 = Digest::MD5.hexdigest(contents[client1_hostdir]) assert_equal("{md5}#{md5}", list[4]) assert_nothing_raised { list = server.describe(sfile, :ignore, client2, ip).split("\t") } assert_equal([], list) sfile = "/fqdn/file.txt" assert_nothing_raised { list = server.describe(sfile, :ignore, client1, ip).split("\t") } assert_equal([], list) assert_nothing_raised { list = server.describe(sfile, :ignore, client2, ip).split("\t") } assert_equal(5, list.size) assert_equal("file", list[1]) md5 = Digest::MD5.hexdigest(contents[client2_fqdndir]) assert_equal("{md5}#{md5}", list[4]) # Check retrieve sfile = "/host/file.txt" assert_nothing_raised { list = server.retrieve(sfile, :ignore, client1, ip).chomp } assert_equal(contents[client1_hostdir].chomp, list) assert_nothing_raised { list = server.retrieve(sfile, :ignore, client2, ip).chomp } assert_equal("", list) sfile = "/fqdn/file.txt" assert_nothing_raised { list = server.retrieve(sfile, :ignore, client1, ip).chomp } assert_equal("", list) assert_nothing_raised { list = server.retrieve(sfile, :ignore, client2, ip).chomp } assert_equal(contents[client2_fqdndir].chomp, list) end # Make sure the 'subdir' method in Mount works. def test_mount_subdir mount = nil base = tempfile() Dir.mkdir(base) subdir = File.join(base, "subdir") Dir.mkdir(subdir) [base, subdir].each do |d| File.open(File.join(d, "file"), "w") { |f| f.puts "bazoo" } end mount = mkmount(base) assert_equal(base, mount.subdir(), "Did not default to base path") assert_equal(subdir, mount.subdir("subdir"), "Did not default to base path") end # Make sure mounts get correctly marked expandable or not, depending on # the path. def test_expandable name = "yaytest" dir = tempfile() Dir.mkdir(dir) mount = mkmount() assert_nothing_raised { mount.path = dir } assert(! mount.expandable?, "Mount incorrectly called expandable") assert_nothing_raised { mount.path = "/dir/a%a" } assert(mount.expandable?, "Mount not called expandable") # This isn't a valid replacement pattern, so it should throw an error # because the dir doesn't exist assert_raise(Puppet::Network::Handler::FileServerError) { mount.path = "/dir/a%" } # Now send it back to a normal path assert_nothing_raised { mount.path = dir } # Make sure it got reverted assert(! mount.expandable?, "Mount incorrectly called expandable") end def test_mount_expand mount = mkmount() check = proc do |client, pattern, repl| path = "/my/#{pattern}/file" assert_equal("/my/#{repl}/file", mount.expand(path, client)) end # Do a round of checks with a fake client client = "host.domain.com" {"%h" => "host", # Short name "%H" => client, # Full name "%d" => "domain.com", # domain "%%" => "%", # escape "%o" => "%o" # other }.each do |pat, repl| result = check.call(client, pat, repl) end # Now, check that they use Facter info - Puppet.notice "The following messages are normal" + Facter.stubs(:value).with("hostname").returns("myhost") + Facter.stubs(:value).with("domain").returns("mydomain") + local = "myhost" + domain = "mydomain" client = nil - local = Facter["hostname"].value - domain = Facter["domain"].value fqdn = [local, domain].join(".") {"%h" => local, # Short name "%H" => fqdn, # Full name "%d" => domain, # domain "%%" => "%", # escape "%o" => "%o" # other }.each do |pat, repl| + Puppet.expects(:notice) check.call(client, pat, repl) end end # Test that the fileserver expands the %h and %d things. def test_fileserver_expansion server = nil assert_nothing_raised { server = Puppet::Network::Handler.fileserver.new( :Local => true, :Config => false ) } dir = tempfile() ip = Facter.value(:ipaddress) Dir.mkdir(dir) host = "host.domain.com" { "%H" => "host.domain.com", "%h" => "host", "%d" => "domain.com" }.each do |pattern, string| file = File.join(dir, string) mount = File.join(dir, pattern) File.open(file, "w") do |f| f.puts "yayness: %s" % string end name = "name" obj = nil assert_nothing_raised { obj = server.mount(mount, name) } obj.allow "*" ret = nil assert_nothing_raised do ret = server.list("/name", :ignore, false, false, host, ip) end assert_equal("/\tfile", ret) assert_nothing_raised do ret = server.describe("/name", :ignore, host, ip) end assert(ret =~ /\tfile\t/, "Did not get valid a description") assert_nothing_raised do ret = server.retrieve("/name", :ignore, host, ip) end assert_equal(ret, File.read(file)) server.umount(name) File.unlink(file) end end # Test the default modules fileserving def test_modules_default moddir = tempfile Dir.mkdir(moddir) mounts = {} Puppet[:modulepath] = moddir mods = %w{green red}.collect do |name| path = File::join(moddir, name, Puppet::Module::FILES) FileUtils::mkdir_p(path) if name == "green" file = File::join(path, "test.txt") File::open(file, "w") { |f| f.print name } end Puppet::Module::find(name) end conffile = tempfile @@tmpfiles << conffile File.open(conffile, "w") { |f| f.puts "# a test config file" } # create a server with the file server = nil assert_nothing_raised { server = Puppet::Network::Handler::FileServer.new( :Local => false , :Config => conffile ) } mods.each do |mod| mount = "/#{mod.name}/" list = nil assert_nothing_raised { list = server.list(mount, :ignore, true, false) } list = list.split("\n") if mod.name == "green" assert_equal(2, list.size) assert_equal("/\tdirectory", list[0]) assert_equal("/test.txt\tfile", list[1]) else assert_equal(1, list.size) assert_equal("/\tdirectory", list[0]) end assert_nothing_raised("Host 'allow' denied #{mount}") { server.list(mount, :ignore, true, false, 'allow.example.com', "192.168.0.1") } end end # Test that configuring deny/allow for modules works def test_modules_config moddir = tempfile Dir.mkdir(moddir) mounts = {} Puppet[:modulepath] = moddir path = File::join(moddir, "amod", Puppet::Module::FILES) file = File::join(path, "test.txt") FileUtils::mkdir_p(path) File::open(file, "w") { |f| f.print "Howdy" } mod = Puppet::Module::find("amod") conffile = tempfile @@tmpfiles << conffile File.open(conffile, "w") { |f| f.print "# a test config file [modules] path #{basedir}/thing allow 192.168.0.* " } # create a server with the file server = nil assert_nothing_raised { server = Puppet::Network::Handler::FileServer.new( :Local => false, :Config => conffile ) } list = nil mount = "/#{mod.name}/" assert_nothing_raised { list = server.list(mount, :ignore, true, false) } assert_nothing_raised { list.split("\n").each { |line| file, type = line.split("\t") server.describe(mount + file) } } assert_describe(mount, file, server) # now let's check that things are being correctly forbidden assert_raise(Puppet::AuthorizationError, "Host 'deny' allowed #{mount}") { server.list(mount, :ignore, true, false, 'deny.example.com', "192.168.1.1") } assert_nothing_raised("Host 'allow' denied #{mount}") { server.list(mount, :ignore, true, false, 'allow.example.com', "192.168.0.1") } end # Make sure we successfully throw errors -- someone ran into this with # 0.22.4. def test_failures # create a server with the file server = nil + Facter.stubs(:[]).with("hostname").returns("myhost") + Facter.stubs(:[]).with("domain").returns("mydomain") config = tempfile [ "[this is invalid]\nallow one.two.com", # invalid name "[valid]\nallow *.testing something.com", # invalid allow "[valid]\nallow one.two.com\ndeny *.testing something.com", # invalid deny ].each do |failer| File.open(config, "w") { |f| f.puts failer } assert_raise(Puppet::Network::Handler::FileServerError, "Did not fail on %s" % failer.inspect) { server = Puppet::Network::Handler::FileServer.new( :Local => false, :Config => config ) } end end end diff --git a/test/network/handler/resource.rb b/test/network/handler/resource.rb index 0d6373160..247014a47 100755 --- a/test/network/handler/resource.rb +++ b/test/network/handler/resource.rb @@ -1,293 +1,292 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../lib/puppettest' require 'puppettest' +require 'puppettest/support/utils' require 'base64' require 'cgi' class TestResourceServer < Test::Unit::TestCase + include PuppetTest include PuppetTest::ServerTest def verify_described(type, described) described.each do |name, trans| type.clear obj = nil assert_nothing_raised do obj = trans.to_type end assert(obj, "Could not create object") assert_nothing_raised do obj.retrieve end if trans.type == :package assert_equal(Puppet::Type.type(:package).defaultprovider.name, obj[:provider]) end end type.clear end def test_describe_file # Make a file to describe file = tempfile() str = "yayness\n" server = nil assert_nothing_raised do server = Puppet::Network::Handler.resource.new() end # The first run we create the file on the copy, the second run # the file is already there so the object should be in sync 2.times do |i| [ [nil], [[:content, :mode], []], [[], [:content]], [[:content], [:mode]] ].each do |ary| retrieve = ary[0] || [] ignore = ary[1] || [] File.open(file, "w") { |f| f.print str } result = nil assert_nothing_raised do result = server.describe("file", file, *ary) end assert(result, "Could not retrieve file information") assert_instance_of(Puppet::TransObject, result) # Now we have to clear, so that the server's object gets removed Puppet::Type.type(:file).clear # And remove the file, so we can verify it gets recreated if i == 0 File.unlink(file) end object = nil assert_nothing_raised do object = result.to_type end assert(object, "Could not create type") retrieve.each do |property| assert(object.should(property), "Did not retrieve %s" % property) end ignore.each do |property| assert(! object.should(property), "Incorrectly retrieved %s" % property) end if i == 0 assert_events([:file_created], object) else assert_nothing_raised { assert(object.insync?(object.retrieve), "Object was not in sync") } end assert(FileTest.exists?(file), "File did not get recreated") if i == 0 if object.should(:content) assert_equal(str, File.read(file), "File contents are not the same") else assert_equal("", File.read(file), "File content was incorrectly made") end end if FileTest.exists? file File.unlink(file) end end end end def test_describe_directory # Make a file to describe file = tempfile() server = nil assert_nothing_raised do server = Puppet::Network::Handler.resource.new() end [ [nil], [[:ensure, :checksum, :mode], []], [[], [:checksum]], [[:ensure, :checksum], [:mode]] ].each do |ary| retrieve = ary[0] || [] ignore = ary[1] || [] Dir.mkdir(file) result = nil assert_nothing_raised do result = server.describe("file", file, *ary) end assert(result, "Could not retrieve file information") assert_instance_of(Puppet::TransObject, result) # Now we have to clear, so that the server's object gets removed Puppet::Type.type(:file).clear # And remove the file, so we can verify it gets recreated Dir.rmdir(file) object = nil assert_nothing_raised do object = result.to_type end assert(object, "Could not create type") retrieve.each do |property| assert(object.should(property), "Did not retrieve %s" % property) end ignore.each do |property| assert(! object.should(property), "Incorrectly retrieved %s" % property) end #assert_apply(object) assert_events([:directory_created], object) assert(FileTest.directory?(file), "Directory did not get recreated") Dir.rmdir(file) end end def test_describe_alltypes # Systems get pretty retarded, so I'm going to set the path to some fake # data for ports #Puppet::Type::ParsedType::Port.path = File.join(basedir, # "test/data/types/ports/1") #Puppet.err Puppet::Type::ParsedType::Port.path server = nil assert_nothing_raised do server = Puppet::Network::Handler.resource.new() end require 'etc' - # Make the example schedules, for testing - Puppet::Type.type(:schedule).mkdefaultschedules - Puppet::Type.eachtype do |type| unless type.respond_to? :instances Puppet.warning "%s does not respond to :instances" % type.name next end next unless type.name == :package Puppet.info "Describing each %s" % type.name # First do a listing from the server bucket = nil assert_nothing_raised { bucket = server.list(type.name) } #type.clear count = 0 described = {} bucket.each do |obj| assert_instance_of(Puppet::TransObject, obj) break if count > 5 described[obj.name] = server.describe(obj.type, obj.name) count += 1 end verify_described(type, described) count = 0 described = {} Puppet.info "listing again" type.instances.each do |obj| assert_instance_of(type, obj) break if count > 5 trans = nil assert_nothing_raised do described[obj.name] = server.describe(type.name, obj.name) end count += 1 end if described.empty? Puppet.notice "Got no example objects for %s" % type.name end # We separate these, in case the list operation creates objects verify_described(type, described) end end def test_apply server = nil assert_nothing_raised do server = Puppet::Network::Handler.resource.new(:Local => false) end file = tempfile() str = "yayness\n" File.open(file, "w") { |f| f.print str } filetrans = nil assert_nothing_raised { filetrans = server.describe("file", file) } Puppet::Type.type(:file).clear bucket = Puppet::TransBucket.new bucket.type = "file" bucket.push filetrans oldbucket = bucket.dup File.unlink(file) assert_nothing_raised { server.apply(bucket) } assert(FileTest.exists?(file), "File did not get recreated") # Now try it as a "nonlocal" server server.local = false yaml = nil assert_nothing_raised { yaml = Base64.encode64(YAML::dump(bucket)) } Puppet::Type.type(:file).clear File.unlink(file) if Base64.decode64(yaml) =~ /(.{20}Loglevel.{20})/ Puppet.warning "YAML is broken on this machine" return end # puts Base64.decode64(yaml) objects = nil assert_nothing_raised("Could not reload yaml") { YAML::load(Base64.decode64(yaml)) } # The server is supposed to accept yaml and execute it. assert_nothing_raised { server.apply(yaml) } assert(FileTest.exists?(file), "File did not get recreated from YAML") end end diff --git a/test/other/overrides.rb b/test/other/overrides.rb index 272f78e30..800eab1b7 100755 --- a/test/other/overrides.rb +++ b/test/other/overrides.rb @@ -1,107 +1,106 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../lib/puppettest' require 'puppet' require 'puppettest' class TestOverrides < Test::Unit::TestCase include PuppetTest def mksubdirs(basedir, level) @@tmpfiles << basedir dir = basedir.dup (level + 1).times { |index| Dir.mkdir(dir) path = File.join(dir, "file") File.open(path, "w") { |f| f.puts "yayness" } dir = File.join(dir, index.to_s) } end def test_simpleoverride basedir = File.join(tmpdir(), "overridetesting") mksubdirs(basedir, 1) baseobj = nil basefile = File.join(basedir, "file") - assert_nothing_raised("Could not create base obj") { - baseobj = Puppet.type(:file).create( - :title => "base", - :path => basedir, - :recurse => true, - :mode => "755" - ) - } + config = mk_configuration + + baseobj = config.create_resource(:file, + :title => "base", + :path => basedir, + :recurse => true, + :mode => "755" + ) subobj = nil subdir = File.join(basedir, "0") subfile = File.join(subdir, "file") - assert_nothing_raised("Could not create sub obj") { - subobj = Puppet.type(:file).create( - :title => "sub", - :path => subdir, - :recurse => true, - :mode => "644" - ) - } - assert_apply(baseobj, subobj) + subobj = config.create_resource(:file, + :title => "sub", + :path => subdir, + :recurse => true, + :mode => "644" + ) + + config.apply assert(File.stat(basefile).mode & 007777 == 0755) assert(File.stat(subfile).mode & 007777 == 0644) end def test_deepoverride basedir = File.join(tmpdir(), "deepoverridetesting") mksubdirs(basedir, 10) baseobj = nil assert_nothing_raised("Could not create base obj") { baseobj = Puppet.type(:file).create( :path => basedir, :recurse => true, :mode => "755" ) } children = [] files = {} subdir = basedir.dup mode = nil 10.times { |index| next unless index % 3 subdir = File.join(subdir, index.to_s) path = File.join(subdir, "file") if index % 2 mode = "644" files[path] = 0644 else mode = "750" files[path] = 0750 end assert_nothing_raised("Could not create sub obj") { children << Puppet.type(:file).create( :path => subdir, :recurse => true, :mode => mode ) } } config = mk_configuration(baseobj, *children) assert_nothing_raised("Could not eval component") { config.apply } files.each { |path, mode| assert(FileTest.exists?(path), "File %s does not exist" % path) curmode = File.stat(path).mode & 007777 assert(curmode == mode, "File %s was incorrect mode %o instead of %o" % [path, curmode, mode]) } end end diff --git a/test/other/relationships.rb b/test/other/relationships.rb index 0f2a103fe..fe1ae1ac4 100755 --- a/test/other/relationships.rb +++ b/test/other/relationships.rb @@ -1,217 +1,217 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../lib/puppettest' require 'puppet' require 'puppettest' class TestRelationships < Test::Unit::TestCase include PuppetTest def setup super Puppet::Type.type(:exec) end def newfile assert_nothing_raised() { return Puppet.type(:file).create( :path => tempfile, :check => [:mode, :owner, :group] ) } end def check_relationship(sources, targets, out, refresher) if out deps = sources.builddepends sources = [sources] else deps = targets.builddepends targets = [targets] end assert_instance_of(Array, deps) assert(! deps.empty?, "Did not receive any relationships") deps.each do |edge| assert_instance_of(Puppet::Relationship, edge) end sources.each do |source| targets.each do |target| edge = deps.find { |e| e.source == source and e.target == target } assert(edge, "Could not find edge for %s => %s" % [source.ref, target.ref]) if refresher assert_equal(:ALL_EVENTS, edge.event) assert_equal(:refresh, edge.callback) else assert_nil(edge.event) assert_nil(edge.callback, "Got a callback with no events") end end end end # Make sure our various metaparams work correctly. We're just checking # here whether they correctly set up the callbacks and the direction of # the relationship. def test_relationship_metaparams out = {:require => false, :subscribe => false, :notify => true, :before => true} refreshers = [:subscribe, :notify] [:require, :subscribe, :notify, :before].each do |param| + config = mk_configuration # Create three files to generate our events and three # execs to receive them files = [] execs = [] 3.times do |i| - files << Puppet::Type.newfile( + files << config.create_resource(:file, :title => "file#{i}", :path => tempfile(), :ensure => :file ) path = tempfile() - execs << Puppet::Type.newexec( + execs << config.create_resource(:exec, :title => "notifytest#{i}", :path => "/usr/bin:/bin", :command => "touch #{path}", :refreshonly => true ) end # Add our first relationship if out[param] files[0][param] = execs[0] sources = files[0] targets = [execs[0]] else execs[0][param] = files[0] sources = [files[0]] targets = execs[0] end check_relationship(sources, targets, out[param], refreshers.include?(param)) # Now add another relationship if out[param] files[0][param] = execs[1] targets << execs[1] assert_equal(targets.collect { |t| [t.class.name, t.title]}, files[0][param], "Incorrect target list") else execs[0][param] = files[1] sources << files[1] assert_equal(sources.collect { |t| [t.class.name, t.title]}, execs[0][param], "Incorrect source list") end check_relationship(sources, targets, out[param], refreshers.include?(param)) Puppet::Type.allclear end end def test_munge_relationship file = Puppet::Type.newfile :path => tempfile(), :mode => 0755 execs = [] 3.times do |i| execs << Puppet::Type.newexec(:title => "yay#{i}", :command => "/bin/echo yay") end # First try it with one object, specified as a reference and an array result = nil [execs[0], [:exec, "yay0"], ["exec", "yay0"]].each do |target| assert_nothing_raised do result = file.send(:munge_relationship, :require, target) end assert_equal([[:exec, "yay0"]], result) end # Now try it with multiple objects symbols = execs.collect { |e| [e.class.name, e.title] } strings = execs.collect { |e| [e.class.name.to_s, e.title] } [execs, symbols, strings].each do |target| assert_nothing_raised do result = file.send(:munge_relationship, :require, target) end assert_equal(symbols, result) end # Make sure we can mix it up, even though this shouldn't happen assert_nothing_raised do result = file.send(:munge_relationship, :require, [execs[0], [execs[1].class.name, execs[1].title]]) end assert_equal([[:exec, "yay0"], [:exec, "yay1"]], result) # Finally, make sure that new results get added to old. The only way # to get rid of relationships is to delete the parameter. file[:require] = execs[0] assert_nothing_raised do result = file.send(:munge_relationship, :require, [execs[1], execs[2]]) end assert_equal(symbols, result) end def test_autorequire # We know that execs autorequire their cwd, so we'll use that path = tempfile() file = Puppet::Type.newfile(:title => "myfile", :path => path, :ensure => :directory) exec = Puppet::Type.newexec(:title => "myexec", :cwd => path, :command => "/bin/echo") - + + config = mk_configuration(file, exec) + reqs = nil assert_nothing_raised do reqs = exec.autorequire end assert_instance_of(Puppet::Relationship, reqs[0], "Did not return a relationship edge") assert_equal(file, reqs[0].source, "Did not set the autorequire source correctly") assert_equal(exec, reqs[0].target, "Did not set the autorequire target correctly") - + # Now make sure that these relationships are added to the # relationship graph - config = mk_configuration(file, exec) - config.apply do |trans| - assert(config.relationship_graph.edge?(file, exec), "autorequire edge was not created") - end + assert(config.relationship_graph.edge?(file, exec), "autorequire edge was not created") end def test_requires? # Test the first direction file1 = Puppet::Type.newfile(:title => "one", :path => tempfile, :ensure => :directory) file2 = Puppet::Type.newfile(:title => "two", :path => tempfile, :ensure => :directory) file1[:require] = file2 assert(file1.requires?(file2), "requires? failed to catch :require relationship") file1.delete(:require) assert(! file1.requires?(file2), "did not delete relationship") file1[:subscribe] = file2 assert(file1.requires?(file2), "requires? failed to catch :subscribe relationship") file1.delete(:subscribe) assert(! file1.requires?(file2), "did not delete relationship") file2[:before] = file1 assert(file1.requires?(file2), "requires? failed to catch :before relationship") file2.delete(:before) assert(! file1.requires?(file2), "did not delete relationship") file2[:notify] = file1 assert(file1.requires?(file2), "requires? failed to catch :notify relationship") end # Testing #411. It was a problem with builddepends. def test_missing_deps file = Puppet::Type.newfile :path => tempfile, :require => ["file", "/no/such/file"] assert_raise(Puppet::Error) do - file.builddepends + config = mk_configuration(file) end end end diff --git a/test/other/transactions.rb b/test/other/transactions.rb index 8156ba478..2498cc014 100755 --- a/test/other/transactions.rb +++ b/test/other/transactions.rb @@ -1,1109 +1,1107 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../lib/puppettest' require 'puppet' require 'puppettest' require 'mocha' require 'puppettest/support/resources' +require 'puppettest/support/utils' class TestTransactions < Test::Unit::TestCase + include PuppetTest include PuppetTest::FileTesting include PuppetTest::Support::Resources class Fakeprop true) def finish $finished << self.name end end if block type.class_eval(&block) end cleanup do Puppet::Type.rmtype(:generator) end return type end # Create a new type that generates instances with shorter names. def mkreducer(&block) type = mkgenerator() do def eval_generate ret = [] if title.length > 1 ret << self.class.create(:title => title[0..-2]) else return nil end ret end end if block type.class_eval(&block) end return type end def test_reports path1 = tempfile() path2 = tempfile() objects = [] objects << Puppet::Type.newfile( :path => path1, :content => "yayness" ) objects << Puppet::Type.newfile( :path => path2, :content => "booness" ) trans = assert_events([:file_created, :file_created], *objects) report = nil assert_nothing_raised { report = trans.generate_report } # First test the report logs assert(report.logs.length > 0, "Did not get any report logs") report.logs.each do |obj| assert_instance_of(Puppet::Util::Log, obj) end # Then test the metrics metrics = report.metrics assert(metrics, "Did not get any metrics") assert(metrics.length > 0, "Did not get any metrics") assert(metrics.has_key?("resources"), "Did not get object metrics") assert(metrics.has_key?("changes"), "Did not get change metrics") metrics.each do |name, metric| assert_instance_of(Puppet::Util::Metric, metric) end end def test_prefetch # Create a type just for testing prefetch name = :prefetchtesting $prefetched = false type = Puppet::Type.newtype(name) do newparam(:name) {} end cleanup do Puppet::Type.rmtype(name) end # Now create a provider type.provide(:prefetch) do def self.prefetch(resources) $prefetched = resources end end # Now create an instance inst = type.create :name => "yay" # Create a transaction trans = Puppet::Transaction.new(mk_configuration(inst)) # Make sure prefetch works assert_nothing_raised do trans.prefetch end assert_equal({inst.title => inst}, $prefetched, "type prefetch was not called") # Now make sure it gets called from within evaluate() $prefetched = false assert_nothing_raised do trans.evaluate end assert_equal({inst.title => inst}, $prefetched, "evaluate did not call prefetch") end def test_refreshes_generate_events path = tempfile() firstpath = tempfile() secondpath = tempfile() file = Puppet::Type.newfile(:title => "file", :path => path, :content => "yayness") first = Puppet::Type.newexec(:title => "first", :command => "/bin/echo first > #{firstpath}", :subscribe => [:file, path], :refreshonly => true ) second = Puppet::Type.newexec(:title => "second", :command => "/bin/echo second > #{secondpath}", :subscribe => [:exec, "first"], :refreshonly => true ) assert_apply(file, first, second) assert(FileTest.exists?(secondpath), "Refresh did not generate an event") end unless %x{groups}.chomp.split(/ /).length > 1 $stderr.puts "You must be a member of more than one group to test transactions" else def ingroup(gid) require 'etc' begin group = Etc.getgrgid(gid) rescue => detail puts "Could not retrieve info for group %s: %s" % [gid, detail] return nil end return @groups.include?(group.name) end def setup super @groups = %x{groups}.chomp.split(/ /) unless @groups.length > 1 p @groups raise "You must be a member of more than one group to test this" end end def newfile(hash = {}) tmpfile = tempfile() File.open(tmpfile, "w") { |f| f.puts rand(100) } # XXX now, because os x apparently somehow allows me to make a file # owned by a group i'm not a member of, i have to verify that # the file i just created is owned by one of my groups # grrr unless ingroup(File.stat(tmpfile).gid) Puppet.info "Somehow created file in non-member group %s; fixing" % File.stat(tmpfile).gid require 'etc' firstgr = @groups[0] unless firstgr.is_a?(Integer) str = Etc.getgrnam(firstgr) firstgr = str.gid end File.chown(nil, firstgr, tmpfile) end hash[:name] = tmpfile assert_nothing_raised() { return Puppet.type(:file).create(hash) } end def newexec(file) assert_nothing_raised() { return Puppet.type(:exec).create( :name => "touch %s" % file, :path => "/bin:/usr/bin:/sbin:/usr/sbin", :returns => 0 ) } end # modify a file and then roll the modifications back def test_filerollback transaction = nil file = newfile() properties = {} check = [:group,:mode] file[:check] = check assert_nothing_raised() { file.retrieve } assert_nothing_raised() { check.each { |property| value = file.value(property) assert(value) properties[property] = value } } component = mk_configuration("file",file) require 'etc' groupname = Etc.getgrgid(File.stat(file.name).gid).name assert_nothing_raised() { # Find a group that it's not set to group = @groups.find { |group| group != groupname } unless group raise "Could not find suitable group" end file[:group] = group file[:mode] = "755" } trans = assert_events([:file_changed, :file_changed], component) file.retrieve assert_rollback_events(trans, [:file_changed, :file_changed], "file") assert_nothing_raised() { file.retrieve } properties.each { |property,value| assert_equal( value, file.value(property), "File %s remained %s" % [property, file.value(property)] ) } end # test that services are correctly restarted and that work is done # in the right order def test_refreshing transaction = nil file = newfile() execfile = File.join(tmpdir(), "exectestingness") exec = newexec(execfile) properties = {} check = [:group,:mode] file[:check] = check file[:group] = @groups[0] config = mk_configuration(file) config.apply @@tmpfiles << execfile # 'subscribe' expects an array of arrays exec[:subscribe] = [[file.class.name,file.name]] exec[:refreshonly] = true assert_nothing_raised() { file.retrieve exec.retrieve } check.each { |property| properties[property] = file.value(property) } assert_nothing_raised() { file[:mode] = "755" } # Make a new configuration so the resource relationships get # set up. config = mk_configuration(file, exec) trans = assert_events([:file_changed, :triggered], config) assert(FileTest.exists?(execfile), "Execfile does not exist") File.unlink(execfile) assert_nothing_raised() { file[:group] = @groups[1] } trans = assert_events([:file_changed, :triggered], config) assert(FileTest.exists?(execfile), "Execfile does not exist") end # Verify that one component requiring another causes the contained # resources in the requiring component to get refreshed. def test_refresh_across_two_components transaction = nil file = newfile() execfile = File.join(tmpdir(), "exectestingness2") @@tmpfiles << execfile exec = newexec(execfile) properties = {} check = [:group,:mode] file[:check] = check file[:group] = @groups[0] assert_apply(file) config = Puppet::Node::Configuration.new fcomp = Puppet::Type.type(:component).create(:name => "file") config.add_resource fcomp config.add_resource file config.add_edge!(fcomp, file) ecomp = Puppet::Type.type(:component).create(:name => "exec") config.add_resource ecomp config.add_resource exec config.add_edge!(ecomp, exec) # 'subscribe' expects an array of arrays #component[:require] = [[file.class.name,file.name]] ecomp[:subscribe] = fcomp exec[:refreshonly] = true trans = assert_events([], config) assert_nothing_raised() { file[:group] = @groups[1] file[:mode] = "755" } trans = assert_events([:file_changed, :file_changed, :triggered], config) end # Make sure that multiple subscriptions get triggered. def test_multisubs path = tempfile() file1 = tempfile() file2 = tempfile() file = Puppet.type(:file).create( :path => path, :ensure => "file" ) exec1 = Puppet.type(:exec).create( :path => ENV["PATH"], :command => "touch %s" % file1, :refreshonly => true, :subscribe => [:file, path] ) exec2 = Puppet.type(:exec).create( :path => ENV["PATH"], :command => "touch %s" % file2, :refreshonly => true, :subscribe => [:file, path] ) assert_apply(file, exec1, exec2) assert(FileTest.exists?(file1), "File 1 did not get created") assert(FileTest.exists?(file2), "File 2 did not get created") end # Make sure that a failed trigger doesn't result in other events not # getting triggered. def test_failedrefreshes path = tempfile() newfile = tempfile() file = Puppet.type(:file).create( :path => path, :ensure => "file" ) exec1 = Puppet.type(:exec).create( :path => ENV["PATH"], :command => "touch /this/cannot/possibly/exist", :logoutput => true, :refreshonly => true, :subscribe => file, :title => "one" ) exec2 = Puppet.type(:exec).create( :path => ENV["PATH"], :command => "touch %s" % newfile, :logoutput => true, :refreshonly => true, :subscribe => [file, exec1], :title => "two" ) assert_apply(file, exec1, exec2) assert(FileTest.exists?(newfile), "Refresh file did not get created") end # Make sure that unscheduled and untagged objects still respond to events def test_unscheduled_and_untagged_response - Puppet::Type.type(:schedule).mkdefaultschedules + config = mk_configuration + config.add_resource(*(Puppet::Type.type(:schedule).create_default_resources)) Puppet[:ignoreschedules] = false - file = Puppet.type(:file).create( + file = config.create_resource(:file, :name => tempfile(), :ensure => "file", :backup => false ) fname = tempfile() - exec = Puppet.type(:exec).create( + exec = config.create_resource(:exec, :name => "touch %s" % fname, :path => "/usr/bin:/bin", :schedule => "monthly", :subscribe => ["file", file.name] ) - config = mk_configuration(file, exec) - # Run it once - assert_apply(config) + config.apply assert(FileTest.exists?(fname), "File did not get created") assert(!exec.scheduled?, "Exec is somehow scheduled") # Now remove it, so it can get created again File.unlink(fname) file[:content] = "some content" assert_events([:file_changed, :triggered], config) assert(FileTest.exists?(fname), "File did not get recreated") # Now remove it, so it can get created again File.unlink(fname) # And tag our exec exec.tag("testrun") # And our file, so it runs file.tag("norun") Puppet[:tags] = "norun" file[:content] = "totally different content" assert(! file.insync?(file.retrieve), "Uh, file is in sync?") assert_events([:file_changed, :triggered], config) assert(FileTest.exists?(fname), "File did not get recreated") end def test_failed_reqs_mean_no_run exec = Puppet::Type.type(:exec).create( :command => "/bin/mkdir /this/path/cannot/possibly/exit", :title => "mkdir" ) file1 = Puppet::Type.type(:file).create( :title => "file1", :path => tempfile(), :require => exec, :ensure => :file ) file2 = Puppet::Type.type(:file).create( :title => "file2", :path => tempfile(), :require => file1, :ensure => :file ) config = mk_configuration(exec, file1, file2) assert_apply(config) assert(! FileTest.exists?(file1[:path]), "File got created even tho its dependency failed") assert(! FileTest.exists?(file2[:path]), "File got created even tho its deep dependency failed") end end def test_relationship_graph config = mktree config.meta_def(:f) do |name| self.resource("File[%s]" % name) end {"one" => "two", "File[f]" => "File[c]", "File[h]" => "middle"}.each do |source_ref, target_ref| source = config.resource(source_ref) or raise "Missing %s" % source_ref target = config.resource(target_ref) or raise "Missing %s" % target_ref target[:require] = source end trans = Puppet::Transaction.new(config) graph = nil assert_nothing_raised do graph = trans.relationship_graph end assert_instance_of(Puppet::Node::Configuration, graph, "Did not get relationship graph") # Make sure all of the components are gone comps = graph.vertices.find_all { |v| v.is_a?(Puppet::Type::Component)} assert(comps.empty?, "Deps graph still contains components %s" % comps.collect { |c| c.ref }.join(",")) assert_equal([], comps, "Deps graph still contains components") # It must be reversed because of how topsort works sorted = graph.topsort.reverse # Now make sure the appropriate edges are there and are in the right order assert(graph.dependents(config.f(:f)).include?(config.f(:c)), "c not marked a dep of f") assert(sorted.index(config.f(:c)) < sorted.index(config.f(:f)), "c is not before f") config.resource("one").each do |o| config.resource("two").each do |t| assert(graph.dependents(o).include?(t), "%s not marked a dep of %s" % [t.ref, o.ref]) assert(sorted.index(t) < sorted.index(o), "%s is not before %s" % [t.ref, o.ref]) end end trans.configuration.leaves(config.resource("middle")).each do |child| assert(graph.dependents(config.f(:h)).include?(child), "%s not marked a dep of h" % [child.ref]) assert(sorted.index(child) < sorted.index(config.f(:h)), "%s is not before h" % child.ref) end # Lastly, make sure our 'g' vertex made it into the relationship # graph, since it's not involved in any relationships. assert(graph.vertex?(config.f(:g)), "Lost vertexes with no relations") # Now make the reversal graph and make sure all of the vertices made it into that reverse = graph.reversal %w{a b c d e f g h}.each do |letter| file = config.f(letter) assert(reverse.vertex?(file), "%s did not make it into reversal" % letter) end end # Test pre-evaluation generation def test_generate mkgenerator() do def generate ret = [] if title.length > 1 ret << self.class.create(:title => title[0..-2]) else return nil end ret end end yay = Puppet::Type.newgenerator :title => "yay" rah = Puppet::Type.newgenerator :title => "rah" config = mk_configuration(yay, rah) trans = Puppet::Transaction.new(config) assert_nothing_raised do trans.generate end %w{ya ra y r}.each do |name| - assert(trans.configuration.vertex?(Puppet::Type.type(:generator)[name]), - "Generated %s was not a vertex" % name) + assert(trans.configuration.vertex?(config.resource(:generator, name)), "Generated %s was not a vertex" % name) assert($finished.include?(name), "%s was not finished" % name) end # Now make sure that cleanup gets rid of those generated types. assert_nothing_raised do trans.cleanup end %w{ya ra y r}.each do |name| - assert(!trans.configuration.vertex?(Puppet::Type.type(:generator)[name]), - "Generated vertex %s was not removed from graph" % name) - assert_nil(Puppet::Type.type(:generator)[name], - "Generated vertex %s was not removed from class" % name) + assert(!trans.configuration.vertex?(config.resource(:generator, name)), "Generated vertex %s was not removed from graph" % name) + assert_nil(config.resource(:generator, name), "Generated vertex %s was not removed from class" % name) end end # Test mid-evaluation generation. def test_eval_generate $evaluated = [] cleanup { $evaluated = nil } type = mkreducer() do def evaluate $evaluated << self.title return [] end end yay = Puppet::Type.newgenerator :title => "yay" rah = Puppet::Type.newgenerator :title => "rah", :subscribe => yay config = mk_configuration(yay, rah) trans = Puppet::Transaction.new(config) trans.prepare # Now apply the resources, and make sure they appropriately generate # things. assert_nothing_raised("failed to apply yay") do trans.eval_resource(yay) end - ya = type["ya"] + ya = config.resource(:generator, "ya") assert(ya, "Did not generate ya") assert(trans.relationship_graph.vertex?(ya), "Did not add ya to rel_graph") # Now make sure the appropriate relationships were added assert(trans.relationship_graph.edge?(yay, ya), "parent was not required by child") assert(! trans.relationship_graph.edge?(ya, rah), "generated child ya inherited depencency on rah") # Now make sure it in turn eval_generates appropriately assert_nothing_raised("failed to apply yay") do - trans.eval_resource(type["ya"]) + trans.eval_resource(config.resource(:generator, "ya")) end %w{y}.each do |name| - res = type[name] + res = config.resource(:generator, name) assert(res, "Did not generate %s" % name) assert(trans.relationship_graph.vertex?(res), "Did not add %s to rel_graph" % name) assert($finished.include?("y"), "y was not finished") end assert_nothing_raised("failed to eval_generate with nil response") do - trans.eval_resource(type["y"]) + trans.eval_resource(config.resource(:generator, "y")) end assert(trans.relationship_graph.edge?(yay, ya), "no edge was created for ya => yay") assert_nothing_raised("failed to apply rah") do trans.eval_resource(rah) end - ra = type["ra"] + ra = config.resource(:generator, "ra") assert(ra, "Did not generate ra") assert(trans.relationship_graph.vertex?(ra), "Did not add ra to rel_graph" % name) assert($finished.include?("ra"), "y was not finished") # Now make sure this generated resource has the same relationships as # the generating resource assert(! trans.relationship_graph.edge?(yay, ra), "rah passed its dependencies on to its children") assert(! trans.relationship_graph.edge?(ya, ra), "children have a direct relationship") # Now make sure that cleanup gets rid of those generated types. assert_nothing_raised do trans.cleanup end %w{ya ra y r}.each do |name| - assert(!trans.relationship_graph.vertex?(type[name]), + assert(!trans.relationship_graph.vertex?(config.resource(:generator, name)), "Generated vertex %s was not removed from graph" % name) - assert_nil(type[name], + assert_nil(config.resource(:generator, name), "Generated vertex %s was not removed from class" % name) end # Now, start over and make sure that everything gets evaluated. trans = Puppet::Transaction.new(config) $evaluated.clear assert_nothing_raised do trans.evaluate end assert_equal(%w{yay ya y rah ra r}, $evaluated, "Not all resources were evaluated or not in the right order") end def test_ignore_tags? config = Puppet::Node::Configuration.new config.host_config = true transaction = Puppet::Transaction.new(config) assert(! transaction.ignore_tags?, "Ignoring tags when applying a host configuration") config.host_config = false transaction = Puppet::Transaction.new(config) assert(transaction.ignore_tags?, "Not ignoring tags when applying a non-host configuration") end def test_missing_tags? resource = stub 'resource', :tagged? => true config = Puppet::Node::Configuration.new # Mark it as a host config so we don't care which test is first config.host_config = true transaction = Puppet::Transaction.new(config) assert(! transaction.missing_tags?(resource), "Considered a resource to be missing tags when none are set") # host configurations pay attention to tags, no one else does. Puppet[:tags] = "three,four" config.host_config = false transaction = Puppet::Transaction.new(config) assert(! transaction.missing_tags?(resource), "Considered a resource to be missing tags when not running a host configuration") # config.host_config = true transaction = Puppet::Transaction.new(config) assert(! transaction.missing_tags?(resource), "Considered a resource to be missing tags when running a host configuration and all tags are present") transaction = Puppet::Transaction.new(config) resource.stubs :tagged? => false assert(transaction.missing_tags?(resource), "Considered a resource not to be missing tags when running a host configuration and tags are missing") end # Make sure changes generated by eval_generated resources have proxies # set to the top-level resource. def test_proxy_resources type = mkreducer do def evaluate return Puppet::PropertyChange.new(Fakeprop.new( :path => :path, :is => :is, :should => :should, :name => self.name, :resource => "a parent"), :is) end end resource = type.create :name => "test" config = mk_configuration(resource) trans = Puppet::Transaction.new(config) trans.prepare assert_nothing_raised do trans.eval_resource(resource) end changes = trans.instance_variable_get("@changes") assert(changes.length > 0, "did not get any changes") changes.each do |change| assert_equal(resource, change.source, "change did not get proxy set correctly") end end # Make sure changes in contained files still generate callback events. def test_generated_callbacks dir = tempfile() maker = tempfile() Dir.mkdir(dir) file = File.join(dir, "file") File.open(file, "w") { |f| f.puts "" } File.chmod(0644, file) File.chmod(0755, dir) # So only the child file causes a change dirobj = Puppet::Type.type(:file).create :mode => "755", :recurse => true, :path => dir exec = Puppet::Type.type(:exec).create :title => "make", :command => "touch #{maker}", :path => ENV['PATH'], :refreshonly => true, :subscribe => dirobj assert_apply(dirobj, exec) assert(FileTest.exists?(maker), "Did not make callback file") end # Yay, this out to be fun. def test_trigger $triggered = [] cleanup { $triggered = nil } trigger = Class.new do attr_accessor :name include Puppet::Util::Logging def initialize(name) @name = name end def ref self.name end def refresh $triggered << self.name end def to_s self.name end end # Make a graph with some stuff in it. graph = Puppet::Node::Configuration.new # Add a non-triggering edge. a = trigger.new(:a) b = trigger.new(:b) c = trigger.new(:c) nope = Puppet::Relationship.new(a, b) yep = Puppet::Relationship.new(a, c, {:callback => :refresh}) graph.add_edge!(nope) # And a triggering one. graph.add_edge!(yep) # Create our transaction trans = Puppet::Transaction.new(graph) # Set the non-triggering on assert_nothing_raised do trans.set_trigger(nope) end assert(! trans.targeted?(b), "b is incorrectly targeted") # Now set the other assert_nothing_raised do trans.set_trigger(yep) end assert(trans.targeted?(c), "c is not targeted") # Now trigger our three resources assert_nothing_raised do assert_nil(trans.trigger(a), "a somehow triggered something") end assert_nothing_raised do assert_nil(trans.trigger(b), "b somehow triggered something") end assert_equal([], $triggered,"got something in triggered") result = nil assert_nothing_raised do result = trans.trigger(c) end assert(result, "c did not trigger anything") assert_instance_of(Array, result) event = result.shift assert_instance_of(Puppet::Event, event) assert_equal(:triggered, event.event, "event was not set correctly") assert_equal(c, event.source, "source was not set correctly") assert_equal(trans, event.transaction, "transaction was not set correctly") assert(trans.triggered?(c, :refresh), "Transaction did not store the trigger") end def test_set_target file = Puppet::Type.newfile(:path => tempfile(), :content => "yay") exec1 = Puppet::Type.type(:exec).create :command => "/bin/echo exec1" exec2 = Puppet::Type.type(:exec).create :command => "/bin/echo exec2" trans = Puppet::Transaction.new(mk_configuration(file, exec1, exec2)) # First try it with an edge that has no callback edge = Puppet::Relationship.new(file, exec1) assert_nothing_raised { trans.set_trigger(edge) } assert(! trans.targeted?(exec1), "edge with no callback resulted in a target") # Now with an edge that has an unsupported callback edge = Puppet::Relationship.new(file, exec1, :callback => :nosuchmethod, :event => :ALL_EVENTS) assert_nothing_raised { trans.set_trigger(edge) } assert(! trans.targeted?(exec1), "edge with invalid callback resulted in a target") # Lastly, with an edge with a supported callback edge = Puppet::Relationship.new(file, exec1, :callback => :refresh, :event => :ALL_EVENTS) assert_nothing_raised { trans.set_trigger(edge) } assert(trans.targeted?(exec1), "edge with valid callback did not result in a target") end # Testing #401 -- transactions are calling refresh() on classes that don't support it. def test_callback_availability $called = [] klass = Puppet::Type.newtype(:norefresh) do newparam(:name, :namevar => true) {} def method_missing(method, *args) $called << method end end cleanup do $called = nil Puppet::Type.rmtype(:norefresh) end file = Puppet::Type.newfile :path => tempfile(), :content => "yay" one = klass.create :name => "one", :subscribe => file assert_apply(file, one) assert(! $called.include?(:refresh), "Called refresh when it wasn't set as a method") end # Testing #437 - cyclic graphs should throw failures. def test_fail_on_cycle one = Puppet::Type.type(:exec).create(:name => "/bin/echo one") two = Puppet::Type.type(:exec).create(:name => "/bin/echo two") one[:require] = two two[:require] = one config = mk_configuration(one, two) trans = Puppet::Transaction.new(config) assert_raise(Puppet::Error) do trans.prepare end end def test_errors_during_generation type = Puppet::Type.newtype(:failer) do newparam(:name) {} def eval_generate raise ArgumentError, "Invalid value" end def generate raise ArgumentError, "Invalid value" end end cleanup { Puppet::Type.rmtype(:failer) } obj = type.create(:name => "testing") assert_apply(obj) end def test_self_refresh_causes_triggering type = Puppet::Type.newtype(:refresher, :self_refresh => true) do attr_accessor :refreshed, :testing newparam(:name) {} newproperty(:testing) do def sync self.is = self.should :ran_testing end end def refresh @refreshed = true end end cleanup { Puppet::Type.rmtype(:refresher)} obj = type.create(:name => "yay", :testing => "cool") assert(! obj.insync?(obj.retrieve), "fake object is already in sync") # Now make sure it gets refreshed when the change happens assert_apply(obj) assert(obj.refreshed, "object was not refreshed during transaction") end # Testing #433 def test_explicit_dependencies_beat_automatic # Create a couple of different resource sets that have automatic relationships and make sure the manual relationships win rels = {} # First users and groups group = Puppet::Type.type(:group).create(:name => nonrootgroup.name, :ensure => :present) user = Puppet::Type.type(:user).create(:name => nonrootuser.name, :ensure => :present, :gid => group.title) # Now add the explicit relationship group[:require] = user rels[group] = user # Now files d = tempfile() f = File.join(d, "file") file = Puppet::Type.newfile(:path => f, :content => "yay") dir = Puppet::Type.newfile(:path => d, :ensure => :directory, :require => file) rels[dir] = file rels.each do |after, before| config = mk_configuration(before, after) trans = Puppet::Transaction.new(config) str = "from %s to %s" % [before, after] assert_nothing_raised("Failed to create graph %s" % str) do trans.prepare end graph = trans.relationship_graph assert(graph.edge?(before, after), "did not create manual relationship %s" % str) assert(! graph.edge?(after, before), "created automatic relationship %s" % str) end end # #542 - make sure resources in noop mode still notify their resources, # so that users know if a service will get restarted. def test_noop_with_notify path = tempfile epath = tempfile spath = tempfile file = Puppet::Type.newfile(:path => path, :ensure => :file, :title => "file") exec = Puppet::Type.type(:exec).create(:command => "touch %s" % epath, :path => ENV["PATH"], :subscribe => file, :refreshonly => true, :title => 'exec1') exec2 = Puppet::Type.type(:exec).create(:command => "touch %s" % spath, :path => ENV["PATH"], :subscribe => exec, :refreshonly => true, :title => 'exec2') Puppet[:noop] = true assert(file.noop, "file not in noop") assert(exec.noop, "exec not in noop") @logs.clear assert_apply(file, exec, exec2) assert(! FileTest.exists?(path), "Created file in noop") assert(! FileTest.exists?(epath), "Executed exec in noop") assert(! FileTest.exists?(spath), "Executed second exec in noop") assert(@logs.detect { |l| l.message =~ /should be/ and l.source == file.property(:ensure).path}, "did not log file change") assert(@logs.detect { |l| l.message =~ /Would have/ and l.source == exec.path }, "did not log first exec trigger") assert(@logs.detect { |l| l.message =~ /Would have/ and l.source == exec2.path }, "did not log second exec trigger") end def test_only_stop_purging_with_relations files = [] paths = [] 3.times do |i| path = tempfile paths << path file = Puppet::Type.newfile(:path => path, :ensure => :absent, :backup => false, :title => "file%s" % i) File.open(path, "w") { |f| f.puts "" } files << file end files[0][:ensure] = :file files[0][:require] = files[1..2] # Mark the second as purging files[1].purging assert_apply(*files) assert(FileTest.exists?(paths[1]), "Deleted required purging file") assert(! FileTest.exists?(paths[2]), "Did not delete non-purged file") end def test_flush $state = "absent" $flushed = 0 type = Puppet::Type.newtype(:flushtest) do newparam(:name) newproperty(:ensure) do def retrieve $state end def set(value) $state = value :thing_changed end end def flush $flushed += 1 end end cleanup { Puppet::Type.rmtype(:flushtest) } obj = type.create(:name => "test", :ensure => "present") # first make sure it runs through and flushes assert_apply(obj) assert_equal("present", $state, "Object did not make a change") assert_equal(1, $flushed, "object was not flushed") # Now run a noop and make sure we don't flush obj[:ensure] = "other" obj[:noop] = true assert_apply(obj) assert_equal("present", $state, "Object made a change in noop") assert_equal(1, $flushed, "object was flushed in noop") end end diff --git a/test/ral/types/basic.rb b/test/ral/types/basic.rb index 4427238bf..862beeadc 100755 --- a/test/ral/types/basic.rb +++ b/test/ral/types/basic.rb @@ -1,85 +1,78 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../lib/puppettest' require 'puppettest' class TestBasic < Test::Unit::TestCase include PuppetTest def setup super @component = nil @configfile = nil @command = nil assert_nothing_raised() { @component = Puppet.type(:component).create( :name => "yaytest", :type => "testing" ) } assert_nothing_raised() { @filepath = tempfile() @configfile = Puppet.type(:file).create( :path => @filepath, :ensure => "file", :checksum => "md5" ) } assert_nothing_raised() { @command = Puppet.type(:exec).create( :title => "echo", :command => "echo yay", :path => ENV["PATH"] ) } @config = mk_configuration(@component, @configfile, @command) @config.add_edge! @component, @configfile @config.add_edge! @component, @command end def teardown super stopservices end def test_values [:ensure, :checksum].each do |param| prop = @configfile.property(param) assert(prop, "got no property for %s" % param) assert(prop.value, "got no value for %s" % param) end end def test_name_calls [@command, @configfile].each { |obj| Puppet.debug "obj is %s" % obj assert_nothing_raised(){ obj.name } } end def test_name_equality assert_equal(@filepath, @configfile.title) assert_equal("echo", @command.title) end - def test_object_retrieval - [@command, @configfile].each { |obj| - assert_equal(obj.class[obj.name].object_id, obj.object_id, - "%s did not match class version" % obj.ref) - } - end - def test_paths [@configfile, @command, @component].each { |obj| assert_nothing_raised { assert_instance_of(String, obj.path) } } end end diff --git a/test/ral/types/cron.rb b/test/ral/types/cron.rb index a9a00240c..d6f29efe4 100755 --- a/test/ral/types/cron.rb +++ b/test/ral/types/cron.rb @@ -1,499 +1,485 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../lib/puppettest' require 'puppettest' # Test cron job creation, modification, and destruction class TestCron < Test::Unit::TestCase include PuppetTest def setup super setme() @crontype = Puppet::Type.type(:cron) @provider = @crontype.defaultprovider if @provider.respond_to?(:filetype=) @provider.stubs(:filetype).returns(Puppet::Util::FileType.filetype(:ram)) end @crontype = Puppet::Type.type(:cron) end def teardown super @crontype.defaultprovider = nil Puppet::Util::FileType.filetype(:ram).clear end def eachprovider @crontype.suitableprovider.each do |provider| yield provider end end # Back up the user's existing cron tab if they have one. def cronback tab = nil assert_nothing_raised { tab = Puppet.type(:cron).filetype.read(@me) } if $? == 0 @currenttab = tab else @currenttab = nil end end # Restore the cron tab to its original form. def cronrestore assert_nothing_raised { if @currenttab @crontype.filetype.new(@me).write(@currenttab) else @crontype.filetype.new(@me).remove end } end # Create a cron job with all fields filled in. def mkcron(name, addargs = true) cron = nil command = "date > %s/crontest%s" % [tmpdir(), name] args = nil if addargs args = { :command => command, :name => name, :user => @me, :minute => rand(59), :month => "1", :monthday => "1", :hour => "1" } else args = {:command => command, :name => name} end assert_nothing_raised { cron = @crontype.create(args) } return cron end # Run the cron through its paces -- install it then remove it. def cyclecron(cron) obj = Puppet::Type::Cron.cronobj(@me) text = obj.read name = cron.name comp = mk_configuration(name, cron) assert_events([:cron_created], comp) cron.provider.class.prefetch currentvalue = cron.retrieve assert(cron.insync?(currentvalue), "Cron is not in sync") assert_events([], comp) curtext = obj.read text.split("\n").each do |line| assert(curtext.include?(line), "Missing '%s'" % line) end obj = Puppet::Type::Cron.cronobj(@me) cron[:ensure] = :absent assert_events([:cron_removed], comp) cron.provider.class.prefetch currentvalue = cron.retrieve assert(cron.insync?(currentvalue), "Cron is not in sync") assert_events([], comp) end # Test that a cron job with spaces at the end doesn't get rewritten def test_trailingspaces eachprovider do |provider| cron = nil # make the cron name = "yaytest" command = "date > /dev/null " assert_nothing_raised { cron = @crontype.create( :name => name, :command => "date > /dev/null ", :month => "May", :user => @me ) } property = cron.send(:property, :command) cron.provider.command = command cron.provider.ensure = :present cron.provider.user = @me cron.provider.month = ["4"] cron.provider.class.prefetch currentvalue = cron.retrieve currentvalue.each do |prop, value| # We're only interested in comparing the command. next unless prop.name.to_s == "command" assert(prop.insync?(value), "Property %s is not considered in sync with value %s" % [prop.name, value.inspect]) end @crontype.clear end end - def test_makeandretrievecron - %w{storeandretrieve a-name another-name more_naming SomeName}.each do |name| - cron = mkcron(name) - comp = mk_configuration(name, cron) - trans = assert_events([:cron_created], comp, name) - - cron.provider.class.prefetch - cron = nil - - assert(cron = Puppet.type(:cron)[name], "Could not retrieve named cron") - assert_instance_of(Puppet.type(:cron), cron) - end - end - # Do input validation testing on all of the parameters. def test_arguments values = { :monthday => { :valid => [ 1, 13, "1" ], :invalid => [ -1, 0, 32 ] }, :weekday => { :valid => [ 0, 3, 6, "1", "tue", "wed", "Wed", "MOnday", "SaTurday" ], :invalid => [ -1, 7, "13", "tues", "teusday", "thurs" ] }, :hour => { :valid => [ 0, 21, 23 ], :invalid => [ -1, 24 ] }, :minute => { :valid => [ 0, 34, 59 ], :invalid => [ -1, 60 ] }, :month => { :valid => [ 1, 11, 12, "mar", "March", "apr", "October", "DeCeMbEr" ], :invalid => [ -1, 0, 13, "marc", "sept" ] } } cron = mkcron("valtesting") values.each { |param, hash| # We have to test the valid ones first, because otherwise the # property will fail to create at all. [:valid, :invalid].each { |type| hash[type].each { |value| case type when :valid: assert_nothing_raised { cron[param] = value } if value.is_a?(Integer) assert_equal([value.to_s], cron.should(param), "Cron value was not set correctly") end when :invalid: assert_raise(Puppet::Error, "%s is incorrectly a valid %s" % [value, param]) { cron[param] = value } end if value.is_a?(Integer) value = value.to_s redo end } } } end # Verify that comma-separated numbers are not resulting in rewrites def test_comma_separated_vals_work eachprovider do |provider| cron = nil assert_nothing_raised { cron = @crontype.create( :command => "/bin/date > /dev/null", :minute => [0, 30], :name => "crontest", :provider => provider.name ) } cron.provider.ensure = :present cron.provider.command = '/bin/date > /dev/null' cron.provider.minute = %w{0 30} cron.provider.class.prefetch currentvalue = cron.retrieve currentvalue.each do |prop, value| # We're only interested in comparing minutes. next unless prop.name.to_s == "minute" assert(prop.insync?(value), "Property %s is not considered in sync with value %s" % [prop.name, value.inspect]) end @crontype.clear end end def test_fieldremoval cron = nil assert_nothing_raised { cron = @crontype.create( :command => "/bin/date > /dev/null", :minute => [0, 30], :name => "crontest", :provider => :crontab ) } assert_events([:cron_created], cron) cron.provider.class.prefetch cron[:minute] = :absent assert_events([:cron_changed], cron) current_values = nil assert_nothing_raised { cron.provider.class.prefetch current_values = cron.retrieve } assert_equal(:absent, current_values[cron.property(:minute)]) end def test_listing # Make a crontab cron for testing provider = @crontype.provider(:crontab) return unless provider.suitable? ft = provider.filetype provider.filetype = :ram cleanup { provider.filetype = ft } setme cron = @crontype.create(:name => "testing", :minute => [0, 30], :command => "/bin/testing", :user => @me ) # Write it to our file assert_apply(cron) @crontype.clear crons = [] assert_nothing_raised { @crontype.instances.each do |cron| crons << cron end } crons.each do |cron| assert_instance_of(@crontype, cron, "Did not receive a real cron object") assert_instance_of(String, cron.value(:user), "Cron user is not a string") end end def verify_failonnouser assert_raise(Puppet::Error) do @crontype.retrieve("nosuchuser") end end def test_divisionnumbers cron = mkcron("divtest") cron[:minute] = "*/5" assert_apply(cron) cron.provider.class.prefetch currentvalue = cron.retrieve assert_equal(["*/5"], currentvalue[cron.property(:minute)]) end def test_ranges cron = mkcron("rangetest") cron[:minute] = "2-4" assert_apply(cron) current_values = nil assert_nothing_raised { cron.provider.class.prefetch current_values = cron.retrieve } assert_equal(["2-4"], current_values[cron.property(:minute)]) end def provider_set(cron, param, value) unless param =~ /=$/ param = "%s=" % param end cron.provider.send(param, value) end def test_value cron = mkcron("valuetesting", false) # First, test the normal properties [:minute, :hour, :month].each do |param| cron.newattr(param) property = cron.property(param) assert(property, "Did not get %s property" % param) assert_nothing_raised { # property.is = :absent provider_set(cron, param, :absent) } val = "*" assert_equal(val, cron.value(param)) # Make sure arrays work, too provider_set(cron, param, ["1"]) assert_equal(%w{1}, cron.value(param)) # Make sure values get comma-joined provider_set(cron, param, %w{2 3}) assert_equal(%w{2 3}, cron.value(param)) # Make sure "should" values work, too cron[param] = "4" assert_equal(%w{4}, cron.value(param)) cron[param] = ["4"] assert_equal(%w{4}, cron.value(param)) cron[param] = ["4", "5"] assert_equal(%w{4 5}, cron.value(param)) provider_set(cron, param, :absent) assert_equal(%w{4 5}, cron.value(param)) end Puppet[:trace] = false # Now make sure that :command works correctly cron.delete(:command) cron.newattr(:command) property = cron.property(:command) assert_nothing_raised { provider_set(cron, :command, :absent) } param = :command # Make sure arrays work, too provider_set(cron, param, ["/bin/echo"]) assert_equal("/bin/echo", cron.value(param)) # Make sure values are not comma-joined provider_set(cron, param, %w{/bin/echo /bin/test}) assert_equal("/bin/echo", cron.value(param)) # Make sure "should" values work, too cron[param] = "/bin/echo" assert_equal("/bin/echo", cron.value(param)) cron[param] = ["/bin/echo"] assert_equal("/bin/echo", cron.value(param)) cron[param] = %w{/bin/echo /bin/test} assert_equal("/bin/echo", cron.value(param)) provider_set(cron, param, :absent) assert_equal("/bin/echo", cron.value(param)) end def test_multiple_users crons = [] users = ["root", nonrootuser.name] users.each do |user| cron = Puppet::Type.type(:cron).create( :name => "testcron-#{user}", :user => user, :command => "/bin/echo", :minute => [0,30] ) crons << cron assert_equal(cron.should(:user), cron.should(:target), "Target was not set correctly for %s" % user) end provider = crons[0].provider.class assert_apply(*crons) users.each do |user| users.each do |other| next if user == other text = provider.target_object(other).read assert(text !~ /testcron-#{user}/, "%s's cron job is in %s's tab" % [user, other]) end end end # Make sure the user stuff defaults correctly. def test_default_user crontab = @crontype.provider(:crontab) if crontab.suitable? inst = @crontype.create( :name => "something", :command => "/some/thing", :provider => :crontab) assert_equal(ENV["USER"], inst.should(:user), "user did not default to current user with crontab") assert_equal(ENV["USER"], inst.should(:target), "target did not default to current user with crontab") # Now make a new cron with a user, and make sure it gets copied # over inst = @crontype.create(:name => "yay", :command => "/some/thing", :user => "bin", :provider => :crontab) assert_equal("bin", inst.should(:target), "target did not default to user with crontab") end end # #705 - make sure extra spaces don't screw things up def test_spaces_in_command string = "echo multiple spaces" cron = @crontype.create(:name => "space testing", :command => string) assert_apply(cron) cron.class.clear cron = @crontype.create(:name => "space testing", :command => string) # Now make sure that it's correctly in sync cron.provider.class.prefetch("testing" => cron) properties = cron.retrieve command, result = properties.find { |prop, value| prop.name == :command } assert_equal(string, result, "Cron did not pick up extra spaces in command") assert(command.insync?(string), "Command changed with multiple spaces") end end diff --git a/test/ral/types/exec.rb b/test/ral/types/exec.rb index 11a6d4055..ed0ea9980 100755 --- a/test/ral/types/exec.rb +++ b/test/ral/types/exec.rb @@ -1,730 +1,733 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../lib/puppettest' require 'puppettest' class TestExec < Test::Unit::TestCase include PuppetTest def test_execution command = nil output = nil assert_nothing_raised { command = Puppet.type(:exec).create( :command => "/bin/echo" ) } assert_nothing_raised { command.evaluate } assert_events([:executed_command], command) end def test_numvsstring [0, "0"].each { |val| Puppet.type(:exec).clear Puppet.type(:component).clear command = nil output = nil assert_nothing_raised { command = Puppet.type(:exec).create( :command => "/bin/echo", :returns => val ) } assert_events([:executed_command], command) } end def test_path_or_qualified command = nil output = nil assert_raise(Puppet::Error) { command = Puppet.type(:exec).create( :command => "echo" ) } assert_nothing_raised { command = Puppet.type(:exec).create( :command => "echo", :path => "/usr/bin:/bin:/usr/sbin:/sbin" ) } Puppet.type(:exec).clear assert_nothing_raised { command = Puppet.type(:exec).create( :command => "/bin/echo" ) } Puppet.type(:exec).clear assert_nothing_raised { command = Puppet.type(:exec).create( :command => "/bin/echo", :path => "/usr/bin:/bin:/usr/sbin:/sbin" ) } end def test_nonzero_returns assert_nothing_raised { command = Puppet.type(:exec).create( :command => "mkdir /this/directory/does/not/exist", :path => "/usr/bin:/bin:/usr/sbin:/sbin", :returns => 1 ) } assert_nothing_raised { command = Puppet.type(:exec).create( :command => "touch /etc", :path => "/usr/bin:/bin:/usr/sbin:/sbin", :returns => 1 ) } assert_nothing_raised { command = Puppet.type(:exec).create( :command => "thiscommanddoesnotexist", :path => "/usr/bin:/bin:/usr/sbin:/sbin", :returns => 127 ) } end def test_cwdsettings command = nil dir = "/tmp" wd = Dir.chdir(dir) { Dir.getwd } assert_nothing_raised { command = Puppet.type(:exec).create( :command => "pwd", :cwd => dir, :path => "/usr/bin:/bin:/usr/sbin:/sbin", :returns => 0 ) } assert_events([:executed_command], command) assert_equal(wd,command.output.chomp) end def test_refreshonly_functional file = nil cmd = nil tmpfile = tempfile() @@tmpfiles.push tmpfile trans = nil file = Puppet.type(:file).create( :path => tmpfile, :content => "yay" ) # Get the file in sync assert_apply(file) # Now make an exec maker = tempfile() assert_nothing_raised { cmd = Puppet.type(:exec).create( :command => "touch %s" % maker, :path => "/usr/bin:/bin:/usr/sbin:/sbin", :subscribe => file, :refreshonly => true ) } assert(cmd, "did not make exec") assert_nothing_raised do assert(! cmd.check, "Check passed when refreshonly is set") end assert_events([], file, cmd) assert(! FileTest.exists?(maker), "made file without refreshing") # Now change our content, so we throw a refresh file[:content] = "yayness" assert_events([:file_changed, :triggered], file, cmd) assert(FileTest.exists?(maker), "file was not made in refresh") end def test_refreshonly cmd = true assert_nothing_raised { cmd = Puppet.type(:exec).create( :command => "pwd", :path => "/usr/bin:/bin:/usr/sbin:/sbin", :refreshonly => true ) } # Checks should always fail when refreshonly is enabled assert(!cmd.check, "Check passed with refreshonly true") # Now make sure it passes if we pass in "true" assert(cmd.check(true), "Check failed with refreshonly true while refreshing") # Now set it to false cmd[:refreshonly] = false assert(cmd.check, "Check failed with refreshonly false") end def test_creates file = tempfile() exec = nil assert(! FileTest.exists?(file), "File already exists") assert_nothing_raised { exec = Puppet.type(:exec).create( :command => "touch %s" % file, :path => "/usr/bin:/bin:/usr/sbin:/sbin", :creates => file ) } comp = mk_configuration("createstest", exec) assert_events([:executed_command], comp, "creates") assert_events([], comp, "creates") end # Verify that we can download the file that we're going to execute. def test_retrievethenmkexe exe = tempfile() oexe = tempfile() sh = %x{which sh} File.open(exe, "w") { |f| f.puts "#!#{sh}\necho yup" } file = Puppet.type(:file).create( :path => oexe, :source => exe, :mode => 0755 ) exec = Puppet.type(:exec).create( :command => oexe, :require => [:file, oexe] ) comp = mk_configuration("Testing", file, exec) + Puppet::Node::Facts.indirection.stubs(:terminus_class).returns(:memory) assert_events([:file_created, :executed_command], comp) end # Verify that we auto-require any managed scripts. def test_autorequire_files + config = mk_configuration + exe = tempfile() oexe = tempfile() sh = %x{which sh} File.open(exe, "w") { |f| f.puts "#!#{sh}\necho yup" } - file = Puppet.type(:file).create( + file = config.create_resource(:file, :path => oexe, :source => exe, :mode => 755 ) basedir = File.dirname(oexe) - baseobj = Puppet.type(:file).create( + baseobj = config.create_resource(:file, :path => basedir, :source => exe, :mode => 755 ) - ofile = Puppet.type(:file).create( + ofile = config.create_resource(:file, :path => exe, :mode => 755 ) - exec = Puppet.type(:exec).create( + exec = config.create_resource(:exec, :command => oexe, :path => ENV["PATH"], :cwd => basedir ) - cat = Puppet.type(:exec).create( + cat = config.create_resource(:exec, :command => "cat %s %s" % [exe, oexe], :path => ENV["PATH"] ) rels = nil assert_nothing_raised do rels = exec.autorequire end # Verify we get the script itself assert(rels.detect { |r| r.source == file }, "Exec did not autorequire its command") # Verify we catch the cwd assert(rels.detect { |r| r.source == baseobj }, "Exec did not autorequire its cwd") # Verify we don't require ourselves assert(! rels.detect { |r| r.source == ofile }, "Exec incorrectly required mentioned file") assert(!exec.requires?(ofile), "Exec incorrectly required file") # We not longer autorequire inline files assert_nothing_raised do rels = cat.autorequire end assert(! rels.detect { |r| r.source == ofile }, "Exec required second inline file") assert(! rels.detect { |r| r.source == file }, "Exec required inline file") end def test_ifonly afile = tempfile() bfile = tempfile() exec = nil assert_nothing_raised { exec = Puppet.type(:exec).create( :command => "touch %s" % bfile, :onlyif => "test -f %s" % afile, :path => ENV['PATH'] ) } assert_events([], exec) system("touch %s" % afile) assert_events([:executed_command], exec) assert_events([:executed_command], exec) system("rm %s" % afile) assert_events([], exec) end def test_unless afile = tempfile() bfile = tempfile() exec = nil assert_nothing_raised { exec = Puppet.type(:exec).create( :command => "touch %s" % bfile, :unless => "test -f %s" % afile, :path => ENV['PATH'] ) } comp = mk_configuration(exec) assert_events([:executed_command], comp) assert_events([:executed_command], comp) system("touch %s" % afile) assert_events([], comp) assert_events([], comp) system("rm %s" % afile) assert_events([:executed_command], comp) assert_events([:executed_command], comp) end if Puppet::Util::SUIDManager.uid == 0 # Verify that we can execute commands as a special user def mknverify(file, user, group = nil, id = true) File.umask(0022) args = { :command => "touch %s" % file, :path => "/usr/bin:/bin:/usr/sbin:/sbin", } if user #Puppet.warning "Using user %s" % user.name if id # convert to a string, because that's what the object expects args[:user] = user.uid.to_s else args[:user] = user.name end end if group #Puppet.warning "Using group %s" % group.name if id args[:group] = group.gid.to_s else args[:group] = group.name end end exec = nil assert_nothing_raised { exec = Puppet.type(:exec).create(args) } comp = mk_configuration("usertest", exec) assert_events([:executed_command], comp, "usertest") assert(FileTest.exists?(file), "File does not exist") if user assert_equal(user.uid, File.stat(file).uid, "File UIDs do not match") end # We can't actually test group ownership, unfortunately, because # behaviour changes wildlly based on platform. Puppet::Type.allclear end def test_userngroup file = tempfile() [ [nonrootuser()], # just user, by name [nonrootuser(), nil, true], # user, by uid [nil, nonrootgroup()], # just group [nil, nonrootgroup(), true], # just group, by id [nonrootuser(), nonrootgroup()], # user and group, by name [nonrootuser(), nonrootgroup(), true], # user and group, by id ].each { |ary| mknverify(file, *ary) { } } end end def test_logoutput exec = nil assert_nothing_raised { exec = Puppet.type(:exec).create( :title => "logoutputesting", :path => "/usr/bin:/bin", :command => "echo logoutput is false", :logoutput => false ) } assert_apply(exec) assert_nothing_raised { exec[:command] = "echo logoutput is true" exec[:logoutput] = true } assert_apply(exec) assert_nothing_raised { exec[:command] = "echo logoutput is warning" exec[:logoutput] = "warning" } assert_apply(exec) end def test_execthenfile exec = nil file = nil basedir = tempfile() path = File.join(basedir, "subfile") assert_nothing_raised { exec = Puppet.type(:exec).create( :title => "mkdir", :path => "/usr/bin:/bin", :creates => basedir, :command => "mkdir %s; touch %s" % [basedir, path] ) } assert_nothing_raised { file = Puppet.type(:file).create( :path => basedir, :recurse => true, :mode => "755", :require => ["exec", "mkdir"] ) } comp = mk_configuration(file, exec) comp.finalize assert_events([:executed_command, :file_changed], comp) assert(FileTest.exists?(path), "Exec ran first") assert(File.stat(path).mode & 007777 == 0755) end # Make sure all checks need to be fully qualified. def test_falsevals exec = nil assert_nothing_raised do exec = Puppet.type(:exec).create( :command => "/bin/touch yayness" ) end Puppet.type(:exec).checks.each do |check| klass = Puppet.type(:exec).paramclass(check) next if klass.values.include? :false assert_raise(Puppet::Error, "Check '%s' did not fail on false" % check) do exec[check] = false end end end def test_createcwdandexe exec1 = exec2 = nil dir = tempfile() file = tempfile() assert_nothing_raised { exec1 = Puppet.type(:exec).create( :title => "one", :path => ENV["PATH"], :command => "mkdir #{dir}" ) } assert_nothing_raised("Could not create exec w/out existing cwd") { exec2 = Puppet.type(:exec).create( :title => "two", :path => ENV["PATH"], :command => "touch #{file}", :cwd => dir ) } # Throw a check in there with our cwd and make sure it works assert_nothing_raised("Could not check with a missing cwd") do exec2[:unless] = "test -f /this/file/does/not/exist" exec2.retrieve end assert_raise(Puppet::Error) do exec2.property(:returns).sync end assert_nothing_raised do exec2[:require] = exec1 end assert_apply(exec1, exec2) assert(FileTest.exists?(file)) end def test_checkarrays exec = nil file = tempfile() test = "test -f #{file}" assert_nothing_raised { exec = Puppet.type(:exec).create( :path => ENV["PATH"], :command => "touch #{file}" ) } assert_nothing_raised { exec[:unless] = test } assert_nothing_raised { assert(exec.check, "Check did not pass") } assert_nothing_raised { exec[:unless] = [test, test] } assert_nothing_raised { exec.finish } assert_nothing_raised { assert(exec.check, "Check did not pass") } assert_apply(exec) assert_nothing_raised { assert(! exec.check, "Check passed") } end def test_missing_checks_cause_failures # Solaris's sh exits with 1 here instead of 127 return if Facter.value(:operatingsystem) == "Solaris" exec = Puppet::Type.type(:exec).create( :command => "echo true", :path => ENV["PATH"], :onlyif => "/bin/nosuchthingexists" ) assert_raise(ArgumentError, "Missing command did not raise error") { exec.run("/bin/nosuchthingexists") } end def test_envparam exec = Puppet::Type.newexec( :command => "echo $envtest", :path => ENV["PATH"], :env => "envtest=yayness" ) assert(exec, "Could not make exec") output = status = nil assert_nothing_raised { output, status = exec.run("echo $envtest") } assert_equal("yayness\n", output) # Now check whether we can do multiline settings assert_nothing_raised do exec[:env] = "envtest=a list of things and stuff" end output = status = nil assert_nothing_raised { output, status = exec.run('echo "$envtest"') } assert_equal("a list of things\nand stuff\n", output) # Now test arrays assert_nothing_raised do exec[:env] = ["funtest=A", "yaytest=B"] end output = status = nil assert_nothing_raised { output, status = exec.run('echo "$funtest" "$yaytest"') } assert_equal("A B\n", output) end def test_timeout exec = Puppet::Type.type(:exec).create(:command => "sleep 1", :path => ENV["PATH"], :timeout => "0.2") time = Time.now assert_raise(Timeout::Error) { exec.run("sleep 1") } Puppet.info "%s seconds, vs a timeout of %s" % [Time.now.to_f - time.to_f, exec[:timeout]] assert_apply(exec) end # Testing #470 def test_run_as_created_user exec = nil if Process.uid == 0 user = "nosuchuser" assert_nothing_raised("Could not create exec with non-existent user") do exec = Puppet::Type.type(:exec).create( :command => "/bin/echo yay", :user => user ) end end # Now try the group group = "nosuchgroup" assert_nothing_raised("Could not create exec with non-existent user") do exec = Puppet::Type.type(:exec).create( :command => "/bin/echo yay", :group => group ) end end # make sure paths work both as arrays and strings def test_paths_as_arrays path = %w{/usr/bin /usr/sbin /sbin} exec = nil assert_nothing_raised("Could not use an array for the path") do exec = Puppet::Type.type(:exec).create(:command => "echo yay", :path => path) end assert_equal(path, exec[:path], "array-based path did not match") assert_nothing_raised("Could not use a string for the path") do exec = Puppet::Type.type(:exec).create(:command => "echo yay", :path => path.join(":")) end assert_equal(path, exec[:path], "string-based path did not match") assert_nothing_raised("Could not use a colon-separated strings in an array for the path") do exec = Puppet::Type.type(:exec).create(:command => "echo yay", :path => ["/usr/bin", "/usr/sbin:/sbin"]) end assert_equal(path, exec[:path], "colon-separated array path did not match") end def test_checks_apply_to_refresh file = tempfile() maker = tempfile() exec = Puppet::Type.type(:exec).create( :title => "maker", :command => "touch #{maker}", :path => ENV["PATH"] ) # Make sure it runs normally assert_apply(exec) assert(FileTest.exists?(maker), "exec did not run") File.unlink(maker) # Now make sure it refreshes assert_nothing_raised("Failed to refresh exec") do exec.refresh end assert(FileTest.exists?(maker), "exec did not run refresh") File.unlink(maker) # Now add the checks exec[:creates] = file # Make sure it runs when the file doesn't exist assert_nothing_raised("Failed to refresh exec") do exec.refresh end assert(FileTest.exists?(maker), "exec did not refresh when checks passed") File.unlink(maker) # Now create the file and make sure it doesn't refresh File.open(file, "w") { |f| f.puts "" } assert_nothing_raised("Failed to refresh exec") do exec.refresh end assert(! FileTest.exists?(maker), "exec refreshed with failing checks") end def test_explicit_refresh refresher = tempfile() maker = tempfile() exec = Puppet::Type.type(:exec).create( :title => "maker", :command => "touch #{maker}", :path => ENV["PATH"] ) # Call refresh normally assert_nothing_raised do exec.refresh end # Make sure it created the normal file assert(FileTest.exists?(maker), "normal refresh did not work") File.unlink(maker) # Now reset refresh, and make sure it wins assert_nothing_raised("Could not set refresh parameter") do exec[:refresh] = "touch #{refresher}" end assert_nothing_raised do exec.refresh end # Make sure it created the normal file assert(FileTest.exists?(refresher), "refresh param was ignored") assert(! FileTest.exists?(maker), "refresh param also ran command") end if Puppet.features.root? def test_autorequire_user user = Puppet::Type.type(:user).create(:name => "yay") exec = Puppet::Type.type(:exec).create(:command => "/bin/echo fun", :user => "yay") rels = nil assert_nothing_raised("Could not evaluate autorequire") do rels = exec.autorequire end assert(rels.find { |r| r.source == user and r.target == exec }, "Exec did not autorequire user") end end end diff --git a/test/ral/types/file.rb b/test/ral/types/file.rb index 73095a783..8435855e6 100755 --- a/test/ral/types/file.rb +++ b/test/ral/types/file.rb @@ -1,1821 +1,1799 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../lib/puppettest' require 'puppettest' +require 'puppettest/support/utils' require 'fileutils' class TestFile < Test::Unit::TestCase include PuppetTest::FileTesting + include PuppetTest::Support::Utils # hmmm # this is complicated, because we store references to the created # objects in a central store def mkfile(hash) file = nil assert_nothing_raised { file = Puppet.type(:file).create(hash) } return file end def mktestfile # because luke's home directory is on nfs, it can't be used for testing # as root tmpfile = tempfile() File.open(tmpfile, "w") { |f| f.puts rand(100) } @@tmpfiles.push tmpfile mkfile(:name => tmpfile) end def setup super @file = Puppet::Type.type(:file) $method = @method_name Puppet[:filetimeout] = -1 + Facter.stubs(:each) + Facter.stubs(:to_hash).returns({}) end def teardown Puppet::Util::Storage.clear system("rm -rf %s" % Puppet[:statefile]) super end def initstorage Puppet::Util::Storage.init Puppet::Util::Storage.load end def clearstorage Puppet::Util::Storage.store Puppet::Util::Storage.clear end def test_owner file = mktestfile() users = {} count = 0 # collect five users Etc.passwd { |passwd| if count > 5 break else count += 1 end users[passwd.uid] = passwd.name } fake = {} # find a fake user while true a = rand(1000) begin Etc.getpwuid(a) rescue fake[a] = "fakeuser" break end end uid, name = users.shift us = {} us[uid] = name users.each { |uid, name| assert_apply(file) assert_nothing_raised() { file[:owner] = name } assert_nothing_raised() { file.retrieve } assert_apply(file) } end def test_group file = mktestfile() [%x{groups}.chomp.split(/ /), Process.groups].flatten.each { |group| assert_nothing_raised() { file[:group] = group } assert(file.property(:group)) assert(file.property(:group).should) } end def test_groups_fails_when_invalid assert_raise(Puppet::Error, "did not fail when the group was empty") do Puppet::Type.type(:file).create :path => "/some/file", :group => "" end end if Puppet::Util::SUIDManager.uid == 0 def test_createasuser dir = tmpdir() user = nonrootuser() path = File.join(tmpdir, "createusertesting") @@tmpfiles << path file = nil assert_nothing_raised { file = Puppet.type(:file).create( :path => path, :owner => user.name, :ensure => "file", :mode => "755" ) } comp = mk_configuration("createusertest", file) assert_events([:file_created], comp) end def test_nofollowlinks basedir = tempfile() Dir.mkdir(basedir) file = File.join(basedir, "file") link = File.join(basedir, "link") File.open(file, "w", 0644) { |f| f.puts "yayness"; f.flush } File.symlink(file, link) # First test 'user' user = nonrootuser() inituser = File.lstat(link).uid File.lchown(inituser, nil, link) obj = nil assert_nothing_raised { obj = Puppet.type(:file).create( :title => link, :owner => user.name ) } obj.retrieve # Make sure it defaults to managing the link assert_events([:file_changed], obj) assert_equal(user.uid, File.lstat(link).uid) assert_equal(inituser, File.stat(file).uid) File.chown(inituser, nil, file) File.lchown(inituser, nil, link) # Try following obj[:links] = :follow assert_events([:file_changed], obj) assert_equal(user.uid, File.stat(file).uid) assert_equal(inituser, File.lstat(link).uid) # And then explicitly managing File.chown(inituser, nil, file) File.lchown(inituser, nil, link) obj[:links] = :manage assert_events([:file_changed], obj) assert_equal(user.uid, File.lstat(link).uid) assert_equal(inituser, File.stat(file).uid) obj.delete(:owner) obj[:links] = :ignore # And then test 'group' group = nonrootgroup initgroup = File.stat(file).gid obj[:group] = group.name assert_events([:file_changed], obj) assert_equal(initgroup, File.stat(file).gid) assert_equal(group.gid, File.lstat(link).gid) File.chown(nil, initgroup, file) File.lchown(nil, initgroup, link) obj[:links] = :follow assert_events([:file_changed], obj) assert_equal(group.gid, File.stat(file).gid) File.chown(nil, initgroup, file) File.lchown(nil, initgroup, link) obj[:links] = :manage assert_events([:file_changed], obj) assert_equal(group.gid, File.lstat(link).gid) assert_equal(initgroup, File.stat(file).gid) end def test_ownerasroot file = mktestfile() users = {} count = 0 # collect five users Etc.passwd { |passwd| if count > 5 break else count += 1 end next if passwd.uid < 0 users[passwd.uid] = passwd.name } fake = {} # find a fake user while true a = rand(1000) begin Etc.getpwuid(a) rescue fake[a] = "fakeuser" break end end users.each { |uid, name| assert_nothing_raised() { file[:owner] = name } changes = [] assert_nothing_raised() { changes << file.evaluate } assert(changes.length > 0) assert_apply(file) currentvalue = file.retrieve assert(file.insync?(currentvalue)) assert_nothing_raised() { file[:owner] = uid } assert_apply(file) currentvalue = file.retrieve # make sure changing to number doesn't cause a sync assert(file.insync?(currentvalue)) } # We no longer raise an error here, because we check at run time #fake.each { |uid, name| # assert_raise(Puppet::Error) { # file[:owner] = name # } # assert_raise(Puppet::Error) { # file[:owner] = uid # } #} end def test_groupasroot file = mktestfile() [%x{groups}.chomp.split(/ /), Process.groups].flatten.each { |group| next unless Puppet::Util.gid(group) # grr. assert_nothing_raised() { file[:group] = group } assert(file.property(:group)) assert(file.property(:group).should) assert_apply(file) currentvalue = file.retrieve assert(file.insync?(currentvalue)) assert_nothing_raised() { file.delete(:group) } } end if Facter.value(:operatingsystem) == "Darwin" def test_sillyowner file = tempfile() File.open(file, "w") { |f| f.puts "" } File.chown(-2, nil, file) assert(File.stat(file).uid > 120000, "eh?") user = nonrootuser obj = Puppet::Type.newfile( :path => file, :owner => user.name ) assert_apply(obj) assert_equal(user.uid, File.stat(file).uid) end end else $stderr.puts "Run as root for complete owner and group testing" end def test_create %w{a b c d}.collect { |name| tempfile() + name.to_s }.each { |path| file =nil assert_nothing_raised() { file = Puppet.type(:file).create( :name => path, :ensure => "file" ) } assert_events([:file_created], file) assert_events([], file) assert(FileTest.file?(path), "File does not exist") assert(file.insync?(file.retrieve)) @@tmpfiles.push path } end def test_create_dir basedir = tempfile() Dir.mkdir(basedir) %w{a b c d}.collect { |name| "#{basedir}/%s" % name }.each { |path| file = nil assert_nothing_raised() { file = Puppet.type(:file).create( :name => path, :ensure => "directory" ) } assert(! FileTest.directory?(path), "Directory %s already exists" % [path]) assert_events([:directory_created], file) assert_events([], file) assert(file.insync?(file.retrieve)) assert(FileTest.directory?(path)) @@tmpfiles.push path } end def test_modes file = mktestfile # Set it to something else initially File.chmod(0775, file.title) [0644,0755,0777,0641].each { |mode| assert_nothing_raised() { file[:mode] = mode } assert_events([:file_changed], file) assert_events([], file) assert(file.insync?(file.retrieve)) assert_nothing_raised() { file.delete(:mode) } } end def test_checksums types = %w{md5 md5lite timestamp time} exists = "/tmp/sumtest-exists" nonexists = "/tmp/sumtest-nonexists" @@tmpfiles << exists @@tmpfiles << nonexists # try it both with files that exist and ones that don't files = [exists, nonexists] initstorage File.open(exists,File::CREAT|File::TRUNC|File::WRONLY) { |of| of.puts "initial text" } types.each { |type| files.each { |path| if Puppet[:debug] Puppet.warning "Testing %s on %s" % [type,path] end file = nil events = nil # okay, we now know that we have a file... assert_nothing_raised() { file = Puppet.type(:file).create( :name => path, :ensure => "file", :checksum => type ) } trans = nil currentvalues = file.retrieve if file.title !~ /nonexists/ sum = file.property(:checksum) assert(sum.insync?(currentvalues[sum]), "file is not in sync") end events = assert_apply(file) assert(events) assert(! events.include?(:file_changed), "File incorrectly changed") assert_events([], file) # We have to sleep because the time resolution of the time-based # mechanisms is greater than one second sleep 1 if type =~ /time/ assert_nothing_raised() { File.open(path,File::CREAT|File::TRUNC|File::WRONLY) { |of| of.puts "some more text, yo" } } Puppet.type(:file).clear # now recreate the file assert_nothing_raised() { file = Puppet.type(:file).create( :name => path, :checksum => type ) } trans = nil assert_events([:file_changed], file) # Run it a few times to make sure we aren't getting # spurious changes. sum = nil assert_nothing_raised do sum = file.property(:checksum).retrieve end assert(file.property(:checksum).insync?(sum), "checksum is not in sync") sleep 1.1 if type =~ /time/ assert_nothing_raised() { File.unlink(path) File.open(path,File::CREAT|File::TRUNC|File::WRONLY) { |of| # We have to put a certain amount of text in here or # the md5-lite test fails 2.times { of.puts rand(100) } of.flush } } assert_events([:file_changed], file) # verify that we're actually getting notified when a file changes assert_nothing_raised() { Puppet.type(:file).clear } if path =~ /nonexists/ File.unlink(path) end } } end def cyclefile(path) # i had problems with using :name instead of :path [:name,:path].each { |param| file = nil changes = nil comp = nil trans = nil initstorage assert_nothing_raised { file = Puppet.type(:file).create( param => path, :recurse => true, :checksum => "md5" ) } comp = Puppet.type(:component).create( :name => "component" ) comp.push file assert_nothing_raised { trans = comp.evaluate } assert_nothing_raised { trans.evaluate } clearstorage Puppet::Type.allclear } end def test_localrecurse # Create a test directory path = tempfile() dir = @file.create :path => path, :mode => 0755, :recurse => true config = mk_configuration(dir) Dir.mkdir(path) # Make sure we return nothing when there are no children ret = nil assert_nothing_raised() { ret = dir.localrecurse(true) } assert_equal([], ret, "empty dir returned children") # Now make a file and make sure we get it test = File.join(path, "file") File.open(test, "w") { |f| f.puts "yay" } assert_nothing_raised() { ret = dir.localrecurse(true) } - fileobj = @file[test] + fileobj = config.resource(:file, test) assert(fileobj, "child object was not created") assert_equal([fileobj], ret, "child object was not returned") # And that it inherited our recurse setting assert_equal(true, fileobj[:recurse], "file did not inherit recurse") # Make sure it's not returned again assert_nothing_raised() { ret = dir.localrecurse(true) } assert_equal([], ret, "child object was returned twice") # Now just for completion, make sure we will return many files files = [] 10.times do |i| f = File.join(path, i.to_s) files << f File.open(f, "w") do |o| o.puts "" end end assert_nothing_raised() { ret = dir.localrecurse(true) } assert_equal(files.sort, ret.collect { |f| f.title }.sort, "child object was returned twice") # Clean everything up and start over files << test files.each do |f| File.unlink(f) end # Now make sure we correctly ignore things dir[:ignore] = "*.out" bad = File.join(path, "test.out") good = File.join(path, "yayness") [good, bad].each do |f| File.open(f, "w") { |o| o.puts "" } end assert_nothing_raised() { ret = dir.localrecurse(true) } assert_equal([good], ret.collect { |f| f.title }, "ignore failed") # Now make sure purging works dir[:purge] = true dir[:ignore] = "svn" assert_nothing_raised() { ret = dir.localrecurse(true) } assert_equal([bad], ret.collect { |f| f.title }, "purge failed") - badobj = @file[bad] + badobj = config.resource(:file, bad) assert(badobj, "did not create bad object") end def test_recurse basedir = tempfile() FileUtils.mkdir_p(basedir) # Create our file dir = nil assert_nothing_raised { dir = Puppet.type(:file).create( :path => basedir, :check => %w{owner mode group} ) } return_nil = false # and monkey-patch it [:localrecurse, :linkrecurse].each do |m| dir.meta_def(m) do |recurse| if return_nil # for testing nil return, of course return nil else return [recurse] end end end # We have to special-case this, because it returns a list of # found files. dir.meta_def(:sourcerecurse) do |recurse| if return_nil # for testing nil return, of course return nil else return [recurse], [] end end # First try it with recurse set to false dir[:recurse] = false assert_nothing_raised do assert_nil(dir.recurse) end # Now try it with the different valid positive values [true, "true", "inf", 50].each do |value| assert_nothing_raised { dir[:recurse] = value} # Now make sure the methods are called appropriately ret = nil assert_nothing_raised do ret = dir.recurse end # We should only call the localrecurse method, so make sure # that's the case if value == 50 # Make sure our counter got decremented assert_equal([49], ret, "did not call localrecurse") else assert_equal([true], ret, "did not call localrecurse") end end # Make sure it doesn't recurse when we've set recurse to false [false, "false"].each do |value| assert_nothing_raised { dir[:recurse] = value } ret = nil assert_nothing_raised() { ret = dir.recurse } assert_nil(ret) end dir[:recurse] = true # Now add a target, so we do the linking thing dir[:target] = tempfile() ret = nil assert_nothing_raised { ret = dir.recurse } assert_equal([true, true], ret, "did not call linkrecurse") # And add a source, and make sure we call that dir[:source] = tempfile() assert_nothing_raised { ret = dir.recurse } assert_equal([true, true, true], ret, "did not call linkrecurse") # Lastly, make sure we correctly handle returning nil return_nil = true assert_nothing_raised { ret = dir.recurse } end def test_recurse? file = Puppet::Type.type(:file).create :path => tempfile # Make sure we default to false assert(! file.recurse?, "Recurse defaulted to true") [true, "true", 10, "inf"].each do |value| file[:recurse] = value assert(file.recurse?, "%s did not cause recursion" % value) end [false, "false", 0].each do |value| file[:recurse] = value assert(! file.recurse?, "%s caused recursion" % value) end end def test_recursion basedir = tempfile() subdir = File.join(basedir, "subdir") tmpfile = File.join(basedir,"testing") FileUtils.mkdir_p(subdir) dir = nil [true, "true", "inf", 50].each do |value| assert_nothing_raised { dir = Puppet.type(:file).create( :path => basedir, :recurse => value, :check => %w{owner mode group} ) } config = mk_configuration dir children = nil assert_nothing_raised { children = dir.eval_generate } assert_equal([subdir], children.collect {|c| c.title }, "Incorrect generated children") # Remove our subdir resource, subdir_resource = config.resource(:file, subdir) config.remove_resource(subdir_resource) # Create the test file File.open(tmpfile, "w") { |f| f.puts "yayness" } assert_nothing_raised { children = dir.eval_generate } # And make sure we get both resources back. assert_equal([subdir, tmpfile].sort, children.collect {|c| c.title }.sort, "Incorrect generated children when recurse == %s" % value.inspect) File.unlink(tmpfile) Puppet.type(:file).clear end end def test_filetype_retrieval file = nil # Verify it retrieves files of type directory assert_nothing_raised { file = Puppet.type(:file).create( :name => tmpdir(), :check => :type ) } assert_nothing_raised { file.evaluate } assert_equal("directory", file.property(:type).retrieve) # And then check files assert_nothing_raised { file = Puppet.type(:file).create( :name => tempfile(), :ensure => "file" ) } assert_apply(file) file[:check] = "type" assert_apply(file) assert_equal("file", file.property(:type).retrieve) file[:type] = "directory" currentvalues = {} assert_nothing_raised { currentvalues = file.retrieve } # The 'retrieve' method sets @should to @is, so they're never # out of sync. It's a read-only class. assert(file.insync?(currentvalues)) end - def test_remove - basedir = tempfile() - subdir = File.join(basedir, "this") - FileUtils.mkdir_p(subdir) - - dir = nil - assert_nothing_raised { - dir = Puppet.type(:file).create( - :path => basedir, - :recurse => true, - :check => %w{owner mode group} - ) - } - mk_configuration dir - - assert_nothing_raised { - dir.eval_generate - } - - obj = nil - assert_nothing_raised { - obj = Puppet.type(:file)[subdir] - } - - assert(obj, "Could not retrieve subdir object") - - assert_nothing_raised { - obj.remove(true) - } - - assert_nothing_raised { - obj = Puppet.type(:file)[subdir] - } - - assert_nil(obj, "Retrieved removed object") - end - def test_path dir = tempfile() path = File.join(dir, "subdir") assert_nothing_raised("Could not make file") { FileUtils.mkdir_p(File.dirname(path)) File.open(path, "w") { |f| f.puts "yayness" } } file = nil dirobj = nil assert_nothing_raised("Could not make file object") { dirobj = Puppet.type(:file).create( :path => dir, :recurse => true, :check => %w{mode owner group} ) } - mk_configuration dirobj + config = mk_configuration dirobj assert_nothing_raised { dirobj.eval_generate } assert_nothing_raised { - file = dirobj.class[path] + file = config.resource(:file, path) } assert(file, "Could not retrieve file object") assert_equal("/%s" % file.ref, file.path) end def test_autorequire basedir = tempfile() subfile = File.join(basedir, "subfile") - baseobj = Puppet.type(:file).create( + config = Puppet::Node::Configuration.new + + baseobj = config.create_resource(:file, :name => basedir, :ensure => "directory" ) - subobj = Puppet.type(:file).create( + subobj = config.create_resource(:file, :name => subfile, :ensure => "file" ) edge = nil assert_nothing_raised do edge = subobj.autorequire.shift end assert_equal(baseobj, edge.source, "file did not require its parent dir") assert_equal(subobj, edge.target, "file did not require its parent dir") end def test_content file = tempfile() str = "This is some content" obj = nil assert_nothing_raised { obj = Puppet.type(:file).create( :name => file, :content => str ) } assert(!obj.insync?(obj.retrieve), "Object is incorrectly in sync") assert_events([:file_created], obj) currentvalues = obj.retrieve assert(obj.insync?(currentvalues), "Object is not in sync") text = File.read(file) assert_equal(str, text, "Content did not copy correctly") newstr = "Another string, yo" obj[:content] = newstr assert(!obj.insync?(obj.retrieve), "Object is incorrectly in sync") assert_events([:file_changed], obj) text = File.read(file) assert_equal(newstr, text, "Content did not copy correctly") currentvalues = obj.retrieve assert(obj.insync?(currentvalues), "Object is not in sync") end # Unfortunately, I know this fails def disabled_test_recursivemkdir path = tempfile() subpath = File.join(path, "this", "is", "a", "dir") file = nil assert_nothing_raised { file = Puppet.type(:file).create( :name => subpath, :ensure => "directory", :recurse => true ) } comp = mk_configuration("yay", file) comp.finalize assert_apply(comp) #assert_events([:directory_created], comp) assert(FileTest.directory?(subpath), "Did not create directory") end # Make sure that content updates the checksum on the same run def test_checksumchange_for_content dest = tempfile() File.open(dest, "w") { |f| f.puts "yayness" } file = nil assert_nothing_raised { file = Puppet.type(:file).create( :name => dest, :checksum => "md5", :content => "This is some content" ) } file.retrieve assert_events([:file_changed], file) file.retrieve assert_events([], file) end # Make sure that content updates the checksum on the same run def test_checksumchange_for_ensure dest = tempfile() file = nil assert_nothing_raised { file = Puppet.type(:file).create( :name => dest, :checksum => "md5", :ensure => "file" ) } file.retrieve assert_events([:file_created], file) file.retrieve assert_events([], file) end # Make sure that content gets used before ensure def test_contentbeatsensure dest = tempfile() file = nil assert_nothing_raised { file = Puppet.type(:file).create( :name => dest, :ensure => "file", :content => "this is some content, yo" ) } currentvalues = file.retrieve assert_events([:file_created], file) file.retrieve assert_events([], file) assert_events([], file) end # Make sure that content gets used before ensure def test_deletion_beats_source dest = tempfile() source = tempfile() File.open(source, "w") { |f| f.puts "yay" } file = nil assert_nothing_raised { file = Puppet.type(:file).create( :name => dest, :ensure => :absent, :source => source ) } file.retrieve assert_events([], file) assert(! FileTest.exists?(dest), "file was copied during deletion") # Now create the dest, and make sure it gets deleted File.open(dest, "w") { |f| f.puts "boo" } assert_events([:file_removed], file) assert(! FileTest.exists?(dest), "file was not deleted during deletion") end def test_nameandpath path = tempfile() file = nil assert_nothing_raised { file = Puppet.type(:file).create( :title => "fileness", :path => path, :content => "this is some content" ) } assert_apply(file) assert(FileTest.exists?(path)) end # Make sure that a missing group isn't fatal at object instantiation time. def test_missinggroup file = nil assert_nothing_raised { file = Puppet.type(:file).create( :path => tempfile(), :group => "fakegroup" ) } assert(file.property(:group), "Group property failed") end def test_modecreation path = tempfile() file = Puppet.type(:file).create( :path => path, :ensure => "file", :mode => "0777" ) assert_equal(0777, file.should(:mode), "Mode did not get set correctly") assert_apply(file) assert_equal(0777, File.stat(path).mode & 007777, "file mode is incorrect") File.unlink(path) file[:ensure] = "directory" assert_apply(file) assert_equal(0777, File.stat(path).mode & 007777, "directory mode is incorrect") end def test_followlinks File.umask(0022) basedir = tempfile() Dir.mkdir(basedir) file = File.join(basedir, "file") link = File.join(basedir, "link") File.open(file, "w", 0644) { |f| f.puts "yayness"; f.flush } File.symlink(file, link) obj = nil assert_nothing_raised { obj = Puppet.type(:file).create( :path => link, :mode => "755" ) } obj.retrieve assert_events([], obj) # Assert that we default to not following links assert_equal("%o" % 0644, "%o" % (File.stat(file).mode & 007777)) # Assert that we can manage the link directly, but modes still don't change obj[:links] = :manage assert_events([], obj) assert_equal("%o" % 0644, "%o" % (File.stat(file).mode & 007777)) obj[:links] = :follow assert_events([:file_changed], obj) assert_equal("%o" % 0755, "%o" % (File.stat(file).mode & 007777)) # Now verify that content and checksum don't update, either obj.delete(:mode) obj[:checksum] = "md5" obj[:links] = :ignore assert_events([], obj) File.open(file, "w") { |f| f.puts "more text" } assert_events([], obj) obj[:links] = :follow assert_events([], obj) File.open(file, "w") { |f| f.puts "even more text" } assert_events([:file_changed], obj) obj.delete(:checksum) obj[:content] = "this is some content" obj[:links] = :ignore assert_events([], obj) File.open(file, "w") { |f| f.puts "more text" } assert_events([], obj) obj[:links] = :follow assert_events([:file_changed], obj) end # If both 'ensure' and 'content' are used, make sure that all of the other # properties are handled correctly. def test_contentwithmode path = tempfile() file = nil assert_nothing_raised { file = Puppet.type(:file).create( :path => path, :ensure => "file", :content => "some text\n", :mode => 0755 ) } assert_apply(file) assert_equal("%o" % 0755, "%o" % (File.stat(path).mode & 007777)) end def test_backupmodes File.umask(0022) file = tempfile() newfile = tempfile() + config = mk_configuration + File.open(file, "w", 0411) { |f| f.puts "yayness" } - obj = nil - assert_nothing_raised { - obj = Puppet::Type.type(:file).create( - :path => file, :content => "rahness\n", :backup => ".puppet-bak" - ) - } + resource = config.create_resource(:file, + :path => file, :content => "rahness\n", :backup => ".puppet-bak" + ) - assert_apply(obj) + assert_apply(config) - backupfile = file + obj[:backup] + backupfile = file + resource[:backup] @@tmpfiles << backupfile assert(FileTest.exists?(backupfile), "Backup file %s does not exist" % backupfile) assert_equal(0411, filemode(backupfile), "File mode is wrong for backupfile") bucket = "bucket" bpath = tempfile() Dir.mkdir(bpath) - Puppet::Type.type(:filebucket).create( + config.create_resource(:filebucket, :title => bucket, :path => bpath ) - obj[:backup] = bucket - obj[:content] = "New content" - assert_apply(obj) + resource[:backup] = bucket + resource[:content] = "New content" + + resource.finish + assert_apply(config) md5 = "18cc17fa3047fcc691fdf49c0a7f539a" dir, file, pathfile = Puppet::Network::Handler.filebucket.paths(bpath, md5) assert_equal(0440, filemode(file)) end def test_largefilechanges source = tempfile() dest = tempfile() # Now make a large file File.open(source, "w") { |f| 500.times { |i| f.puts "line %s" % i } } obj = Puppet::Type.type(:file).create( :title => dest, :source => source ) assert_events([:file_created], obj) File.open(source, File::APPEND|File::WRONLY) { |f| f.puts "another line" } assert_events([:file_changed], obj) # Now modify the dest file File.open(dest, File::APPEND|File::WRONLY) { |f| f.puts "one more line" } assert_events([:file_changed, :file_changed], obj) end def test_replacefilewithlink path = tempfile() link = tempfile() File.open(path, "w") { |f| f.puts "yay" } File.open(link, "w") { |f| f.puts "a file" } file = nil assert_nothing_raised { file = Puppet.type(:file).create( :ensure => path, :path => link ) } assert_events([:link_created], file) assert(FileTest.symlink?(link), "Link was not created") assert_equal(path, File.readlink(link), "Link was created incorrectly") end def test_file_with_spaces dir = tempfile() Dir.mkdir(dir) source = File.join(dir, "file spaces") dest = File.join(dir, "another space") File.open(source, "w") { |f| f.puts :yay } obj = Puppet::Type.type(:file).create( :path => dest, :source => source ) assert(obj, "Did not create file") assert_apply(obj) assert(FileTest.exists?(dest), "File did not get created") end def test_present_matches_anything path = tempfile() file = Puppet::Type.newfile(:path => path, :ensure => :present) currentvalues = file.retrieve assert(! file.insync?(currentvalues), "File incorrectly in sync") # Now make a file File.open(path, "w") { |f| f.puts "yay" } currentvalues = file.retrieve assert(file.insync?(currentvalues), "File not in sync") # Now make a directory File.unlink(path) Dir.mkdir(path) currentvalues = file.retrieve assert(file.insync?(currentvalues), "Directory not considered 'present'") Dir.rmdir(path) # Now make a link file[:links] = :manage otherfile = tempfile() File.symlink(otherfile, path) currentvalues = file.retrieve assert(file.insync?(currentvalues), "Symlink not considered 'present'") File.unlink(path) # Now set some content, and make sure it works file[:content] = "yayness" assert_apply(file) assert_equal("yayness", File.read(path), "Content did not get set correctly") end # Make sure unmanaged files are purged. def test_purge sourcedir = tempfile() destdir = tempfile() Dir.mkdir(sourcedir) Dir.mkdir(destdir) sourcefile = File.join(sourcedir, "sourcefile") dsourcefile = File.join(destdir, "sourcefile") localfile = File.join(destdir, "localfile") purgee = File.join(destdir, "to_be_purged") File.open(sourcefile, "w") { |f| f.puts "funtest" } # this file should get removed File.open(purgee, "w") { |f| f.puts "footest" } - lfobj = Puppet::Type.newfile( + config = mk_configuration + + lfobj = config.create_resource(:file, :title => "localfile", :path => localfile, :content => "rahtest", :backup => false ) - destobj = Puppet::Type.newfile(:title => "destdir", :path => destdir, + destobj = config.create_resource(:file, :title => "destdir", :path => destdir, :source => sourcedir, :backup => false, :recurse => true) - config = mk_configuration(lfobj, destobj) config.apply assert(FileTest.exists?(dsourcefile), "File did not get copied") assert(FileTest.exists?(localfile), "File did not get created") assert(FileTest.exists?(purgee), "File got prematurely purged") assert_nothing_raised { destobj[:purge] = true } config.apply assert(FileTest.exists?(localfile), "Local file got purged") assert(FileTest.exists?(dsourcefile), "Source file got purged") assert(! FileTest.exists?(purgee), "File did not get purged") end # Testing #274. Make sure target can be used without 'ensure'. def test_target_without_ensure source = tempfile() dest = tempfile() File.open(source, "w") { |f| f.puts "funtest" } obj = nil assert_nothing_raised { obj = Puppet::Type.newfile(:path => dest, :target => source) } assert_apply(obj) end def test_autorequire_owner_and_group file = tempfile() comp = nil user = nil group =nil home = nil ogroup = nil assert_nothing_raised { user = Puppet.type(:user).create( :name => "pptestu", :home => file, :gid => "pptestg" ) home = Puppet.type(:file).create( :path => file, :owner => "pptestu", :group => "pptestg", :ensure => "directory" ) group = Puppet.type(:group).create( :name => "pptestg" ) comp = mk_configuration(user, group, home) } # Now make sure we get a relationship for each of these rels = nil assert_nothing_raised { rels = home.autorequire } assert(rels.detect { |e| e.source == user }, "owner was not autorequired") assert(rels.detect { |e| e.source == group }, "group was not autorequired") end # Testing #309 -- //my/file => /my/file def test_slash_deduplication ["/my/////file/for//testing", "//my/file/for/testing///", "/my/file/for/testing"].each do |path| file = nil assert_nothing_raised do file = Puppet::Type.newfile(:path => path) end assert_equal("/my/file/for/testing", file.title) assert_equal(file, Puppet::Type.type(:file)["/my/file/for/testing"]) Puppet::Type.type(:file).clear end end # Testing #304 def test_links_to_directories link = tempfile() file = tempfile() dir = tempfile() Dir.mkdir(dir) bucket = Puppet::Type.newfilebucket :name => "main" File.symlink(dir, link) File.open(file, "w") { |f| f.puts "" } assert_equal(dir, File.readlink(link)) obj = Puppet::Type.newfile :path => link, :ensure => :link, :target => file, :recurse => false, :backup => "main" assert_apply(obj) assert_equal(file, File.readlink(link)) end # Testing #303 def test_nobackups_with_links link = tempfile() new = tempfile() File.open(link, "w") { |f| f.puts "old" } File.open(new, "w") { |f| f.puts "new" } obj = Puppet::Type.newfile :path => link, :ensure => :link, :target => new, :recurse => true, :backup => false assert_nothing_raised do obj.handlebackup end bfile = [link, "puppet-bak"].join(".") assert(! FileTest.exists?(bfile), "Backed up when told not to") assert_apply(obj) assert(! FileTest.exists?(bfile), "Backed up when told not to") end # Make sure we consistently handle backups for all cases. def test_ensure_with_backups # We've got three file types, so make sure we can replace any type # with the other type and that backups are done correctly. types = [:file, :directory, :link] dir = tempfile() path = File.join(dir, "test") linkdest = tempfile() creators = { :file => proc { File.open(path, "w") { |f| f.puts "initial" } }, :directory => proc { Dir.mkdir(path) }, :link => proc { File.symlink(linkdest, path) } } - bucket = Puppet::Type.newfilebucket :name => "main", :path => tempfile() + config = mk_configuration + + bucket = config.create_resource :filebucket, :name => "main", :path => tempfile() - obj = Puppet::Type.newfile :path => path, :force => true, - :links => :manage + obj = config.create_resource :file, :path => path, :force => true, :links => :manage Puppet[:trace] = true ["main", false].each do |backup| obj[:backup] = backup obj.finish types.each do |should| types.each do |is| # It makes no sense to replace a directory with a directory # next if should == :directory and is == :directory Dir.mkdir(dir) # Make the thing creators[is].call obj[:ensure] = should if should == :link obj[:target] = linkdest else if obj.property(:target) obj.delete(:target) end end # First try just removing the initial data assert_nothing_raised do obj.remove_existing(should) end unless is == should # Make sure the original is gone assert(! FileTest.exists?(obj[:path]), "remove_existing did not work: " + "did not remove %s with %s" % [is, should]) end FileUtils.rmtree(obj[:path]) # Now make it again creators[is].call property = obj.property(:ensure) currentvalue = property.retrieve unless property.insync?(currentvalue) assert_nothing_raised do property.sync end end FileUtils.rmtree(dir) end end end end if Process.uid == 0 # Testing #364. def test_writing_in_directories_with_no_write_access # Make a directory that our user does not have access to dir = tempfile() Dir.mkdir(dir) # Get a fake user user = nonrootuser # and group group = nonrootgroup # First try putting a file in there path = File.join(dir, "file") file = Puppet::Type.newfile :path => path, :owner => user.name, :group => group.name, :content => "testing" # Make sure we can create it assert_apply(file) assert(FileTest.exists?(path), "File did not get created") # And that it's owned correctly assert_equal(user.uid, File.stat(path).uid, "File has the wrong owner") assert_equal(group.gid, File.stat(path).gid, "File has the wrong group") assert_equal("testing", File.read(path), "file has the wrong content") # Now make a dir subpath = File.join(dir, "subdir") subdir = Puppet::Type.newfile :path => subpath, :owner => user.name, :group => group.name, :ensure => :directory # Make sure we can create it assert_apply(subdir) assert(FileTest.directory?(subpath), "File did not get created") # And that it's owned correctly assert_equal(user.uid, File.stat(subpath).uid, "File has the wrong owner") assert_equal(group.gid, File.stat(subpath).gid, "File has the wrong group") assert_equal("testing", File.read(path), "file has the wrong content") end end # #366 def test_replace_aliases file = Puppet::Type.newfile :path => tempfile() file[:replace] = :yes assert_equal(:true, file[:replace], ":replace did not alias :true to :yes") file[:replace] = :no assert_equal(:false, file[:replace], ":replace did not alias :false to :no") end # #365 -- make sure generated files also use filebuckets. def test_recursive_filebuckets source = tempfile() dest = tempfile() s1 = File.join(source, "1") sdir = File.join(source, "dir") s2 = File.join(sdir, "2") Dir.mkdir(source) Dir.mkdir(sdir) [s1, s2].each { |file| File.open(file, "w") { |f| f.puts "yay: %s" % File.basename(file) } } sums = {} [s1, s2].each do |f| sums[File.basename(f)] = Digest::MD5.hexdigest(File.read(f)) end dfiles = [File.join(dest, "1"), File.join(dest, "dir", "2")] bpath = tempfile - bucket = Puppet::Type.type(:filebucket).create :name => "rtest", :path => bpath + + config = mk_configuration + bucket = config.create_resource :filebucket, :name => "rtest", :path => bpath dipper = bucket.bucket dipper = Puppet::Network::Handler.filebucket.new( :Path => bpath ) assert(dipper, "did not receive bucket client") - file = Puppet::Type.newfile :path => dest, :source => source, :recurse => true, :backup => "rtest" + file = config.create_resource :file, :path => dest, :source => source, :recurse => true, :backup => "rtest" - assert_apply(file) + config.apply dfiles.each do |f| assert(FileTest.exists?(f), "destfile %s was not created" % f) end # Now modify the source files to make sure things get backed up correctly [s1, s2].each { |sf| File.open(sf, "w") { |f| f.puts "boo: %s" % File.basename(sf) } } - assert_apply(file) + config.apply dfiles.each do |f| assert_equal("boo: %s\n" % File.basename(f), File.read(f), "file was not copied correctly") end # Make sure we didn't just copy the files over to backup locations dfiles.each do |f| assert(! FileTest.exists?(f + "rtest"), "file %s was copied for backup instead of bucketed" % File.basename(f)) end # Now make sure we can get the source sums from the bucket sums.each do |f, sum| result = nil assert_nothing_raised do result = dipper.getfile(sum) end assert(result, "file %s was not backed to filebucket" % f) assert_equal("yay: %s\n" % f, result, "file backup was not correct") end end def test_backup path = tempfile() - file = Puppet::Type.newfile :path => path, :content => "yay" + config = Puppet::Node::Configuration.new + file = config.create_resource :file, :path => path, :content => "yay" [false, :false, "false"].each do |val| assert_nothing_raised do file[:backup] = val end assert_equal(false, file[:backup], "%s did not translate" % val.inspect) end [true, :true, "true", ".puppet-bak"].each do |val| assert_nothing_raised do file[:backup] = val end assert_equal(".puppet-bak", file[:backup], "%s did not translate" % val.inspect) end # Now try a non-bucket string assert_nothing_raised do file[:backup] = ".bak" end assert_equal(".bak", file[:backup], ".bak did not translate") # Now try a non-existent bucket assert_nothing_raised do file[:backup] = "main" end assert_equal("main", file[:backup], "bucket name was not retained") assert_equal("main", file.bucket, "file's bucket was not set") # And then an existing bucket - obj = Puppet::Type.type(:filebucket).create :name => "testing" + obj = config.create_resource :filebucket, :name => "testing" bucket = obj.bucket assert_nothing_raised do file[:backup] = "testing" end assert_equal("testing", file[:backup], "backup value was reset") assert_equal(obj.bucket, file.bucket, "file's bucket was not set") end def test_pathbuilder dir = tempfile() Dir.mkdir(dir) file = File.join(dir, "file") File.open(file, "w") { |f| f.puts "" } obj = Puppet::Type.newfile :path => dir, :recurse => true, :mode => 0755 - mk_configuration obj + config = mk_configuration obj assert_equal("/%s" % obj.ref, obj.path) list = obj.eval_generate - fileobj = obj.class[file] + fileobj = config.resource(:file, file) assert(fileobj, "did not generate file object") assert_equal("/%s" % fileobj.ref, fileobj.path, "did not generate correct subfile path") end # Testing #403 def test_removal_with_content_set path = tempfile() File.open(path, "w") { |f| f.puts "yay" } file = Puppet::Type.newfile(:name => path, :ensure => :absent, :content => "foo") assert_apply(file) assert(! FileTest.exists?(path), "File was not removed") end # Testing #434 def test_stripping_extra_slashes_during_lookup - file = Puppet::Type.newfile(:path => "/one/two") + config = mk_configuration + file = config.create_resource(:file, :path => "/one/two") %w{/one/two/ /one/two /one//two //one//two//}.each do |path| assert(Puppet::Type.type(:file)[path], "could not look up file via path %s" % path) end end # Testing #438 def test_creating_properties_conflict file = tempfile() first = tempfile() second = tempfile() params = [:content, :source, :target] params.each do |param| assert_nothing_raised("%s conflicted with ensure" % [param]) do Puppet::Type.newfile(:path => file, param => first, :ensure => :file) end Puppet::Type.type(:file).clear params.each do |other| next if other == param assert_raise(Puppet::Error, "%s and %s did not conflict" % [param, other]) do Puppet::Type.newfile(:path => file, other => first, param => second) end end end end # Testing #508 if Process.uid == 0 def test_files_replace_with_right_attrs source = tempfile() File.open(source, "w") { |f| f.puts "some text" } File.chmod(0755, source) user = nonrootuser group = nonrootgroup path = tempfile() good = {:uid => user.uid, :gid => group.gid, :mode => 0640} run = Proc.new do |obj, msg| assert_apply(obj) stat = File.stat(obj[:path]) good.each do |should, sval| if should == :mode current = filemode(obj[:path]) else current = stat.send(should) end assert_equal(sval, current, "Attr %s was not correct %s" % [should, msg]) end end file = Puppet::Type.newfile(:path => path, :owner => user.name, :group => group.name, :mode => 0640, :backup => false) {:source => source, :content => "some content"}.each do |attr, value| file[attr] = value # First create the file run.call(file, "upon creation with %s" % attr) # Now change something so that we replace the file case attr when :source: File.open(source, "w") { |f| f.puts "some different text" } when :content: file[:content] = "something completely different" else raise "invalid attr %s" % attr end # Run it again run.call(file, "after modification with %s" % attr) # Now remove the file and the attr file.delete(attr) File.unlink(path) end end end # #505 def test_numeric_recurse dir = tempfile() subdir = File.join(dir, "subdir") other = File.join(subdir, "deeper") file = File.join(other, "file") [dir, subdir, other].each { |d| Dir.mkdir(d) } File.open(file, "w") { |f| f.puts "yay" } File.chmod(0644, file) obj = Puppet::Type.newfile(:path => dir, :mode => 0750, :recurse => "2") config = mk_configuration(obj) children = nil assert_nothing_raised("Failure when recursing") do children = obj.eval_generate end - assert(obj.class[subdir], "did not create subdir object") + assert(config.resource(:file, subdir), "did not create subdir object") children.each do |c| assert_nothing_raised("Failure when recursing on %s" % c) do c.configuration = config others = c.eval_generate end end - oobj = obj.class[other] + oobj = config.resource(:file, other) assert(oobj, "did not create other object") assert_nothing_raised do assert_nil(oobj.eval_generate, "recursed too far") end end # Make sure we default to the "puppet" filebucket, rather than a string def test_backup_defaults_to_bucket path = tempfile - file = Puppet::Type.newfile(:path => path, :content => 'some content') + config = Puppet::Node::Configuration.new + config.add_resource(Puppet::Type.type(:filebucket).create_default_resources) + file = config.create_resource :file, :path => path, :content => 'some content' file.finish - assert_instance_of(Puppet::Network::Client::Dipper, file.bucket, + assert_instance_of(Puppet::Network::Client.dipper, file.bucket, "did not default to a filebucket for backups") - assert_equal(Puppet::Type.type(:filebucket)["puppet"].bucket, file.bucket, + assert_equal(config.resource(:filebucket, "puppet").bucket, file.bucket, "did not default to the 'puppet' filebucket") end # #515 - make sure 'ensure' other than "link" is deleted during recursion def test_ensure_deleted_during_recursion dir = tempfile() Dir.mkdir(dir) file = File.join(dir, "file") File.open(file, "w") { |f| f.puts "asdfasdf" } obj = Puppet::Type.newfile(:path => dir, :ensure => :directory, :recurse => true) config = mk_configuration(obj) children = nil assert_nothing_raised do children = obj.eval_generate end - fobj = obj.class[file] + fobj = config.resource(:file,file) assert(fobj, "did not create file object") assert(fobj.should(:ensure) != :directory, "ensure was passed to child") end # #567 def test_missing_files_are_in_sync file = tempfile obj = Puppet::Type.newfile(:path => file, :mode => 0755) changes = obj.evaluate assert(changes.empty?, "Missing file with no ensure resulted in changes") end end diff --git a/test/ral/types/file/target.rb b/test/ral/types/file/target.rb index f5cbe4a45..cc53e2645 100755 --- a/test/ral/types/file/target.rb +++ b/test/ral/types/file/target.rb @@ -1,362 +1,364 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../../lib/puppettest' require 'puppettest' +require 'puppettest/support/assertions' require 'fileutils' class TestFileTarget < Test::Unit::TestCase + include PuppetTest include PuppetTest::FileTesting def setup super @file = Puppet::Type.type(:file) end # Make sure we can create symlinks def test_symlinks path = tempfile() link = tempfile() File.open(path, "w") { |f| f.puts "yay" } file = nil assert_nothing_raised { file = Puppet.type(:file).create( :title => "somethingelse", :ensure => path, :path => link ) } assert_events([:link_created], file) assert(FileTest.symlink?(link), "Link was not created") assert_equal(path, File.readlink(link), "Link was created incorrectly") # Make sure running it again works assert_events([], file) assert_events([], file) assert_events([], file) end def test_linkrecurse dest = tempfile() link = @file.create :path => tempfile(), :recurse => true, :ensure => dest - mk_configuration link + config = mk_configuration link ret = nil # Start with nothing, just to make sure we get nothing back assert_nothing_raised { ret = link.linkrecurse(true) } assert_nil(ret, "got a return when the dest doesn't exist") # then with a directory with only one file Dir.mkdir(dest) one = File.join(dest, "one") File.open(one, "w") { |f| f.puts "" } link[:ensure] = dest assert_nothing_raised { ret = link.linkrecurse(true) } assert_equal(:directory, link.should(:ensure), "ensure was not set to directory") assert_equal([File.join(link.title, "one")], ret.collect { |f| f.title }, "Did not get linked file") - oneobj = @file[File.join(link.title, "one")] + oneobj = config.resource(:file, File.join(link.title, "one")) + assert_equal(one, oneobj.should(:target), "target was not set correctly") oneobj.remove File.unlink(one) # Then make sure we get multiple files returns = [] 5.times do |i| path = File.join(dest, i.to_s) returns << File.join(link.title, i.to_s) File.open(path, "w") { |f| f.puts "" } end assert_nothing_raised { ret = link.linkrecurse(true) } assert_equal(returns.sort, ret.collect { |f| f.title }.sort, "Did not get links back") returns.each do |path| - obj = @file[path] + obj = config.resource(:file, path) assert(path, "did not get obj for %s" % path) sdest = File.join(dest, File.basename(path)) assert_equal(sdest, obj.should(:target), "target was not set correctly for %s" % path) end end def test_simplerecursivelinking source = tempfile() path = tempfile() subdir = File.join(source, "subdir") file = File.join(subdir, "file") system("mkdir -p %s" % subdir) system("touch %s" % file) link = nil - assert_nothing_raised { - link = Puppet.type(:file).create( - :ensure => source, - :path => path, - :recurse => true - ) - } + config = mk_configuration + link = config.create_resource(:file, + :ensure => source, + :path => path, + :recurse => true + ) - assert_apply(link) + config.apply sublink = File.join(path, "subdir") linkpath = File.join(sublink, "file") assert(File.directory?(path), "dest is not a dir") assert(File.directory?(sublink), "subdest is not a dir") assert(File.symlink?(linkpath), "path is not a link") assert_equal(file, File.readlink(linkpath)) - assert_nil(@file[sublink], "objects were not removed") + assert_nil(config.resource(:file, sublink), "objects were not removed") assert_equal([], link.evaluate, "Link is not in sync") end def test_recursivelinking source = tempfile() dest = tempfile() files = [] dirs = [] # Make a bunch of files and dirs Dir.mkdir(source) Dir.chdir(source) do system("mkdir -p %s" % "some/path/of/dirs") system("mkdir -p %s" % "other/path/of/dirs") system("touch %s" % "file") system("touch %s" % "other/file") system("touch %s" % "some/path/of/file") system("touch %s" % "some/path/of/dirs/file") system("touch %s" % "other/path/of/file") files = %x{find . -type f}.chomp.split(/\n/) dirs = %x{find . -type d}.chomp.split(/\n/).reject{|d| d =~ /^\.+$/ } end link = nil assert_nothing_raised { link = Puppet.type(:file).create( :ensure => source, :path => dest, :recurse => true ) } assert_apply(link) files.each do |f| f.sub!(/^\.#{File::SEPARATOR}/, '') path = File.join(dest, f) assert(FileTest.exists?(path), "Link %s was not created" % path) assert(FileTest.symlink?(path), "%s is not a link" % f) target = File.readlink(path) assert_equal(File.join(source, f), target) end dirs.each do |d| d.sub!(/^\.#{File::SEPARATOR}/, '') path = File.join(dest, d) assert(FileTest.exists?(path), "Dir %s was not created" % path) assert(FileTest.directory?(path), "%s is not a directory" % d) end end def test_localrelativelinks dir = tempfile() Dir.mkdir(dir) source = File.join(dir, "source") File.open(source, "w") { |f| f.puts "yay" } dest = File.join(dir, "link") link = nil assert_nothing_raised { link = Puppet.type(:file).create( :path => dest, :ensure => "source" ) } assert_events([:link_created], link) assert(FileTest.symlink?(dest), "Did not create link") assert_equal("source", File.readlink(dest)) assert_equal("yay\n", File.read(dest)) end def test_recursivelinkingmissingtarget source = tempfile() dest = tempfile() objects = [] objects << Puppet.type(:exec).create( :command => "mkdir %s; touch %s/file" % [source, source], :title => "yay", :path => ENV["PATH"] ) objects << Puppet.type(:file).create( :ensure => source, :path => dest, :recurse => true, :require => objects[0] ) assert_apply(*objects) link = File.join(dest, "file") assert(FileTest.symlink?(link), "Did not make link") assert_equal(File.join(source, "file"), File.readlink(link)) end def test_insync? source = tempfile() dest = tempfile() obj = @file.create(:path => source, :target => dest) prop = obj.send(:property, :target) prop.send(:instance_variable_set, "@should", [:nochange]) assert(prop.insync?(prop.retrieve), "Property not in sync with should == :nochange") prop = obj.send(:property, :target) prop.send(:instance_variable_set, "@should", [:notlink]) assert(prop.insync?(prop.retrieve), "Property not in sync with should == :nochange") # Lastly, make sure that we don't try to do anything when we're # recursing, since 'ensure' does the work. obj[:recurse] = true prop.should = dest assert(prop.insync?(prop.retrieve), "Still out of sync during recursion") end def test_replacedirwithlink Puppet[:trace] = false path = tempfile() link = tempfile() File.open(path, "w") { |f| f.puts "yay" } Dir.mkdir(link) File.open(File.join(link, "yay"), "w") do |f| f.puts "boo" end file = nil assert_nothing_raised { file = Puppet.type(:file).create( :ensure => path, :path => link, :backup => false ) } # First run through without :force assert_events([], file) assert(FileTest.directory?(link), "Link replaced dir without force") assert_nothing_raised { file[:force] = true } assert_events([:link_created], file) assert(FileTest.symlink?(link), "Link was not created") assert_equal(path, File.readlink(link), "Link was created incorrectly") end def test_replace_links_with_files base = tempfile() Dir.mkdir(base) file = File.join(base, "file") link = File.join(base, "link") File.open(file, "w") { |f| f.puts "yayness" } File.symlink(file, link) obj = Puppet::Type.type(:file).create( :path => link, :ensure => "file" ) assert_apply(obj) assert_equal("yayness\n", File.read(file), "Original file got changed") assert_equal("file", File.lstat(link).ftype, "File is still a link") end def test_no_erase_linkedto_files base = tempfile() Dir.mkdir(base) dirs = {} %w{other source target}.each do |d| dirs[d] = File.join(base, d) Dir.mkdir(dirs[d]) end file = File.join(dirs["other"], "file") sourcefile = File.join(dirs["source"], "sourcefile") link = File.join(dirs["target"], "sourcefile") File.open(file, "w") { |f| f.puts "other" } File.open(sourcefile, "w") { |f| f.puts "source" } File.symlink(file, link) obj = Puppet::Type.type(:file).create( :path => dirs["target"], :ensure => :file, :source => dirs["source"], :recurse => true ) config = mk_configuration obj config.apply newfile = File.join(dirs["target"], "sourcefile") assert(File.directory?(dirs["target"]), "Dir did not get created") assert(File.file?(newfile), "File did not get copied") assert_equal(File.read(sourcefile), File.read(newfile), "File did not get copied correctly.") assert_equal("other\n", File.read(file), "Original file got changed") assert_equal("file", File.lstat(link).ftype, "File is still a link") end def test_replace_links dest = tempfile() otherdest = tempfile() link = tempfile() File.open(dest, "w") { |f| f.puts "boo" } File.open(otherdest, "w") { |f| f.puts "yay" } obj = Puppet::Type.type(:file).create( :path => link, :ensure => otherdest ) assert_apply(obj) assert_equal(otherdest, File.readlink(link), "Link did not get created") obj[:ensure] = dest assert_apply(obj) assert_equal(dest, File.readlink(link), "Link did not get changed") end end diff --git a/test/ral/types/filebucket.rb b/test/ral/types/filebucket.rb index ecfe0e167..3681febf4 100755 --- a/test/ral/types/filebucket.rb +++ b/test/ral/types/filebucket.rb @@ -1,155 +1,153 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../lib/puppettest' require 'puppettest' +require 'puppettest/support/utils' require 'fileutils' class TestFileBucket < Test::Unit::TestCase include PuppetTest::FileTesting + include PuppetTest::Support::Utils # hmmm # this is complicated, because we store references to the created # objects in a central store def mkfile(hash) file = nil assert_nothing_raised { file = Puppet.type(:file).create(hash) } return file end def mkbucket(name,path) bucket = nil assert_nothing_raised { bucket = Puppet.type(:filebucket).create( :name => name, :path => path ) } @@tmpfiles.push path return bucket end def mktestfile # because luke's home directory is on nfs, it can't be used for testing # as root tmpfile = tempfile() File.open(tmpfile, "w") { |f| f.puts rand(100) } @@tmpfiles.push tmpfile mkfile(:name => tmpfile) end def setup super begin initstorage rescue system("rm -rf %s" % Puppet[:statefile]) end end def initstorage Puppet::Util::Storage.init Puppet::Util::Storage.load end def clearstorage Puppet::Util::Storage.store Puppet::Util::Storage.clear end def test_simplebucket name = "yayness" bucketpath = tempfile() - mkbucket(name, bucketpath) + bucket_resource = mkbucket(name, bucketpath) - bucket = nil - assert_nothing_raised { - bucket = Puppet.type(:filebucket).bucket(name) - } - - assert_instance_of(Puppet::Network::Client.dipper, bucket) + bucket = bucket_resource.bucket md5 = nil newpath = tempfile() @@tmpfiles << newpath system("cp /etc/passwd %s" % newpath) assert_nothing_raised { md5 = bucket.backup(newpath) } assert(md5) dir, file, pathfile = Puppet::Network::Handler.filebucket.paths(bucketpath, md5) assert(FileTest.directory?(dir), "MD5 directory does not exist") newmd5 = nil # Just in case the file isn't writable File.chmod(0644, newpath) File.open(newpath, "w") { |f| f.puts ";lkjasdf;lkjasdflkjwerlkj134lkj" } assert_nothing_raised { newmd5 = bucket.backup(newpath) } assert(md5 != newmd5) assert_nothing_raised { bucket.restore(newpath, md5) } File.open(newpath) { |f| newmd5 = Digest::MD5.hexdigest(f.read) } assert_equal(md5, newmd5) end def test_fileswithbuckets + Facter.stubs(:to_hash).returns({}) name = "yayness" - mkbucket(name, tempfile()) + resource = mkbucket(name, tempfile()) - bucket = nil - assert_nothing_raised { - bucket = Puppet.type(:filebucket).bucket(name) - } + config = mk_configuration resource + + bucket = resource.bucket file = mktestfile() + config.add_resource(file) assert_nothing_raised { file[:backup] = name } opath = tempfile() @@tmpfiles << opath File.open(opath, "w") { |f| f.puts "yaytest" } origmd5 = File.open(file.name) { |f| newmd5 = Digest::MD5.hexdigest(f.read) } file[:source] = opath #assert_nothing_raised { # file[:backup] = true #} - assert_apply(file) + config.apply # so, we've now replaced the file with the opath file assert_equal( File.open(opath) { |f| newmd5 = Digest::MD5.hexdigest(f.read) }, File.open(file.name) { |f| newmd5 = Digest::MD5.hexdigest(f.read) } ) #File.chmod(0644, file.name) assert_nothing_raised { bucket.restore(file.name, origmd5) } assert_equal( origmd5, File.open(file.name) { |f| newmd5 = Digest::MD5.hexdigest(f.read) } ) end end diff --git a/test/ral/types/fileignoresource.rb b/test/ral/types/fileignoresource.rb index 5c7536453..501672ab7 100755 --- a/test/ral/types/fileignoresource.rb +++ b/test/ral/types/fileignoresource.rb @@ -1,243 +1,248 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../lib/puppettest' require 'puppettest' +require 'puppettest/support/utils' require 'cgi' require 'fileutils' class TestFileIgnoreSources < Test::Unit::TestCase include PuppetTest::FileTesting + include PuppetTest::Support::Utils def setup super begin initstorage rescue system("rm -rf %s" % Puppet[:statefile]) end end -#This is not needed unless using md5 (correct me if I'm wrong) + #This is not needed unless using md5 (correct me if I'm wrong) def initstorage Puppet::Util::Storage.init Puppet::Util::Storage.load end def clearstorage Puppet::Util::Storage.store Puppet::Util::Storage.clear end def test_ignore_simple_source + Facter.stubs(:to_hash).returns({}) #Temp directory to run tests in path = tempfile() @@tmpfiles.push path #source directory sourcedir = "sourcedir" sourcefile1 = "sourcefile1" sourcefile2 = "sourcefile2" frompath = File.join(path,sourcedir) FileUtils.mkdir_p frompath topath = File.join(path,"destdir") FileUtils.mkdir topath #initialize variables before block tofile = nil trans = nil #create source files File.open(File.join(frompath,sourcefile1), File::WRONLY|File::CREAT|File::APPEND) { |of| of.puts "yayness" } File.open(File.join(frompath,sourcefile2), File::WRONLY|File::CREAT|File::APPEND) { |of| of.puts "even yayer" } #makes Puppet file Object assert_nothing_raised { tofile = Puppet.type(:file).create( :name => topath, :source => frompath, :recurse => true, :ignore => "sourcefile2" ) } config = mk_configuration(tofile) config.apply #topath should exist as a directory with sourcedir as a directory #This file should exist assert(FileTest.exists?(File.join(topath,sourcefile1))) #This file should not assert(!(FileTest.exists?(File.join(topath,sourcefile2)))) Puppet::Type.allclear end def test_ignore_with_wildcard + Facter.stubs(:to_hash).returns({}) #Temp directory to run tests in path = tempfile() @@tmpfiles.push path #source directory sourcedir = "sourcedir" subdir = "subdir" subdir2 = "subdir2" sourcefile1 = "sourcefile1" sourcefile2 = "sourcefile2" frompath = File.join(path,sourcedir) FileUtils.mkdir_p frompath FileUtils.mkdir_p(File.join(frompath, subdir)) FileUtils.mkdir_p(File.join(frompath, subdir2)) dir = Dir.glob(File.join(path,"**/*")) topath = File.join(path,"destdir") FileUtils.mkdir topath #initialize variables before block tofile = nil trans = nil #create source files dir.each { |dir| File.open(File.join(dir,sourcefile1), File::WRONLY|File::CREAT|File::APPEND) { |of| of.puts "yayness" } File.open(File.join(dir,sourcefile2), File::WRONLY|File::CREAT|File::APPEND) { |of| of.puts "even yayer" } } #makes Puppet file Object assert_nothing_raised { tofile = Puppet.type(:file).create( :name => topath, :source => frompath, :recurse => true, :ignore => "*2" ) } config = mk_configuration(tofile) config.apply #topath should exist as a directory with sourcedir as a directory #This file should exist assert(FileTest.exists?(File.join(topath,sourcefile1))) assert(FileTest.exists?(File.join(topath,subdir))) assert(FileTest.exists?(File.join(File.join(topath,subdir),sourcefile1))) #This file should not assert(!(FileTest.exists?(File.join(topath,sourcefile2)))) assert(!(FileTest.exists?(File.join(topath,subdir2)))) assert(!(FileTest.exists?(File.join(File.join(topath,subdir),sourcefile2)))) Puppet::Type.allclear end def test_ignore_array + Facter.stubs(:to_hash).returns({}) #Temp directory to run tests in path = tempfile() @@tmpfiles.push path #source directory sourcedir = "sourcedir" subdir = "subdir" subdir2 = "subdir2" subdir3 = "anotherdir" sourcefile1 = "sourcefile1" sourcefile2 = "sourcefile2" frompath = File.join(path,sourcedir) FileUtils.mkdir_p frompath FileUtils.mkdir_p(File.join(frompath, subdir)) FileUtils.mkdir_p(File.join(frompath, subdir2)) FileUtils.mkdir_p(File.join(frompath, subdir3)) sourcedir = Dir.glob(File.join(path,"**/*")) topath = File.join(path,"destdir") FileUtils.mkdir topath #initialize variables before block tofile = nil trans = nil #create source files sourcedir.each { |dir| File.open(File.join(dir,sourcefile1), File::WRONLY|File::CREAT|File::APPEND) { |of| of.puts "yayness" } File.open(File.join(dir,sourcefile2), File::WRONLY|File::CREAT|File::APPEND) { |of| of.puts "even yayer" } } #makes Puppet file Object assert_nothing_raised { tofile = Puppet.type(:file).create( :name => topath, :source => frompath, :recurse => true, :ignore => ["*2", "an*"] # :ignore => ["*2", "an*", "nomatch"] ) } config = mk_configuration(tofile) config.apply #topath should exist as a directory with sourcedir as a directory # This file should exist # proper files in destination assert(FileTest.exists?(File.join(topath,sourcefile1)), "file1 not in destdir") assert(FileTest.exists?(File.join(topath,subdir)), "subdir1 not in destdir") assert(FileTest.exists?(File.join(File.join(topath,subdir),sourcefile1)), "file1 not in subdir") # proper files in source assert(FileTest.exists?(File.join(frompath,subdir)), "subdir not in source") assert(FileTest.exists?(File.join(frompath,subdir2)), "subdir2 not in source") assert(FileTest.exists?(File.join(frompath,subdir3)), "subdir3 not in source") # This file should not assert(!(FileTest.exists?(File.join(topath,sourcefile2))), "file2 in dest") assert(!(FileTest.exists?(File.join(topath,subdir2))), "subdir2 in dest") assert(!(FileTest.exists?(File.join(topath,subdir3))), "anotherdir in dest") assert(!(FileTest.exists?(File.join(File.join(topath,subdir),sourcefile2))), "file2 in dest/sub") Puppet::Type.allclear end end diff --git a/test/ral/types/filesources.rb b/test/ral/types/filesources.rb index 1f22fc9e0..bfbc185e3 100755 --- a/test/ral/types/filesources.rb +++ b/test/ral/types/filesources.rb @@ -1,1007 +1,1005 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../lib/puppettest' require 'puppettest' +require 'puppettest/support/assertions' require 'cgi' require 'fileutils' require 'mocha' class TestFileSources < Test::Unit::TestCase + include PuppetTest include PuppetTest::FileTesting def setup super if defined? @port @port += 1 else @port = 12345 end @file = Puppet::Type.type(:file) Puppet[:filetimeout] = -1 Puppet::Util::SUIDManager.stubs(:asuser).yields end def use_storage begin initstorage rescue system("rm -rf %s" % Puppet[:statefile]) end end def initstorage Puppet::Util::Storage.init Puppet::Util::Storage.load end # Make a simple recursive tree. def mk_sourcetree source = tempfile() sourcefile = File.join(source, "file") Dir.mkdir source File.open(sourcefile, "w") { |f| f.puts "yay" } dest = tempfile() destfile = File.join(dest, "file") return source, dest, sourcefile, destfile end def test_newchild path = tempfile() @@tmpfiles.push path FileUtils.mkdir_p path File.open(File.join(path,"childtest"), "w") { |of| of.puts "yayness" } file = nil comp = nil trans = nil assert_nothing_raised { file = Puppet.type(:file).create( :name => path ) } config = mk_configuration(file) child = nil assert_nothing_raised { child = file.newchild("childtest", true) } assert(child) assert_raise(Puppet::DevError) { file.newchild(File.join(path,"childtest"), true) } end def test_describe source = tempfile() dest = tempfile() file = Puppet::Type.newfile :path => dest, :source => source, :title => "copier" property = file.property(:source) # First try describing with a normal source result = nil assert_nothing_raised do result = property.describe(source) end assert_nil(result, "Got a result back when source is missing") # Now make a remote directory Dir.mkdir(source) assert_nothing_raised do result = property.describe(source) end assert_equal("directory", result[:type]) # And as a file Dir.rmdir(source) File.open(source, "w") { |f| f.puts "yay" } assert_nothing_raised do result = property.describe(source) end assert_equal("file", result[:type]) assert(result[:checksum], "did not get value for checksum") if Puppet::Util::SUIDManager.uid == 0 assert(result.has_key?(:owner), "Lost owner in describe") else assert(! result.has_key?(:owner), "Kept owner in describe even tho not root") end # Now let's do the various link things File.unlink(source) target = tempfile() File.open(target, "w") { |f| f.puts "yay" } File.symlink(target, source) file[:links] = :ignore assert_nil(property.describe(source), "Links were not ignored") file[:links] = :manage # We can't manage links at this point assert_raise(Puppet::Network::Handler::FileServerError) do property.describe(source) end # And then make sure links get followed, otherwise file[:links] = :follow assert_equal("file", property.describe(source)[:type]) end def test_source_retrieve source = tempfile() dest = tempfile() file = Puppet::Type.newfile :path => dest, :source => source, :title => "copier" assert(file.property(:checksum), "source property did not create checksum property") property = file.property(:source) assert(property, "did not get source property") # Make sure the munge didn't actually change the source assert_equal([source], property.should, "munging changed the source") # First try it with a missing source currentvalue = nil assert_nothing_raised do currentvalue = property.retrieve end # And make sure the property considers itself in sync, since there's nothing # to do assert(property.insync?(currentvalue), "source thinks there's work to do with no file or dest") # Now make the dest a directory, and make sure the object sets :ensure # up to create a directory Dir.mkdir(source) assert_nothing_raised do currentvalue = property.retrieve end assert_equal(:directory, file.should(:ensure), "Did not set to create directory") # And make sure the source property won't try to do anything with a # remote dir assert(property.insync?(currentvalue), "Source was out of sync even tho remote is dir") # Now remove the source, and make sure :ensure was not modified Dir.rmdir(source) assert_nothing_raised do property.retrieve end assert_equal(:directory, file.should(:ensure), "Did not keep :ensure setting") # Now have a remote file and make sure things work correctly File.open(source, "w") { |f| f.puts "yay" } File.chmod(0755, source) assert_nothing_raised do property.retrieve end assert_equal(:file, file.should(:ensure), "Did not make correct :ensure setting") assert_equal(0755, file.should(:mode), "Mode was not copied over") # Now let's make sure that we get the first found source fake = tempfile() property.should = [fake, source] assert_nothing_raised do property.retrieve end assert_equal(Digest::MD5.hexdigest(File.read(source)), property.checksum.sub(/^\{\w+\}/, ''), "Did not catch later source") end def test_insync source = tempfile() dest = tempfile() file = Puppet::Type.newfile :path => dest, :source => source, :title => "copier" property = file.property(:source) assert(property, "did not get source property") # Try it with no source at all currentvalues = file.retrieve assert(property.insync?(currentvalues[property]), "source property not in sync with missing source") # with a directory Dir.mkdir(source) currentvalues = file.retrieve assert(property.insync?(currentvalues[property]), "source property not in sync with directory as source") Dir.rmdir(source) # with a file File.open(source, "w") { |f| f.puts "yay" } currentvalues = file.retrieve assert(!property.insync?(currentvalues[property]), "source property was in sync when file was missing") # With a different file File.open(dest, "w") { |f| f.puts "foo" } currentvalues = file.retrieve assert(!property.insync?(currentvalues[property]), "source property was in sync with different file") # with matching files File.open(dest, "w") { |f| f.puts "yay" } currentvalues = file.retrieve assert(property.insync?(currentvalues[property]), "source property was not in sync with matching file") end def test_source_sync source = tempfile() dest = tempfile() file = Puppet::Type.newfile :path => dest, :source => source, :title => "copier" property = file.property(:source) File.open(source, "w") { |f| f.puts "yay" } currentvalues = file.retrieve assert(! property.insync?(currentvalues[property]), "source thinks it's in sync") event = nil assert_nothing_raised do event = property.sync end assert_equal(:file_created, event) assert_equal(File.read(source), File.read(dest), "File was not copied correctly") # Now write something different File.open(source, "w") { |f| f.puts "rah" } currentvalues = file.retrieve assert(! property.insync?(currentvalues[property]), "source should be out of sync") assert_nothing_raised do event = property.sync end assert_equal(:file_changed, event) assert_equal(File.read(source), File.read(dest), "File was not copied correctly") end # XXX This test doesn't cover everything. Specifically, # it doesn't handle 'ignore' and 'links'. def test_sourcerecurse source, dest, sourcefile, destfile = mk_sourcetree # The sourcerecurse method will only ever get called when we're # recursing, so we go ahead and set it. obj = Puppet::Type.newfile :source => source, :path => dest, :recurse => true config = mk_configuration(obj) result = nil sourced = nil assert_nothing_raised do result, sourced = obj.sourcerecurse(true) end assert_equal([destfile], sourced, "Did not get correct list of sourced objects") - dfileobj = @file[destfile] + dfileobj = config.resource(:file, destfile) assert(dfileobj, "Did not create destfile object") assert_equal([dfileobj], result) # Clean this up so it can be recreated config.remove_resource(dfileobj) # Make sure we correctly iterate over the sources nosource = tempfile() obj[:source] = [nosource, source] result = nil assert_nothing_raised do result, sourced = obj.sourcerecurse(true) end assert_equal([destfile], sourced, "Did not get correct list of sourced objects") - dfileobj = @file[destfile] + dfileobj = config.resource(:file, destfile) assert(dfileobj, "Did not create destfile object with a missing source") assert_equal([dfileobj], result) dfileobj.remove # Lastly, make sure we return an empty array when no sources are there obj[:source] = [nosource, tempfile()] assert_nothing_raised do result, sourced = obj.sourcerecurse(true) end assert_equal([], sourced, "Did not get correct list of sourced objects") assert_equal([], result, "Sourcerecurse failed when all sources are missing") end def test_simplelocalsource path = tempfile() FileUtils.mkdir_p path frompath = File.join(path,"source") topath = File.join(path,"dest") fromfile = nil tofile = nil trans = nil File.open(frompath, File::WRONLY|File::CREAT|File::APPEND) { |of| of.puts "yayness" } assert_nothing_raised { tofile = Puppet.type(:file).create( :name => topath, :source => frompath ) } assert_apply(tofile) assert(FileTest.exists?(topath), "File #{topath} is missing") from = File.open(frompath) { |o| o.read } to = File.open(topath) { |o| o.read } assert_equal(from,to) end # Make sure a simple recursive copy works def test_simple_recursive_source source, dest, sourcefile, destfile = mk_sourcetree file = Puppet::Type.newfile :path => dest, :source => source, :recurse => true assert_events([:directory_created, :file_created], file) assert(FileTest.directory?(dest), "Dest dir was not created") assert(FileTest.file?(destfile), "dest file was not created") assert_equal("yay\n", File.read(destfile), "dest file was not copied correctly") end def recursive_source_test(fromdir, todir) Puppet::Type.allclear initstorage tofile = nil trans = nil assert_nothing_raised { tofile = Puppet.type(:file).create( :path => todir, :recurse => true, :backup => false, :source => fromdir ) } assert_apply(tofile) assert(FileTest.exists?(todir), "Created dir %s does not exist" % todir) Puppet::Type.allclear end def run_complex_sources(networked = false) path = tempfile() # first create the source directory FileUtils.mkdir_p path # okay, let's create a directory structure fromdir = File.join(path,"fromdir") Dir.mkdir(fromdir) FileUtils.cd(fromdir) { File.open("one", "w") { |f| f.puts "onefile"} File.open("two", "w") { |f| f.puts "twofile"} } todir = File.join(path, "todir") source = fromdir if networked source = "puppet://localhost/%s%s" % [networked, fromdir] end recursive_source_test(source, todir) return [fromdir,todir, File.join(todir, "one"), File.join(todir, "two")] end def test_complex_sources_twice fromdir, todir, one, two = run_complex_sources assert_trees_equal(fromdir,todir) recursive_source_test(fromdir, todir) assert_trees_equal(fromdir,todir) # Now remove the whole tree and try it again. [one, two].each do |f| File.unlink(f) end Dir.rmdir(todir) recursive_source_test(fromdir, todir) assert_trees_equal(fromdir,todir) end def test_sources_with_deleted_destfiles fromdir, todir, one, two = run_complex_sources assert(FileTest.exists?(todir)) - - # We shouldn't have a 'two' file object in memory - assert_nil(@file[two], "object for 'two' is still in memory") # then delete a file File.unlink(two) # and run recursive_source_test(fromdir, todir) assert(FileTest.exists?(two), "Deleted file was not recopied") # and make sure they're still equal assert_trees_equal(fromdir,todir) end def test_sources_with_readonly_destfiles fromdir, todir, one, two = run_complex_sources assert(FileTest.exists?(todir)) File.chmod(0600, one) recursive_source_test(fromdir, todir) # and make sure they're still equal assert_trees_equal(fromdir,todir) # Now try it with the directory being read-only File.chmod(0111, todir) recursive_source_test(fromdir, todir) # and make sure they're still equal assert_trees_equal(fromdir,todir) end def test_sources_with_modified_dest_files fromdir, todir, one, two = run_complex_sources assert(FileTest.exists?(todir)) # Modify a dest file File.open(two, "w") { |f| f.puts "something else" } recursive_source_test(fromdir, todir) # and make sure they're still equal assert_trees_equal(fromdir,todir) end def test_sources_with_added_destfiles fromdir, todir = run_complex_sources assert(FileTest.exists?(todir)) # and finally, add some new files add_random_files(todir) recursive_source_test(fromdir, todir) fromtree = file_list(fromdir) totree = file_list(todir) assert(fromtree != totree, "Trees are incorrectly equal") # then remove our new files FileUtils.cd(todir) { %x{find . 2>/dev/null}.chomp.split(/\n/).each { |file| if file =~ /file[0-9]+/ File.unlink(file) end } } # and make sure they're still equal assert_trees_equal(fromdir,todir) end # Make sure added files get correctly caught during recursion def test_RecursionWithAddedFiles basedir = tempfile() Dir.mkdir(basedir) @@tmpfiles << basedir file1 = File.join(basedir, "file1") file2 = File.join(basedir, "file2") subdir1 = File.join(basedir, "subdir1") file3 = File.join(subdir1, "file") File.open(file1, "w") { |f| f.puts "yay" } rootobj = nil assert_nothing_raised { rootobj = Puppet.type(:file).create( :name => basedir, :recurse => true, :check => %w{type owner}, :mode => 0755 ) } assert_apply(rootobj) assert_equal(0755, filemode(file1)) File.open(file2, "w") { |f| f.puts "rah" } assert_apply(rootobj) assert_equal(0755, filemode(file2)) Dir.mkdir(subdir1) File.open(file3, "w") { |f| f.puts "foo" } assert_apply(rootobj) assert_equal(0755, filemode(file3)) end def mkfileserverconf(mounts) file = tempfile() File.open(file, "w") { |f| mounts.each { |path, name| f.puts "[#{name}]\n\tpath #{path}\n\tallow *\n" } } @@tmpfiles << file return file end def test_NetworkSources server = nil mounts = { "/" => "root" } fileserverconf = mkfileserverconf(mounts) Puppet[:autosign] = true Puppet[:masterport] = 8762 Puppet[:name] = "puppetmasterd" serverpid = nil assert_nothing_raised() { server = Puppet::Network::HTTPServer::WEBrick.new( :Handlers => { :CA => {}, # so that certs autogenerate :FileServer => { :Config => fileserverconf } } ) } serverpid = fork { assert_nothing_raised() { #trap(:INT) { server.shutdown; Kernel.exit! } trap(:INT) { server.shutdown } server.start } } @@tmppids << serverpid sleep(1) fromdir, todir = run_complex_sources("root") assert_trees_equal(fromdir,todir) recursive_source_test(fromdir, todir) assert_trees_equal(fromdir,todir) assert_nothing_raised { system("kill -INT %s" % serverpid) } end def test_unmountedNetworkSources server = nil mounts = { "/" => "root", "/noexistokay" => "noexist" } fileserverconf = mkfileserverconf(mounts) Puppet[:autosign] = true Puppet[:masterport] = @port serverpid = nil assert_nothing_raised("Could not start on port %s" % @port) { server = Puppet::Network::HTTPServer::WEBrick.new( :Port => @port, :Handlers => { :CA => {}, # so that certs autogenerate :FileServer => { :Config => fileserverconf } } ) } serverpid = fork { assert_nothing_raised() { #trap(:INT) { server.shutdown; Kernel.exit! } trap(:INT) { server.shutdown } server.start } } @@tmppids << serverpid sleep(1) name = File.join(tmpdir(), "nosourcefile") file = Puppet.type(:file).create( :source => "puppet://localhost/noexist/file", :name => name ) assert_nothing_raised { file.retrieve } comp = mk_configuration(file) comp.apply assert(!FileTest.exists?(name), "File with no source exists anyway") end def test_alwayschecksum from = tempfile() to = tempfile() File.open(from, "w") { |f| f.puts "yayness" } File.open(to, "w") { |f| f.puts "yayness" } file = nil # Now the files should be exactly the same, so we should not see attempts # at copying assert_nothing_raised { file = Puppet.type(:file).create( :path => to, :source => from ) } currentvalue = file.retrieve assert(currentvalue[file.property(:checksum)], "File does not have a checksum property") assert_equal(0, file.evaluate.length, "File produced changes") end def test_sourcepaths files = [] 3.times { files << tempfile() } to = tempfile() File.open(files[-1], "w") { |f| f.puts "yee-haw" } file = nil assert_nothing_raised { file = Puppet.type(:file).create( :name => to, :source => files ) } comp = mk_configuration(file) assert_events([:file_created], comp) assert(File.exists?(to), "File does not exist") txt = nil File.open(to) { |f| txt = f.read.chomp } assert_equal("yee-haw", txt, "Contents do not match") end # Make sure that source-copying updates the checksum on the same run def test_checksumchange source = tempfile() dest = tempfile() File.open(dest, "w") { |f| f.puts "boo" } File.open(source, "w") { |f| f.puts "yay" } file = nil assert_nothing_raised { file = Puppet.type(:file).create( :name => dest, :source => source ) } file.retrieve assert_events([:file_changed], file) file.retrieve assert_events([], file) end # Make sure that source-copying updates the checksum on the same run def test_sourcebeatsensure source = tempfile() dest = tempfile() File.open(source, "w") { |f| f.puts "yay" } file = nil assert_nothing_raised { file = Puppet.type(:file).create( :name => dest, :ensure => "file", :source => source ) } file.retrieve assert_events([:file_created], file) file.retrieve assert_events([], file) assert_events([], file) end def test_sourcewithlinks source = tempfile() link = tempfile() dest = tempfile() File.open(source, "w") { |f| f.puts "yay" } File.symlink(source, link) file = nil assert_nothing_raised { file = Puppet.type(:file).create( :name => dest, :source => link ) } # Default to skipping links assert_events([], file) assert(! FileTest.exists?(dest), "Created link") # Now follow the links file[:links] = :follow assert_events([:file_created], file) assert(FileTest.file?(dest), "Destination is not a file") # Now copy the links #assert_raise(Puppet::Network::Handler::FileServerError) { trans = nil assert_nothing_raised { file[:links] = :manage comp = mk_configuration(file) trans = comp.apply } assert(trans.failed?(file), "Object did not fail to copy links") end def test_changes source = tempfile() dest = tempfile() File.open(source, "w") { |f| f.puts "yay" } obj = nil assert_nothing_raised { obj = Puppet.type(:file).create( :name => dest, :source => source ) } assert_events([:file_created], obj) assert_equal(File.read(source), File.read(dest), "Files are not equal") assert_events([], obj) File.open(source, "w") { |f| f.puts "boo" } assert_events([:file_changed], obj) assert_equal(File.read(source), File.read(dest), "Files are not equal") assert_events([], obj) File.open(dest, "w") { |f| f.puts "kaboom" } # There are two changes, because first the checksum is noticed, and # then the source causes a change assert_events([:file_changed, :file_changed], obj) assert_equal(File.read(source), File.read(dest), "Files are not equal") assert_events([], obj) end def test_file_source_with_space dir = tempfile() source = File.join(dir, "file with spaces") Dir.mkdir(dir) File.open(source, "w") { |f| f.puts "yayness" } newdir = tempfile() newpath = File.join(newdir, "file with spaces") file = Puppet::Type.newfile( :path => newdir, :source => dir, :recurse => true ) assert_apply(file) assert(FileTest.exists?(newpath), "Did not create file") assert_equal("yayness\n", File.read(newpath)) end # Make sure files aren't replaced when replace is false, but otherwise # are. def test_replace source = tempfile() File.open(source, "w") { |f| f.puts "yayness" } dest = tempfile() file = Puppet::Type.newfile( :path => dest, :source => source, :recurse => true ) assert_apply(file) assert(FileTest.exists?(dest), "Did not create file") assert_equal("yayness\n", File.read(dest)) # Now set :replace assert_nothing_raised { file[:replace] = false } File.open(source, "w") { |f| f.puts "funtest" } assert_apply(file) # Make sure it doesn't change. assert_equal("yayness\n", File.read(dest), "File got replaced when :replace was false") # Now set it to true and make sure it does change. assert_nothing_raised { file[:replace] = true } assert_apply(file) # Make sure it doesn't change. assert_equal("funtest\n", File.read(dest), "File was not replaced when :replace was true") end # Testing #285. This just makes sure that URI parsing works correctly. def test_fileswithpoundsigns dir = tstdir() subdir = File.join(dir, "#dir") Dir.mkdir(subdir) file = File.join(subdir, "file") File.open(file, "w") { |f| f.puts "yayness" } dest = tempfile() source = "file://localhost#{dir}" obj = Puppet::Type.newfile( :path => dest, :source => source, :recurse => true ) newfile = File.join(dest, "#dir", "file") poundsource = "file://localhost#{subdir}" sourceobj = path = nil assert_nothing_raised { sourceobj, path = obj.uri2obj(poundsource) } assert_equal("/localhost" + URI.escape(subdir), path) assert_apply(obj) assert(FileTest.exists?(newfile), "File did not get created") assert_equal("yayness\n", File.read(newfile)) end def test_sourceselect dest = tempfile() sources = [] 2.times { |i| i = i + 1 source = tempfile() sources << source file = File.join(source, "file%s" % i) Dir.mkdir(source) File.open(file, "w") { |f| f.print "yay" } } file1 = File.join(dest, "file1") file2 = File.join(dest, "file2") file3 = File.join(dest, "file3") # Now make different files with the same name in each source dir sources.each_with_index do |source, i| File.open(File.join(source, "file3"), "w") { |f| f.print i.to_s } end obj = Puppet::Type.newfile(:path => dest, :recurse => true, :source => sources) assert_equal(:first, obj[:sourceselect], "sourceselect has the wrong default") # First, make sure we default to just copying file1 assert_apply(obj) assert(FileTest.exists?(file1), "File from source 1 was not copied") assert(! FileTest.exists?(file2), "File from source 2 was copied") assert(FileTest.exists?(file3), "File from source 1 was not copied") assert_equal("0", File.read(file3), "file3 got wrong contents") # Now reset sourceselect assert_nothing_raised do obj[:sourceselect] = :all end File.unlink(file1) File.unlink(file3) - Puppet.err :yay assert_apply(obj) assert(FileTest.exists?(file1), "File from source 1 was not copied") assert(FileTest.exists?(file2), "File from source 2 was copied") assert(FileTest.exists?(file3), "File from source 1 was not copied") assert_equal("0", File.read(file3), "file3 got wrong contents") end def test_recursive_sourceselect dest = tempfile() source1 = tempfile() source2 = tempfile() files = [] [source1, source2, File.join(source1, "subdir"), File.join(source2, "subdir")].each_with_index do |dir, i| Dir.mkdir(dir) # Make a single file in each directory file = File.join(dir, "file%s" % i) File.open(file, "w") { |f| f.puts "yay%s" % i} # Now make a second one in each directory file = File.join(dir, "second-file%s" % i) File.open(file, "w") { |f| f.puts "yaysecond-%s" % i} files << file end obj = Puppet::Type.newfile(:path => dest, :source => [source1, source2], :sourceselect => :all, :recurse => true) assert_apply(obj) ["file0", "file1", "second-file0", "second-file1", "subdir/file2", "subdir/second-file2", "subdir/file3", "subdir/second-file3"].each do |file| path = File.join(dest, file) assert(FileTest.exists?(path), "did not create %s" % file) assert_equal("yay%s\n" % File.basename(file).sub("file", ''), File.read(path), "file was not copied correctly") end end # #594 def test_purging_missing_remote_files source = tempfile() dest = tempfile() s1 = File.join(source, "file1") s2 = File.join(source, "file2") d1 = File.join(dest, "file1") d2 = File.join(dest, "file2") Dir.mkdir(source) [s1, s2].each { |name| File.open(name, "w") { |file| file.puts "something" } } # We have to add a second parameter, because that's the only way to expose the "bug". file = Puppet::Type.newfile(:path => dest, :source => source, :recurse => true, :purge => true, :mode => "755") assert_apply(file) assert(FileTest.exists?(d1), "File1 was not copied") assert(FileTest.exists?(d2), "File2 was not copied") File.unlink(s2) assert_apply(file) assert(FileTest.exists?(d1), "File1 was not kept") assert(! FileTest.exists?(d2), "File2 was not purged") end end diff --git a/test/ral/types/host.rb b/test/ral/types/host.rb index 1013651c5..5e91a8c58 100755 --- a/test/ral/types/host.rb +++ b/test/ral/types/host.rb @@ -1,165 +1,167 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../lib/puppettest' require 'puppettest' require 'test/unit' require 'facter' class TestHost < Test::Unit::TestCase include PuppetTest def setup super @hosttype = Puppet::Type.type(:host) @provider = @hosttype.defaultprovider # Make sure they aren't using something funky like netinfo unless @provider.name == :parsed @hosttype.defaultprovider = @hosttype.provider(:parsed) end cleanup do @hosttype.defaultprovider = nil end if @provider.respond_to?(:default_target=) @default_file = @provider.default_target cleanup do @provider.default_target = @default_file end @target = tempfile() @provider.default_target = @target end end def mkhost if defined? @hcount @hcount += 1 else @hcount = 1 end @configuration ||= mk_configuration host = nil assert_nothing_raised { host = Puppet.type(:host).create( :name => "fakehost%s" % @hcount, :ip => "192.168.27.%s" % @hcount, :alias => "alias%s" % @hcount, :configuration => @configuration ) } return host end def test_list assert_nothing_raised do @hosttype.defaultprovider.prefetch end count = 0 @hosttype.each do |h| count += 1 end assert_equal(0, count, "Found hosts in empty file somehow") end # Darwin will actually write to netinfo here. if Facter.value(:operatingsystem) != "Darwin" or Process.uid == 0 def test_simplehost host = nil # We want to actually use the netinfo provider on darwin if Facter.value(:operatingsystem) == "Darwin" Puppet::Type.type(:host).defaultprovider = nil end assert_nothing_raised { host = Puppet.type(:host).create( :name => "culain", :ip => "192.168.0.3" ) } current_values = nil assert_nothing_raised { current_values = host.retrieve } assert_events([:host_created], host) assert_nothing_raised { current_values = host.retrieve } assert_equal(:present, current_values[host.property(:ensure)]) host[:ensure] = :absent assert_events([:host_removed], host) assert_nothing_raised { current_values = host.retrieve } assert_equal(:absent, current_values[host.property(:ensure)]) end def test_moddinghost # We want to actually use the netinfo provider on darwin if Facter.value(:operatingsystem) == "Darwin" Puppet::Type.type(:host).defaultprovider = nil end host = mkhost() if Facter.value(:operatingsystem) == "Darwin" assert_equal(:netinfo, host[:provider], "Got incorrect provider") end cleanup do host[:ensure] = :absent assert_apply(host) end assert_events([:host_created], host) current_values = nil assert_nothing_raised { current_values = host.retrieve } # This was a hard bug to track down. assert_instance_of(String, current_values[host.property(:ip)]) host[:alias] = %w{madstop kirby yayness} assert_events([:host_changed], host) assert_nothing_raised { current_values = host.retrieve } assert_equal(%w{madstop kirby yayness}, current_values[host.property(:alias)]) host[:ensure] = :absent assert_events([:host_removed], host) end end def test_aliasisproperty assert_equal(:property, @hosttype.attrtype(:alias)) end def test_multivalues host = mkhost assert_raise(Puppet::Error) { host[:alias] = "puppetmasterd yayness" } end def test_puppetalias host = mkhost() + config = mk_configuration(host) + assert_nothing_raised { host[:alias] = "testing" } - same = host.class["testing"] + same = config.resource(:host, "testing") assert(same, "Could not retrieve by alias") end end diff --git a/test/ral/types/package.rb b/test/ral/types/package.rb index 7d0caa0ba..d09f7d9b7 100755 --- a/test/ral/types/package.rb +++ b/test/ral/types/package.rb @@ -1,156 +1,157 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../lib/puppettest' require 'puppettest' require 'facter' require 'mocha' $platform = Facter["operatingsystem"].value class TestPackages < Test::Unit::TestCase + include PuppetTest include PuppetTest::FileTesting def setup super Puppet.type(:package).clear @type = Puppet::Type.type(:package) end # This is a bare-minimum test and *really* needs to do much more. def test_package_actions @type.provide :fake, :parent => PuppetTest::FakeProvider do apimethods :ensure def install self.ensure = @resource.should(:ensure) end def uninstall self.ensure = :absent end def query case self.ensure when :absent, nil: nil else {:ensure => self.ensure} end end end pkg = nil assert_nothing_raised do pkg = @type.create :name => "testing", :provider => :fake end assert(pkg, "did not create package") current_values = nil assert_nothing_raised do current_values = pkg.retrieve end assert_equal(:absent, current_values[pkg.property(:ensure)], "package not considered missing") assert_equal(:present, pkg.should(:ensure), "package did not default to installed") assert_events([:package_installed], pkg) pkg[:ensure] = :absent assert_events([:package_removed], pkg) end def test_packagedefaults should = case Facter["operatingsystem"].value when "Debian": :apt when "Darwin": :apple when "RedHat": :up2date when "Fedora": :yum when "FreeBSD": :ports when "OpenBSD": :openbsd when "Solaris": :sun end unless default = Puppet::Type.type(:package).defaultprovider $stderr.puts "no default provider for %s" % Facter["operatingsystem"].value return end if should assert_equal(should, default.name, "Incorrect default package format") end end # Make sure we can prefetch and retrieve packages def test_package_instances providers = [] instances = nil assert_nothing_raised("Could not get package instances") do instances = @type.instances end instances.each do |resource| # Just do one of each type next if providers.include?(resource.provider.class) providers << resource.provider.class # We should have data on the resource assert(resource.exists?, "Listed resource thinks it's absent") # Now flush the resource and make sure it clears the property_hash assert_nothing_raised("Could not flush package") do resource.flush end assert_equal(:absent, resource.provider.get(:ensure), "Flushing did not empty property hash") # And query anew props = nil assert_nothing_raised("Could not retrieve package again") do props = resource.retrieve end provider_props = resource.provider.send(:instance_variable_get, "@property_hash") props.each do |prop, value| assert_equal(value, provider_props[prop.name], "Query did not return same result as the property_hash for %s" % prop.name) end end end # Make sure we can prefetch package information, rather than getting it one package at a time. def test_prefetch @type.providers_by_source.each do |provider| # The yum provider can't be used if you're not root next if provider.name == :yum && Process.euid != 0 # First get a list of packages list = provider.instances packages = {} list.each do |package| packages[package.name] = @type.create(:name => package.name, :ensure => :installed) break if packages.length > 4 end # Now prefetch using that list of packages assert_nothing_raised("Could not prefetch with %s" % provider.name) do provider.prefetch(packages) end # And make sure each package is marked as existing, without calling query packages.each do |name, package| assert(package.exists?, "Package of type %s not marked present" % provider.name) package.provider.expects(:query).never end end end # #716 def test_purge_is_not_installed package = @type.create(:ensure => :installed, :name => "whatever") property = package.property(:ensure) assert(! property.insync?(:purged), "Package in state 'purged' was considered in sync") end end diff --git a/test/ral/types/parameter.rb b/test/ral/types/parameter.rb index 1d402cb85..7eda05bb5 100755 --- a/test/ral/types/parameter.rb +++ b/test/ral/types/parameter.rb @@ -1,174 +1,173 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../lib/puppettest' require 'puppettest' class TestParameter < Test::Unit::TestCase include PuppetTest def newparam(name = :fakeparam) assert_nothing_raised { param = Class.new(Puppet::Parameter) do @name = :fakeparam end param.initvars return param } end def newinst(param) assert_nothing_raised { return param.new(:resource => "yay") } end # Test the basic newvalue stuff. def test_newvalue param = newparam() # Try it with both symbols and strings. assert_nothing_raised { param.newvalues(:one, "two") } inst = newinst(param) assert_nothing_raised { inst.value = "one" } assert_equal(:one, inst.value) assert_nothing_raised { inst.value = :two } assert_equal(:two, inst.value) assert_raise(ArgumentError) { inst.value = :three } assert_equal(:two, inst.value) end # Test using regexes. def test_regexvalues param = newparam assert_nothing_raised { param.newvalues(/^\d+$/) } assert(param.match?("14")) assert(param.match?(14)) inst = newinst(param) assert_nothing_raised { inst.value = 14 } assert_nothing_raised { inst.value = "14" } assert_raise(ArgumentError) { inst.value = "a14" } end # Test using both. Equality should beat matching. def test_regexesandnormals param = newparam assert_nothing_raised { param.newvalues(:one, /^\w+$/) } inst = newinst(param) assert_nothing_raised { inst.value = "one" } assert_equal(:one, inst.value, "Value used regex instead of equality") assert_nothing_raised { inst.value = "two" } assert_equal("two", inst.value, "Matched value didn't take") end def test_shadowing type = Puppet::Type.newtype(:faketype) { newparam(:name) {} } cleanup { Puppet::Type.rmtype(:faketype) } param = nil assert_nothing_raised do param = type.newproperty(:alias) end assert(param, "did not create param") inst = type.create(:name => "test") - config = mk_configuration - inst.configuration = config + config = mk_configuration(inst) assert_nothing_raised("Could not create shadowed param") { inst[:alias] = "foo" } # Get the parameter hash from the instance so we can check the shadow params = inst.instance_variable_get("@parameters") obj = params[:alias] assert(obj, "did not get alias parameter") assert(obj.shadow, "shadow was not created for alias param") assert(obj.is_a?(Puppet::Property), "alias instance is not a property") assert_instance_of(param, obj, "alias is an instance of the wrong class") # Make sure the alias got created - assert(type["foo"], "Did not retrieve object by its alias") + assert(config.resource(inst.class.name, "foo"), "Did not retrieve object by its alias") # Now try it during initialization other = nil assert_nothing_raised("Could not create instance with shadow") do other = type.create(:name => "rah", :alias => "one", :configuration => config) end params = other.instance_variable_get("@parameters") obj = params[:alias] assert(obj, "did not get alias parameter") assert(obj.shadow, "shadow was not created for alias param") assert_instance_of(param, obj, "alias is an instance of the wrong class") assert(obj.is_a?(Puppet::Property), "alias instance is not a property") # Now change the alias and make sure it works out well assert_nothing_raised("Could not modify shadowed alias") do other[:alias] = "two" end obj = params[:alias] assert(obj, "did not get alias parameter") assert_instance_of(param, obj, "alias is now an instance of the wrong class") assert(obj.is_a?(Puppet::Property), "alias instance is now not a property") end # Make sure properties can correctly require features and behave appropriately when # those features are missing. def test_requires_features param = newparam(:feature_tests) assert_nothing_raised("could not add feature requirements to property") do param.required_features = "testing" end assert_equal([:testing], param.required_features, "required features value was not arrayfied and interned") end end diff --git a/test/ral/types/schedule.rb b/test/ral/types/schedule.rb index 2870b8db6..06698f288 100755 --- a/test/ral/types/schedule.rb +++ b/test/ral/types/schedule.rb @@ -1,356 +1,347 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../lib/puppettest' require 'puppettest' require 'puppet/type/schedule' class TestSchedule < Test::Unit::TestCase include PuppetTest def setup super @stype = Puppet::Type::Schedule # This will probably get overridden by different tests @now = Time.now Puppet[:ignoreschedules] = false end def mksched s = nil assert_nothing_raised { s = @stype.create( :name => "testsched" ) } s end def diff(unit, incr, method, count) diff = @now.to_i.send(method, incr * count) t = Time.at(diff) #Puppet.notice "%s: %s %s %s = %s" % # [unit, @now.send(unit), method, count, t] #t.strftime("%H:%M:%S") t end def month(method, count) diff(:hour, 3600 * 24 * 30, method, count) end def week(method, count) diff(:hour, 3600 * 24 * 7, method, count) end def day(method, count) diff(:hour, 3600 * 24, method, count) end def hour(method, count) diff(:hour, 3600, method, count) end def min(method, count) diff(:min, 60, method, count) end def sec(method, count) diff(:sec, 1, method, count) end def settimes unless defined? @@times @@times = [Time.now] # Make one with an edge year on each side ary = Time.now.to_a [1999, 2000, 2001].each { |y| ary[5] = y; @@times << Time.local(*ary) } # And with edge hours ary = Time.now.to_a #[23, 0].each { |h| ary[2] = h; @@times << Time.local(*ary) } # 23 hour ary[2] = 23 @@times << Time.local(*ary) # 0 hour, next day ary[2] = 0 @@times << addday(Time.local(*ary)) # And with edge minutes #[59, 0].each { |m| ary[1] = m; @@times << Time.local(*ary) } ary = Time.now.to_a ary[1] = 59; @@times << Time.local(*ary) ary[1] = 0 #if ary[2] == 23 @@times << Time.local(*ary) #else # @@times << addday(Time.local(*ary)) #end end Puppet.err @@times.inspect @@times.each { |time| @now = time yield time } @now = Time.now end def test_range s = mksched ary = @now.to_a ary[2] = 12 @now = Time.local(*ary) data = { true => [ # An hour previous, an hour after [hour("-", 1), hour("+", 1)], # an hour previous but a couple minutes later, and an hour plus [min("-", 57), hour("+", 1)] ], false => [ # five minutes from now, an hour from now [min("+", 5), hour("+", 1)], # an hour ago, 20 minutes ago [hour("-", 1), min("-", 20)] ] } data.each { |result, values| values = values.collect { |value| "%s - %s" % [value[0].strftime("%H:%M:%S"), value[1].strftime("%H:%M:%S")] } values.each { |value| assert_nothing_raised("Could not parse %s" % value) { s[:range] = value } assert_equal(result, s.match?(nil, @now), "%s matched %s incorrectly" % [value.inspect, @now]) } assert_nothing_raised("Could not parse %s" % [values]) { s[:range] = values } assert_equal(result, s.match?(nil, @now), "%s matched %s incorrectly" % [values.inspect, @now]) } end def test_period_by_distance previous = @now s = mksched assert_nothing_raised { s[:period] = :daily } assert(s.match?(day("-", 1)), "did not match minus a day") assert(s.match?(day("-", 2)), "did not match two days") assert(! s.match?(@now), "matched today") assert(! s.match?(hour("-", 11)), "matched minus 11 hours") # Now test hourly assert_nothing_raised { s[:period] = :hourly } assert(s.match?(hour("-", 1)), "did not match minus an hour") assert(s.match?(hour("-", 2)), "did not match two hours") assert(! s.match?(@now), "matched now") assert(! s.match?(min("-", 59)), "matched minus 11 hours") # and weekly assert_nothing_raised { s[:period] = :weekly } assert(s.match?(week("-", 1)), "did not match minus a week") assert(s.match?(day("-", 7)), "did not match minus 7 days") assert(s.match?(day("-", 8)), "did not match minus 8 days") assert(s.match?(week("-", 2)), "did not match two weeks") assert(! s.match?(@now), "matched now") assert(! s.match?(day("-", 6)), "matched minus 6 days") # and monthly assert_nothing_raised { s[:period] = :monthly } assert(s.match?(month("-", 1)), "did not match minus a month") assert(s.match?(week("-", 5)), "did not match minus 5 weeks") assert(s.match?(week("-", 7)), "did not match minus 7 weeks") assert(s.match?(day("-", 50)), "did not match minus 50 days") assert(s.match?(month("-", 2)), "did not match two months") assert(! s.match?(@now), "matched now") assert(! s.match?(week("-", 3)), "matched minus 3 weeks") assert(! s.match?(day("-", 20)), "matched minus 20 days") end # A shortened test... def test_period_by_number s = mksched assert_nothing_raised { s[:periodmatch] = :number } assert_nothing_raised { s[:period] = :daily } assert(s.match?(day("+", 1)), "didn't match plus a day") assert(s.match?(week("+", 1)), "didn't match plus a week") assert(! s.match?(@now), "matched today") assert(! s.match?(hour("-", 1)), "matched minus an hour") assert(! s.match?(hour("-", 2)), "matched plus two hours") # Now test hourly assert_nothing_raised { s[:period] = :hourly } assert(s.match?(hour("+", 1)), "did not match plus an hour") assert(s.match?(hour("+", 2)), "did not match plus two hours") assert(! s.match?(@now), "matched now") assert(! s.match?(sec("+", 20)), "matched plus 20 seconds") end def test_periodmatch_default s = mksched match = s[:periodmatch] assert(match, "Could not find periodmatch") assert_equal(:distance, match, "Periodmatch was %s" % match) end def test_scheduled_objects s = mksched s[:period] = :hourly - f = nil path = tempfile() - assert_nothing_raised { - f = Puppet.type(:file).create( - :name => path, - :schedule => s.name, - :ensure => "file" - ) - } + f = Puppet.type(:file).create( + :name => path, + :schedule => s.name, + :ensure => "file" + ) + + config = mk_configuration(s, f) assert(f.scheduled?, "File is not scheduled to run") - assert_apply(f) + config.apply assert(! f.scheduled?, "File is scheduled to run already") File.unlink(path) - assert_apply(f) + config.apply assert(! FileTest.exists?(path), "File was created when not scheduled") end def test_latebinding_schedules f = nil path = tempfile() assert_nothing_raised { f = Puppet.type(:file).create( :name => path, :schedule => "testsched", :ensure => "file" ) } s = mksched s[:period] = :hourly assert_nothing_raised { f.schedule } assert(f.scheduled?, "File is not scheduled to run") end # Verify that each of our default schedules exist def test_defaultschedules - assert_nothing_raised do - Puppet.type(:schedule).mkdefaultschedules + defaults = nil + assert_nothing_raised("Could not create default schedules") do + defaults = Puppet.type(:schedule).create_default_resources end s = {} %w{puppet hourly daily weekly monthly}.each { |period| - obj = Puppet.type(:schedule)[period] - assert(obj, "Could not find %s schedule" % + schedule = defaults.find { |r| r.title == period } + assert(schedule, "Could not find %s schedule" % period) - s[period] = obj + s[period] = schedule } - assert_nothing_raised("Could not rerun mkdefaultschedules") do - Puppet.type(:schedule).mkdefaultschedules - end - s.each do |period, obj| - newobj = Puppet.type(:schedule)[period] - assert(newobj, "somehow lost schedule for %s" % period) - assert_equal(obj.object_id, newobj.object_id, - "created a new schedule instead of reusing existing one") - end end def test_period_with_repeat previous = @now s = mksched s[:period] = :hourly assert_nothing_raised("Was not able to set periodmatch") { s[:periodmatch] = :number } assert_raise(Puppet::Error) { s[:repeat] = 2 } assert_nothing_raised("Was not able to reset periodmatch") { s[:periodmatch] = :distance } assert(! s.match?(min("-", 40)), "matched minus 40 minutes") assert_nothing_raised("Was not able to set period") { s[:repeat] = 2 } assert(! s.match?(min("-", 20)), "matched minus 20 minutes with half-hourly") assert(s.match?(min("-", 40)), "Did not match minus 40 with half-hourly") assert_nothing_raised("Was not able to set period") { s[:repeat] = 3 } assert(! s.match?(min("-", 15)), "matched minus 15 minutes with half-hourly") assert(s.match?(min("-", 25)), "Did not match minus 25 with half-hourly") end # #526 def test_never_period schedule = nil assert_nothing_raised do schedule = Puppet::Type.type(:schedule).create( :name => "test", :period => :never ) end assert(! schedule.match?, "schedule matched with period == never") end end diff --git a/test/ral/types/sshkey.rb b/test/ral/types/sshkey.rb index c99f7562a..a2daf6cfb 100755 --- a/test/ral/types/sshkey.rb +++ b/test/ral/types/sshkey.rb @@ -1,194 +1,198 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../lib/puppettest' require 'puppettest' require 'puppet/type/sshkey' require 'facter' class TestSSHKey < Test::Unit::TestCase include PuppetTest def setup super # god i'm lazy @sshkeytype = Puppet.type(:sshkey) @provider = @sshkeytype.defaultprovider # Make sure they aren't using something funky like netinfo unless @provider.name == :parsed @sshkeytype.defaultprovider = @sshkeytype.provider(:parsed) end cleanup do @sshkeytype.defaultprovider = nil end if @provider.respond_to?(:default_target) oldpath = @provider.default_target cleanup do @provider.default_target = oldpath end @provider.default_target = tempfile() end end def teardown super if @provider.respond_to?(:clear) @provider.clear end end def mkkey key = nil if defined? @kcount @kcount += 1 else @kcount = 1 end @config ||= mk_configuration assert_nothing_raised { key = @sshkeytype.create( :name => "host%s.madstop.com" % @kcount, :key => "%sAAAAB3NzaC1kc3MAAACBAMnhSiku76y3EGkNCDsUlvpO8tRgS9wL4Eh54WZfQ2lkxqfd2uT/RTT9igJYDtm/+UHuBRdNGpJYW1Nw2i2JUQgQEEuitx4QKALJrBotejGOAWxxVk6xsh9xA0OW8Q3ZfuX2DDitfeC8ZTCl4xodUMD8feLtP+zEf8hxaNamLlt/AAAAFQDYJyf3vMCWRLjTWnlxLtOyj/bFpwAAAIEAmRxxXb4jjbbui9GYlZAHK00689DZuX0EabHNTl2yGO5KKxGC6Esm7AtjBd+onfu4Rduxut3jdI8GyQCIW8WypwpJofCIyDbTUY4ql0AQUr3JpyVytpnMijlEyr41FfIb4tnDqnRWEsh2H7N7peW+8DWZHDFnYopYZJ9Yu4/jHRYAAACAERG50e6aRRb43biDr7Ab9NUCgM9bC0SQscI/xdlFjac0B/kSWJYTGVARWBDWug705hTnlitY9cLC5Ey/t/OYOjylTavTEfd/bh/8FkAYO+pWdW3hx6p97TBffK0b6nrc6OORT2uKySbbKOn0681nNQh4a6ueR3JRppNkRPnTk5c=" % @kcount, :type => "ssh-dss", :alias => ["192.168.0.%s" % @kcount], :configuration => @config ) } return key end def test_instances assert_nothing_raised { Puppet.type(:sshkey).instances } count = 0 @sshkeytype.each do |h| count += 1 end assert_equal(0, count, "Found sshkeys in empty file somehow") end def test_simplekey key = mkkey file = tempfile() key[:target] = file key[:provider] = :parsed assert_apply(key) assert_events([], key, "created events on in-sync key") assert(key.provider.exists?, "Key did not get created") # Now create a new key object name = key.name key = nil @sshkeytype.clear key = @sshkeytype.create :name => name, :target => file, :provider => :parsed key.retrieve assert(key.provider.exists?, "key thinks it does not exist") end def test_moddingkey key = mkkey() + config = mk_configuration(key) + assert_events([:sshkey_created], key) key.retrieve aliases = %w{madstop kirby yayness} key[:alias] = aliases params = key.instance_variable_get("@parameters") assert_events([:sshkey_changed], key) aliases.each do |name| - assert_equal(key, key.class[name], + assert_equal(key, config.resource(:sshkey, name), "alias was not set") end end def test_aliasisproperty assert_equal(:property, @sshkeytype.attrtype(:alias)) end def test_multivalues key = mkkey assert_raise(Puppet::Error) { key[:alias] = "puppetmasterd yayness" } end def test_puppetalias key = mkkey() + config = mk_configuration(key) assert_nothing_raised { key[:alias] = "testing" } - same = key.class["testing"] + same = config.resource(:sshkey, "testing") assert(same, "Could not retrieve by alias") end def test_removal sshkey = mkkey() assert_nothing_raised { sshkey[:ensure] = :present } assert_events([:sshkey_created], sshkey) assert(sshkey.provider.exists?, "key was not created") assert_nothing_raised { sshkey[:ensure] = :absent } assert_events([:sshkey_removed], sshkey) assert(! sshkey.provider.exists?, "Key was not deleted") assert_events([], sshkey) end # Make sure changes actually modify the file. def test_modifyingfile keys = [] names = [] 3.times { k = mkkey() #h[:ensure] = :present #h.retrieve keys << k names << k.name } - assert_apply(*keys) - keys.clear - Puppet.type(:sshkey).clear + config = mk_configuration(*keys) + config.apply + newkey = mkkey() - #newkey[:ensure] = :present + config = mk_configuration(newkey) names << newkey.name - assert_apply(newkey) + + config.apply # Verify we can retrieve that info assert_nothing_raised("Could not retrieve after second write") { newkey.provider.class.prefetch newkey.retrieve } # And verify that we have data for everything names.each { |name| key = Puppet.type(:sshkey)[name] || Puppet.type(:sshkey).create(:name => name) assert(key, "Could not retrieve key for %s" % name) assert(key.provider.exists?, "key %s is missing" % name) } end end diff --git a/test/ral/types/tidy.rb b/test/ral/types/tidy.rb index e95f26b95..6cdf0f050 100755 --- a/test/ral/types/tidy.rb +++ b/test/ral/types/tidy.rb @@ -1,230 +1,226 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../lib/puppettest' require 'puppettest' +require 'puppettest/support/utils' class TestTidy < Test::Unit::TestCase + include PuppetTest include PuppetTest::FileTesting def mktmpfile # because luke's home directory is on nfs, it can't be used for testing # as root tmpfile = tempfile() File.open(tmpfile, "w") { |f| f.puts rand(100) } @@tmpfiles.push tmpfile return tmpfile end def mktmpdir dir = File.join(tmpdir(), "puppetlinkdir") unless FileTest.exists?(dir) Dir.mkdir(dir) end @@tmpfiles.push dir return dir end def test_tidydirs dir = mktmpdir file = File.join(dir, "file") File.open(file, "w") { |f| f.puts "some stuff" } tidy = Puppet.type(:tidy).create( :name => dir, :size => "1b", :rmdirs => true, :recurse => true ) assert_events([:file_tidied, :file_tidied], tidy) assert(!FileTest.exists?(file), "Tidied %s still exists" % file) assert(!FileTest.exists?(dir), "Tidied %s still exists" % dir) end def disabled_test_recursion source = mktmpdir() FileUtils.cd(source) { mkranddirsandfiles() } link = nil assert_nothing_raised { link = newlink(:target => source, :recurse => true) } comp = mk_configuration("linktest",link) cycle(comp) path = link.name list = file_list(path) FileUtils.cd(path) { list.each { |file| unless FileTest.directory?(file) assert(FileTest.symlink?(file)) target = File.readlink(file) assert_equal(target,File.join(source,file.sub(/^\.\//,''))) end } } end # Test the different age iterations. def test_age_conversions tidy = Puppet::Type.newtidy :path => tempfile(), :age => "1m" convertors = { :second => 1, :minute => 60 } convertors[:hour] = convertors[:minute] * 60 convertors[:day] = convertors[:hour] * 24 convertors[:week] = convertors[:day] * 7 # First make sure we default to days assert_nothing_raised do tidy[:age] = "2" end assert_equal(2 * convertors[:day], tidy.should(:age), "Converted 2 wrong") convertors.each do |name, number| init = name.to_s[0..0] # The first letter [0, 1, 5].each do |multi| [init, init.upcase].each do |letter| age = multi.to_s + letter.to_s assert_nothing_raised do tidy[:age] = age end assert_equal(multi * convertors[name], tidy.should(:age), "Converted %s wrong" % age) end end end end def test_size_conversions convertors = { :b => 0, :kb => 1, :mb => 2, :gb => 3 } tidy = Puppet::Type.newtidy :path => tempfile(), :age => "1m" # First make sure we default to kb assert_nothing_raised do tidy[:size] = "2" end assert_equal(2048, tidy.should(:size), "Converted 2 wrong") convertors.each do |name, number| init = name.to_s[0..0] # The first letter [0, 1, 5].each do |multi| [init, init.upcase].each do |letter| size = multi.to_s + letter.to_s assert_nothing_raised do tidy[:size] = size end total = multi number.times do total *= 1024 end assert_equal(total, tidy.should(:size), "Converted %s wrong" % size) end end end end def test_agetest tidy = Puppet::Type.newtidy :path => tempfile(), :age => "1m" age = tidy.property(:age) # Set it to something that should be fine assert(age.insync?(Time.now.to_i - 5), "Tried to tidy a low age") # Now to something that should fail assert(! age.insync?(Time.now.to_i - 120), "Incorrectly skipped tidy") end def test_sizetest tidy = Puppet::Type.newtidy :path => tempfile(), :size => "1k" size = tidy.property(:size) # Set it to something that should be fine assert(size.insync?(50), "Tried to tidy a low size") # Now to something that should fail assert(! size.insync?(2048), "Incorrectly skipped tidy") end # Make sure we can remove different types of files def test_tidytypes path = tempfile() tidy = Puppet::Type.newtidy :path => path, :size => "1b", :age => "1s" # Start with a file File.open(path, "w") { |f| f.puts "this is a test" } assert_events([:file_tidied], tidy) assert(! FileTest.exists?(path), "File was not removed") # Then a link dest = tempfile File.open(dest, "w") { |f| f.puts "this is a test" } File.symlink(dest, path) assert_events([:file_tidied], tidy) assert(! FileTest.exists?(path), "Link was not removed") assert(FileTest.exists?(dest), "Destination was removed") # And a directory Dir.mkdir(path) tidy[:rmdirs] = true assert_events([:file_tidied], tidy) assert(! FileTest.exists?(path), "File was not removed") end # Make sure we can specify either attribute and get appropriate behaviour. # I found that the original implementation of this did not work unless both # size and age were specified. def test_one_attribute path = tempfile() File.open(path, "w") { |f| 10.times { f.puts "yayness " } } tidy = Puppet::Type.type(:tidy).create :path => path, :size => "1b" - - assert_apply(tidy) - assert(! FileTest.exists?(path), "file did not get tidied") - - # Now try one with just an age attribute. - File.open(path, "w") { |f| 10.times { f.puts "yayness " } } - tidy = Puppet::Type.type(:tidy).create :path => path, :age => "5s" - + config = mk_configuration(tidy) + + config.apply - assert_apply(tidy) assert(! FileTest.exists?(path), "file did not get tidied") end # Testing #355. def test_remove_dead_links dir = tempfile() link = File.join(dir, "link") target = tempfile() Dir.mkdir(dir) File.symlink(target, link) tidy = Puppet::Type.newtidy :path => dir, :size => "1b", :recurse => true assert_apply(tidy) assert(! FileTest.symlink?(link), "link was not tidied") end end diff --git a/test/util/filetype.rb b/test/util/filetype.rb index 6c7ede07b..a53fb385f 100755 --- a/test/util/filetype.rb +++ b/test/util/filetype.rb @@ -1,137 +1,136 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../lib/puppettest' require 'puppettest' require 'puppet/util/filetype' require 'mocha' class TestFileType < Test::Unit::TestCase include PuppetTest def test_flat obj = nil path = tempfile() type = nil assert_nothing_raised { type = Puppet::Util::FileType.filetype(:flat) } assert(type, "Could not retrieve flat filetype") assert_nothing_raised { obj = type.new(path) } text = "This is some text\n" newtext = nil assert_nothing_raised { newtext = obj.read } # The base class doesn't allow a return of nil assert_equal("", newtext, "Somehow got some text") assert_nothing_raised { obj.write(text) } assert_nothing_raised { newtext = obj.read } assert_equal(text, newtext, "Text was changed somehow") File.open(path, "w") { |f| f.puts "someyayness" } text = File.read(path) assert_nothing_raised { newtext = obj.read } assert_equal(text, newtext, "Text was changed somehow") end # Make sure that modified files are backed up before they're changed. def test_backup_is_called path = tempfile File.open(path, "w") { |f| f.print 'yay' } obj = Puppet::Util::FileType.filetype(:flat).new(path) obj.expects(:backup) obj.write("something") assert_equal("something", File.read(path), "File did not get changed") end def test_backup path = tempfile type = Puppet::Type.type(:filebucket) obj = Puppet::Util::FileType.filetype(:flat).new(path) # First try it when the file does not yet exist. assert_nothing_raised("Could not call backup when file does not exist") do obj.backup end # Then create the file File.open(path, "w") { |f| f.print 'one' } # Then try it with no filebucket objects assert_nothing_raised("Could not call backup with no buckets") do obj.backup end - puppet = type["puppet"] - assert(puppet, "Did not create default filebucket") + puppet = Puppet::Type.type(:filebucket).create_default_resources assert_equal("one", puppet.bucket.getfile(Digest::MD5.hexdigest(File.read(path))), "Could not get file from backup") # Try it again when the default already exists File.open(path, "w") { |f| f.print 'two' } assert_nothing_raised("Could not call backup with no buckets") do obj.backup end assert_equal("two", puppet.bucket.getfile(Digest::MD5.hexdigest(File.read(path))), "Could not get file from backup") end if Facter["operatingsystem"].value == "Darwin" def test_ninfotoarray obj = nil type = nil assert_nothing_raised { type = Puppet::Util::FileType.filetype(:netinfo) } assert(type, "Could not retrieve netinfo filetype") %w{users groups aliases}.each do |map| assert_nothing_raised { obj = type.new(map) } assert_nothing_raised("could not read map %s" % map) { obj.read } array = nil assert_nothing_raised("Failed to parse %s map" % map) { array = obj.to_array } assert_instance_of(Array, array) array.each do |record| assert_instance_of(Hash, record) assert(record.length != 0) end end end end end