diff --git a/lib/puppet.rb b/lib/puppet.rb index f6debc0b1..e0d8a8e6c 100644 --- a/lib/puppet.rb +++ b/lib/puppet.rb @@ -1,531 +1,532 @@ # see the bottom of the file for further inclusions require 'singleton' require 'puppet/error' require 'puppet/event-loop' require 'puppet/util' require 'puppet/log' require 'puppet/config' +require 'puppet/suidmanager' #------------------------------------------------------------ # the top-level module # # all this really does is dictate how the whole system behaves, through # preferences for things like debugging # # it's also a place to find top-level commands like 'debug' module Puppet PUPPETVERSION = '0.19.2' def Puppet.version return PUPPETVERSION end class << self # So we can monitor signals and such. include SignalObserver include Puppet::Util # To keep a copy of arguments. Set within Config#addargs, because I'm # lazy. attr_accessor :args end def self.name unless defined? @name @name = $0.gsub(/.+#{File::SEPARATOR}/,'').sub(/\.rb$/, '') end return @name end # the hash that determines how our system behaves @@config = Puppet::Config.new # The services running in this process. @services ||= [] # define helper messages for each of the message levels Puppet::Log.eachlevel { |level| define_method(level,proc { |args| if args.is_a?(Array) args = args.join(" ") end Puppet::Log.create( :level => level, :message => args ) }) module_function level } # I keep wanting to use Puppet.error # XXX this isn't actually working right now alias :error :err # Store a new default value. def self.setdefaults(section, hash) @@config.setdefaults(section, hash) end # If we're running the standalone puppet process as a non-root user, # use basedirs that are in the user's home directory. conf = nil var = nil - if self.name == "puppet" and Process.uid != 0 + if self.name == "puppet" and Puppet::SUIDManager.uid != 0 conf = File.expand_path("~/.puppet") var = File.expand_path("~/.puppet/var") else # Else, use system-wide directories. conf = "/etc/puppet" var = "/var/puppet" end self.setdefaults(:puppet, :confdir => [conf, "The main Puppet configuration directory."], :vardir => [var, "Where Puppet stores dynamic and growing data."] ) if self.name == "puppetmasterd" self.setdefaults(:puppetmasterd, :logdir => {:default => "$vardir/log", :mode => 0750, :owner => "$user", :group => "$group", :desc => "The Puppet log directory." } ) else self.setdefaults(:puppet, :logdir => ["$vardir/log", "The Puppet log directory."] ) end self.setdefaults(:puppet, :trace => [false, "Whether to print stack traces on some errors"], :statedir => { :default => "$vardir/state", :mode => 01777, :desc => "The directory where Puppet state is stored. Generally, this directory can be removed without causing harm (although it might result in spurious service restarts)." }, :rundir => { :default => "$vardir/run", :mode => 01777, :desc => "Where Puppet PID files are kept." }, :lockdir => { :default => "$vardir/locks", :mode => 01777, :desc => "Where lock files are kept." }, :statefile => { :default => "$statedir/state.yaml", :mode => 0660, :desc => "Where puppetd and puppetmasterd store state associated with the running configuration. In the case of puppetmasterd, this file reflects the state discovered through interacting with clients." }, :ssldir => { :default => "$confdir/ssl", :mode => 0771, :owner => "root", :desc => "Where SSL certificates are kept." }, :genconfig => [false, "Whether to just print a configuration to stdout and exit. Only makes sense when used interactively. Takes into account arguments specified on the CLI."], :genmanifest => [false, "Whether to just print a manifest to stdout and exit. Only makes sense when used interactively. Takes into account arguments specified on the CLI."], :configprint => ["", "Print the value of a specific configuration parameter. If a parameter is provided for this, then the value is printed and puppet exits. Comma-separate multiple values. For a list of all values, specify 'all'. This feature is only available in Puppet versions higher than 0.18.4."], :color => [true, "Whether to use ANSI colors when logging to the console."], :mkusers => [false, "Whether to create the necessary user and group that puppetd will run as."] ) # Define the config default. self.setdefaults(self.name, :config => ["$confdir/#{self.name}.conf", "The configuration file for #{self.name}."] ) self.setdefaults("puppetmasterd", :user => ["puppet", "The user puppetmasterd should run as."], :group => ["puppet", "The group puppetmasterd should run as."], :manifestdir => ["$confdir/manifests", "Where puppetmasterd looks for its manifests."], :manifest => ["$manifestdir/site.pp", "The entry-point manifest for puppetmasterd."], :masterlog => { :default => "$logdir/puppetmaster.log", :owner => "$user", :group => "$group", :mode => 0660, :desc => "Where puppetmasterd logs. This is generally not used, since syslog is the default log destination." }, :masterhttplog => { :default => "$logdir/masterhttp.log", :owner => "$user", :group => "$group", :mode => 0660, :create => true, :desc => "Where the puppetmasterd web server logs." }, :masterport => [8140, "Which port puppetmasterd listens on."], :parseonly => [false, "Just check the syntax of the manifests."], :node_name => ["cert", "How the puppetmaster determines the client's identity and sets the 'hostname' fact for use in the manifest, in particular for determining which 'node' statement applies to the client. Possible values are 'cert' (use the subject's CN in the client's certificate) and 'facter' (use the hostname that the client reported in its facts)"] ) self.setdefaults("puppetd", :localconfig => { :default => "$confdir/localconfig", :owner => "root", :mode => 0660, :desc => "Where puppetd caches the local configuration. An extension indicating the cache format is added automatically."}, :classfile => { :default => "$confdir/classes.txt", :owner => "root", :mode => 0644, :desc => "The file in which puppetd stores a list of the classes associated with the retrieved configuratiion. Can be loaded in the separate ``puppet`` executable using the ``--loadclasses`` option."}, :puppetdlog => { :default => "$logdir/puppetd.log", :owner => "root", :mode => 0640, :desc => "The log file for puppetd. This is generally not used." }, :httplog => { :default => "$logdir/http.log", :owner => "root", :mode => 0640, :desc => "Where the puppetd web server logs." }, :server => ["puppet", "The server to which server puppetd should connect"], :ignoreschedules => [false, "Boolean; whether puppetd should ignore schedules. This is useful for initial puppetd runs."], :puppetport => [8139, "Which port puppetd listens on."], :noop => [false, "Whether puppetd should be run in noop mode."], :runinterval => [1800, # 30 minutes "How often puppetd applies the client configuration; in seconds"] ) # configuration parameter access and stuff def self.[](param) case param when :debug: if Puppet::Log.level == :debug return true else return false end else return @@config[param] end end # configuration parameter access and stuff def self.[]=(param,value) @@config[param] = value end def self.clear @@config.clear end def self.debug=(value) if value Puppet::Log.level=(:debug) else Puppet::Log.level=(:notice) end end def self.config @@config end def self.genconfig if Puppet[:configprint] != "" val = Puppet[:configprint] if val == "all" hash = {} Puppet.config.each do |name, obj| val = obj.value case val when true, false, "": val = val.inspect end hash[name] = val end hash.sort { |a,b| a[0].to_s <=> b[0].to_s }.each do |name, val| puts "%s = %s" % [name, val] end elsif val =~ /,/ val.split(/\s*,\s*/).sort.each do |v| puts "%s = %s" % [v, Puppet[v]] end else puts Puppet[val] end exit(0) end if Puppet[:genconfig] puts Puppet.config.to_config exit(0) end end def self.genmanifest if Puppet[:genmanifest] puts Puppet.config.to_manifest exit(0) end end # Run all threads to their ends def self.join defined? @threads and @threads.each do |t| t.join end end # Create a new service that we're supposed to run def self.newservice(service) @services ||= [] @services << service end def self.newthread(&block) @threads ||= [] @threads << Thread.new do yield end end def self.newtimer(hash, &block) timer = nil threadlock(:timers) do @timers ||= [] timer = EventLoop::Timer.new(hash) @timers << timer if block_given? observe_signal(timer, :alarm, &block) end end # In case they need it for something else. timer end # Relaunch the executable. def self.restart command = $0 + " " + self.args.join(" ") Puppet.notice "Restarting with '%s'" % command Puppet.shutdown(false) exec(command) end # Trap a couple of the main signals. This should probably be handled # in a way that anyone else can register callbacks for traps, but, eh. def self.settraps [:INT, :TERM].each do |signal| trap(signal) do Puppet.notice "Caught #{signal}; shutting down" Puppet.shutdown end end # Handle restarting. trap(:HUP) do if client = @services.find { |s| s.is_a? Puppet::Client::MasterClient } and client.running? client.restart else Puppet.restart end end # Provide a hook for running clients where appropriate trap(:USR1) do done = 0 Puppet.notice "Caught USR1; triggering client run" @services.find_all { |s| s.is_a? Puppet::Client }.each do |client| if client.respond_to? :running? if client.running? Puppet.info "Ignoring running %s" % client.class else done += 1 begin client.runnow rescue => detail Puppet.err "Could not run client: %s" % detail end end else Puppet.info "Ignoring %s; cannot test whether it is running" % client.class end end unless done > 0 Puppet.notice "No clients were run" end end end # Shutdown our server process, meaning stop all services and all threads. # Optionally, exit. def self.shutdown(leave = true) Puppet.notice "Shutting down" # Unmonitor our timers defined? @timers and @timers.each do |timer| EventLoop.current.ignore_timer timer end # This seems to exit the process, although I can't find where it does # so. Leaving it out doesn't seem to hurt anything. #if EventLoop.current.running? # EventLoop.current.quit #end # Stop our services defined? @services and @services.each do |svc| begin timeout(20) do svc.shutdown end rescue TimeoutError Puppet.err "%s could not shut down within 20 seconds" % svc.class end end # And wait for them all to die, giving a decent amount of time defined? @threads and @threads.each do |thr| begin timeout(20) do thr.join end rescue TimeoutError # Just ignore this, since we can't intelligently provide a warning end end if leave exit(0) end end # Start all of our services and optionally our event loop, which blocks, # waiting for someone, somewhere, to generate events of some kind. def self.start(block = true) # Starting everything in its own thread, fwiw defined? @services and @services.each do |svc| newthread do begin svc.start rescue => detail if Puppet[:debug] puts detail.backtrace end @services.delete svc Puppet.err "Could not start %s: %s" % [svc.class, detail] end end end unless @services.length > 0 Puppet.notice "No remaining services; exiting" exit(1) end # We need to give the services a chance to register their timers before # we try to start monitoring them. sleep 0.5 if defined? @timers and ! @timers.empty? @timers.each do |timer| EventLoop.current.monitor_timer timer end end if block EventLoop.current.run end end # Create the timer that our different objects (uh, mostly the client) # check. def self.timer unless defined? @timer #Puppet.info "Interval is %s" % Puppet[:runinterval] #@timer = EventLoop::Timer.new(:interval => Puppet[:runinterval]) @timer = EventLoop::Timer.new( :interval => Puppet[:runinterval], :tolerance => 1, :start? => true ) EventLoop.current.monitor_timer @timer end @timer end # XXX this should all be done using puppet objects, not using # normal mkdir def self.recmkdir(dir,mode = 0755) if FileTest.exist?(dir) return false else tmp = dir.sub(/^\//,'') path = [File::SEPARATOR] tmp.split(File::SEPARATOR).each { |dir| path.push dir if ! FileTest.exist?(File.join(path)) begin Dir.mkdir(File.join(path), mode) rescue Errno::EACCES => detail Puppet.err detail.to_s return false rescue => detail Puppet.err "Could not create %s: %s" % [path, detail.to_s] return false end elsif FileTest.directory?(File.join(path)) next else FileTest.exist?(File.join(path)) raise Puppet::Error, "Cannot create %s: basedir %s is a file" % [dir, File.join(path)] end } return true end end # Create a new type. Just proxy to the Type class. def self.newtype(name, parent = nil, &block) Puppet::Type.newtype(name, parent, &block) end # Retrieve a type by name. Just proxy to the Type class. def self.type(name) Puppet::Type.type(name) end end require 'puppet/server' require 'puppet/type' require 'puppet/storage' # $Id$ diff --git a/lib/puppet/config.rb b/lib/puppet/config.rb index 81ec4fe85..337a50bbc 100644 --- a/lib/puppet/config.rb +++ b/lib/puppet/config.rb @@ -1,928 +1,928 @@ require 'puppet' require 'sync' require 'puppet/transportable' module Puppet # The class for handling configuration files. class Config include Enumerable @@sync = Sync.new attr_reader :file, :timer # Retrieve a config value def [](param) param = symbolize(param) # Yay, recursion. self.reparse() unless param == :filetimeout if @config.include?(param) if @config[param] val = @config[param].value return val end else raise ArgumentError, "Undefined configuration parameter '%s'" % param end end # Set a config value. This doesn't set the defaults, it sets the value itself. def []=(param, value) @@sync.synchronize do # yay, thread-safe param = symbolize(param) unless @config.include?(param) raise Puppet::Error, "Unknown configuration parameter %s" % param.inspect end unless @order.include?(param) @order << param end @config[param].value = value end return value end # A simplified equality operator. def ==(other) self.each { |myname, myobj| unless other[myname] == myobj.value return false end } return true end # Generate the list of valid arguments, in a format that GetoptLong can # understand, and add them to the passed option list. def addargs(options) require 'getoptlong' # Hackish, but acceptable. Copy the current ARGV for restarting. Puppet.args = ARGV.dup # Add all of the config parameters as valid options. self.each { |param, obj| if self.boolean?(param) options << ["--#{param}", GetoptLong::NO_ARGUMENT] options << ["--no-#{param}", GetoptLong::NO_ARGUMENT] else options << ["--#{param}", GetoptLong::REQUIRED_ARGUMENT] end } return options end # Turn the config into a transaction and apply it def apply trans = self.to_transportable begin comp = trans.to_type trans = comp.evaluate trans.evaluate comp.remove rescue => detail if Puppet[:trace] puts detail.backtrace end Puppet.err "Could not configure myself: %s" % detail end end # Is our parameter a boolean parameter? def boolean?(param) param = symbolize(param) if @config.include?(param) and @config[param].kind_of? CBoolean return true else return false end end # Remove all set values, potentially skipping cli values. def clear(exceptcli = false) @config.each { |name, obj| unless exceptcli and obj.setbycli obj.clear end } # Don't clear the 'used' in this case, since it's a config file reparse, # and we want to retain this info. unless exceptcli @used = [] end end # This is mostly just used for testing. def clearused @used = [] end def each @order.each { |name| if @config.include?(name) yield name, @config[name] else raise Puppet::DevError, "%s is in the order but does not exist" % name end } end # Iterate over each section name. def eachsection yielded = [] @order.each { |name| if @config.include?(name) section = @config[name].section unless yielded.include? section yield section yielded << section end else raise Puppet::DevError, "%s is in the order but does not exist" % name end } end # Return an object by name. def element(param) param = symbolize(param) @config[param] end # Handle a command-line argument. def handlearg(opt, value = nil) value = mungearg(value) if value str = opt.sub(/^--/,'') bool = true newstr = str.sub(/^no-/, '') if newstr != str str = newstr bool = false end if self.valid?(str) if self.boolean?(str) self[str] = bool else self[str] = value end # Mark that this was set on the cli, so it's not overridden if the # config gets reread. @config[str.intern].setbycli = true else raise ArgumentError, "Invalid argument %s" % opt end end def include?(name) name = name.intern if name.is_a? String @config.include?(name) end # Create a new config object def initialize @order = [] @config = {} @created = [] end # Make a directory with the appropriate user, group, and mode def mkdir(default) obj = nil unless obj = @config[default] raise ArgumentError, "Unknown default %s" % default end unless obj.is_a? CFile raise ArgumentError, "Default %s is not a file" % default end - Puppet::Util.asuser(obj.owner, obj.group) do + Puppet::SUIDManager.asuser(obj.owner, obj.group) do mode = obj.mode || 0750 Dir.mkdir(obj.value, mode) end end # Convert arguments appropriately. def mungearg(value) # Handle different data types correctly return case value when /^false$/i: false when /^true$/i: true when /^\d+$/i: Integer(value) else value.gsub(/^["']|["']$/,'') end end # Return all of the parameters associated with a given section. def params(section) section = section.intern if section.is_a? String @config.find_all { |name, obj| obj.section == section }.collect { |name, obj| name } end # Parse a configuration file. def parse(file) text = nil if file.is_a? Puppet::LoadedFile @file = file else @file = Puppet::LoadedFile.new(file) end # Create a timer so that this. settimer() begin text = File.read(@file.file) rescue Errno::ENOENT raise Puppet::Error, "No such file %s" % file rescue Errno::EACCES raise Puppet::Error, "Permission denied to file %s" % file end @values = Hash.new { |names, name| names[name] = {} } # Get rid of the values set by the file, keeping cli values. self.clear(true) section = "puppet" metas = %w{owner group mode} values = Hash.new { |hash, key| hash[key] = {} } text.split(/\n/).each { |line| case line when /^\[(\w+)\]$/: section = $1 # Section names when /^\s*#/: next # Skip comments when /^\s*$/: next # Skip blanks when /^\s*(\w+)\s*=\s*(.+)$/: # settings var = $1.intern value = mungearg($2) # Mmm, "special" attributes if metas.include?(var.to_s) unless values.include?(section) values[section] = {} end values[section][var.to_s] = value # Do some annoying skullduggery here. This is so that # the group can be set in the config file. The problem # is that we're using the word 'group' twice, which is # confusing. if var == :group and section == Puppet.name and @config.include?(:group) @config[:group].value = value end next end # Don't override set parameters, since the file is parsed # after cli arguments are handled. unless @config.include?(var) and @config[var].setbycli Puppet.debug "%s: Setting %s to '%s'" % [section, var, value] self[var] = value end @config[var].section = symbolize(section) metas.each { |meta| if values[section][meta] if @config[var].respond_to?(meta + "=") @config[var].send(meta + "=", values[section][meta]) end end } else raise Puppet::Error, "Could not match line %s" % line end } end # Create a new element. The value is passed in because it's used to determine # what kind of element we're creating, but the value itself might be either # a default or a value, so we can't actually assign it. def newelement(hash) value = hash[:value] || hash[:default] klass = nil if hash[:section] hash[:section] = symbolize(hash[:section]) end case value when true, false, "true", "false": klass = CBoolean when /^\$\w+\//, /^\//: klass = CFile when String, Integer, Float: # nothing klass = CElement else raise Puppet::Error, "Invalid value '%s' for %s" % [value.inspect, hash[:name]] end hash[:parent] = self element = klass.new(hash) @order << element.name return element end # Iterate across all of the objects in a given section. def persection(section) section = symbolize(section) self.each { |name, obj| if obj.section == section yield obj end } end # Reparse our config file, if necessary. def reparse if defined? @file and @file.changed? Puppet.notice "Reparsing %s" % @file.file @@sync.synchronize do parse(@file) end reuse() end end def reuse return unless defined? @used @@sync.synchronize do # yay, thread-safe @used.each do |section| @used.delete(section) self.use(section) end end end # Get a list of objects per section def sectionlist sectionlist = [] self.each { |name, obj| section = obj.section || "puppet" sections[section] ||= [] unless sectionlist.include?(section) sectionlist << section end sections[section] << obj } return sectionlist, sections end # Convert a single section into transportable objects. def section_to_transportable(section, done = nil, includefiles = true) done ||= Hash.new { |hash, key| hash[key] = {} } objects = [] persection(section) do |obj| if @config[:mkusers] and @config[:mkusers].value [:owner, :group].each do |attr| type = nil if attr == :owner type = :user else type = attr end # If a user and/or group is set, then make sure we're # managing that object if obj.respond_to? attr and name = obj.send(attr) # Skip owners and groups we've already done, but tag # them with our section if necessary if done[type].include?(name) tags = done[type][name].tags unless tags.include?(section) done[type][name].tags = tags << section end elsif newobj = Puppet::Type.type(type)[name] unless newobj.state(:ensure) newobj[:ensure] = "present" end newobj.tag(section) else newobj = TransObject.new(name, type.to_s) newobj.tags = ["puppet", "configuration", section] newobj[:ensure] = "present" # Set the group appropriately for the user if type == :user newobj[:gid] = Puppet[:group] end done[type][name] = newobj objects << newobj end end end end if obj.respond_to? :to_transportable next if obj.value =~ /^\/dev/ transobjects = obj.to_transportable transobjects = [transobjects] unless transobjects.is_a? Array transobjects.each do |trans| # transportable could return nil next unless trans unless done[:file].include? trans.name @created << trans.name objects << trans done[:file][trans.name] = trans end end end end bucket = Puppet::TransBucket.new bucket.type = section bucket.push(*objects) bucket.keyword = "class" return bucket end # Set a bunch of defaults in a given section. The sections are actually pretty # pointless, but they help break things up a bit, anyway. def setdefaults(section, defs) section = symbolize(section) defs.each { |name, hash| if hash.is_a? Array tmp = hash hash = {} [:default, :desc].zip(tmp).each { |p,v| hash[p] = v } end name = symbolize(name) hash[:name] = name hash[:section] = section name = hash[:name] if @config.include?(name) raise Puppet::Error, "Parameter %s is already defined" % name end @config[name] = newelement(hash) } end # Create a timer to check whether the file should be reparsed. def settimer if Puppet[:filetimeout] > 0 @timer = Puppet.newtimer( :interval => Puppet[:filetimeout], :tolerance => 1, :start? => true ) do self.reparse() end end end def symbolize(param) case param when String: return param.intern when Symbol: return param else raise ArgumentError, "Invalid param type %s" % param.class end end # Convert our list of objects into a component that can be applied. def to_component transport = self.to_transportable return transport.to_type end # Convert our list of objects into a configuration file. def to_config str = %{The configuration file for #{Puppet.name}. Note that this file is likely to have unused configuration parameters in it; any parameter that's valid anywhere in Puppet can be in any config file, even if it's not used. Every section can specify three special parameters: owner, group, and mode. These parameters affect the required permissions of any files specified after their specification. Puppet will sometimes use these parameters to check its own configured state, so they can be used to make Puppet a bit more self-managing. Note also that the section names are entirely for human-level organizational purposes; they don't provide separate namespaces. All parameters are in a single namespace. Generated on #{Time.now}. }.gsub(/^/, "# ") eachsection do |section| str += "[#{section}]\n" persection(section) do |obj| str += obj.to_config + "\n" end end return str end # Convert our configuration into a list of transportable objects. def to_transportable done = Hash.new { |hash, key| hash[key] = {} } topbucket = Puppet::TransBucket.new if defined? @file.file and @file.file topbucket.name = @file.file else topbucket.name = "configtop" end topbucket.type = "puppetconfig" topbucket.top = true # Now iterate over each section eachsection do |section| topbucket.push section_to_transportable(section, done) end topbucket end # Convert to a parseable manifest def to_manifest transport = self.to_transportable manifest = transport.to_manifest + "\n" eachsection { |section| manifest += "include #{section}\n" } return manifest end # Create the necessary objects to use a section. This is idempotent; # you can 'use' a section as many times as you want. def use(*sections) @@sync.synchronize do # yay, thread-safe unless defined? @used @used = [] end runners = sections.collect { |s| symbolize(s) }.find_all { |s| ! @used.include? s } return if runners.empty? bucket = Puppet::TransBucket.new bucket.type = "puppetconfig" bucket.top = true # Create a hash to keep track of what we've done so far. @done = Hash.new { |hash, key| hash[key] = {} } runners.each do |section| bucket.push section_to_transportable(section, @done, false) end objects = bucket.to_type objects.finalize tags = nil if Puppet[:tags] tags = Puppet[:tags] Puppet[:tags] = "" end trans = objects.evaluate trans.ignoretags = true trans.evaluate if tags Puppet[:tags] = tags end # Remove is a recursive process, so it's sufficient to just call # it on the component. objects.remove(true) objects = nil runners.each { |s| @used << s } end end def valid?(param) param = symbolize(param) @config.has_key?(param) end # Open a file with the appropriate user, group, and mode def write(default, *args) obj = nil unless obj = @config[default] raise ArgumentError, "Unknown default %s" % default end unless obj.is_a? CFile raise ArgumentError, "Default %s is not a file" % default end chown = nil - if Process.uid == 0 + if Puppet::SUIDManager.uid == 0 chown = [obj.owner, obj.group] else chown = [nil, nil] end - Puppet::Util.asuser(*chown) do + Puppet::SUIDManager.asuser(*chown) do mode = obj.mode || 0640 if args.empty? args << "w" end args << mode File.open(obj.value, *args) do |file| yield file end end end # Open a non-default file under a default dir with the appropriate user, # group, and mode def writesub(default, file, *args) obj = nil unless obj = @config[default] raise ArgumentError, "Unknown default %s" % default end unless obj.is_a? CFile raise ArgumentError, "Default %s is not a file" % default end chown = nil - if Process.uid == 0 + if Puppet::SUIDManager.uid == 0 chown = [obj.owner, obj.group] else chown = [nil, nil] end - Puppet::Util.asuser(*chown) do + Puppet::SUIDManager.asuser(*chown) do mode = obj.mode || 0640 if args.empty? args << "w" end args << mode # Update the umask to make non-executable files Puppet::Util.withumask(File.umask ^ 0111) do File.open(file, *args) do |file| yield file end end end end # The base element type. class CElement attr_accessor :name, :section, :default, :parent, :setbycli attr_reader :desc # Unset any set value. def clear @value = nil end def convert(value) return value unless value return value unless value.is_a? String if value =~ /\$(\w+)/ parent = $1 if pval = @parent[parent] newval = value.to_s.sub(/\$#{parent.to_s}/, pval.to_s) #return File.join(newval.split("/")) return newval else raise Puppet::DevError, "Could not find value for %s" % parent end else return value end end def desc=(value) @desc = value.gsub(/^\s*/, '') end # Create the new element. Pretty much just sets the name. def initialize(args = {}) if args.include?(:parent) self.parent = args[:parent] args.delete(:parent) end args.each do |param, value| method = param.to_s + "=" unless self.respond_to? method raise ArgumentError, "%s does not accept %s" % [self.class, param] end self.send(method, value) end end def iscreated @iscreated = true end def iscreated? if defined? @iscreated return @iscreated else return false end end def set? if defined? @value and ! @value.nil? return true else return false end end # Convert the object to a config statement. def to_config str = @desc.gsub(/^/, "# ") + "\n" # Add in a statement about the default. if defined? @default and @default str += "# The default value is '%s'.\n" % @default end line = "%s = %s" % [@name, self.value] # If the value has not been overridden, then print it out commented # and unconverted, so it's clear that that's the default and how it # works. if defined? @value and ! @value.nil? line = "%s = %s" % [@name, self.value] else line = "# %s = %s" % [@name, @default] end str += line + "\n" str.gsub(/^/, " ") end # Retrieves the value, or if it's not set, retrieves the default. def value retval = nil if defined? @value and ! @value.nil? retval = @value elsif defined? @default retval = @default else return nil end if retval.is_a? String return convert(retval) else return retval end end # Set the value. def value=(value) if respond_to?(:validate) validate(value) end if respond_to?(:munge) @value = munge(value) else @value = value end end end # A file. class CFile < CElement attr_writer :owner, :group attr_accessor :mode, :create def group if defined? @group return convert(@group) else return nil end end def owner if defined? @owner return convert(@owner) else return nil end end # Set the type appropriately. Yep, a hack. This supports either naming # the variable 'dir', or adding a slash at the end. def munge(value) if value.to_s =~ /\/$/ @type = :directory return value.sub(/\/$/, '') end return value end # Return the appropriate type. def type value = self.value if @name.to_s =~ /dir/ return :directory elsif value.to_s =~ /\/$/ return :directory elsif value.is_a? String return :file else return nil end end # Convert the object to a TransObject instance. # FIXME There's no dependency system in place right now; if you use # a section that requires another section, there's nothing done to # correct that for you, at the moment. def to_transportable type = self.type return nil unless type path = self.value.split(File::SEPARATOR) path.shift # remove the leading nil objects = [] obj = Puppet::TransObject.new(self.value, "file") # Only create directories, or files that are specifically marked to # create. if type == :directory or self.create obj[:ensure] = type end [:mode].each { |var| if value = self.send(var) obj[var] = "%o" % value end } # Only chown or chgrp when root - if Process.uid == 0 + if Puppet::SUIDManager.uid == 0 [:group, :owner].each { |var| if value = self.send(var) obj[var] = value end } end # And set the loglevel to debug for everything obj[:loglevel] = "debug" if self.section obj.tags += ["puppet", "configuration", self.section, self.name] end objects << obj objects end # Make sure any provided variables look up to something. def validate(value) return true unless value.is_a? String value.scan(/\$(\w+)/) { |name| name = $1 unless @parent.include?(name) raise ArgumentError, "Configuration parameter '%s' is undefined" % name end } end end # A simple boolean. class CBoolean < CElement def munge(value) case value when true, "true": return true when false, "false": return false else raise Puppet::Error, "Invalid value '%s' for %s" % [value.inspect, @name] end end end end end # $Id$ diff --git a/lib/puppet/filetype.rb b/lib/puppet/filetype.rb index 081448f33..d05c1469d 100755 --- a/lib/puppet/filetype.rb +++ b/lib/puppet/filetype.rb @@ -1,302 +1,302 @@ module Puppet # Basic classes for reading, writing, and emptying files. Not much # to see here. class 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 def initialize(path) @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) 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 == Process.uid + if @uid == Puppet::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.asuser(@path) { + Puppet::SUIDManager.asuser(@path) { %x{crontab -l 2>/dev/null} } end # Remove a specific @path's cron tab. def remove - Puppet::Util.asuser(@path) { + Puppet::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.asuser(@path) { + Puppet::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 end # $Id$ diff --git a/lib/puppet/sslcertificates/ca.rb b/lib/puppet/sslcertificates/ca.rb index b1c5b34e6..19ea27228 100644 --- a/lib/puppet/sslcertificates/ca.rb +++ b/lib/puppet/sslcertificates/ca.rb @@ -1,457 +1,457 @@ class Puppet::SSLCertificates::CA include Puppet::Util::Warnings Certificate = Puppet::SSLCertificates::Certificate attr_accessor :keyfile, :file, :config, :dir, :cert, :crl Puppet.setdefaults(:ca, :cadir => { :default => "$ssldir/ca", :owner => "$user", :group => "$group", :mode => 0770, :desc => "The root directory for the certificate authority." }, :cacert => { :default => "$cadir/ca_crt.pem", :owner => "$user", :group => "$group", :mode => 0660, :desc => "The CA certificate." }, :cakey => { :default => "$cadir/ca_key.pem", :owner => "$user", :group => "$group", :mode => 0660, :desc => "The CA private key." }, :capub => { :default => "$cadir/ca_pub.pem", :owner => "$user", :group => "$group", :desc => "The CA public key." }, :cacrl => { :default => "$cadir/ca_crl.pem", :owner => "$user", :group => "$group", :mode => 0664, :desc => "The certificate revocation list (CRL) for the CA. Set this to 'none' if you do not want to use a CRL." }, :caprivatedir => { :default => "$cadir/private", :owner => "$user", :group => "$group", :mode => 0770, :desc => "Where the CA stores private certificate information." }, :csrdir => { :default => "$cadir/requests", :owner => "$user", :group => "$group", :desc => "Where the CA stores certificate requests" }, :signeddir => { :default => "$cadir/signed", :owner => "$user", :group => "$group", :mode => 0770, :desc => "Where the CA stores signed certificates." }, :capass => { :default => "$caprivatedir/ca.pass", :owner => "$user", :group => "$group", :mode => 0660, :desc => "Where the CA stores the password for the private key" }, :serial => { :default => "$cadir/serial", :owner => "$user", :group => "$group", :desc => "Where the serial number for certificates is stored." }, :autosign => { :default => "$confdir/autosign.conf", :mode => 0644, :desc => "Whether to enable autosign. Valid values are true (which autosigns any key request, and is a very bad idea), false (which never autosigns any key request), and the path to a file, which uses that configuration file to determine which keys to sign."}, :ca_days => ["", "How long a certificate should be valid. This parameter is deprecated, use ca_ttl instead"], :ca_ttl => ["5y", "The default TTL for new certificates; valid values must be an integer, optionally followed by one of the units 'y' (years of 365 days), 'd' (days), 'h' (hours), or 's' (seconds). The unit defaults to seconds. If this parameter is set, ca_days is ignored. Examples are '3600' (one hour) and '1825d', which is the same as '5y' (5 years) "], :ca_md => ["md5", "The type of hash used in certificates."], :req_bits => [2048, "The bit length of the certificates."], :keylength => [1024, "The bit length of keys."] ) def certfile @config[:cacert] end # TTL for new certificates in seconds. If config param :ca_ttl is set, # use that, otherwise use :ca_days for backwards compatibility def ttl days = @config[:ca_days] if days && days.size > 0 warnonce "Parameter ca_ttl is not set. Using depecated ca_days instead." return @config[:ca_days] * 24 * 60 * 60 else ttl = @config[:ca_ttl] if ttl.is_a?(String) unless ttl =~ /^(\d+)(y|d|h|s)$/ raise ArgumentError, "Invalid ca_ttl #{ttl}" end case $2 when 'y' unit = 365 * 24 * 60 * 60 when 'd' unit = 24 * 60 * 60 when 'h' unit = 60 * 60 when 's' unit = 1 else raise ArgumentError, "Invalid unit for ca_ttl #{ttl}" end return $1.to_i * unit else return ttl end end end # Remove all traces of a given host. This is kind of hackish, but, eh. def clean(host) [:csrdir, :signeddir, :publickeydir, :privatekeydir, :certdir].each do |name| dir = Puppet[name] file = File.join(dir, host + ".pem") if FileTest.exists?(file) begin if Puppet.name == "puppetca" puts "Removing %s" % file else Puppet.info "Removing %s" % file end File.unlink(file) rescue => detail raise Puppet::Error, "Could not delete %s: %s" % [file, detail] end end end end def host2csrfile(hostname) File.join(Puppet[:csrdir], [hostname, "pem"].join(".")) end # this stores signed certs in a directory unrelated to # normal client certs def host2certfile(hostname) File.join(Puppet[:signeddir], [hostname, "pem"].join(".")) end # Turn our hostname into a Name object def thing2name(thing) thing.subject.to_a.find { |ary| ary[0] == "CN" }[1] end def initialize(hash = {}) Puppet.config.use(:puppet, :certificates, :ca) self.setconfig(hash) if Puppet[:capass] if FileTest.exists?(Puppet[:capass]) #puts "Reading %s" % Puppet[:capass] #system "ls -al %s" % Puppet[:capass] #File.read Puppet[:capass] @config[:password] = self.getpass else # Don't create a password if the cert already exists unless FileTest.exists?(@config[:cacert]) @config[:password] = self.genpass end end end self.getcert init_crl unless FileTest.exists?(@config[:serial]) Puppet.config.write(:serial) do |f| f << "%04X" % 1 end end end # Generate a new password for the CA. def genpass pass = "" 20.times { pass += (rand(74) + 48).chr } begin Puppet.config.write(:capass) { |f| f.print pass } rescue Errno::EACCES => detail raise Puppet::Error, detail.to_s end return pass end # Get the CA password. def getpass if @config[:capass] and File.readable?(@config[:capass]) return File.read(@config[:capass]) else raise Puppet::Error, "Could not read CA passfile %s" % @config[:capass] end end # Get the CA cert. def getcert if FileTest.exists?(@config[:cacert]) @cert = OpenSSL::X509::Certificate.new( File.read(@config[:cacert]) ) else self.mkrootcert end end # Retrieve a client's CSR. def getclientcsr(host) csrfile = host2csrfile(host) unless File.exists?(csrfile) return nil end return OpenSSL::X509::Request.new(File.read(csrfile)) end # Retrieve a client's certificate. def getclientcert(host) certfile = host2certfile(host) unless File.exists?(certfile) return [nil, nil] end return [OpenSSL::X509::Certificate.new(File.read(certfile)), @cert] end # List certificates waiting to be signed. def list return Dir.entries(Puppet[:csrdir]).find_all { |file| file =~ /\.pem$/ }.collect { |file| file.sub(/\.pem$/, '') } end # Create the root certificate. def mkrootcert # Make the root cert's name the FQDN of the host running the CA. name = Facter["hostname"].value if domain = Facter["domain"].value name += "." + domain end cert = Certificate.new( :name => name, :cert => @config[:cacert], :encrypt => @config[:capass], :key => @config[:cakey], :selfsign => true, :ttl => ttl, :type => :ca ) # This creates the cakey file - Puppet::Util.asuser(Puppet[:user], Puppet[:group]) do + Puppet::SUIDManager.asuser(Puppet[:user], Puppet[:group]) do @cert = cert.mkselfsigned end Puppet.config.write(:cacert) do |f| f.puts @cert.to_pem end return cert end def removeclientcsr(host) csrfile = host2csrfile(host) unless File.exists?(csrfile) raise Puppet::Error, "No certificate request for %s" % host end File.unlink(csrfile) end # Take the Puppet config and store it locally. def setconfig(hash) @config = {} Puppet.config.params("ca").each { |param| param = param.intern if param.is_a? String if hash.include?(param) @config[param] = hash[param] Puppet[param] = hash[param] hash.delete(param) else @config[param] = Puppet[param] end } if hash.include?(:password) @config[:password] = hash[:password] hash.delete(:password) end if hash.length > 0 raise ArgumentError, "Unknown parameters %s" % hash.keys.join(",") end [:cadir, :csrdir, :signeddir].each { |dir| unless @config[dir] raise Puppet::DevError, "%s is undefined" % dir end } end # Sign a given certificate request. def sign(csr) unless csr.is_a?(OpenSSL::X509::Request) raise Puppet::Error, "CA#sign only accepts OpenSSL::X509::Request objects, not %s" % csr.class end unless csr.verify(csr.public_key) raise Puppet::Error, "CSR sign verification failed" end serial = File.read(@config[:serial]).chomp.hex newcert = Puppet::SSLCertificates.mkcert( :type => :server, :name => csr.subject, :ttl => ttl, :issuer => @cert, :serial => serial, :publickey => csr.public_key ) # increment the serial Puppet.config.write(:serial) do |f| f << "%04X" % (serial + 1) end sign_with_key(newcert) self.storeclientcert(newcert) return [newcert, @cert] end # Store the client's CSR for later signing. This is called from # server/ca.rb, and the CSRs are deleted once the certificate is actually # signed. def storeclientcsr(csr) host = thing2name(csr) csrfile = host2csrfile(host) if File.exists?(csrfile) raise Puppet::Error, "Certificate request for %s already exists" % host end Puppet.config.writesub(:csrdir, csrfile) do |f| f.print csr.to_pem end end # Revoke the certificate with serial number SERIAL issued by this # CA. The REASON must be one of the OpenSSL::OCSP::REVOKED_* reasons def revoke(serial, reason = OpenSSL::OCSP::REVOKED_STATUS_KEYCOMPROMISE) if @config[:cacrl] == 'none' raise Puppet::Error, "Revocation requires a CRL, but ca_crl is set to 'none'" end time = Time.now revoked = OpenSSL::X509::Revoked.new revoked.serial = serial revoked.time = time enum = OpenSSL::ASN1::Enumerated(reason) ext = OpenSSL::X509::Extension.new("CRLReason", enum) revoked.add_extension(ext) @crl.add_revoked(revoked) store_crl end # Store the certificate that we generate. def storeclientcert(cert) host = thing2name(cert) certfile = host2certfile(host) if File.exists?(certfile) Puppet.notice "Overwriting signed certificate %s for %s" % [certfile, host] end Puppet::SSLCertificates::Inventory::add(cert) Puppet.config.writesub(:signeddir, certfile) do |f| f.print cert.to_pem end end private def init_crl if FileTest.exists?(@config[:cacrl]) @crl = OpenSSL::X509::CRL.new( File.read(@config[:cacrl]) ) elsif @config[:cacrl] == 'none' @crl = nil else # Create new CRL @crl = OpenSSL::X509::CRL.new @crl.issuer = @cert.subject @crl.version = 1 store_crl @crl end end def store_crl # Increment the crlNumber e = @crl.extensions.find { |e| e.oid == 'crlNumber' } ext = @crl.extensions.reject { |e| e.oid == 'crlNumber' } crlNum = OpenSSL::ASN1::Integer(e ? e.value.to_i + 1 : 0) ext << OpenSSL::X509::Extension.new("crlNumber", crlNum) @crl.extensions = ext # Set last/next update now = Time.now @crl.last_update = now # Keep CRL valid for 5 years @crl.next_update = now + 5 * 365*24*60*60 sign_with_key(@crl) Puppet.config.write(:cacrl) do |f| f.puts @crl.to_pem end end def sign_with_key(signable, digest = OpenSSL::Digest::SHA1.new) cakey = nil if @config[:password] cakey = OpenSSL::PKey::RSA.new( File.read(@config[:cakey]), @config[:password] ) else cakey = OpenSSL::PKey::RSA.new( File.read(@config[:cakey]) ) end unless @cert.check_private_key(cakey) raise Puppet::Error, "CA Certificate is invalid" end signable.sign(cakey, digest) end end # $Id$ diff --git a/lib/puppet/suidmanager.rb b/lib/puppet/suidmanager.rb new file mode 100644 index 000000000..2f4d428e3 --- /dev/null +++ b/lib/puppet/suidmanager.rb @@ -0,0 +1,74 @@ +require 'facter' +require 'puppet' + +module Puppet + module SUIDManager + platform = Facter["kernel"].value + [:uid=, :uid, :gid=, :gid].each do |method| + define_method(method) do |*args| + if platform == "Darwin" and (Facter['rubyversion'] <=> "1.8.5") < 0 + Puppet.warning "Cannot change real UID on Darwin on Ruby versions earlier than 1.8.5" + method = ("e" + method.to_s).intern unless method.to_s[0] == 'e' + end + + return Process.send(method, *args) + end + module_function method + end + + [:euid=, :euid, :egid=, :egid].each do |method| + define_method(method) do |*args| + Process.send(method, *args) + end + module_function method + end + + def run_and_capture(command, new_uid=self.euid, new_gid=self.egid) + output = nil + + asuser(new_uid, new_gid) do + # capture both stdout and stderr unless we are on ruby < 1.8.4 + # NOTE: this would be much better facilitated with a specialized popen() + # (see the test suite for more details.) + if (Facter['rubyversion'].value <=> "1.8.4") < 0 + unless @@alreadywarned + Puppet.warning "Cannot capture STDERR when running as another user on Ruby < 1.8.4" + @@alreadywarned = true + end + output = %x{#{command}} + else + output = %x{#{command} 2>&1} + end + end + + [output, $?.dup] + end + + module_function :run_and_capture + + def system(command, new_uid=self.euid, new_gid=self.egid) + asuser(new_uid, new_gid) do + Kernel.system(command) + end + end + + module_function :system + + def asuser(new_euid, new_egid) + new_euid = Puppet::Util.uid(new_euid) + new_egid = Puppet::Util.uid(new_egid) + + old_euid, old_egid = [ self.euid, self.egid ] + self.egid = new_egid ? new_egid : old_egid + self.euid = new_euid ? new_euid : old_euid + output = yield + self.egid = old_egid + self.euid = old_euid + + output + end + + module_function :asuser + end +end + diff --git a/lib/puppet/type/exec.rb b/lib/puppet/type/exec.rb index 442eb311f..8b964cbb3 100755 --- a/lib/puppet/type/exec.rb +++ b/lib/puppet/type/exec.rb @@ -1,586 +1,569 @@ module Puppet newtype(:exec) do include Puppet::Util::Execution @doc = "Executes external commands. It is critical that all commands executed using this mechanism can be run multiple times without harm, i.e., they are *idempotent*. One useful way to create idempotent commands is to use the *creates* parameter. It is worth noting that ``exec`` is special, in that it is not currently considered an error to have multiple ``exec`` instances with the same name. This was done purely because it had to be this way in order to get certain functionality, but it complicates things. In particular, you will not be able to use ``exec`` instances that share their commands with other instances as a dependency, since Puppet has no way of knowing which instance you mean. For example: # defined in the production class exec { \"make\": cwd => \"/prod/build/dir\", path => \"/usr/bin:/usr/sbin:/bin\" } . etc. . # defined in the test class exec { \"make\": cwd => \"/test/build/dir\", path => \"/usr/bin:/usr/sbin:/bin\" } Any other type would throw an error, complaining that you had the same instance being managed in multiple places, but these are obviously different images, so ``exec`` had to be treated specially. It is recommended to avoid duplicate names whenever possible. There is a strong tendency to use ``exec`` to do whatever work Puppet can't already do; while this is obviously acceptable (and unavoidable) in the short term, it is highly recommended to migrate work from ``exec`` to real Puppet element types as quickly as possible. If you find that you are doing a lot of work with ``exec``, please at least notify us at Reductive Labs what you are doing, and hopefully we can work with you to get a native element type for the work you are doing. In general, it is a Puppet bug if you need ``exec`` to do your work." require 'open3' require 'puppet/type/state' # Create a new check mechanism. It's basically just a parameter that # provides one extra 'check' method. def self.newcheck(name, &block) @checks ||= {} check = newparam(name, &block) @checks[name] = check end def self.checks @checks.keys end newstate(:returns) do |state| include Puppet::Util::Execution munge do |value| value.to_s end defaultto "0" attr_reader :output desc "The expected return code. An error will be returned if the executed command returns something else. Defaults to 0." # Make output a bit prettier def change_to_s return "executed successfully" end # Verify that we have the executable def checkexe cmd = self.parent[:command] if cmd =~ /^\// exe = cmd.split(/ /)[0] unless FileTest.exists?(exe) self.fail( "Could not find executable %s" % exe ) end unless FileTest.executable?(exe) self.fail( "%s is not executable" % exe ) end elsif path = self.parent[:path] exe = cmd.split(/ /)[0] withenv :PATH => self.parent[:path].join(":") do path = %{which #{exe}}.chomp if path == "" self.fail( "Could not find command '%s'" % exe ) end end else self.fail( "%s is somehow not qualified with no search path" % self.parent[:command] ) end end # First verify that all of our checks pass. def retrieve # Default to somethinng if @parent.check self.is = :notrun else self.is = self.should end end # Actually execute the command. def sync olddir = nil self.checkexe # We need a dir to change to, even if it's just the cwd dir = self.parent[:cwd] || Dir.pwd event = :executed_command @output, status = @parent.run(self.parent[:command]) loglevel = @parent[:loglevel] if status.exitstatus.to_s != self.should.to_s self.fail("%s returned %s instead of %s" % [self.parent[:command], status.exitstatus, self.should.to_s]) end if log = @parent[:logoutput] if log == :true log = @parent[:loglevel] end unless log == :false @output.split(/\n/).each { |line| self.send(log, line) } end end return event end end newparam(:command) do isnamevar desc "The actual command to execute. Must either be fully qualified or a search path for the command must be provided. If the command succeeds, any output produced will be logged at the instance's normal log level (usually ``notice``), but if the command fails (meaning its return code does not match the specified code) then any output is logged at the ``err`` log level." end newparam(:path) do desc "The search path used for command execution. Commands must be fully qualified if no path is specified. Paths can be specified as an array or as a colon-separated list." # Support both arrays and colon-separated fields. def value=(*values) @value = values.collect { |val| val.split(":") }.flatten end end newparam(:user) do desc "The user to run the command as. Note that if you use this then any error output is not currently captured. This is because of a bug within Ruby." munge do |user| - unless Process.uid == 0 + unless Puppet::SUIDManager.uid == 0 self.fail "Only root can execute commands as other users" end require 'etc' method = :getpwnam case user when Integer method = :getpwuid when /^\d+$/ user = user.to_i method = :getpwuid end begin Etc.send(method, user) rescue ArgumentError self.fail "No such user %s" % user end return user end end newparam(:group) do desc "The group to run the command as. This seems to work quite haphazardly on different platforms -- it is a platform issue not a Ruby or Puppet one, since the same variety exists when running commnands as different users in the shell." # Execute the command as the specified group munge do |group| require 'etc' method = :getgrnam case group when Integer: method = :getgrgid when /^\d+$/ group = group.to_i method = :getgrgid end begin Etc.send(method, group) rescue ArgumentError self.fail "No such group %s" % group end group end end newparam(:cwd) do desc "The directory from which to run the command. If this directory does not exist, the command will fail." validate do |dir| unless dir =~ /^#{File::SEPARATOR}/ self.fail("CWD must be a fully qualified path") end end munge do |dir| if dir.is_a?(Array) dir = dir[0] end dir end end newparam(:logoutput) do desc "Whether to log output. Defaults to logging output at the loglevel for the ``exec`` element. Values are **true**, *false*, and any legal log level." values = [:true, :false] # And all of the log levels Puppet::Log.eachlevel { |level| values << level } newvalues(*values) end newparam(:env) do desc "Any additional environment variables you want to set for a command. Note that if you use this to set PATH, it will override the ``path`` attribute. Multiple environment variables should be specified as an array." validate do |values| values = [values] unless values.is_a? Array values.each do |value| unless value =~ /\w+=/ raise ArgumentError, "Invalid environment setting '%s'" % value end end end end newcheck(:refreshonly) do desc "The command should only be run as a refresh mechanism for when a dependent object is changed. It only makes sense to use this option when this command depends on some other object; it is useful for triggering an action: # Pull down the main aliases file file { \"/etc/aliases\": source => \"puppet://server/module/aliases\" } # Rebuild the database, but only when the file changes exec { newaliases: path => [\"/usr/bin\", \"/usr/sbin\"], subscribe => file[\"/etc/aliases\"], refreshonly => true } Note that only ``subscribe`` can trigger actions, not ``require``, so it only makes sense to use ``refreshonly`` with ``subscribe``." newvalues(:true, :false) # We always fail this test, because we're only supposed to run # on refresh. def check(value) false end end newcheck(:creates) do desc "A file that this command creates. If this parameter is provided, then the command will only be run if the specified file does not exist. exec { \"tar xf /my/tar/file.tar\": cwd => \"/var/tmp\", creates => \"/var/tmp/myfile\", path => [\"/usr/bin\", \"/usr/sbin\"] } " # FIXME if they try to set this and fail, then we should probably # fail the entire exec, right? validate do |files| files = [files] unless files.is_a? Array files.each do |file| self.fail("'creates' must be set to a fully qualified path") unless file unless file =~ %r{^#{File::SEPARATOR}} self.fail "'creates' files must be fully qualified." end end end # If the file exists, return false (i.e., don't run the command), # else return true def check(value) return ! FileTest.exists?(value) end end newcheck(:unless) do desc "If this parameter is set, then this ``exec`` will run unless the command returns 0. For example: exec { \"/bin/echo root >> /usr/lib/cron/cron.allow\": path => \"/usr/bin:/usr/sbin:/bin\", unless => \"grep root /usr/lib/cron/cron.allow 2>/dev/null\" } This would add ``root`` to the cron.allow file (on Solaris) unless ``grep`` determines it's already there. Note that this command follows the same rules as the main command, which is to say that it must be fully qualified if the path is not set. " validate do |cmds| cmds = [cmds] unless cmds.is_a? Array cmds.each do |cmd| @parent.validatecmd(cmd) end end # Return true if the command does not return 0. def check(value) output, status = @parent.run(value, true) return status.exitstatus != 0 end end newcheck(:onlyif) do desc "If this parameter is set, then this ``exec`` will only run if the command returns 0. For example: exec { \"logrotate\": path => \"/usr/bin:/usr/sbin:/bin\", onlyif => \"test `du /var/log/messages | cut -f1` -gt 100000\" } This would run ``logrotate`` only if that test returned true. Note that this command follows the same rules as the main command, which is to say that it must be fully qualified if the path is not set. " validate do |cmds| cmds = [cmds] unless cmds.is_a? Array cmds.each do |cmd| @parent.validatecmd(cmd) end end # Return true if the command returns 0. def check(value) output, status = @parent.run(value, true) return status.exitstatus == 0 end end # Exec names are not isomorphic with the objects. @isomorphic = false validate do validatecmd(self[:command]) end # FIXME exec should autorequire any exec that 'creates' our cwd autorequire(:file) do reqs = [] # Stick the cwd in there if we have it if self[:cwd] reqs << self[:cwd] end self[:command].scan(/^(#{File::SEPARATOR}\S+)/) { |str| reqs << str } [:onlyif, :unless].each { |param| next unless tmp = self[param] tmp = [tmp] unless tmp.is_a? Array tmp.each do |line| # And search the command line for files, adding any we # find. This will also catch the command itself if it's # fully qualified. It might not be a bad idea to add # unqualified files, but, well, that's a bit more annoying # to do. reqs += line.scan(%r{(#{File::SEPARATOR}\S+)}) end } # For some reason, the += isn't causing a flattening reqs.flatten! reqs end def self.list self.collect { |i| i } end # Verify that we pass all of the checks. def check self.class.checks.each { |check| if @parameters.include?(check) val = @parameters[check].value val = [val] unless val.is_a? Array val.each do |value| unless @parameters[check].check(value) return false end end end } return true end def output if self.state(:returns).nil? return nil else return self.state(:returns).output end end # this might be a very, very bad idea... def refresh self.state(:returns).sync end # Run a command. def run(command, check = false) output = nil status = nil dir = nil if dir = self[:cwd] unless File.directory?(dir) if check dir = nil else self.fail "Working directory '%s' does not exist" % dir end end end dir ||= Dir.pwd if check debug "Executing check '#{command}'" else debug "Executing '#{command}'" end begin # Do our chdir Dir.chdir(dir) do env = {} if self[:path] env[:PATH] = self[:path].join(":") end if envlist = self[:env] envlist = [envlist] unless envlist.is_a? Array envlist.each do |setting| if setting =~ /^(\w+)=((.|\n)+)$/ name = $1 value = $2 if env.include? name warning( "Overriding environment setting '%s' with '%s'" % [name, value] ) end env[name] = value else warning "Cannot understand env setting '%s'" % setting end end end withenv env do - # The user and group default to nil, which 'asuser' - # handlers correctly - Puppet::Util.asuser(self[:user], self[:group]) { - # capture both stdout and stderr - if self[:user] - unless defined? @@alreadywarned - Puppet.warning( - "Cannot capture STDERR when running as another user" - ) - @@alreadywarned = true - end - output = %x{#{command}} - else - output = %x{#{command} 2>&1} - end - } - status = $?.dup - + output, status = Puppet::SUIDManager.run_and_capture(command, self[:user], self[:group]) # The shell returns 127 if the command is missing. - if $?.exitstatus == 127 + if status.exitstatus == 127 raise ArgumentError, output end end end rescue Errno::ENOENT => detail self.fail detail.to_s end return output, status end def to_s "exec(%s)" % self.name end def validatecmd(cmd) # if we're not fully qualified, require a path if cmd !~ /^\// if self[:path].nil? self.fail "both unqualifed and specified no search path" end end end end end # $Id$ diff --git a/lib/puppet/type/pfile.rb b/lib/puppet/type/pfile.rb index 7ce384077..5d7a3e881 100644 --- a/lib/puppet/type/pfile.rb +++ b/lib/puppet/type/pfile.rb @@ -1,993 +1,993 @@ require 'digest/md5' require 'cgi' require 'etc' require 'uri' require 'fileutils' require 'puppet/type/state' require 'puppet/server/fileserver' module Puppet newtype(:file) do @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`` element will be used less and less to manage content, and instead native elements will be used to do so. If you find that you are often copying files in from a central location, rather than using native elements, please contact Reductive Labs and we can hopefully work with you to develop a native element 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. If a filebucket is specified, files will be backed up there; else, they will be backed up in the same directory with a ``.puppet-bak`` extension,, and no backups will be made if backup is ``false``. To use filebuckets, you must first create a filebucket 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 only benefits to doing so 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. " attr_reader :bucket defaultto ".puppet-bak" munge do |value| case value when false, "false", :false: false when true, "true", ".puppet-bak", :true: ".puppet-bak" 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. @bucket = value value else self.fail "Invalid backup type %s" % value.inspect end end # Provide a straight-through hook for setting the bucket. def bucket=(bucket) @value = bucket @bucket = bucket end end newparam(:linkmaker) do desc "An internal parameter used by the *symlink* type to do recursive link creation." end newparam(:recurse) do desc "Whether and how deeply to do recursive management." newvalues(:true, :false, :inf, /^[0-9]+$/) munge do |value| newval = super(value) case newval when :true, :inf: true when :false: false else newval end end end newparam(:replace) 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) defaultto :true end newparam(:force) 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) 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." defaultto :false newvalues(:true, :false) end autorequire(:file) do cur = [] pary = self[:path].split(File::SEPARATOR) pary.shift # remove the initial nil pary.pop # remove us pary.inject([""]) do |ary, dir| ary << dir cur << ary.join(File::SEPARATOR) ary end cur end validate do if self[:content] and self[:source] self.fail "You cannot specify both content and a source" end end # List files, but only one level deep. def self.list(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.asuser(self.should(:owner)) { + writeable = Puppet::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' state 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 ||= {} # Look up our bucket, if there is one if @parameters.include?(:backup) and bucket = @parameters[:backup].bucket case bucket when String: if obj = @@filebuckets[bucket] # This sets the @value on :backup, too @parameters[:backup].bucket = obj elsif obj = Puppet.type(:filebucket).bucket(bucket) @@filebuckets[bucket] = obj @parameters[:backup].bucket = obj else self.fail "Could not find filebucket %s" % bucket end when Puppet::Client::Dipper: # things are hunky-dorey else self.fail "Invalid bucket type %s" % bucket.class end end super 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[:backup] case backup when Puppet::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 require 'fileutils' FileUtils.rmtree(self[:path]) return true when String: newfile = file + backup # Just move it, since it's a directory. if FileTest.directory?(newfile) raise Puppet::Error, "Will not replace directory backup; use a filebucket" elsif FileTest.exists?(newfile) 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 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[:backup] case backup when Puppet::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) begin File.unlink(newfile) rescue => detail self.err "Could not remove old backup: %s" % detail return false end 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 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 # 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) if @arghash.include?(:source) @arghash.delete(:source) end if @arghash.include?(:parent) @arghash.delete(:parent) end @stat = nil end # Create a new file or directory object as a child to the current # object. def newchild(path, local, hash = {}) # make local copy of arguments args = @arghash.dup 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 klass = nil # We specifically look in @parameters here, because 'linkmaker' isn't # a valid attribute for subclasses, so using 'self[:linkmaker]' throws # an error. if @parameters.include?(:linkmaker) and args.include?(:source) and ! FileTest.directory?(args[:source]) klass = Puppet.type(:symlink) # clean up the args a lot for links old = args.dup args = { :ensure => old[:source], :path => path } else klass = self.class end # 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 = klass[path] unless @children.include?(child) 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 else # create it anew #notice "Creating new file with args %s" % args.inspect args[:parent] = self begin child = klass.implicitcreate(args) # implicit creation can return nil if child.nil? return nil end @children << child rescue Puppet::Error => detail self.notice( "Cannot manage: %s" % [detail.message] ) self.debug args.inspect child = nil rescue => detail self.notice( "Cannot manage: %s" % [detail] ) self.debug args.inspect child = nil end end return child end # Paths are special for files, because we don't actually want to show # the parent's full path. def path unless defined? @path if defined? @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 ppath = @parent.path.sub(/\/?file=.+/, '') @path = [] if ppath != "/" and ppath != "" @path << ppath end @path << self.class.name.to_s + "=" + self.name else super end else # The top-level name is always puppet[top], so we don't # bother with that. And we don't add the hostname # here, it gets added in the log server thingy. if self.name == "puppet[top]" @path = ["/"] else # We assume that if we don't have a parent that we # should not cache the path @path = [self.class.name.to_s + "=" + self.name] end end end return @path.join("/") end # Recurse into the directory. This basically just calls 'localrecurse' # and maybe 'sourcerecurse'. def 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) #elsif recurse =~ /^inf/ # infinite recursion else # anything else is infinite recursion recurse = true end end # are we at the end of the recursion? #if recurse == 0 unless self.recurse? return end if recurse.is_a?(Integer) recurse -= 1 end self.localrecurse(recurse) if @states.include? :target self.linkrecurse(recurse) end if @states.include?(:source) self.sourcerecurse(recurse) end end 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 # Build a recursive map of a link source def linkrecurse(recurse) target = @states[:target].should method = :lstat if self[:links] == :follow method = :stat end targetstat = nil unless FileTest.exist?(target) #self.info "%s does not exist; not recursing" % # target return end # Now stat our target targetstat = File.send(method, target) unless targetstat.ftype == "directory" #self.info "%s is not a directory; not recursing" % # target 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) unless @children.include?(child) self.push child added.push file end end end end 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) # Mark any unmanaged files for removal if purge is set. # Use the array rather than [] because tidy uses this method, too. if @parameters.include?(:purge) and self[:purge] == :true and child.implicit? child[:ensure] = :absent end unless @children.include?(child) self.push child added.push file end end } end # This recurses against the remote source and makes sure the local # and remote structures match. It's run after 'localrecurse'. def sourcerecurse(recurse) # FIXME sourcerecurse should support purging non-remote files source = @states[:source].source unless ! source.nil? and source !~ /^\s*$/ self.notice "source %s does not exist" % @states[:source].should return nil end sourceobj, path = uri2obj(source) # we'll set this manually as necessary if @arghash.include?(:ensure) @arghash.delete(:ensure) end # 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 sum = "md5" if state = self.state(:checksum) sum = state.checktype end r = false if recurse unless recurse == 0 r = 1 end end ignore = self[:ignore] desc = server.list(path, self[:links], r, ignore) # Now create a new child for every file returned in the list. desc.split("\n").each { |line| file, type = line.split("\t") next if file == "/" # skip the listing object name = file.sub(/^\//, '') args = {:source => source + file} if type == file args[:recurse] = nil end self.newchild(name, false, args) } end # a wrapper method to make sure the file exists before doing anything def retrieve if @states.include?(:source) # This probably isn't the best place for it, but we need # to make sure that we have a corresponding checksum state. unless @states.include?(:checksum) self[:checksum] = "md5" end # We have to retrieve the source info before the recursion happens, # although I'm not exactly clear on why. @states[:source].retrieve end if @parameters.include?(:recurse) self.recurse end unless stat = self.stat(true) self.debug "File does not exist" @states.each { |name,state| # We've already retrieved the source, and we don't # want to overwrite whatever it did. This is a bit # of a hack, but oh well, source is definitely special. next if name == :source state.is = :absent } return end states().each { |state| # We don't want to call 'describe()' twice, so only do a local # retrieve on the source. if state.name == :source state.retrieve(false) else state.retrieve end } end # Set the checksum, from another state. There are multiple states 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 @states.include? :checksum if sum @states[:checksum].checksum = sum else # If they didn't pass in a sum, then tell checksum to # figure it out. @states[:checksum].retrieve @states[:checksum].checksum = @states[:checksum].is end end end # Stat our file. Depending on the value of the 'links' attribute, we use # either 'stat' or 'lstat', and we expect the states 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 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 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": unless defined? @@localfileserver @@localfileserver = Puppet::Server::FileServer.new( :Local => true, :Mount => { "/" => "localhost" }, :Config => false ) #@@localfileserver.mount("/", "localhost") end sourceobj.server = @@localfileserver path = "/localhost" + uri.path when "puppet": args = { :Server => uri.host } if uri.port args[:Port] = uri.port end # FIXME We should cache a copy of this server #sourceobj.server = Puppet::NetworkClient.new(args) unless @clients.include?(source) @clients[source] = Puppet::Client::FileClient.new(args) 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 recursive file proto %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(usetmp = true) mode = self.should(:mode) #if FileTest.exists?(self[:path]) if s = stat(false) # this makes sure we have a copy for posterity @backed = self.handlebackup if s.ftype == "link" and self[:links] != :follow # Remove existing links, since we're writing out a file File.unlink(self[:path]) end end # The temporary file path = nil if usetmp path = self[:path] + ".puppettmp" else path = self[:path] end # As the correct user and group - Puppet::Util.asuser(asuser(), self.should(:group)) do + Puppet::SUIDManager.asuser(asuser(), self.should(:group)) 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 # 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 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 states 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 state list. 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' require 'puppet/type/pfile/ensure' # can create the file require 'puppet/type/pfile/uid' require 'puppet/type/pfile/group' require 'puppet/type/pfile/mode' require 'puppet/type/pfile/type' end # $Id$ diff --git a/lib/puppet/type/pfile/ensure.rb b/lib/puppet/type/pfile/ensure.rb index ac045dfd6..2e48e0165 100755 --- a/lib/puppet/type/pfile/ensure.rb +++ b/lib/puppet/type/pfile/ensure.rb @@ -1,176 +1,176 @@ module Puppet Puppet.type(:file).ensurable do require 'etc' desc "Whether to create files that don't currently exist. Possible values are *absent*, *present* (equivalent to ``exists`` in most file tests -- will match any form of file existence, and if the file is missing will create an empty file), *file*, and *directory*. Specifying ``absent`` will delete the file, although currently this will not recursively delete directories. Anything other than those values will be considered to be a symlink. For instance, the following text creates a link: # Useful on solaris file { \"/etc/inetd.conf\": ensure => \"/etc/inet/inetd.conf\" } You can make relative links: # Useful on solaris file { \"/etc/inetd.conf\": ensure => \"inet/inetd.conf\" } If you need to make a relative link to a file named the same as one of the valid values, you must prefix it with ``./`` or something similar. You can also make recursive symlinks, which will create a directory structure that maps to the target directory, with directories corresponding to each directory and links corresponding to each file." # Most 'ensure' states have a default, but with files we, um, don't. nodefault newvalue(:absent) do File.unlink(@parent[:path]) end aliasvalue(:false, :absent) newvalue(:file) do # Make sure we're not managing the content some other way if state = @parent.state(:content) or state = @parent.state(:source) state.sync else @parent.write(false) { |f| f.flush } mode = @parent.should(:mode) end return :file_created end #aliasvalue(:present, :file) newvalue(:present) do # Make a file if they want something, but this will match almost # anything. set_file end newvalue(:directory) do mode = @parent.should(:mode) parent = File.dirname(@parent[:path]) unless FileTest.exists? parent raise Puppet::Error, "Cannot create %s; parent directory %s does not exist" % [@parent[:path], parent] end - Puppet::Util.asuser(@parent.asuser()) { + Puppet::SUIDManager.asuser(@parent.asuser()) { if mode Puppet::Util.withumask(000) do Dir.mkdir(@parent[:path],mode) end else Dir.mkdir(@parent[:path]) end } @parent.setchecksum return :directory_created end newvalue(:link) do if state = @parent.state(:target) state.retrieve if state.linkmaker self.set_directory return :directory_created else return state.mklink end else self.fail "Cannot create a symlink without a target" end end # Symlinks. newvalue(/./) do # This code never gets executed. We need the regex to support # specifying it, but the work is done in the 'symlink' code block. end munge do |value| value = super(value) return value if value.is_a? Symbol @parent[:target] = value return :link end # Check that we can actually create anything def check basedir = File.dirname(@parent[:path]) if ! FileTest.exists?(basedir) raise Puppet::Error, "Can not create %s; parent directory does not exist" % @parent.title elsif ! FileTest.directory?(basedir) raise Puppet::Error, "Can not create %s; %s is not a directory" % [@parent.title, dirname] end end # We have to treat :present specially, because it works with any # type of file. def insync? if self.should == :present if @is.nil? or @is == :absent return false else return true end else return super end end def retrieve if stat = @parent.stat(false) @is = stat.ftype.intern else if self.should == :false @is = :false else @is = :absent end end end def sync event = super # There are some cases where all of the work does not get done on # file creation, so we have to do some extra checking. @parent.each do |thing| next unless thing.is_a? Puppet::State next if thing == self thing.retrieve unless thing.insync? thing.sync end end return event end end end # $Id$ diff --git a/lib/puppet/type/pfile/source.rb b/lib/puppet/type/pfile/source.rb index 65aec1dfd..9ee236850 100755 --- a/lib/puppet/type/pfile/source.rb +++ b/lib/puppet/type/pfile/source.rb @@ -1,290 +1,290 @@ require 'puppet/server/fileserver' module Puppet # Copy files from a local or remote source. Puppet.type(:file).newstate(:source) do PINPARAMS = Puppet::Server::FileServer::CHECKPARAMS attr_accessor :source, :local desc "Copy a file over the current file. Uses ``checksum`` to determine when a file should be copied. Valid values are either fully qualified paths to files, or URIs. Currently supported URI types are *puppet* and *file*. This is one of the primary mechanisms for getting content into applications that Puppet does not directly support and is very useful for those configuration files that don't change much across sytems. For instance: class sendmail { file { \"/etc/mail/sendmail.cf\": source => \"puppet://server/module/sendmail.cf\" } } See the [fileserver docs][] for information on how to configure and use file services within Puppet. If you specify multiple file sources for a file, then the first source that exists will be used. This allows you to specify what amount to search paths for files: file { \"/path/to/my/file\": source => [ \"/nfs/files/file.$host\", \"/nfs/files/file.$operatingsystem\", \"/nfs/files/file\" ] } This will use the first found file as the source. [fileserver docs]: fsconfigref.html " uncheckable # Ask the file server to describe our file. def describe(source) sourceobj, path = @parent.uri2obj(source) server = sourceobj.server begin desc = server.describe(path, @parent[:links]) rescue NetworkClientError => detail self.err "Could not describe %s: %s" % [path, detail] return nil end args = {} PINPARAMS.zip( desc.split("\t") ).each { |param, value| if value =~ /^[0-9]+$/ value = value.to_i end unless value.nil? args[param] = value end } # we can't manage ownership as root, so don't even try - unless Process.uid == 0 + unless Puppet::SUIDManager.uid == 0 args.delete(:owner) end if args.empty? return nil else return args end end # This basically calls describe() on our file, and then sets all # of the local states appropriately. If the remote file is a normal # file then we set it to copy; if it's a directory, then we just mark # that the local directory should be created. def retrieve(remote = true) sum = nil unless defined? @shouldorig raise Puppet::DevError, "No sources defined for %s" % @parent.title end @source = nil unless defined? @source # This is set to false by the File#retrieve function on the second # retrieve, so that we do not do two describes. if remote @source = nil # Find the first source that exists. @shouldorig contains # the sources as specified by the user. @shouldorig.each { |source| if @stats = self.describe(source) @source = source break end } end if @stats.nil? or @stats[:type].nil? @is = :notdescribed @source = nil return nil end # If we're a normal file, then set things up to copy the file down. case @stats[:type] when "file": if sum = @parent.state(:checksum) if sum.is if sum.is == :absent sum.retrieve(true) end @is = sum.is else @is = :absent end else self.info "File does not have checksum" @is = :absent end # if replace => false then fake the checksum so that the file # is not overwritten. unless @is == :absent if @parent[:replace] == :false info "Not replacing existing file" @is = @stats[:checksum] end end @should = [@stats[:checksum]] # If we're a directory, then do not copy anything, and instead just # create the directory using the 'create' state. when "directory": if state = @parent.state(:ensure) unless state.should == "directory" state.should = "directory" end else @parent[:ensure] = "directory" @parent.state(:ensure).retrieve end # we'll let the :ensure state do our work @should.clear @is = true when "link": case @parent[:links] when :ignore @is = :nocopy @should = [:nocopy] self.info "Ignoring link %s" % @source return when :follow @stats = self.describe(source, :follow) if @stats.empty? raise Puppet::Error, "Could not follow link %s" % @source end when :copy raise Puppet::Error, "Cannot copy links yet" end else self.info @stats.inspect self.err "Cannot use files of type %s as sources" % @stats[:type] @should = [:nocopy] @is = :nocopy end # Take each of the stats and set them as states on the local file # if a value has not already been provided. @stats.each { |stat, value| next if stat == :checksum next if stat == :type # was the stat already specified, or should the value # be inherited from the source? unless @parent.argument?(stat) if state = @parent.state(stat) state.should = value else @parent[stat] = value end #else # @parent.info "Already specified %s" % stat end } end # The special thing here is that we need to make sure that 'should' # is only set for files, not directories. The processing we're doing # here doesn't really matter, because the @should values will be # overridden when we 'retrieve'. munge do |source| if source.is_a? Symbol return source end # Remove any trailing slashes source.sub!(/\/$/, '') unless @parent.uri2obj(source) raise Puppet::Error, "Invalid source %s" % source end if ! defined? @stats or @stats.nil? # stupid hack for now; it'll get overriden return source else if @stats[:type] == "directory" @is = true return nil else return source end end end def sync if @is == :notdescribed self.retrieve # try again if @is == :notdescribed @parent.log "Could not retrieve information on %s" % @parent.title return nil end if @is == @should return nil end end case @stats[:type] when "link": end unless @stats[:type] == "file" #if @stats[:type] == "directory" #[@parent.name, @is.inspect, @should.inspect] #end raise Puppet::DevError, "Got told to copy non-file %s" % @parent[:path] end unless defined? @source raise Puppet::DevError, "Somehow source is still undefined" end sourceobj, path = @parent.uri2obj(@source) begin contents = sourceobj.server.retrieve(path, @parent[:links]) rescue NetworkClientError => detail self.err "Could not retrieve %s: %s" % [path, detail] return nil end # FIXME It's stupid that this isn't taken care of in the # protocol. unless sourceobj.server.local contents = CGI.unescape(contents) end if contents == "" self.notice "Could not retrieve contents for %s" % @source end exists = File.exists?(@parent[:path]) @parent.write { |f| f.print contents } if exists return :file_changed else return :file_created end end end end # $Id$ diff --git a/lib/puppet/type/pfile/target.rb b/lib/puppet/type/pfile/target.rb index 23fb30390..a2d174c2e 100644 --- a/lib/puppet/type/pfile/target.rb +++ b/lib/puppet/type/pfile/target.rb @@ -1,100 +1,100 @@ module Puppet Puppet.type(:file).newstate(:target) do attr_accessor :linkmaker desc "The target for creating a link. Currently, symlinks are the only type supported." newvalue(:notlink) do # We do nothing if the value is absent return :nochange end # Anything else, basically newvalue(/./) do if ! @parent.should(:ensure) @parent[:ensure] = :link elsif @parent.should(:ensure) != :link raise Puppet::Error, "You cannot specify a target unless 'ensure' is set to 'link'" end if @parent.state(:ensure).insync? mklink() end end # Create our link. def mklink target = self.should if stat = @parent.stat unless @parent.handlebackup self.fail "Could not back up; will not replace with link" end case stat.ftype when "directory": if @parent[:force] == :true FileUtils.rmtree(@parent[:path]) else notice "Not replacing directory with link; use 'force' to override" return :nochange end else File.unlink(@parent[:path]) end end Dir.chdir(File.dirname(@parent[:path])) do - Puppet::Util.asuser(@parent.asuser()) do + Puppet::SUIDManager.asuser(@parent.asuser()) do mode = @parent.should(:mode) if mode Puppet::Util.withumask(000) do File.symlink(target, @parent[:path]) end else File.symlink(target, @parent[:path]) end end :link_created end end def retrieve if @parent.state(:ensure).should == :directory @is = self.should @linkmaker = true else if stat = @parent.stat # If we're just checking the value if (should = self.should) and (should != :notlink) and File.exists?(should) and (tstat = File.lstat(should)) and (tstat.ftype == "directory") and @parent.recurse? unless @parent.recurse? raise "wtf?" end warning "Changing ensure to directory; recurse is %s but %s" % [@parent[:recurse].inspect, @parent.recurse?] @parent[:ensure] = :directory @is = should @linkmaker = true else if stat.ftype == "link" @is = File.readlink(@parent[:path]) @linkmaker = false else @is = :notlink end end else @is = :absent end end end end end # $Id$ diff --git a/lib/puppet/type/pfile/uid.rb b/lib/puppet/type/pfile/uid.rb index 166adac32..72d2a7e03 100755 --- a/lib/puppet/type/pfile/uid.rb +++ b/lib/puppet/type/pfile/uid.rb @@ -1,170 +1,170 @@ module Puppet Puppet.type(:file).newstate(:owner) do require 'etc' desc "To whom the file should belong. Argument can be user name or user ID." @event = :file_changed def id2name(id) if id.is_a?(Symbol) return id.to_s end begin user = Etc.getpwuid(id) rescue TypeError return nil rescue ArgumentError return nil end if user.uid == "" return nil else return user.name end end def name2id(value) if value.is_a?(Symbol) return value.to_s end begin user = Etc.getpwnam(value) if user.uid == "" return nil end return user.uid rescue ArgumentError => detail return nil end end # Determine if the user is valid, and if so, return the UID def validuser?(value) if value =~ /^\d+$/ value = value.to_i end if value.is_a?(Integer) # verify the user is a valid user if tmp = id2name(value) return value else return false end else if tmp = name2id(value) return tmp else return false end end end # We want to print names, not numbers def is_to_s id2name(@is) || @is end def should_to_s case self.should when Symbol self.should.to_s when Integer id2name(self.should) || self.should when String self.should else raise Puppet::DevError, "Invalid uid type %s(%s)" % [self.should.class, self.should] end end def retrieve unless stat = @parent.stat(false) @is = :absent return end # Set our method appropriately, depending on links. if stat.ftype == "link" and @parent[:links] != :follow @method = :lchown else @method = :chown end self.is = stat.uid # On OS X, files that are owned by -2 get returned as really # large UIDs instead of negative ones. This isn't a Ruby bug, # it's an OS X bug, since it shows up in perl, too. if @is > 120000 self.warning "current state is silly: %s" % @is @is = :silly end end # If we're not root, we can check the values but we cannot change # them. We can't really do any processing here, because users # might not exist yet. FIXME There's still a bit of a problem here # if the user's UID changes at run time, but we're just going to # have to be okay with that for now, unfortunately. munge do |value| if tmp = self.validuser?(value) return tmp else return value end end def sync - unless Process.uid == 0 + unless Puppet::SUIDManager.uid == 0 unless defined? @@notifieduid self.notice "Cannot manage ownership unless running as root" #@parent.delete(self.name) @@notifieduid = true end return nil end user = nil unless user = self.validuser?(self.should) tmp = self.should unless defined? @@usermissing @@usermissing = {} end if @@usermissing.include?(tmp) @@usermissing[tmp] += 1 else self.notice "user %s does not exist" % tmp @@usermissing[tmp] = 1 end return nil end if @is == :absent @parent.stat(true) self.retrieve if @is == :absent self.debug "File does not exist; cannot set owner" return nil end if self.insync? return nil end #self.debug "%s: after refresh, is '%s'" % [self.class.name,@is] end begin File.send(@method, user, nil, @parent[:path]) rescue => detail raise Puppet::Error, "Failed to set owner to '%s': %s" % [user, detail] end return :file_changed end end end # $Id$ diff --git a/lib/puppet/util.rb b/lib/puppet/util.rb index c6ad30e3b..049d66b49 100644 --- a/lib/puppet/util.rb +++ b/lib/puppet/util.rb @@ -1,528 +1,458 @@ # A module to collect utility functions. require 'sync' require 'puppet/lock' module Puppet # A command failed to execute. class ExecutionFailure < RuntimeError end module Util require 'benchmark' # Create a hash to store the different sync objects. @@syncresources = {} # Return the sync object associated with a given resource. def self.sync(resource) @@syncresources[resource] ||= Sync.new return @@syncresources[resource] end - # Execute a block as a given user or group - def self.asuser(user = nil, group = nil) - require 'etc' - - uid = nil - gid = nil - olduid = nil - oldgid = nil - - # If they're running as a normal user, then just execute as that same - # user. - unless Process.uid == 0 - retval = yield - return retval - end - - begin - # the groupid, if we got passed a group - # The gid has to be changed first, because, well, otherwise we won't - # be able to - if group - if group.is_a? Integer - gid = group - else - gid = self.gid(group) - end - - if gid - if Process.gid != gid - oldgid = Process.gid - begin - Process.egid = gid - rescue => detail - raise Puppet::Error, "Could not change GID: %s" % detail - end - end - else - Puppet.warning "Could not retrieve GID for %s" % group - end - end - - if user - if user.is_a? Integer - uid = user - else - uid = self.uid(user) - end - uid = self.uid(user) - - if uid - # Now change the uid - if Process.uid != uid - olduid = Process.uid - begin - Process.euid = uid - rescue => detail - raise Puppet::Error, "Could not change UID: %s" % detail - end - end - else - Puppet.warning "Could not retrieve UID for %s" % user - end - end - retval = yield - ensure - if olduid - Process.euid = olduid - end - - if oldgid - Process.egid = oldgid - end - end - - return retval - end - # Change the process to a different user def self.chuser if Facter["operatingsystem"].value == "Darwin" $stderr.puts "Ruby on darwin is broken; puppetmaster must run as root" return end if group = Puppet[:group] group = self.gid(group) unless group raise Puppet::Error, "No such group %s" % Puppet[:group] end - unless Process.gid == group + unless Puppet::SUIDManager.gid == group begin - Process.egid = group - Process.gid = group + Puppet::SUIDManager.egid = group + Puppet::SUIDManager.gid = group rescue => detail Puppet.warning "could not change to group %s: %s" % [group.inspect, detail] $stderr.puts "could not change to group %s" % group.inspect # Don't exit on failed group changes, since it's # not fatal #exit(74) end end end if user = Puppet[:user] user = self.uid(user) unless user raise Puppet::Error, "No such user %s" % Puppet[:user] end - unless Process.uid == user + unless Puppet::SUIDManager.uid == user begin - Process.uid = user - Process.euid = user + Puppet::SUIDManager.uid = user + Puppet::SUIDManager.euid = user rescue $stderr.puts "could not change to user %s" % user exit(74) end end end end # Create a shared lock for reading def self.readlock(file) self.sync(file).synchronize(Sync::SH) do File.open(file) { |f| f.lock_shared { |lf| yield lf } } end end # Create an exclusive lock for writing, and do the writing in a # tmp file. def self.writelock(file, mode = 0600) tmpfile = file + ".tmp" self.sync(file).synchronize(Sync::EX) do File.open(file, "w", mode) do |rf| rf.lock_exclusive do |lrf| File.open(tmpfile, "w", mode) do |tf| yield tf end begin File.rename(tmpfile, file) rescue => detail Puppet.err "Could not rename %s to %s: %s" % [file, tmpfile, detail] end end end end end # Get the GID of a given group, provided either a GID or a name def self.gid(group) if group =~ /^\d+$/ group = Integer(group) end unless group raise Puppet::DevError, "Invalid group %s" % group.inspect end gid = nil obj = nil # We want to look the group up either way if group.is_a?(Integer) # If this doesn't find anything obj = Puppet.type(:group).find { |gobj| gobj.should(:gid) == group || gobj.is(:gid) == group } unless obj begin gobj = Etc.getgrgid(group) gid = gobj.gid rescue ArgumentError => detail # ignore it; we couldn't find the group end end else if obj = Puppet.type(:group)[group] obj[:check] = [:gid] else obj = Puppet.type(:group).create( :name => group, :check => [:gid] ) end obj.retrieve end if obj gid = obj.should(:gid) || obj.is(:gid) if gid == :absent gid = nil end end return gid end # Get the UID of a given user, whether a UID or name is provided def self.uid(user) uid = nil + + # if we don't have any user info, warn and GTFO. + if !user + Puppet.warning "Username provided for lookup is nil" + return nil + end + if user =~ /^\d+$/ user = Integer(user) end if user.is_a?(Integer) # If this doesn't find anything obj = Puppet.type(:user).find { |uobj| uobj.should(:uid) == user || uobj.is(:uid) == user } unless obj begin uobj = Etc.getpwuid(user) uid = uobj.uid rescue ArgumentError => detail # ignore it; we couldn't find the user end end else unless obj = Puppet.type(:user)[user] obj = Puppet.type(:user).create( :name => user ) end obj[:check] = [:uid, :gid] end if obj obj.retrieve uid = obj.should(:uid) || obj.is(:uid) if uid == :absent uid = nil end end return uid end # Create instance methods for each of the log levels. This allows # the messages to be a little richer. Most classes will be calling this # method. def self.logmethods(klass, useself = true) Puppet::Log.eachlevel { |level| klass.send(:define_method, level, proc { |args| if args.is_a?(Array) args = args.join(" ") end if useself Puppet::Log.create( :level => level, :source => self, :message => args ) else Puppet::Log.create( :level => level, :message => args ) end }) } end # Proxy a bunch of methods to another object. def self.classproxy(klass, objmethod, *methods) classobj = class << klass; self; end methods.each do |method| classobj.send(:define_method, method) do |*args| obj = self.send(objmethod) obj.send(method, *args) end end end # Proxy a bunch of methods to another object. def self.proxy(klass, objmethod, *methods) methods.each do |method| klass.send(:define_method, method) do |*args| obj = self.send(objmethod) obj.send(method, *args) end end end # XXX this should all be done using puppet objects, not using # normal mkdir def self.recmkdir(dir,mode = 0755) if FileTest.exist?(dir) return false else tmp = dir.sub(/^\//,'') path = [File::SEPARATOR] tmp.split(File::SEPARATOR).each { |dir| path.push dir if ! FileTest.exist?(File.join(path)) Dir.mkdir(File.join(path), mode) elsif FileTest.directory?(File.join(path)) next else FileTest.exist?(File.join(path)) raise "Cannot create %s: basedir %s is a file" % [dir, File.join(path)] end } return true end end # Execute a given chunk of code with a new umask. def self.withumask(mask) cur = File.umask(mask) begin yield ensure File.umask(cur) end end def benchmark(*args) msg = args.pop level = args.pop object = nil if args.empty? object = Puppet else object = args.pop end unless level puts caller.join("\n") raise Puppet::DevError, "Failed to provide level" end unless object.respond_to? level raise Puppet::DevError, "Benchmarked object does not respond to %s" % level end # Only benchmark if our log level is high enough if level != :none and Puppet::Log.sendlevel?(level) result = nil seconds = Benchmark.realtime { yield } object.send(level, msg + (" in %0.2f seconds" % seconds)) return seconds else yield end end def binary(bin) if bin =~ /^\// if FileTest.exists? bin return true else return nil end else ENV['PATH'].split(":").each do |dir| if FileTest.exists? File.join(dir, bin) return File.join(dir, bin) end end return nil end end module_function :binary # Execute the provided command in a pipe, yielding the pipe object. def execpipe(command, failonfail = true) if respond_to? :debug debug "Executing '%s'" % command else Puppet.debug "Executing '%s'" % command end output = open("| #{command} 2>&1") do |pipe| yield pipe end if failonfail unless $? == 0 raise ExecutionFailure, output end end return output end def execfail(command, exception) begin output = execute(command) return output rescue ExecutionFailure raise exception, output end end # Execute the desired command, and return the status and output. def execute(command, failonfail = true) if respond_to? :debug debug "Executing '%s'" % command else Puppet.debug "Executing '%s'" % command end command += " 2>&1" unless command =~ />/ output = %x{#{command}} if failonfail unless $? == 0 raise ExecutionFailure, "Could not execute '%s': %s" % [command, output] end end return output end module_function :execute # Create an exclusive lock. def threadlock(resource, type = Sync::EX) Puppet::Util.sync(resource).synchronize(type) do yield end end # Because some modules provide their own version of this method. alias util_execute execute module_function :benchmark def memory unless defined? @pmap pmap = %x{which pmap 2>/dev/null}.chomp if $? != 0 or pmap =~ /^no/ @pmap = nil else @pmap = pmap end end if @pmap return %x{pmap #{Process.pid}| grep total}.chomp.sub(/^\s*total\s+/, '').sub(/K$/, '').to_i else 0 end end def symbolize(value) if value.respond_to? :intern return value.intern else value end end def symbolizehash(hash) newhash = {} hash.each do |name, val| if name.is_a? String newhash[name.intern] = val else newhash[name] = val end end end def symbolizehash!(hash) hash.each do |name, val| if name.is_a? String hash[name.intern] = val hash.delete(name) end end return hash end module_function :symbolize, :symbolizehash, :symbolizehash! # Just benchmark, with no logging. def thinmark seconds = Benchmark.realtime { yield } return seconds end module_function :memory end end require 'puppet/util/methodhelper' require 'puppet/util/metaid' require 'puppet/util/classgen' require 'puppet/util/docs' require 'puppet/util/execution' require 'puppet/util/package' require 'puppet/util/warnings' # $Id$ diff --git a/test/executables/puppetca.rb b/test/executables/puppetca.rb index a6ea9aae4..d7a6933b4 100755 --- a/test/executables/puppetca.rb +++ b/test/executables/puppetca.rb @@ -1,107 +1,107 @@ require 'puppet' require 'puppet/server' require 'puppet/sslcertificates' require 'puppettest' class TestPuppetCA < Test::Unit::TestCase include PuppetTest::ExeTest def mkcert(hostname) cert = nil assert_nothing_raised { cert = Puppet::SSLCertificates::Certificate.new( :name => hostname ) cert.mkcsr } return cert end def runca(args) debug = "" if Puppet[:debug] debug = "-d " end return %x{puppetca --user=#{Puppet[:user]} #{debug} --group=#{Puppet[:group]} --confdir=#{Puppet[:confdir]} --vardir=#{Puppet[:vardir]} #{args} 2>&1} end def test_signing ca = nil Puppet[:autosign] = false assert_nothing_raised { ca = Puppet::Server::CA.new() } #Puppet.warning "SSLDir is %s" % Puppet[:confdir] #system("find %s" % Puppet[:confdir]) cert = mkcert("host.test.com") resp = nil assert_nothing_raised { # We need to use a fake name so it doesn't think the cert is from # itself. resp = ca.getcert(cert.csr.to_pem, "fakename", "127.0.0.1") } assert_equal(["",""], resp) #Puppet.warning "SSLDir is %s" % Puppet[:confdir] #system("find %s" % Puppet[:confdir]) output = nil assert_nothing_raised { output = runca("--list").chomp.split("\n").reject { |line| line =~ /warning:/ } # stupid ssl.rb } #Puppet.warning "SSLDir is %s" % Puppet[:confdir] #system("find %s" % Puppet[:confdir]) assert_equal($?,0) assert_equal(%w{host.test.com}, output) assert_nothing_raised { output = runca("--sign -a").chomp.split("\n") } assert_equal($?,0) assert_equal(["Signed host.test.com"], output) signedfile = File.join(Puppet[:signeddir], "host.test.com.pem") assert(FileTest.exists?(signedfile), "cert does not exist") assert(! FileTest.executable?(signedfile), "cert is executable") uid = Puppet::Util.uid(Puppet[:user]) - if Process.uid == 0 + if Puppet::SUIDManager.uid == 0 assert(! FileTest.owned?(signedfile), "cert is owned by root") end assert_nothing_raised { output = runca("--list").chomp.split("\n") } assert_equal($?,0) assert_equal(["No certificates to sign"], output) end # This method takes a long time to run because of all of the external # executable calls. def test_revocation ca = Puppet::SSLCertificates::CA.new() host1 = gen_cert(ca, "host1.example.com") host2 = gen_cert(ca, "host2.example.com") host3 = gen_cert(ca, "host3.example.com") runca("-r host1.example.com") runca("-r #{host2.serial}") runca("-r 0x#{host3.serial.to_s(16)}") runca("-r 0xff") # Recreate CA to force reading of CRL ca = Puppet::SSLCertificates::CA.new() crl = ca.crl revoked = crl.revoked.collect { |r| r.serial } exp = [host1.serial, host2.serial, host3.serial, 255] assert_equal(exp, revoked) end def gen_cert(ca, host) runca("-g #{host}") ca.getclientcert(host)[0] end end # $Id$ diff --git a/test/language/interpreter.rb b/test/language/interpreter.rb index 37b4e9022..c127ab517 100755 --- a/test/language/interpreter.rb +++ b/test/language/interpreter.rb @@ -1,392 +1,392 @@ #!/usr/bin/ruby require 'facter' require 'puppet' require 'puppet/parser/interpreter' require 'puppet/parser/parser' require 'puppet/client' require 'puppet/rails' require 'puppettest' class TestInterpreter < Test::Unit::TestCase include PuppetTest include PuppetTest::ServerTest AST = Puppet::Parser::AST # create a simple manifest that uses nodes to create a file def mknodemanifest(node, file) createdfile = tempfile() File.open(file, "w") { |f| f.puts "node %s { file { \"%s\": ensure => file, mode => 755 } }\n" % [node, createdfile] } return [file, createdfile] end def test_simple file = tempfile() File.open(file, "w") { |f| f.puts "file { \"/etc\": owner => root }" } assert_nothing_raised { Puppet::Parser::Interpreter.new(:Manifest => file) } end def test_reloadfiles hostname = Facter["hostname"].value file = tempfile() # Create a first version createdfile = mknodemanifest(hostname, file) interp = nil assert_nothing_raised { interp = Puppet::Parser::Interpreter.new(:Manifest => file) } config = nil assert_nothing_raised { config = interp.run(hostname, {}) } sleep(1) # Now create a new file createdfile = mknodemanifest(hostname, file) newconfig = nil assert_nothing_raised { newconfig = interp.run(hostname, {}) } assert(config != newconfig, "Configs are somehow the same") end if defined? ActiveRecord def test_hoststorage assert_nothing_raised { Puppet[:storeconfigs] = true } file = tempfile() File.open(file, "w") { |f| f.puts "file { \"/etc\": owner => root }" } interp = nil assert_nothing_raised { interp = Puppet::Parser::Interpreter.new( :Manifest => file, :UseNodes => false, :ForkSave => false ) } facts = {} Facter.each { |fact, val| facts[fact] = val } objects = nil assert_nothing_raised { objects = interp.run(facts["hostname"], facts) } obj = Puppet::Rails::Host.find_by_name(facts["hostname"]) assert(obj, "Could not find host object") end else $stderr.puts "No ActiveRecord -- skipping collection tests" end if Facter["domain"].value == "madstop.com" begin require 'ldap' $haveldap = true rescue LoadError $stderr.puts "Missing ldap; skipping ldap source tests" $haveldap = false end # Only test ldap stuff on luke's network, since that's the only place we # have data for. if $haveldap def ldapconnect @ldap = LDAP::Conn.new("ldap", 389) @ldap.set_option( LDAP::LDAP_OPT_PROTOCOL_VERSION, 3 ) @ldap.simple_bind("", "") return @ldap end def ldaphost(node) parent = nil classes = nil @ldap.search( "ou=hosts, dc=madstop, dc=com", 2, "(&(objectclass=puppetclient)(cn=%s))" % node ) do |entry| parent = entry.vals("parentnode").shift classes = entry.vals("puppetclass") || [] end return parent, classes end def test_ldapnodes Puppet[:ldapbase] = "ou=hosts, dc=madstop, dc=com" Puppet[:ldapnodes] = true ldapconnect() file = tempfile() files = [] parentfile = tempfile() + "-parent" files << parentfile hostname = Facter["hostname"].value lparent, lclasses = ldaphost(Facter["hostname"].value) assert(lclasses, "Did not retrieve info from ldap") File.open(file, "w") { |f| f.puts "node #{lparent} { file { \"#{parentfile}\": ensure => file } }" lclasses.each { |klass| kfile = tempfile() + "-klass" files << kfile f.puts "class #{klass} { file { \"#{kfile}\": ensure => file } }" } } interp = nil assert_nothing_raised { interp = Puppet::Parser::Interpreter.new( :Manifest => file ) } parent = nil classes = nil # First make sure we get the default node for unknown hosts dparent, dclasses = ldaphost("default") assert_nothing_raised { parent, classes = interp.nodesearch("nosuchhostokay") } assert_equal(dparent, parent, "Default parent node did not match") assert_equal(dclasses, classes, "Default parent class list did not match") # Look for a host we know doesn't have a parent npparent, npclasses = ldaphost("noparent") assert_nothing_raised { #parent, classes = interp.nodesearch_ldap("noparent") parent, classes = interp.nodesearch("noparent") } assert_equal(npparent, parent, "Parent node did not match") assert_equal(npclasses, classes, "Class list did not match") # Now look for our normal host assert_nothing_raised { parent, classes = interp.nodesearch_ldap(hostname) } assert_equal(lparent, parent, "Parent node did not match") assert_equal(lclasses, classes, "Class list did not match") objects = nil assert_nothing_raised { objects = interp.run(hostname, Puppet::Client::MasterClient.facts) } comp = nil assert_nothing_raised { comp = objects.to_type } assert_apply(comp) files.each { |cfile| @@tmpfiles << cfile assert(FileTest.exists?(cfile), "Did not make %s" % cfile) } end - if Process.uid == 0 and Facter["hostname"].value == "culain" + if Puppet::SUIDManager.uid == 0 and Facter["hostname"].value == "culain" def test_ldapreconnect Puppet[:ldapbase] = "ou=hosts, dc=madstop, dc=com" Puppet[:ldapnodes] = true interp = nil assert_nothing_raised { interp = Puppet::Parser::Interpreter.new( :Manifest => mktestmanifest() ) } hostname = "culain.madstop.com" # look for our host assert_nothing_raised { parent, classes = interp.nodesearch_ldap(hostname) } # Now restart ldap system("/etc/init.d/slapd restart 2>/dev/null >/dev/null") sleep(1) # and look again assert_nothing_raised { parent, classes = interp.nodesearch_ldap(hostname) } # Now stop ldap system("/etc/init.d/slapd stop 2>/dev/null >/dev/null") cleanup do system("/etc/init.d/slapd start 2>/dev/null >/dev/null") end # And make sure we actually fail here assert_raise(Puppet::Error) { parent, classes = interp.nodesearch_ldap(hostname) } end else $stderr.puts "Run as root for ldap reconnect tests" end end else $stderr.puts "Not in madstop.com; skipping ldap tests" end # Make sure searchnode behaves as we expect. def test_nodesearch # First create a fake nodesearch algorithm i = 0 bucket = [] Puppet::Parser::Interpreter.send(:define_method, "nodesearch_fake") do |node| return nil, nil if node == "default" return bucket[0], bucket[1] end text = %{ node nodeparent {} node othernodeparent {} class nodeclass {} class nothernode {} } manifest = tempfile() File.open(manifest, "w") do |f| f.puts text end interp = nil assert_nothing_raised { interp = Puppet::Parser::Interpreter.new( :Manifest => manifest, :NodeSources => [:fake] ) } # Make sure it behaves correctly for all forms [[nil, nil], ["nodeparent", nil], [nil, ["nodeclass"]], [nil, ["nodeclass", "nothernode"]], ["othernodeparent", ["nodeclass", "nothernode"]],].each do |ary| # Set the return values bucket = ary # Look them back up parent, classes = interp.nodesearch("mynode") # Basically, just make sure that if we have either or both, # we get a result back. assert_equal(ary[0], parent, "Parent is not %s" % parent) assert_equal(ary[1], classes, "Parent is not %s" % parent) next if ary == [nil, nil] # Now make sure we actually get the configuration. This will throw # an exception if we don't. assert_nothing_raised do interp.run("mynode", {}) end end end # Make sure nodesearch uses all names, not just one. def test_nodesearch_multiple_names bucket = {} Puppet::Parser::Interpreter.send(:define_method, "nodesearch_multifake") do |node| if bucket[node] return *bucket[node] else return nil, nil end end manifest = tempfile() File.open(manifest, "w") do |f| f.puts "" end interp = nil assert_nothing_raised { interp = Puppet::Parser::Interpreter.new( :Manifest => manifest, :NodeSources => [:multifake] ) } bucket["name.domain.com"] = [:parent, [:classes]] ret = nil assert_nothing_raised do assert_equal bucket["name.domain.com"], interp.nodesearch("name", "name.domain.com") end end def test_parsedate Puppet[:filetimeout] = 0 main = tempfile() sub = tempfile() mainfile = tempfile() subfile = tempfile() count = 0 updatemain = proc do count += 1 File.open(main, "w") { |f| f.puts "import '#{sub}' file { \"#{mainfile}\": content => #{count} } " } end updatesub = proc do count += 1 File.open(sub, "w") { |f| f.puts "file { \"#{subfile}\": content => #{count} } " } end updatemain.call updatesub.call interp = Puppet::Parser::Interpreter.new( :Manifest => main, :Local => true ) date = interp.parsedate # Now update the site file and make sure we catch it sleep 1 updatemain.call newdate = interp.parsedate assert(date != newdate, "Parsedate was not updated") date = newdate # And then the subfile sleep 1 updatesub.call newdate = interp.parsedate assert(date != newdate, "Parsedate was not updated") end end diff --git a/test/lib/puppettest/exetest.rb b/test/lib/puppettest/exetest.rb index 5f155b47f..94e8ec8c9 100644 --- a/test/lib/puppettest/exetest.rb +++ b/test/lib/puppettest/exetest.rb @@ -1,122 +1,122 @@ require 'puppettest/servertest' module PuppetTest::ExeTest include PuppetTest::ServerTest def setup super setbindir setlibdir end def bindir File.join(basedir, "bin") end def setbindir unless ENV["PATH"].split(":").include?(bindir) ENV["PATH"] = [bindir, ENV["PATH"]].join(":") end end def setlibdir ENV["RUBYLIB"] = $:.find_all { |dir| dir =~ /puppet/ or dir =~ /\.\./ }.join(":") end # Run a ruby command. This explicitly uses ruby to run stuff, since we # don't necessarily know where our ruby binary is, dernit. # Currently unused, because I couldn't get it to work. def rundaemon(*cmd) @ruby ||= %x{which ruby}.chomp cmd = cmd.unshift(@ruby).join(" ") out = nil Dir.chdir(bindir()) { out = %x{#{@ruby} #{cmd}} } return out end def startmasterd(args = "") output = nil manifest = mktestmanifest() args += " --manifest %s" % manifest args += " --confdir %s" % Puppet[:confdir] args += " --vardir %s" % Puppet[:vardir] args += " --masterport %s" % @@port - args += " --user %s" % Process.uid - args += " --group %s" % Process.gid + args += " --user %s" % Puppet::SUIDManager.uid + args += " --group %s" % Puppet::SUIDManager.gid args += " --nonodes" args += " --autosign true" #if Puppet[:debug] # args += " --debug" #end cmd = "puppetmasterd %s" % args assert_nothing_raised { output = %x{#{cmd}}.chomp } assert_equal("", output, "Puppetmasterd produced output %s" % output) assert($? == 0, "Puppetmasterd exit status was %s" % $?) sleep(1) cleanup do stopmasterd sleep(1) end return manifest end def stopmasterd(running = true) ps = Facter["ps"].value || "ps -ef" pidfile = File.join(Puppet[:vardir], "run", "puppetmasterd.pid") pid = nil if FileTest.exists?(pidfile) pid = File.read(pidfile).chomp.to_i File.unlink(pidfile) end return unless running if running or pid runningpid = nil %x{#{ps}}.chomp.split(/\n/).each { |line| if line =~ /ruby.+puppetmasterd/ next if line =~ /\.rb/ # skip the test script itself next if line =~ /^puppet/ # skip masters running as 'puppet' ary = line.sub(/^\s+/, '').split(/\s+/) pid = ary[1].to_i end } end # we default to mandating that it's running, but teardown # doesn't require that if pid if pid == $$ raise Puppet::Error, "Tried to kill own pid" end begin Process.kill(:INT, pid) rescue # ignore it end end end def teardown stopmasterd(false) super end end # $Id$ diff --git a/test/lib/puppettest/support/helpers.rb b/test/lib/puppettest/support/helpers.rb index 7fae994d9..cbcbcb1f6 100644 --- a/test/lib/puppettest/support/helpers.rb +++ b/test/lib/puppettest/support/helpers.rb @@ -1,21 +1,21 @@ require 'puppettest' module PuppetTest def nonrootuser Etc.passwd { |user| - if user.uid != Process.uid and user.uid > 0 + if user.uid != Puppet::SUIDManager.uid and user.uid > 0 return user end } end def nonrootgroup Etc.group { |group| - if group.gid != Process.gid and group.gid > 0 + if group.gid != Puppet::SUIDManager.gid and group.gid > 0 return group end } end end # $Id$ diff --git a/test/other/config.rb b/test/other/config.rb index 3e2c125c7..0afe8979b 100755 --- a/test/other/config.rb +++ b/test/other/config.rb @@ -1,805 +1,805 @@ #!/usr/bin/env ruby require 'puppet' require 'puppet/config' require 'puppettest' class TestConfig < Test::Unit::TestCase include PuppetTest def check_for_users count = Puppet::Type.type(:user).inject(0) { |c,o| c + 1 } assert(count > 0, "Found no users") end def check_to_transportable(config) trans = nil assert_nothing_raised("Could not convert to a transportable") { trans = config.to_transportable } comp = nil assert_nothing_raised("Could not convert transportable to component") { comp = trans.to_type } check_for_users() assert_nothing_raised("Could not retrieve transported config") { comp.retrieve } end def check_to_manifest(config) manifest = nil assert_nothing_raised("Could not convert to a manifest") { manifest = config.to_manifest } Puppet[:parseonly] = true parser = Puppet::Parser::Parser.new() objects = nil assert_nothing_raised("Could not parse generated manifest") { parser.string = manifest objects = parser.parse } scope = Puppet::Parser::Scope.new assert_nothing_raised("Could not compile objects") { scope.evaluate(:ast => objects) } trans = nil assert_nothing_raised("Could not convert objects to transportable") { trans = scope.to_trans } assert_nothing_raised("Could not instantiate objects") { trans.to_type } check_for_users() end def check_to_comp(config) comp = nil assert_nothing_raised("Could not convert to a component") { comp = config.to_component } assert_nothing_raised("Could not retrieve component") { comp.retrieve } check_for_users() end def check_to_config(config) newc = config.dup newfile = tempfile() File.open(newfile, "w") { |f| f.print config.to_config } assert_nothing_raised("Could not parse generated configuration") { newc.parse(newfile) } assert_equal(config, newc, "Configurations are not equal") end def mkconfig c = nil assert_nothing_raised { c = Puppet::Config.new } return c end def test_addbools c = mkconfig assert_nothing_raised { c.setdefaults(:testing, :booltest => [true, "testing"]) } assert(c[:booltest]) c = mkconfig assert_nothing_raised { c.setdefaults(:testing, :booltest => ["true", "testing"]) } assert(c[:booltest]) assert_nothing_raised { c[:booltest] = false } assert(! c[:booltest], "Booltest is not false") assert_nothing_raised { c[:booltest] = "false" } assert(! c[:booltest], "Booltest is not false") assert_raise(Puppet::Error) { c[:booltest] = "yayness" } assert_raise(Puppet::Error) { c[:booltest] = "/some/file" } end def test_strings c = mkconfig val = "this is a string" assert_nothing_raised { c.setdefaults(:testing, :strtest => [val, "testing"]) } assert_equal(val, c[:strtest]) # Verify that variables are interpolated assert_nothing_raised { c.setdefaults(:testing, :another => ["another $strtest", "testing"]) } assert_equal("another #{val}", c[:another]) end def test_files c = mkconfig parent = "/puppet" assert_nothing_raised { c.setdefaults(:testing, :parentdir => [parent, "booh"]) } assert_nothing_raised { c.setdefaults(:testing, :child => ["$parent/child", "rah"]) } assert_equal(parent, c[:parentdir]) assert_equal("/puppet/child", File.join(c[:parentdir], "child")) end def test_getset c = mkconfig initial = "an initial value" assert_raise(Puppet::Error) { c[:yayness] = initial } default = "this is a default" assert_nothing_raised { c.setdefaults(:testing, :yayness => [default, "rah"]) } assert_equal(default, c[:yayness]) assert_nothing_raised { c[:yayness] = initial } assert_equal(initial, c[:yayness]) assert_nothing_raised { c.clear } assert_equal(default, c[:yayness]) assert_nothing_raised { c[:yayness] = "not default" } assert_equal("not default", c[:yayness]) end def test_parse text = %{ one = this is a test two = another test owner = root group = root yay = /a/path [section1] attr = value owner = puppet group = puppet attrdir = /some/dir attr3 = $attrdir/other } file = tempfile() File.open(file, "w") { |f| f.puts text } c = mkconfig assert_nothing_raised { c.setdefaults("puppet", :one => ["a", "one"], :two => ["a", "two"], :yay => ["/default/path", "boo"], :mkusers => [true, "uh, yeah"] ) } assert_nothing_raised { c.setdefaults("section1", :attr => ["a", "one"], :attrdir => ["/another/dir", "two"], :attr3 => ["$attrdir/maybe", "boo"] ) } assert_nothing_raised { c.parse(file) } assert_equal("value", c[:attr]) assert_equal("/some/dir", c[:attrdir]) assert_equal(:directory, c.element(:attrdir).type) assert_equal("/some/dir/other", c[:attr3]) elem = nil assert_nothing_raised { elem = c.element(:attr3) } assert(elem) assert_equal("puppet", elem.owner) config = nil assert_nothing_raised { config = c.to_config } assert_nothing_raised("Could not create transportable config") { c.to_transportable } check_to_comp(c) Puppet::Type.allclear check_to_manifest(c) Puppet::Type.allclear check_to_config(c) Puppet::Type.allclear check_to_transportable(c) end def test_arghandling c = mkconfig assert_nothing_raised { c.setdefaults("testing", :onboolean => [true, "An on bool"], :offboolean => [false, "An off bool"], :string => ["a string", "A string arg"], :file => ["/path/to/file", "A file arg"] ) } data = { :onboolean => [true, false], :offboolean => [true, false], :string => ["one string", "another string"], :file => %w{/a/file /another/file} } data.each { |param, values| values.each { |val| opt = nil arg = nil if c.boolean?(param) if val opt = "--%s" % param else opt = "--no-%s" % param end else opt = "--%s" % param arg = val end assert_nothing_raised("Could not handle arg %s with value %s" % [opt, val]) { c.handlearg(opt, arg) } } } end def test_argadding c = mkconfig assert_nothing_raised { c.setdefaults("testing", :onboolean => [true, "An on bool"], :offboolean => [false, "An off bool"], :string => ["a string", "A string arg"], :file => ["/path/to/file", "A file arg"] ) } options = [] c.addargs(options) c.each { |param, obj| opt = "--%s" % param assert(options.find { |ary| ary[0] == opt }, "Argument %s was not added" % opt) if c.boolean?(param) o = "--no-%s" % param assert(options.find { |ary| ary[0] == o }, "Boolean off %s was not added" % o) end } end def test_usesection c = mkconfig dir = tempfile() file = "$mydir/myfile" realfile = File.join(dir, "myfile") otherfile = File.join(dir, "otherfile") section = "testing" assert_nothing_raised { c.setdefaults(section, :mydir => [dir, "A dir arg"], :otherfile => { :default => "$mydir/otherfile", :create => true, :desc => "A file arg" }, :myfile => [file, "A file arg"] ) } assert_nothing_raised("Could not use a section") { c.use(section) } assert_nothing_raised("Could not reuse a section") { c.use(section) } assert(FileTest.directory?(dir), "Did not create directory") assert(FileTest.exists?(otherfile), "Did not create file") assert(!FileTest.exists?(realfile), "Created file") end def test_setdefaultsarray c = mkconfig assert_nothing_raised { c.setdefaults("yay", :a => [false, "some value"], :b => ["/my/file", "a file"] ) } assert_equal(false, c[:a], "Values are not equal") assert_equal("/my/file", c[:b], "Values are not equal") end def test_setdefaultshash c = mkconfig assert_nothing_raised { c.setdefaults("yay", :a => {:default => false, :desc => "some value"}, :b => {:default => "/my/file", :desc => "a file"} ) } assert_equal(false, c[:a], "Values are not equal") assert_equal("/my/file", c[:b], "Values are not equal") end def test_reuse c = mkconfig file = tempfile() section = "testing" assert_nothing_raised { c.setdefaults(section, :myfile => {:default => file, :create => true} ) } assert_nothing_raised("Could not use a section") { c.use(section) } assert(FileTest.exists?(file), "Did not create file") assert(! Puppet::Type.type(:file)[file], "File obj still exists") File.unlink(file) c.reuse assert(FileTest.exists?(file), "Did not create file") end def test_mkusers c = mkconfig file = tempfile() section = "testing" assert_nothing_raised { c.setdefaults(section, :mkusers => [false, "yay"], :myfile => { :default => file, :owner => "pptest", :group => "pptest", :create => true } ) } comp = nil assert_nothing_raised { comp = c.to_component } [:user, :group].each do |type| # The objects might get created internally by Puppet::Util; just # make sure they're not being managed if obj = Puppet.type(type)["pptest"] assert(! obj.managed?, "%s objectis managed" % type) end end comp.each { |o| o.remove } c[:mkusers] = true assert_nothing_raised { c.to_component } assert(Puppet.type(:user)["pptest"], "User object did not get created") assert(Puppet.type(:user)["pptest"].managed?, "User object is not managed." ) assert(Puppet.type(:group)["pptest"], "Group object did not get created") assert(Puppet.type(:group)["pptest"].managed?, "Group object is not managed." ) end def test_notmanagingdev c = mkconfig path = "/dev/testing" c.setdefaults(:test, :file => { :default => path, :mode => 0640, :desc => 'yay' } ) assert_nothing_raised { c.to_component } assert(! Puppet.type(:file)["/dev/testing"], "Created dev file") end def test_groupsetting cfile = tempfile() group = "yayness" File.open(cfile, "w") do |f| f.puts "[#{Puppet.name}] group = #{group} " end config = mkconfig config.setdefaults(Puppet.name, :group => ["puppet", "a group"]) assert_nothing_raised { config.parse(cfile) } assert_equal(group, config[:group], "Group did not take") end # provide a method to modify and create files w/out specifying the info # already stored in a config def test_writingfiles path = tempfile() mode = 0644 config = mkconfig args = { :default => path, :mode => mode } user = nonrootuser() group = nonrootgroup() - if Process.uid == 0 + if Puppet::SUIDManager.uid == 0 args[:owner] = user.name args[:group] = group.name end config.setdefaults(:testing, :myfile => args) assert_nothing_raised { config.write(:myfile) do |file| file.puts "yay" end } assert_equal(mode, filemode(path), "Modes are not equal") # OS X is broken in how it chgrps files - if Process.uid == 0 + if Puppet::SUIDManager.uid == 0 assert_equal(user.uid, File.stat(path).uid, "UIDS are not equal") case Facter["operatingsystem"].value when /BSD/, "Darwin": # nothing else assert_equal(group.gid, File.stat(path).gid, "GIDS are not equal") end end end def test_mkdir path = tempfile() mode = 0755 config = mkconfig args = { :default => path, :mode => mode } user = nonrootuser() group = nonrootgroup() - if Process.uid == 0 + if Puppet::SUIDManager.uid == 0 args[:owner] = user.name args[:group] = group.name end config.setdefaults(:testing, :mydir => args) assert_nothing_raised { config.mkdir(:mydir) } assert_equal(mode, filemode(path), "Modes are not equal") # OS X and *BSD is broken in how it chgrps files - if Process.uid == 0 + if Puppet::SUIDManager.uid == 0 assert_equal(user.uid, File.stat(path).uid, "UIDS are not equal") case Facter["operatingsystem"].value when /BSD/, "Darwin": # nothing else assert_equal(group.gid, File.stat(path).gid, "GIDS are not equal") end end end def test_booleans_and_integers config = mkconfig config.setdefaults(:mysection, :booltest => [false, "yay"], :inttest => [14, "yay"] ) file = tempfile() File.open(file, "w") do |f| f.puts %{ [mysection] booltest = true inttest = 27 } end assert_nothing_raised { config.parse(file) } assert_equal(true, config[:booltest], "Boolean was not converted") assert_equal(27, config[:inttest], "Integer was not converted") # Now make sure that they get converted through handlearg config.handlearg("--inttest", "true") assert_equal(true, config[:inttest], "Boolean was not converted") config.handlearg("--no-booltest", "false") assert_equal(false, config[:booltest], "Boolean was not converted") end # Make sure that tags are ignored when configuring def test_configs_ignore_tags config = mkconfig file = tempfile() config.setdefaults(:mysection, :mydir => [file, "a file"] ) Puppet[:tags] = "yayness" assert_nothing_raised { config.use(:mysection) } assert(FileTest.directory?(file), "Directory did not get created") assert_equal("yayness", Puppet[:tags], "Tags got changed during config") end def test_configs_replace_in_url config = mkconfig config.setdefaults(:mysection, :name => ["yayness", "yay"]) config.setdefaults(:mysection, :url => ["http://$name/rahness", "yay"]) val = nil assert_nothing_raised { val = config[:url] } assert_equal("http://yayness/rahness", val, "Config got messed up") end def test_correct_type_assumptions config = mkconfig file = Puppet::Config::CFile element = Puppet::Config::CElement bool = Puppet::Config::CBoolean # We have to keep these ordered, unfortunately. [ ["/this/is/a/file", file], ["true", bool], [true, bool], ["false", bool], ["server", element], ["http://$server/yay", element], ["$server/yayness", file], ["$server/yayness.conf", file] ].each do |ary| value, type = ary elem = nil assert_nothing_raised { elem = config.newelement( :name => value, :default => value, :section => :yayness ) } assert_instance_of(type, elem, "%s got created as wrong type" % value.inspect) end end # Make sure we correctly reparse our config files but don't lose CLI values. def test_reparse Puppet[:filetimeout] = 0 config = mkconfig() config.setdefaults(:mysection, :default => ["default", "yay"]) config.setdefaults(:mysection, :clichange => ["clichange", "yay"]) config.setdefaults(:mysection, :filechange => ["filechange", "yay"]) file = tempfile() # Set one parameter in the file File.open(file, "w") { |f| f.puts %{[mysection]\nfilechange = filevalue} } assert_nothing_raised { config.parse(file) } # Set another "from the cli" assert_nothing_raised { config.handlearg("clichange", "clivalue") } # And leave the other unset assert_equal("default", config[:default]) assert_equal("filevalue", config[:filechange]) assert_equal("clivalue", config[:clichange]) # Now rewrite the file File.open(file, "w") { |f| f.puts %{[mysection]\nfilechange = newvalue} } cfile = config.file cfile.send("tstamp=".intern, Time.now - 50) # And check all of the values assert_equal("default", config[:default]) assert_equal("clivalue", config[:clichange]) assert_equal("newvalue", config[:filechange]) end def test_parse_removes_quotes config = mkconfig() config.setdefaults(:mysection, :singleq => ["single", "yay"]) config.setdefaults(:mysection, :doubleq => ["double", "yay"]) config.setdefaults(:mysection, :none => ["noquote", "yay"]) config.setdefaults(:mysection, :middle => ["midquote", "yay"]) file = tempfile() # Set one parameter in the file File.open(file, "w") { |f| f.puts %{[mysection]\n singleq = 'one' doubleq = "one" none = one middle = mid"quote } } assert_nothing_raised { config.parse(file) } %w{singleq doubleq none}.each do |p| assert_equal("one", config[p], "%s did not match" % p) end assert_equal('mid"quote', config["middle"], "middle did not match") end def test_timer Puppet[:filetimeout] = 0.1 origpath = tempfile() config = mkconfig() config.setdefaults(:mysection, :paramdir => [tempfile(), "yay"]) file = tempfile() # Set one parameter in the file File.open(file, "w") { |f| f.puts %{[mysection]\n paramdir = #{origpath} } } assert_nothing_raised { config.parse(file) config.use(:mysection) } assert(FileTest.directory?(origpath), "dir did not get created") # Now start the timer assert_nothing_raised { EventLoop.current.monitor_timer config.timer } newpath = tempfile() File.open(file, "w") { |f| f.puts %{[mysection]\n paramdir = #{newpath} } } config.file.send("tstamp=".intern, Time.now - 50) sleep 1 assert_equal(newpath, config["paramdir"], "File did not get reparsed from timer") assert(FileTest.directory?(newpath), "new dir did not get created") end end # $Id$ diff --git a/test/providers/group.rb b/test/providers/group.rb index 63aafc1f7..1948dbdc4 100755 --- a/test/providers/group.rb +++ b/test/providers/group.rb @@ -1,239 +1,239 @@ require 'etc' require 'puppet/type' require 'puppettest' class TestGroupProvider < Test::Unit::TestCase include PuppetTest def setup super @@tmpgroups = [] @provider = nil assert_nothing_raised { @provider = Puppet::Type.type(:group).defaultprovider } assert(@provider, "Could not find default group provider") assert(@provider.name != :fake, "Got a fake provider") end def teardown super Puppet.type(:group).clear @@tmpgroups.each { |group| unless missing?(group) remove(group) end } end def mkgroup(name, hash = {}) fakemodel = fakemodel(:group, name) group = nil assert_nothing_raised { group = @provider.new(fakemodel) } hash.each do |name, val| fakemodel[name] = val end assert(group, "Could not create provider group") return group end case Facter["operatingsystem"].value when "Darwin": def missing?(group) output = %x{nidump -r /groups/#{group} / 2>/dev/null}.chomp if output == "" return true else return false end assert_equal("", output, "Group %s is present:\n%s" % [group, output]) end def gid(name) %x{nireport / /groups name gid}.split("\n").each { |line| group, id = line.chomp.split(/\s+/) assert(id =~ /^-?\d+$/, "Group id %s for %s is not a number" % [id.inspect, group]) if group == name return Integer(id) end } return nil end def remove(group) system("niutil -destroy / /groups/%s" % group) end else def missing?(group) begin obj = Etc.getgrnam(group) return false rescue ArgumentError return true end end def gid(name) assert_nothing_raised { obj = Etc.getgrnam(name) return obj.gid } return nil end def remove(group) system("groupdel %s" % group) end end def groupnames %x{groups}.chomp.split(/ /) end def groupids Process.groups end def attrtest_ensure(group) old = group.ensure assert_nothing_raised { group.ensure = :absent } assert(!group.exists?, "Group was not deleted") assert_nothing_raised { group.ensure = :present } assert(group.exists?, "Group was not created") unless old == :present assert_nothing_raised { group.ensure = old } end end def attrtest_gid(group) old = gid(group.name) newgid = old while true newgid += 1 if newgid - old > 1000 $stderr.puts "Could not find extra test UID" return end begin Etc.getgrgid(newgid) rescue ArgumentError => detail break end end assert_nothing_raised("Failed to change group id") { group.gid = newgid } curgid = nil assert_nothing_raised { curgid = gid(group.name) } assert_equal(newgid, curgid, "GID was not changed") # Refresh group.getinfo(true) assert_equal(newgid, group.gid, "Object got wrong gid") assert_nothing_raised("Failed to change group id") { group.gid = old } end # Iterate over each of our groups and try to grab the gid. def test_ownprovidergroups groupnames().each { |group| gobj = nil comp = nil fakemodel = fakemodel(:group, group) assert_nothing_raised { gobj = @provider.new(fakemodel) } assert(gobj.gid, "Failed to retrieve gid") } end - if Process.uid == 0 + if Puppet::SUIDManager.uid == 0 def test_mkgroup gobj = nil comp = nil name = "pptestgr" assert(missing?(name), "Group %s is still present" % name) group = mkgroup(name) @@tmpgroups << name assert(group.respond_to?(:addcmd), "no respondo?") assert_nothing_raised { group.create } assert(!missing?(name), "Group %s is missing" % name) tests = Puppet.type(:group).validstates tests.each { |test| if self.respond_to?("attrtest_%s" % test) self.send("attrtest_%s" % test, group) else $stderr.puts "Not testing attr %s of group" % test end } assert_nothing_raised { group.delete } end # groupadd -o is broken in FreeBSD. unless Facter["operatingsystem"].value == "FreeBSD" def test_duplicateIDs group1 = mkgroup("group1", :gid => 125) group2 = mkgroup("group2", :gid => 125) @@tmpgroups << "group1" @@tmpgroups << "group2" # Create the first group assert_nothing_raised { group1.create } # Not all OSes fail here, so we can't test that it doesn't work with # it off, only that it does work with it on. assert_nothing_raised { group2.model[:allowdupe] = :true } # Now create the second group assert_nothing_raised { group2.create } assert_equal(:present, group2.ensure, "Group did not get created") end end else $stderr.puts "Not running as root; skipping group creation tests." end end # $Id$ diff --git a/test/providers/package.rb b/test/providers/package.rb index 1c3621909..64c443c00 100644 --- a/test/providers/package.rb +++ b/test/providers/package.rb @@ -1,79 +1,79 @@ require 'etc' require 'puppet/type' require 'puppettest' class TestPackageProvider < Test::Unit::TestCase include PuppetTest def setup super @provider = nil assert_nothing_raised { @provider = Puppet::Type.type(:package).defaultprovider } assert(@provider, "Could not find default package provider") assert(@provider.name != :fake, "Got a fake provider") end def test_nothing end - if Facter["operatingsystem"].value == "Solaris" and Process.uid == 0 + if Facter["operatingsystem"].value == "Solaris" and Puppet::SUIDManager.uid == 0 if Puppet.type(:package).provider(:blastwave).suitable? # FIXME The packaging crap needs to be rewritten to support testing # multiple package types on the same platform. def test_list_blastwave pkgs = nil assert_nothing_raised { pkgs = Puppet::Type.type(:package).provider(:blastwave).list } pkgs.each do |pkg| if pkg[:name] =~ /^CSW/ assert_equal(:blastwave, pkg[:provider], "Type was not set correctly") end end end def test_install_blastwave pkg = nil name = "cabextract" model = fakemodel(:package, name) assert_nothing_raised { pkg = Puppet::Type.type(:package).provider(:blastwave).new(model) } if hash = pkg.query and hash[:ensure] != :absent p hash $stderr.puts "Cannot test pkg installation; %s is already installed" % name return end assert_nothing_raised { pkg.install } hash = nil assert(hash = pkg.query, "package did not install") assert(hash[:ensure] != :absent, "package did not install") latest = nil assert_nothing_raised { latest = pkg.latest } assert(latest, "Could not find latest package version") assert_nothing_raised { pkg.uninstall } end else $stderr.puts "No pkg-get scripting; skipping blastwave tests" end end end # $Id$ diff --git a/test/providers/user.rb b/test/providers/user.rb index 15fc202f5..1244f6acd 100644 --- a/test/providers/user.rb +++ b/test/providers/user.rb @@ -1,521 +1,521 @@ require 'puppettest' require 'puppet' require 'facter' class TestUserProvider < Test::Unit::TestCase include PuppetTest::FileTesting def setup super setme() @@tmpusers = [] @provider = nil assert_nothing_raised { @provider = Puppet::Type.type(:user).defaultprovider } assert(@provider, "Could not find default user provider") end def teardown @@tmpusers.each { |user| unless missing?(user) remove(user) end } super #Puppet.type(:user).clear end case Facter["operatingsystem"].value when "Darwin": def missing?(user) output = %x{nidump -r /users/#{user} / 2>/dev/null}.chomp if output == "" return true else return false end assert_equal("", output, "User %s is present:\n%s" % [user, output]) end def current?(param, user) state = Puppet.type(:user).states.find { |st| st.name == param } output = %x{nireport / /users name #{state.netinfokey}} output.split("\n").each { |line| if line =~ /^(\w+)\s+(.+)$/ username = $1 id = $2.sub(/\s+$/, '') if username == user.name if id =~ /^[-0-9]+$/ return Integer(id) else return id end end else raise "Could not match %s" % line end } return nil end def remove(user) system("niutil -destroy / /users/%s" % user) end else def missing?(user) begin obj = Etc.getpwnam(user) return false rescue ArgumentError return true end end def current?(param, user) state = Puppet.type(:user).states.find { |st| st.name == param } assert_nothing_raised { obj = Etc.getpwnam(user.name) return obj.send(user.posixmethod(param)) } return nil end def remove(user) system("userdel %s" % user) end end def eachstate Puppet::Type.type(:user).validstates.each do |state| yield state end end def findshell(old = nil) %w{/bin/sh /bin/bash /sbin/sh /bin/ksh /bin/zsh /bin/csh /bin/tcsh /usr/bin/sh /usr/bin/bash /usr/bin/ksh /usr/bin/zsh /usr/bin/csh /usr/bin/tcsh}.find { |shell| if old FileTest.exists?(shell) and shell != old else FileTest.exists?(shell) end } end def fakedata(name, param) case param when :name: name when :ensure: :present when :comment: "Puppet Testing User %s" % name when :gid: nonrootgroup.name when :shell: findshell() when :home: "/home/%s" % name else return nil end end def mkuser(name) fakemodel = fakemodel(:user, name) user = nil assert_nothing_raised { user = @provider.new(fakemodel) } assert(user, "Could not create provider user") return user end def test_list names = nil assert_nothing_raised { names = @provider.listbyname } assert(names.length > 0, "Listed no users") # Now try it by object assert_nothing_raised { names = @provider.list } assert(names.length > 0, "Listed no users as objects") names.each do |obj| assert_instance_of(Puppet::Type.type(:user), obj) assert(obj[:provider], "Provider was not set") end end def test_infocollection fakemodel = fakemodel(:user, @me) user = nil assert_nothing_raised { user = @provider.new(fakemodel) } assert(user, "Could not create user provider") Puppet::Type.type(:user).validstates.each do |state| next if state == :ensure val = nil assert_nothing_raised { val = user.send(state) } assert(val != :absent, "State %s is missing" % state) assert(val, "Did not get value for %s" % state) end end def test_exists user = mkuser("nosuchuserok") assert(! user.exists?, "Fake user exists?") user = mkuser(@me) assert(user.exists?, "I don't exist?") end def attrtest_ensure(user) old = user.ensure assert_nothing_raised { user.ensure = :absent } assert(missing?(user.name), "User is still present") assert_nothing_raised { user.ensure = :present } assert(!missing?(user.name), "User is absent") assert_nothing_raised { user.ensure = :absent } unless old == :absent user.ensure = old end end def attrtest_comment(user) old = user.comment assert_nothing_raised { user.comment = "A different comment" } assert_equal("A different comment", current?(:comment, user), "Comment was not changed") assert_nothing_raised { user.comment = old } assert_equal(old, current?(:comment, user), "Comment was not reverted") end def attrtest_home(user) old = current?(:home, user) assert_nothing_raised { user.home = "/tmp" } assert_equal("/tmp", current?(:home, user), "Home was not changed") assert_nothing_raised { user.home = old } assert_equal(old, current?(:home, user), "Home was not reverted") end def attrtest_shell(user) old = current?(:shell, user) newshell = findshell(old) unless newshell $stderr.puts "Cannot find alternate shell; skipping shell test" return end assert_nothing_raised { user.shell = newshell } assert_equal(newshell, current?(:shell, user), "Shell was not changed") assert_nothing_raised { user.shell = old } assert_equal(old, current?(:shell, user), "Shell was not reverted") end def attrtest_gid(user) old = current?(:gid, user) newgroup = %w{nogroup nobody staff users daemon}.find { |gid| begin group = Etc.getgrnam(gid) rescue ArgumentError => detail next end old != group.gid } group = Etc.getgrnam(newgroup) unless newgroup $stderr.puts "Cannot find alternate group; skipping gid test" return end assert_raise(ArgumentError, "gid allowed a non-integer value") do user.gid = group.name end assert_nothing_raised("Failed to specify group by id") { user.gid = group.gid } assert_equal(group.gid, current?(:gid,user), "GID was not changed") assert_nothing_raised("Failed to change back to old gid") { user.gid = old } end def attrtest_uid(user) old = current?(:uid, user) newuid = old while true newuid += 1 if newuid - old > 1000 $stderr.puts "Could not find extra test UID" return end begin newuser = Etc.getpwuid(newuid) rescue ArgumentError => detail break end end assert_nothing_raised("Failed to change user id") { user.uid = newuid } assert_equal(newuid, current?(:uid, user), "UID was not changed") assert_nothing_raised("Failed to change user id") { user.uid = old } assert_equal(old, current?(:uid, user), "UID was not changed back") end def attrtest_groups(user) Etc.setgrent max = 0 while group = Etc.getgrent if group.gid > max and group.gid < 5000 max = group.gid end end groups = [] main = [] extra = [] 5.times do |i| i += 1 name = "pptstgr%s" % i tmpgroup = Puppet.type(:group).create( :name => name, :gid => max + i ) groups << tmpgroup cleanup do tmpgroup.provider.delete if tmpgroup.provider.exists? end if i < 3 main << name else extra << name end end # Create our test groups assert_apply(*groups) # Now add some of them to our user assert_nothing_raised { user.model[:groups] = extra.join(",") } # Some tests to verify that groups work correctly startig from nothing # Remove our user user.ensure = :absent # And add it again user.ensure = :present # Make sure that the group list is added at creation time. # This is necessary because we don't have default fakedata for groups. assert(user.groups, "Did not retrieve group list") list = user.groups.split(",") assert_equal(extra.sort, list.sort, "Group list was not set at creation time") # Now set to our main list of groups assert_nothing_raised { user.groups = main.join(",") } list = user.groups.split(",") assert_equal(main.sort, list.sort, "Group list is not equal") end - if Process.uid == 0 + if Puppet::SUIDManager.uid == 0 def test_simpleuser name = "pptest" assert(missing?(name), "User %s is present" % name) user = mkuser(name) eachstate do |state| if val = fakedata(user.name, state) user.model[state] = val end end @@tmpusers << name assert_nothing_raised { user.create } assert_equal("Puppet Testing User pptest", user.comment, "Comment was not set") assert_nothing_raised { user.delete } assert(missing?(user.name), "User was not deleted") end def test_alluserstates user = nil name = "pptest" assert(missing?(name), "User %s is present" % name) user = mkuser(name) eachstate do |state| if val = fakedata(user.name, state) user.model[state] = val end end @@tmpusers << name assert_nothing_raised { user.create } assert_equal("Puppet Testing User pptest", user.comment, "Comment was not set") tests = Puppet::Type.type(:user).validstates just = nil tests.each { |test| next unless test == :groups if self.respond_to?("attrtest_%s" % test) self.send("attrtest_%s" % test, user) else Puppet.err "Not testing attr %s of user" % test end } assert_nothing_raised { user.delete } end # This is a weird method that shows how annoying the interface between # types and providers is. Grr. def test_duplicateIDs user1 = mkuser("user1") user1.create user1.uid = 125 user2 = mkuser("user2") user2.model[:uid] = 125 cleanup do user1.ensure = :absent user2.ensure = :absent end # Not all OSes fail here, so we can't test that it doesn't work with # it off, only that it does work with it on. assert_nothing_raised { user2.model[:allowdupe] = :true } assert_nothing_raised { user2.create } assert_equal(:present, user2.ensure, "User did not get created") end else $stderr.puts "Not root; skipping user creation/modification tests" end # Here is where we test individual providers def test_useradd_flags useradd = nil assert_nothing_raised { useradd = Puppet::Type.type(:user).provider(:useradd) } assert(useradd, "Did not retrieve useradd provider") user = nil assert_nothing_raised { fakemodel = fakemodel(:user, @me) user = useradd.new(fakemodel) } assert_equal("-d", user.send(:flag, :home), "Incorrect home flag") assert_equal("-s", user.send(:flag, :shell), "Incorrect shell flag") end end # $Id$ diff --git a/test/puppet/defaults.rb b/test/puppet/defaults.rb index 46accc9c6..250fd29c0 100755 --- a/test/puppet/defaults.rb +++ b/test/puppet/defaults.rb @@ -1,95 +1,95 @@ require 'puppet' require 'puppettest' # $Id$ class TestPuppetDefaults < Test::Unit::TestCase include PuppetTest @@dirs = %w{rrddir confdir vardir logdir statedir} @@files = %w{statefile manifest masterlog} @@normals = %w{puppetport masterport server} @@booleans = %w{rrdgraph noop} def testVersion assert( Puppet.version =~ /^[0-9]+(\.[0-9]+)*$/ ) end def testStringOrParam [@@dirs,@@files,@@booleans].flatten.each { |param| assert_nothing_raised { Puppet[param] } assert_nothing_raised { Puppet[param.intern] } } end def test_valuesForEach [@@dirs,@@files,@@booleans].flatten.each { |param| param = param.intern assert_nothing_raised { Puppet[param] } } end def testValuesForEach [@@dirs,@@files,@@booleans].flatten.each { |param| assert_nothing_raised { Puppet[param] } } end if __FILE__ == $0 def disabled_testContained confdir = Regexp.new(Puppet[:confdir]) vardir = Regexp.new(Puppet[:vardir]) [@@dirs,@@files].flatten.each { |param| value = Puppet[param] unless value =~ confdir or value =~ vardir assert_nothing_raised { raise "%s is in wrong dir: %s" % [param,value] } end } end end def testArgumentTypes assert_raise(ArgumentError) { Puppet[["string"]] } assert_raise(ArgumentError) { Puppet[[:symbol]] } end def testFailOnBogusArgs [0, "ashoweklj", ";"].each { |param| assert_raise(ArgumentError, "No error on %s" % param) { Puppet[param] } } end # we don't want user defaults in /, or root defaults in ~ def testDefaultsInCorrectRoots notval = nil - if Process.uid == 0 + if Puppet::SUIDManager.uid == 0 notval = Regexp.new(File.expand_path("~")) else notval = /^\/var|^\/etc/ end [@@dirs,@@files].flatten.each { |param| value = Puppet[param] unless value !~ notval assert_nothing_raised { raise "%s is incorrectly set to %s" % [param,value] } end } end def test_settingdefaults testvals = { :fakeparam => "$confdir/yaytest", :anotherparam => "$vardir/goodtest", :string => "a yay string", :boolean => true } testvals.each { |param, default| assert_nothing_raised { Puppet.setdefaults("testing", param => [default, "a value"]) } } end end diff --git a/test/puppet/suidmanager.rb b/test/puppet/suidmanager.rb new file mode 100644 index 000000000..f5cb8496e --- /dev/null +++ b/test/puppet/suidmanager.rb @@ -0,0 +1,71 @@ +require 'test/unit' +require 'puppettest' + +class TestProcess < Test::Unit::TestCase + def setup + if Process.uid != 0 + $stderr.puts "Process tests must be run as root" + @run = false + else + @run = true + end + end + + def test_id_set + if @run + # FIXME: use the test framework uid finder + assert_nothing_raised do + Puppet::SUIDManager.egid = 501 + Puppet::SUIDManager.euid = 501 + end + + assert_equal(Puppet::SUIDManager.euid, Process.euid) + assert_equal(Puppet::SUIDManager.egid, Process.egid) + + assert_nothing_raised do + Puppet::SUIDManager.euid = 0 + Puppet::SUIDManager.egid = 0 + end + + assert_uid_gid(501, 501) + end + end + + def test_asuser + if @run + uid, gid = [nil, nil] + + assert_nothing_raised do + Puppet::SUIDManager.asuser(501, 501) do + uid = Puppet::SUIDManager.euid + gid = Puppet::SUIDManager.egid + end + end + + assert_equal(501, uid) + assert_equal(501, gid) + end + end + + def test_system + # NOTE: not sure what shells this will work on.. + # FIXME: use the test framework uid finder, however the uid needs to be < 255 + if @run + Puppet::SUIDManager.system("exit $EUID", 10, 10) + assert_equal($?.exitstatus, 10) + end + end + + def test_run_and_capture + if (RUBY_VERSION <=> "1.8.4") < 0 + warn "Cannot run this test on ruby < 1.8.4" + else + # NOTE: because of the way that run_and_capture currently + # works, we cannot just blindly echo to stderr. This little + # hack gets around our problem, but the real problem is the + # way that run_and_capture works. + output = Puppet::SUIDManager.run_and_capture("ruby -e '$stderr.puts \"foo\"'")[0].chomp + assert_equal(output, 'foo') + end + end +end diff --git a/test/types/cron.rb b/test/types/cron.rb index b802a1c77..2794ec358 100755 --- a/test/types/cron.rb +++ b/test/types/cron.rb @@ -1,735 +1,735 @@ # Test cron job creation, modification, and destruction require 'puppettest' require 'puppet' require 'facter' class TestCron < Test::Unit::TestCase include PuppetTest def setup super setme() # god i'm lazy @crontype = Puppet.type(:cron) @oldfiletype = @crontype.filetype @fakefiletype = Puppet::FileType.filetype(:ram) @crontype.filetype = @fakefiletype end def teardown @crontype.filetype = @oldfiletype Puppet::FileType.filetype(:ram).clear super 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 = newcomp(name, cron) assert_events([:cron_created], comp) cron.retrieve assert(cron.insync?, "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.retrieve assert(cron.insync?, "Cron is not in sync") assert_events([], comp) end # A simple test to see if we can load the cron from disk. def test_load assert_nothing_raised { @crontype.retrieve(@me) } end # Test that a cron job turns out as expected, by creating one and generating # it directly def test_simple_to_cron cron = nil # make the cron name = "yaytest" assert_nothing_raised { cron = @crontype.create( :name => name, :command => "date > /dev/null", :user => @me ) } str = nil # generate the text assert_nothing_raised { str = cron.to_record } assert_equal(str, "# Puppet Name: #{name}\n* * * * * date > /dev/null", "Cron did not generate correctly") end def test_simpleparsing @fakefiletype = Puppet::FileType.filetype(:ram) @crontype.filetype = @fakefiletype @crontype.retrieve(@me) obj = Puppet::Type::Cron.cronobj(@me) text = "5 1,2 * 1 0 /bin/echo funtest" assert_nothing_raised { @crontype.parse(@me, text) } @crontype.each do |obj| assert_equal(["5"], obj.is(:minute), "Minute was not parsed correctly") assert_equal(["1", "2"], obj.is(:hour), "Hour was not parsed correctly") assert_equal([:absent], obj.is(:monthday), "Monthday was not parsed correctly") assert_equal(["1"], obj.is(:month), "Month was not parsed correctly") assert_equal(["0"], obj.is(:weekday), "Weekday was not parsed correctly") assert_equal(["/bin/echo funtest"], obj.is(:command), "Command was not parsed correctly") end end # Test that changing any field results in the cron tab being rewritten. # it directly def test_any_field_changes cron = nil # make the cron name = "yaytest" assert_nothing_raised { cron = @crontype.create( :name => name, :command => "date > /dev/null", :month => "May", :user => @me ) } assert(cron, "Cron did not get created") comp = newcomp(cron) assert_events([:cron_created], comp) assert_nothing_raised { cron[:month] = "June" } cron.retrieve assert_events([:cron_changed], comp) end # Test that a cron job with spaces at the end doesn't get rewritten def test_trailingspaces cron = nil # make the cron name = "yaytest" assert_nothing_raised { cron = @crontype.create( :name => name, :command => "date > /dev/null ", :month => "May", :user => @me ) } comp = newcomp(cron) assert_events([:cron_created], comp, "did not create cron job") cron.retrieve assert_events([], comp, "cron job got rewritten") end # Test that comments are correctly retained def test_retain_comments str = "# this is a comment\n#and another comment\n" user = "fakeuser" @crontype.retrieve(@me) assert_nothing_raised { @crontype.parse(@me, str) } assert_nothing_raised { newstr = @crontype.tab(@me) assert(newstr.include?(str), "Comments were lost") } end # Test that a specified cron job will be matched against an existing job # with no name, as long as all fields match def test_matchcron str = "0,30 * * * * date\n" assert_nothing_raised { cron = @crontype.create( :name => "yaycron", :minute => [0, 30], :command => "date", :user => @me ) } assert_nothing_raised { @crontype.parse(@me, str) } count = @crontype.inject(0) do |c, obj| c + 1 end assert_equal(1, count, "Did not match cron job") modstr = "# Puppet Name: yaycron\n%s" % str assert_nothing_raised { newstr = @crontype.tab(@me) assert(newstr.include?(modstr), "Cron was not correctly matched") } end # Test adding a cron when there is currently no file. def test_mkcronwithnotab tab = @fakefiletype.new(@me) tab.remove @crontype.retrieve(@me) cron = mkcron("testwithnotab") cyclecron(cron) end def test_mkcronwithtab @crontype.retrieve(@me) obj = Puppet::Type::Cron.cronobj(@me) obj.write( "1 1 1 1 * date > %s/crontesting\n" % tstdir() ) cron = mkcron("testwithtab") cyclecron(cron) end def test_makeandretrievecron tab = @fakefiletype.new(@me) tab.remove %w{storeandretrieve a-name another-name more_naming SomeName}.each do |name| cron = mkcron(name) comp = newcomp(name, cron) trans = assert_events([:cron_created], comp, name) cron = nil Puppet.type(:cron).retrieve(@me) 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 # state 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 # Test that we can read and write cron tabs def test_crontab Puppet.type(:cron).filetype = Puppet.type(:cron).defaulttype type = nil unless type = Puppet.type(:cron).filetype $stderr.puts "No crontab type; skipping test" end obj = nil assert_nothing_raised { - obj = type.new(Process.uid) + obj = type.new(Puppet::SUIDManager.uid) } txt = nil assert_nothing_raised { txt = obj.read } assert_nothing_raised { obj.write(txt) } end # Verify that comma-separated numbers are not resulting in rewrites def test_norewrite cron = nil assert_nothing_raised { cron = Puppet.type(:cron).create( :command => "/bin/date > /dev/null", :minute => [0, 30], :name => "crontest" ) } assert_events([:cron_created], cron) cron.retrieve assert_events([], cron) end def test_fieldremoval cron = nil assert_nothing_raised { cron = Puppet.type(:cron).create( :command => "/bin/date > /dev/null", :minute => [0, 30], :name => "crontest" ) } assert_events([:cron_created], cron) cron[:minute] = :absent assert_events([:cron_changed], cron) assert_nothing_raised { cron.retrieve } assert_equal(:absent, cron.is(:minute)) end def test_listing @crontype.filetype = @oldfiletype crons = [] assert_nothing_raised { Puppet::Type.type(:cron).list.each do |cron| crons << cron end } crons.each do |cron| assert(cron, "Did not receive a real cron object") assert_instance_of(String, cron[:user], "Cron user is not a string") end end def verify_failonnouser assert_raise(Puppet::Error) do @crontype.retrieve("nosuchuser") end end def test_names cron = mkcron("nametest") ["bad name", "bad.name"].each do |name| assert_raise(ArgumentError) do cron[:name] = name end end ["good-name", "good-name", "AGoodName"].each do |name| assert_nothing_raised do cron[:name] = name end end end # Make sure we don't puke on env settings def test_envsettings cron = mkcron("envtst") assert_apply(cron) obj = Puppet::Type::Cron.cronobj(@me) assert(obj) text = obj.read text = "SHELL = /path/to/some/thing\n" + text obj.write(text) assert_nothing_raised { cron.retrieve } cron[:command] = "/some/other/command" assert_apply(cron) assert(obj.read =~ /SHELL/, "lost env setting") env1 = "TEST = /bin/true" env2 = "YAY = fooness" assert_nothing_raised { cron[:environment] = [env1, env2] } assert_apply(cron) cron.retrieve vals = cron.is(:environment) assert(vals, "Did not get environment settings") assert(vals != :absent, "Env is incorrectly absent") assert_instance_of(Array, vals) assert(vals.include?(env1), "Missing first env setting") assert(vals.include?(env2), "Missing second env setting") end def test_divisionnumbers cron = mkcron("divtest") cron[:minute] = "*/5" assert_apply(cron) cron.retrieve assert_equal(["*/5"], cron.is(:minute)) end def test_ranges cron = mkcron("rangetest") cron[:minute] = "2-4" assert_apply(cron) cron.retrieve assert_equal(["2-4"], cron.is(:minute)) end def test_data @fakefiletype = Puppet::FileType.filetype(:ram) @crontype.filetype = @fakefiletype @crontype.retrieve(@me) obj = Puppet::Type::Cron.cronobj(@me) fakedata("data/types/cron").each do |file| names = [] text = File.read(file) obj.write(File.read(file)) @crontype.retrieve(@me) @crontype.each do |cron| names << cron.name end name = File.basename(file) cron = mkcron("filetest-#{name}") assert_apply(cron) @crontype.retrieve(@me) names.each do |name| assert(@crontype[name], "Could not retrieve %s" % name) end tablines = @crontype.tab(@me).split("\n") text.split("\n").each do |line| assert(tablines.include?(line), "Did not get %s back out" % line.inspect) end end end def test_value cron = mkcron("valuetesting", false) # First, test the normal states [:minute, :hour, :month].each do |param| cron.newstate(param) state = cron.state(param) assert(state, "Did not get %s state" % param) assert_nothing_raised { state.is = :absent } # Make sure our minute default is 0, not * val = if param == :minute "*" # the "0" thing is disabled for now else "*" end assert_equal(val, cron.value(param)) # Make sure we correctly get the "is" value if that's all there is cron.is = [param, "1"] assert_equal("1", cron.value(param)) # Make sure arrays work, too cron.is = [param, ["1"]] assert_equal("1", cron.value(param)) # Make sure values get comma-joined cron.is = [param, ["2", "3"]] assert_equal("2,3", cron.value(param)) # Make sure "should" values work, too cron[param] = "4" assert_equal("4", cron.value(param)) cron[param] = ["4"] assert_equal("4", cron.value(param)) cron[param] = ["4", "5"] assert_equal("4,5", cron.value(param)) cron.is = [param, :absent] assert_equal("4,5", cron.value(param)) end # Now make sure that :command works correctly cron.delete(:command) cron.newstate(:command) state = cron.state(:command) assert_nothing_raised { state.is = :absent } assert(state, "Did not get command state") assert_raise(Puppet::DevError) do cron.value(:command) end param = :command # Make sure we correctly get the "is" value if that's all there is cron.is = [param, "1"] assert_equal("1", cron.value(param)) # Make sure arrays work, too cron.is = [param, ["1"]] assert_equal("1", cron.value(param)) # Make sure values are not comma-joined cron.is = [param, ["2", "3"]] assert_equal("2", cron.value(param)) # Make sure "should" values work, too cron[param] = "4" assert_equal("4", cron.value(param)) cron[param] = ["4"] assert_equal("4", cron.value(param)) cron[param] = ["4", "5"] assert_equal("4", cron.value(param)) cron.is = [param, :absent] assert_equal("4", cron.value(param)) end # Make sure we can successfully list all cron jobs on all users def test_cron_listing crons = [] %w{fake1 fake2 fake3 fake4 fake5}.each do |user| crons << @crontype.create( :name => "#{user}-1", :command => "/usr/bin/#{user}", :minute => "0", :user => user, :hour => user.sub("fake",'') ) crons << @crontype.create( :name => "#{user}-2", :command => "/usr/sbin/#{user}", :minute => "0", :user => user, :weekday => user.sub("fake",'') ) assert_apply(*crons) end list = @crontype.list.collect { |c| c.name } crons.each do |cron| assert(list.include?(cron.name), "Did not match cron %s" % name) end end # Make sure we can create a cron in an empty tab def test_mkcron_if_empty @crontype.filetype = @oldfiletype @crontype.retrieve(@me) # Backup our tab text = @crontype.tabobj(@me).read cleanup do if text == "" @crontype.tabobj(@me).remove else @crontype.tabobj(@me).write(text) end end # Now get rid of it @crontype.tabobj(@me).remove @crontype.clear cron = mkcron("emptycron") assert_apply(cron) # Clear the type, but don't clear the filetype @crontype.clear # Get the stuff again @crontype.retrieve(@me) assert(@crontype["emptycron"], "Did not retrieve cron") end def test_multiple_users crons = [] users = ["root", nonrootuser.name] users.each do |user| crons << Puppet::Type.type(:cron).create( :name => "testcron-#{user}", :user => user, :command => "/bin/echo", :minute => [0,30] ) end assert_apply(*crons) users.each do |user| users.each do |other| next if user == other assert(Puppet::Type.type(:cron).tabobj(other).read !~ /testcron-#{user}/, "%s's cron job is in %s's tab" % [user, other]) end end end end # $Id$ diff --git a/test/types/exec.rb b/test/types/exec.rb index 615dd86cd..c5decb80a 100755 --- a/test/types/exec.rb +++ b/test/types/exec.rb @@ -1,581 +1,581 @@ require 'puppet' require 'puppettest' require 'facter' 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 file = nil cmd = nil tmpfile = tempfile() @@tmpfiles.push tmpfile trans = nil File.open(tmpfile, File::WRONLY|File::CREAT|File::TRUNC) { |of| of.puts rand(100) } file = Puppet.type(:file).create( :path => tmpfile, :checksum => "md5" ) assert_instance_of(Puppet.type(:file), file) assert_nothing_raised { cmd = Puppet.type(:exec).create( :command => "pwd", :path => "/usr/bin:/bin:/usr/sbin:/sbin", :subscribe => [[file.class.name,file.name]], :refreshonly => true ) } assert_instance_of(Puppet.type(:exec), cmd) comp = Puppet.type(:component).create(:name => "RefreshTest") [file,cmd].each { |obj| comp.push obj } events = nil assert_nothing_raised { trans = comp.evaluate file.retrieve sum = file.state(:checksum) assert_equal(sum.is, sum.should) events = trans.evaluate.collect { |event| event.event } } # the first checksum shouldn't result in a changed file assert_equal([],events) File.open(tmpfile, File::WRONLY|File::CREAT|File::TRUNC) { |of| of.puts rand(100) of.puts rand(100) of.puts rand(100) } assert_nothing_raised { trans = comp.evaluate sum = file.state(:checksum) events = trans.evaluate.collect { |event| event.event } } # verify that only the file_changed event was kicked off, not the # command_executed assert_equal( [:file_changed, :triggered], events ) 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 = newcomp("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 = newcomp("Testing", file, exec) assert_events([:file_created, :executed_command], comp) end # Verify that we auto-require any managed scripts. def test_autorequire 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 => 755 ) basedir = File.dirname(oexe) baseobj = Puppet.type(:file).create( :path => basedir, :source => exe, :mode => 755 ) ofile = Puppet.type(:file).create( :path => exe, :mode => 755 ) exec = Puppet.type(:exec).create( :command => oexe, :path => ENV["PATH"], :cwd => basedir ) cat = Puppet.type(:exec).create( :command => "cat %s %s" % [exe, oexe], :path => ENV["PATH"] ) comp = newcomp(ofile, exec, cat, file, baseobj) comp.finalize # Verify we get the script itself assert(exec.requires?(file), "Exec did not autorequire %s" % file) # Verify we catch the cwd assert(exec.requires?(baseobj), "Exec did not autorequire cwd") # Verify we don't require ourselves assert(!exec.requires?(ofile), "Exec incorrectly required file") # Verify that we catch inline files # We not longer autorequire inline files assert(! cat.requires?(ofile), "Exec required second inline file") assert(! cat.requires?(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 = newcomp(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 Process.uid == 0 + if Puppet::SUIDManager.uid == 0 # Verify that we can execute commands as a special user def mknverify(file, user, group = nil, id = true) 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 = newcomp("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 = newcomp(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 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( :path => ENV["PATH"], :command => "mkdir #{dir}" ) } assert_nothing_raised("Could not create exec w/out existing cwd") { exec2 = Puppet.type(:exec).create( :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.state(:returns).sync end assert_nothing_raised do exec2[:require] = ["exec", exec1.name] exec2.finish 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.newexec( :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 end # $Id$ diff --git a/test/types/file.rb b/test/types/file.rb index d2c593c8b..71a04a40d 100644 --- a/test/types/file.rb +++ b/test/types/file.rb @@ -1,1425 +1,1425 @@ require 'puppet' require 'fileutils' require 'puppettest' class TestFile < Test::Unit::TestCase include PuppetTest::FileTesting # 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 begin initstorage rescue system("rm -rf %s" % Puppet[:statefile]) end end def teardown Puppet::Storage.clear system("rm -rf %s" % Puppet[:statefile]) super end def initstorage Puppet::Storage.init Puppet::Storage.load end def clearstorage Puppet::Storage.store Puppet::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.state(:group)) assert(file.state(:group).should) } end - if Process.uid == 0 + if Puppet::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 = newcomp("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) file.retrieve assert(file.insync?()) assert_nothing_raised() { file[:owner] = uid } assert_apply(file) file.retrieve # make sure changing to number doesn't cause a sync assert(file.insync?()) } # 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| assert_nothing_raised() { file[:group] = group } assert(file.state(:group)) assert(file.state(:group).should) assert_apply(file) file.retrieve assert(file.insync?()) 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?()) @@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?()) 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?()) 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 ) } comp = Puppet.type(:component).create( :name => "checksum %s" % type ) comp.push file trans = nil file.retrieve if file.title !~ /nonexists/ sum = file.state(:checksum) assert_equal(sum.is, sum.should) assert(sum.insync?) end events = assert_apply(comp) assert(! events.include?(:file_changed), "File incorrectly changed") assert_events([], comp) # We have to sleep because the time resolution of the time-based # mechanisms is greater than one second sleep 1 assert_nothing_raised() { File.open(path,File::CREAT|File::TRUNC|File::WRONLY) { |of| of.puts "some more text, yo" } } Puppet.type(:file).clear Puppet.type(:component).clear # now recreate the file assert_nothing_raised() { file = Puppet.type(:file).create( :name => path, :checksum => type ) } comp = Puppet.type(:component).create( :name => "checksum, take 2, %s" % type ) comp.push file trans = nil # If the file was missing, it should not generate an event # when it gets created. #if path =~ /nonexists/ # assert_events([], comp) #else assert_events([:file_changed], comp) #end 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_apply(comp) assert_events([:file_changed], comp) # verify that we're actually getting notified when a file changes assert_nothing_raised() { Puppet.type(:file).clear Puppet.type(:component).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_recursion basedir = tempfile() subdir = File.join(basedir, "this", "is", "sub", "dir") tmpfile = File.join(subdir,"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} ) } assert_nothing_raised { dir.evaluate } subobj = nil assert_nothing_raised { subobj = Puppet.type(:file)[subdir] } assert(subobj, "Could not retrieve %s object" % subdir) File.open(tmpfile, "w") { |f| f.puts "yayness" } dir.evaluate file = nil assert_nothing_raised { file = Puppet.type(:file)[tmpfile] } assert(file, "Could not retrieve %s object" % tmpfile) #system("rm -rf %s" % basedir) Puppet.type(:file).clear end end =begin def test_ignore end =end # XXX disabled until i change how dependencies work def disabled_test_recursionwithcreation path = "/tmp/this/directory/structure/does/not/exist" @@tmpfiles.push "/tmp/this" file = nil assert_nothing_raised { file = mkfile( :name => path, :recurse => true, :ensure => "file" ) } trans = nil comp = newcomp("recursewithfiles", file) assert_nothing_raised { trans = comp.evaluate } events = nil assert_nothing_raised { events = trans.evaluate.collect { |e| e.event.to_s } } puts "events are %s" % events.join(", ") 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.state(:type).is) # 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.state(:type).is) file[:type] = "directory" assert_nothing_raised { 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?) 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} ) } assert_nothing_raised { dir.retrieve } 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, "and", "a", "sub", "dir") 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} ) } assert_nothing_raised { dirobj.evaluate } assert_nothing_raised { file = dirobj.class[path] } assert(file, "Could not retrieve file object") assert_equal("file=%s" % file.title, file.path) end def test_autorequire basedir = tempfile() subfile = File.join(basedir, "subfile") baseobj = Puppet.type(:file).create( :name => basedir, :ensure => "directory" ) subobj = Puppet.type(:file).create( :name => subfile, :ensure => "file" ) comp = newcomp(baseobj, subobj) comp.finalize assert(subobj.requires?(baseobj), "File did not require basedir") assert(!subobj.requires?(subobj), "File required itself") assert_events([:directory_created, :file_created], comp) 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?, "Object is incorrectly in sync") assert_events([:file_created], obj) obj.retrieve assert(obj.insync?, "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?, "Object is incorrectly in sync") assert_events([:file_changed], obj) text = File.read(file) assert_equal(newstr, text, "Content did not copy correctly") obj.retrieve assert(obj.insync?, "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 = newcomp("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" ) } file.retrieve assert_events([:file_created], file) file.retrieve assert_events([], file) assert_events([], file) 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.state(:group), "Group state failed") end def test_modecreation path = tempfile() file = Puppet.type(:file).create( :path => path, :ensure => "file", :mode => "0777" ) assert_apply(file) assert_equal(0777, File.stat(path).mode & 007777) File.unlink(path) file[:ensure] = "directory" assert_apply(file) assert_equal(0777, File.stat(path).mode & 007777) end def test_followlinks 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 # states 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 # 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( :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) end def test_simplerecursivelinking source = tempfile() dest = 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 => dest, :recurse => true ) } assert_apply(link) subdest = File.join(dest, "subdir") linkpath = File.join(subdest, "file") assert(File.directory?(dest), "dest is not a dir") assert(File.directory?(subdest), "subdest is not a dir") assert(File.symlink?(linkpath), "path is not a link") assert_equal(file, File.readlink(linkpath)) assert_events([], link) 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], :path => ENV["PATH"] ) objects << Puppet.type(:file).create( :ensure => source, :path => dest, :recurse => true ) 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_backupmodes file = tempfile() newfile = tempfile() 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" ) } # user = group = nil # if Process.uid == 0 # user = nonrootuser # group = nonrootgroup # obj[:owner] = user.name # obj[:group] = group.name # File.chown(user.uid, group.gid, file) # end assert_apply(obj) backupfile = file + obj[: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") # if Process.uid == 0 # assert_equal(user.uid, File.stat(backupfile).uid) # assert_equal(group.gid, File.stat(backupfile).gid) # end bucket = "bucket" bpath = tempfile() Dir.mkdir(bpath) Puppet::Type.type(:filebucket).create( :title => bucket, :path => bpath ) obj[:backup] = bucket obj[:content] = "New content" assert_apply(obj) bucketedpath = File.join(bpath, "18cc17fa3047fcc691fdf49c0a7f539a", "contents") assert_equal(0440, filemode(bucketedpath)) 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_replacedirwithlink 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"], "link") 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 ) trans = assert_events([:file_created, :file_created], obj) newfile = File.join(dirs["target"], "sourcefile") assert(File.exists?(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 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) file.retrieve assert(! file.insync?, "File incorrectly in sync") # Now make a file File.open(path, "w") { |f| f.puts "yay" } file.retrieve assert(file.insync?, "File not in sync") # Now make a directory File.unlink(path) Dir.mkdir(path) file.retrieve assert(file.insync?, "Directory not considered 'present'") Dir.rmdir(path) # Now make a link file[:links] = :manage otherfile = tempfile() File.symlink(otherfile, path) file.retrieve assert(file.insync?, "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 can be 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") randfile = File.join(destdir, "random") File.open(sourcefile, "w") { |f| f.puts "funtest" } # this file should get removed File.open(randfile, "w") { |f| f.puts "footest" } lfobj = Puppet::Type.newfile(:path => localfile, :content => "rahtest") destobj = Puppet::Type.newfile(:path => destdir, :source => sourcedir, :recurse => true) assert_apply(lfobj, destobj) assert(FileTest.exists?(dsourcefile), "File did not get copied") assert(FileTest.exists?(localfile), "File did not get created") assert(FileTest.exists?(randfile), "File got prematurely purged") assert_nothing_raised { destobj[:purge] = true } assert_apply(lfobj, destobj) assert(FileTest.exists?(dsourcefile), "File got purged") assert(FileTest.exists?(localfile), "File got purged") assert(! FileTest.exists?(randfile), "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 end # $Id$ diff --git a/test/types/mount.rb b/test/types/mount.rb index e0838e072..78a5443b2 100755 --- a/test/types/mount.rb +++ b/test/types/mount.rb @@ -1,287 +1,287 @@ # Test host job creation, modification, and destruction require 'puppettest' require 'puppet' require 'puppet/type/parsedtype/mount' require 'facter' class TestMounts < Test::Unit::TestCase include PuppetTest def setup super @mounttype = Puppet.type(:mount) @oldfiletype = @mounttype.filetype end def teardown @mounttype.filetype = @oldfiletype Puppet.type(:file).clear super end # Here we just create a fake host type that answers to all of the methods # but does not modify our actual system. def mkfaketype pfile = tempfile() old = @mounttype.filetype @mounttype.filetype = Puppet::FileType.filetype(:ram) cleanup do @mounttype.filetype = old @mounttype.fileobj = nil end # Reset this, just in case @mounttype.fileobj = nil end def mkmount mount = nil if defined? @pcount @pcount += 1 else @pcount = 1 end args = { :path => "/fspuppet%s" % @pcount, :device => "/dev/dsk%s" % @pcount, } Puppet.type(:mount).fields.each do |field| unless args.include? field args[field] = "fake%s" % @pcount end end assert_nothing_raised { mount = Puppet.type(:mount).create(args) } return mount end def test_simplemount mkfaketype host = nil assert_nothing_raised { assert_nil(Puppet.type(:mount).retrieve) } mount = mkmount assert_nothing_raised { Puppet.type(:mount).store } assert_nothing_raised { assert( Puppet.type(:mount).to_file.include?( Puppet.type(:mount).fileobj.read ), "File does not include all of our objects" ) } end unless Facter["operatingsystem"].value == "Darwin" def test_mountsparse use_fake_fstab assert_nothing_raised { @mounttype.retrieve } # Now just make we've got some mounts we know will be there root = @mounttype["/"] assert(root, "Could not retrieve root mount") end def test_rootfs fs = nil use_fake_fstab assert_nothing_raised { Puppet.type(:mount).retrieve } assert_nothing_raised { fs = Puppet.type(:mount)["/"] } assert(fs, "Could not retrieve root fs") assert_nothing_raised { assert(fs.mounted?, "Root is considered not mounted") } end end # Make sure it reads and writes correctly. def test_readwrite use_fake_fstab assert_nothing_raised { Puppet::Type.type(:mount).retrieve } oldtype = Puppet::Type.type(:mount).filetype # Now switch to storing in ram mkfaketype fs = mkmount assert(Puppet::Type.type(:mount).filetype != oldtype) assert_events([:mount_created], fs) text = Puppet::Type.type(:mount).fileobj.read assert(text =~ /#{fs[:path]}/, "Text did not include new fs") fs[:ensure] = :absent assert_events([:mount_removed], fs) text = Puppet::Type.type(:mount).fileobj.read assert(text !~ /#{fs[:path]}/, "Text still includes new fs") fs[:ensure] = :present assert_events([:mount_created], fs) text = Puppet::Type.type(:mount).fileobj.read assert(text =~ /#{fs[:path]}/, "Text did not include new fs") fs[:options] = "rw,noauto" assert_events([:mount_changed], fs) end - if Process.uid == 0 + if Puppet::SUIDManager.uid == 0 def test_mountfs fs = nil case Facter["hostname"].value when "culain": fs = "/ubuntu" when "atalanta": fs = "/mnt" when "figurehead": fs = "/cg4/net/depts" else $stderr.puts "No mount for mount testing; skipping" return end assert_nothing_raised { Puppet.type(:mount).retrieve } oldtext = Puppet::Type.type(:mount).fileobj.read ftype = Puppet::Type.type(:mount).filetype # Make sure the original gets reinstalled. if ftype == Puppet::FileType.filetype(:netinfo) cleanup do IO.popen("niload -r /mounts .", "w") do |file| file.puts oldtext end end else cleanup do Puppet::Type.type(:mount).fileobj.write(oldtext) end end obj = Puppet.type(:mount)[fs] assert(obj, "Could not retrieve %s object" % fs) current = nil assert_nothing_raised { current = obj.mounted? } if current # Make sure the original gets reinstalled. cleanup do unless obj.mounted? obj.mount end end end unless current assert_nothing_raised { obj.mount } end # Now copy all of the states' "is" values to the "should" values obj.each do |state| state.should = state.is end # Verify we can remove the mount assert_nothing_raised { obj[:ensure] = :absent } assert_events([:mount_removed], obj) assert_events([], obj) # And verify it's gone assert(!obj.mounted?, "Object is mounted after being removed") text = Puppet.type(:mount).fileobj.read assert(text !~ /#{fs}/, "Fstab still contains %s" % fs) assert_nothing_raised { obj[:ensure] = :present } assert_events([:mount_created], obj) assert_events([], obj) text = Puppet::Type.type(:mount).fileobj.read assert(text =~ /#{fs}/, "Fstab does not contain %s" % fs) assert(! obj.mounted?, "Object is mounted incorrectly") assert_nothing_raised { obj[:ensure] = :mounted } assert_events([:mount_mounted], obj) assert_events([], obj) text = Puppet::Type.type(:mount).fileobj.read assert(text =~ /#{fs}/, "Fstab does not contain %s" % fs) obj.retrieve assert(obj.mounted?, "Object is not mounted") unless current assert_nothing_raised { obj.unmount } end end end def use_fake_fstab os = Facter['operatingsystem'] if os == "Solaris" name = "solaris.fstab" elsif os == "FreeBSD" name = "freebsd.fstab" else # Catchall for other fstabs name = "linux.fstab" end fstab = fakefile(File::join("data/types/mount", name)) Puppet::Type.type(:mount).path = fstab end end # $Id$ diff --git a/test/types/package.rb b/test/types/package.rb index 38bb3b08d..e290d12ad 100644 --- a/test/types/package.rb +++ b/test/types/package.rb @@ -1,488 +1,488 @@ require 'puppettest' require 'puppet' require 'facter' $platform = Facter["operatingsystem"].value unless Puppet.type(:package).defaultprovider puts "No default package type for %s; skipping package tests" % $platform else class TestPackages < Test::Unit::TestCase include PuppetTest::FileTesting def setup super #@list = Puppet.type(:package).getpkglist Puppet.type(:package).clear end # These are packages that we're sure will be installed def installedpkgs pkgs = nil case $platform when "SunOS" pkgs = %w{SMCossh} when "Debian": pkgs = %w{ssh openssl} when "Fedora": pkgs = %w{openssh} when "OpenBSD": pkgs = %w{vim} when "FreeBSD": pkgs = %w{sudo} when "Darwin": pkgs = %w{gettext} else Puppet.notice "No test package for %s" % $platform return [] end return pkgs end def modpkg(pkg) case $platform when "Solaris": pkg[:adminfile] = "/usr/local/pkg/admin_file" end end def mkpkgs(list = nil, useensure = true) list ||= tstpkgs() list.each { |pkg, source| hash = {:name => pkg} if useensure hash[:ensure] = "installed" end if source source = source[0] if source.is_a? Array hash[:source] = source end # Override the default package type for our test packages. if Facter["operatingsystem"].value == "Darwin" hash[:provider] = "darwinport" end obj = Puppet.type(:package).create(hash) assert(pkg, "Could not create package") modpkg(obj) yield obj } end def tstpkgs retval = [] case $platform when "Solaris": arch = Facter["hardwareisa"].value + Facter["operatingsystemrelease"].value case arch when "i3865.10": retval = {"SMCrdesk" => [ "/usr/local/pkg/rdesktop-1.3.1-sol10-intel-local", "/usr/local/pkg/rdesktop-1.4.1-sol10-x86-local" ]} when "sparc5.8": retval = {"SMCarc" => "/usr/local/pkg/arc-5.21e-sol8-sparc-local"} when "i3865.8": retval = {"SMCarc" => "/usr/local/pkg/arc-5.21e-sol8-intel-local"} end when "OpenBSD": retval = {"aalib" => "ftp://ftp.usa.openbsd.org/pub/OpenBSD/3.8/packages/i386/aalib-1.2-no_x11.tgz"} when "Debian": retval = {"zec" => nil} #when "RedHat": type = :rpm when "Fedora": retval = {"wv" => nil} when "CentOS": retval = {"enhost" => [ "/home/luke/rpm/RPMS/noarch/enhost-1.0.1-1.noarch.rpm", "/home/luke/rpm/RPMS/noarch/enhost-1.0.2-1.noarch.rpm" ]} when "Darwin": retval = {"aop" => nil} when "FreeBSD": retval = {"yahtzee" => nil} when "RedHat": retval = {"puppet" => "/home/luke/rpm/RPMS/i386/puppet-0.16.1-1.i386.rpm"} else Puppet.notice "No test packages for %s" % $platform end return retval end def mkpkgcomp(pkg) assert_nothing_raised { pkg = Puppet.type(:package).create(:name => pkg, :ensure => "present") } assert_nothing_raised { pkg.retrieve } comp = newcomp("package", pkg) return comp end def test_retrievepkg mkpkgs(installedpkgs()) { |obj| assert(obj, "could not create package") assert_nothing_raised { obj.retrieve } assert_instance_of(String, obj[:ensure], "Ensure did not return a version number") assert(obj[:ensure] =~ /[0-9.]/, "Ensure did not return a version number") } end def test_nosuchpkg obj = nil assert_nothing_raised { obj = Puppet.type(:package).create( :name => "thispackagedoesnotexist", :ensure => :installed ) } assert(obj, "Failed to create fake package") assert_nothing_raised { obj.retrieve } assert_equal(:absent, obj.is(:ensure), "Somehow retrieved unknown pkg's version") state = obj.state(:ensure) assert(state, "Could not retrieve ensure state") # Add a fake state, for those that need it file = tempfile() File.open(file, "w") { |f| f.puts :yayness } obj[:source] = file assert_raise(Puppet::Error, "Successfully installed nonexistent package") { state.sync } end def test_latestpkg mkpkgs { |pkg| next unless pkg.respond_to? :latest assert_nothing_raised { assert(pkg.latest, "Package %s did not return value for 'latest'" % pkg.name) } } end # Make sure our package type supports listing. def test_listing pkgtype = Puppet::Type.type(:package) assert_nothing_raised("Could not list packages") do count = 0 pkgtype.list.each do |pkg| assert_instance_of(Puppet::Type.type(:package), pkg) count += 1 end assert(count > 1, "Did not get any packages") end end - unless Process.uid == 0 + unless Puppet::SUIDManager.uid == 0 $stderr.puts "Run as root to perform package installation tests" else def test_installpkg mkpkgs { |pkg| # we first set install to 'true', and make sure something gets # installed assert_nothing_raised { pkg.retrieve } if hash = pkg.provider.query and hash[:ensure] != :absent Puppet.notice "Test package %s is already installed; please choose a different package for testing" % pkg next end comp = newcomp("package", pkg) assert_events([:package_installed], comp, "package") pkg.retrieve assert(pkg.insync?, "Package is not in sync") # then uninstall it assert_nothing_raised { pkg[:ensure] = "absent" } pkg.retrieve assert(! pkg.insync?, "Package is in sync") assert_events([:package_removed], comp, "package") # and now set install to 'latest' and verify it installs if pkg.respond_to?(:latest) assert_nothing_raised { pkg[:ensure] = "latest" } assert_events([:package_installed], comp, "package") pkg.retrieve assert(pkg.insync?, "After install, package is not insync") assert_nothing_raised { pkg[:ensure] = "absent" } pkg.retrieve assert(! pkg.insync?, "Package is insync") assert_events([:package_removed], comp, "package") end } end # Make sure that a default is used for 'ensure' def test_ensuredefault # Tell mkpkgs not to set 'ensure'. mkpkgs(nil, false) { |pkg| assert_nothing_raised { pkg.retrieve } assert(!pkg.insync?, "Package thinks it's in sync") assert_apply(pkg) pkg.retrieve assert(pkg.insync?, "Package does not think it's in sync") pkg[:ensure] = :absent assert_apply(pkg) } end def test_upgradepkg tstpkgs.each do |name, sources| unless sources and sources.is_a? Array $stderr.puts "Skipping pkg upgrade test for %s" % name next end first, second = sources unless FileTest.exists?(first) and FileTest.exists?(second) $stderr.puts "Could not find upgrade test pkgs; skipping" return end pkg = nil assert_nothing_raised { pkg = Puppet.type(:package).create( :name => name, :ensure => :latest, :source => first ) } assert(pkg, "Failed to create package %s" % name) modpkg(pkg) assert(pkg.provider.latest, "Could not retrieve latest value") assert_events([:package_installed], pkg) assert_nothing_raised { pkg.retrieve } assert(pkg.insync?, "Package is not in sync") pkg.clear assert_nothing_raised { pkg[:source] = second } assert_events([:package_changed], pkg) assert_nothing_raised { pkg.retrieve } assert(pkg.insync?, "Package is not in sync") assert_nothing_raised { pkg[:ensure] = :absent } assert_events([:package_removed], pkg) assert_nothing_raised { pkg.retrieve } assert(pkg.insync?, "Package is not in sync") end end # Stupid darwin, not supporting package uninstallation if Facter["operatingsystem"].value == "Darwin" and FileTest.exists? "/Users/luke/Documents/Puppet/pkgtesting.pkg" def test_darwinpkgs pkg = nil assert_nothing_raised { pkg = Puppet::Type.type(:package).create( :name => "pkgtesting", :source => "/Users/luke/Documents/Puppet/pkgtesting.pkg", :ensure => :present, :provider => :apple ) } assert_nothing_raised { pkg.retrieve } if pkg.insync? Puppet.notice "Test package is already installed; please remove it" next end # The file installed, and the receipt @@tmpfiles << "/tmp/file" @@tmpfiles << "/Library/Receipts/pkgtesting.pkg" assert_events([:package_installed], pkg, "package") assert_nothing_raised { pkg.retrieve } assert(pkg.insync?, "Package is not insync") assert(FileTest.exists?("/tmp/pkgtesting/file"), "File did not get created") end end # Yay, gems. They're special because any OS can test them. if Puppet::Type.type(:package).provider(:gem).suitable? def test_list_gems gems = nil assert_nothing_raised { gems = Puppet::Type.type(:package).provider(:gem).list } gems.each do |gem| assert_equal(:gem, gem[:provider], "Type was not set correctly") end end def test_install_gems gem = nil name = "wxrubylayouts" assert_nothing_raised { gem = Puppet::Type.newpackage( :name => name, :ensure => "0.0.2", :provider => :gem ) } assert_nothing_raised { gem.retrieve } if gem.is(:ensure) != :absent $stderr.puts "Cannot test gem installation; %s is already installed" % name return end assert_events([:package_installed], gem) assert_nothing_raised { gem.retrieve } assert_equal("0.0.2", gem.is(:ensure), "Incorrect version was installed") latest = nil assert_nothing_raised { latest = gem.provider.latest } assert(latest != gem[:ensure], "Did not correctly find latest value") gem[:ensure] = :latest assert_events([:package_changed], gem) gem.retrieve assert("0.0.2" != gem.is(:ensure), "Package was not updated.") gem[:ensure] = :absent assert_events([:package_removed], gem) end else $stderr.puts "Install gems for gem tests" def test_failure_when_no_gems obj = nil assert_raise(ArgumentError) do Puppet::Type.newpackage( :name => "yayness", :provider => "gem", :ensure => "installed" ) end end end end if Puppet.type(:package).provider(:rpm).suitable? and FileTest.exists?("/home/luke/rpm/RPMS/i386/puppet-server-0.16.1-1.i386.rpm") # We have a special test here, because we don't actually want to install the # package, just make sure it's getting the "latest" value. def test_rpmlatest pkg = nil assert_nothing_raised { pkg = Puppet::Type.type(:package).create( :provider => :rpm, :name => "puppet-server", :source => "/home/luke/rpm/RPMS/i386/puppet-server-0.16.1-1.i386.rpm" ) } assert_equal("0.16.1-1", pkg.provider.latest, "RPM did not provide correct value for latest") end end def test_packagedefaults should = case Facter["operatingsystem"].value when "Debian": :apt when "Darwin": :apple when "RedHat": :rpm when "Fedora": :yum when "FreeBSD": :ports when "OpenBSD": :openbsd when "Solaris": :sun end default = Puppet.type(:package).defaultprovider assert(default, "No default package provider for %s" % Facter["operatingsystem"].value) if should assert_equal(should, default.name, "Incorrect default package format") end end end end # $Id$ diff --git a/test/types/service.rb b/test/types/service.rb index f9d6251ad..79ec0c40c 100644 --- a/test/types/service.rb +++ b/test/types/service.rb @@ -1,300 +1,300 @@ require 'puppet' require 'puppettest' $skipsvcs = false case Facter["operatingsystem"].value when "Darwin", "OpenBSD": $skipsvcs = true end if $skipsvcs puts "Skipping service testing on %s" % Facter["operatingsystem"].value else #class TestInitService < Test::Unit::TestCase class TestInitService include PuppetTest def setup super sleeper = nil script = exampledir("root/etc/init.d/sleeper") @init = exampledir("root/etc/init.d") @status = script + " status" end def teardown super stopservices end def tstsvcs case Facter["operatingsystem"].value.downcase when "solaris": return ["smtp", "xf"] when "redhat": return ["sshd"] end end def mksleeper(hash = {}) hash[:name] = "sleeper" hash[:path] = exampledir("root/etc/init.d") hash[:ensure] = true hash[:hasstatus] = true hash[:hasrestart] = true #hash[:type] = "init" assert_nothing_raised() { return Puppet.type(:service).create(hash) } end def cyclesleeper(sleeper) assert_nothing_raised() { sleeper.retrieve } assert(!sleeper.insync?()) comp = newcomp(sleeper) assert_events([:service_started], comp) assert_nothing_raised() { sleeper.retrieve } assert(sleeper.insync?) # test refreshing it assert_nothing_raised() { sleeper.refresh } assert(sleeper.respond_to?(:refresh)) # now stop it assert_nothing_raised() { sleeper[:ensure] = 0 } assert_nothing_raised() { sleeper.retrieve } assert(!sleeper.insync?()) assert_events([:service_stopped], comp) assert_nothing_raised() { sleeper.retrieve } assert(sleeper.insync?) end def test_processStartWithPattern sleeper = mksleeper(:pattern => "bin/sleeper") cyclesleeper(sleeper) end def test_processStartWithStatus sleeper = mksleeper(:hasstatus => true) cyclesleeper(sleeper) end def test_invalidpathsremoved sleeper = mksleeper() fakedir = [@init, "/thisdirnoexist"] sleeper[:path] = fakedir assert(! sleeper[:path].include?(fakedir)) end end class TestLocalService < Test::Unit::TestCase include PuppetTest def teardown Puppet.type(:service).clear super end def mktestsvcs list = tstsvcs.collect { |svc,svcargs| args = svcargs.dup args[:name] = svc Puppet.type(:service).create(args) } end def tstsvcs case Facter["operatingsystem"].value.downcase when "solaris": case Facter["operatingsystemrelease"].value when "5.10": return {"smtp" => {}, "xfs" => {}} end when "debian": return {"hddtemp" => {:hasrestart => true}} when "centos": return {"cups" => {:hasstatus => true}} when "redhat": return {"saslauthd" => {:hasstatus => true}} end Puppet.notice "No test services for %s-%s" % [Facter["operatingsystem"].value, Facter["operatingsystemrelease"].value] return [] end def cycleservice(service) assert_nothing_raised() { service.retrieve } comp = newcomp("servicetst", service) service[:ensure] = true Puppet.info "Starting %s" % service.name assert_apply(service) # Some package systems background the work, so we need to give them # time to do their work. sleep(1.5) assert_nothing_raised() { service.retrieve } assert(service.insync?, "Service %s is not running" % service.name) # test refreshing it assert_nothing_raised() { service.refresh } # now stop it assert_nothing_raised() { service[:ensure] = :stopped } assert_nothing_raised() { service.retrieve } assert(!service.insync?(), "Service %s is not running" % service.name) Puppet.info "stopping %s" % service.name assert_events([:service_stopped], comp) sleep(1.5) assert_nothing_raised() { service.retrieve } assert(service.insync?, "Service %s has not stopped" % service.name) end def cycleenable(service) assert_nothing_raised() { service.retrieve } comp = newcomp("servicetst", service) service[:enable] = true Puppet.info "Enabling %s" % service.name assert_apply(service) # Some package systems background the work, so we need to give them # time to do their work. sleep(1.5) assert_nothing_raised() { service.retrieve } assert(service.insync?, "Service %s is not enabled" % service.name) # now stop it assert_nothing_raised() { service[:enable] = false } assert_nothing_raised() { service.retrieve } assert(!service.insync?(), "Service %s is not enabled" % service.name) Puppet.info "disabling %s" % service.name assert_events([:service_disabled], comp) sleep(1.5) assert_nothing_raised() { service.retrieve } assert(service.insync?, "Service %s has not been disabled" % service.name) end def test_status mktestsvcs.each { |svc| val = nil assert_nothing_raised("Could not get status") { val = svc.provider.status } assert_instance_of(Symbol, val) } end - unless Process.uid == 0 + unless Puppet::SUIDManager.uid == 0 puts "run as root to test service start/stop" else def test_servicestartstop mktestsvcs.each { |svc| startstate = nil assert_nothing_raised("Could not get status") { startstate = svc.provider.status } cycleservice(svc) svc[:ensure] = startstate assert_apply(svc) Puppet.type(:component).clear } end def test_serviceenabledisable mktestsvcs.each { |svc| assert(svc[:name], "Service has no name") startstate = nil svc[:check] = :enable assert_nothing_raised("Could not get status") { startstate = svc.provider.enabled? } cycleenable(svc) svc[:enable] = startstate assert_apply(svc) Puppet.type(:component).clear } end def test_serviceenableandrun mktestsvcs.each do |svc| startenable = nil startensure = nil svc[:check] = [:ensure, :enable] svc.retrieve assert_nothing_raised("Could not get status") { startenable = svc.state(:enable).is startensure = svc.state(:ensure).is } svc[:enable] = false svc[:ensure] = :stopped assert_apply(svc) sleep 1 svc.retrieve assert(svc.insync?, "Service did not sync both states") svc[:enable] = true svc[:ensure] = :running assert_apply(svc) sleep 1 svc.retrieve assert(svc.insync?, "Service did not sync both states") svc[:enable] = startenable svc[:ensure] = startensure assert_apply(svc) Puppet.type(:component).clear end end end end end # $Id$ diff --git a/test/types/user.rb b/test/types/user.rb index 703004f57..9a2781308 100755 --- a/test/types/user.rb +++ b/test/types/user.rb @@ -1,444 +1,444 @@ require 'etc' require 'puppet/type' require 'puppettest' class TestUser < Test::Unit::TestCase include PuppetTest p = Puppet::Type.type(:user).provide :fake, :parent => PuppetTest::FakeProvider do @name = :fake apimethods def create @ensure = :present @model.eachstate do |state| next if state.name == :ensure state.sync end end def delete @ensure = :absent @model.eachstate do |state| send(state.name.to_s + "=", :absent) end end def exists? if defined? @ensure and @ensure == :present true else false end end end FakeUserProvider = p @@fakeproviders[:group] = p def findshell(old = nil) %w{/bin/sh /bin/bash /sbin/sh /bin/ksh /bin/zsh /bin/csh /bin/tcsh /usr/bin/sh /usr/bin/bash /usr/bin/ksh /usr/bin/zsh /usr/bin/csh /usr/bin/tcsh}.find { |shell| if old FileTest.exists?(shell) and shell != old else FileTest.exists?(shell) end } end def setup super Puppet::Type.type(:user).defaultprovider = FakeUserProvider end def teardown Puppet::Type.type(:user).defaultprovider = nil super end def mkuser(name) user = nil assert_nothing_raised { user = Puppet.type(:user).create( :name => name, :comment => "Puppet Testing User", - :gid => Process.gid, + :gid => Puppet::SUIDManager.gid, :shell => findshell(), :home => "/home/%s" % name ) } assert(user, "Did not create user") return user end def attrtest_ensure(user) old = user.provider.ensure user[:ensure] = :absent comp = newcomp("ensuretest", user) assert_apply(user) assert(!user.provider.exists?, "User is still present") user[:ensure] = :present assert_events([:user_created], comp) assert(user.provider.exists?, "User is absent") user[:ensure] = :absent trans = assert_events([:user_removed], comp) assert_rollback_events(trans, [:user_created], "user") user[:ensure] = old assert_apply(user) end def attrtest_comment(user) user.retrieve old = user.provider.comment user[:comment] = "A different comment" comp = newcomp("commenttest", user) trans = assert_events([:user_changed], comp, "user") assert_equal("A different comment", user.provider.comment, "Comment was not changed") assert_rollback_events(trans, [:user_changed], "user") assert_equal(old, user.provider.comment, "Comment was not reverted") end def attrtest_home(user) obj = nil comp = newcomp("hometest", user) old = user.provider.home user[:home] = old trans = assert_events([], comp, "user") user[:home] = "/tmp" trans = assert_events([:user_changed], comp, "user") assert_equal("/tmp", user.provider.home, "Home was not changed") assert_rollback_events(trans, [:user_changed], "user") assert_equal(old, user.provider.home, "Home was not reverted") end def attrtest_shell(user) old = user.provider.shell comp = newcomp("shelltest", user) user[:shell] = old trans = assert_events([], comp, "user") newshell = findshell(old) unless newshell $stderr.puts "Cannot find alternate shell; skipping shell test" return end user[:shell] = newshell trans = assert_events([:user_changed], comp, "user") user.retrieve assert_equal(newshell, user.provider.shell, "Shell was not changed") assert_rollback_events(trans, [:user_changed], "user") user.retrieve assert_equal(old, user.provider.shell, "Shell was not reverted") end def attrtest_gid(user) obj = nil old = user.provider.gid comp = newcomp("gidtest", user) user.retrieve user[:gid] = old trans = assert_events([], comp, "user") newgid = %w{nogroup nobody staff users daemon}.find { |gid| begin group = Etc.getgrnam(gid) rescue ArgumentError => detail next end old != group.gid } unless newgid $stderr.puts "Cannot find alternate group; skipping gid test" return end # first test by name assert_nothing_raised("Failed to specify group by name") { user[:gid] = newgid } trans = assert_events([:user_changed], comp, "user") # then by id newgid = Etc.getgrnam(newgid).gid assert_nothing_raised("Failed to specify group by id") { user[:gid] = newgid } user.retrieve assert_events([], comp, "user") assert_equal(newgid, user.provider.gid, "GID was not changed") assert_rollback_events(trans, [:user_changed], "user") assert_equal(old, user.provider.gid, "GID was not reverted") end def attrtest_uid(user) obj = nil comp = newcomp("uidtest", user) user.provider.uid = 1 old = 1 newuid = 1 while true newuid += 1 if newuid - old > 1000 $stderr.puts "Could not find extra test UID" return end begin newuser = Etc.getpwuid(newuid) rescue ArgumentError => detail break end end assert_nothing_raised("Failed to change user id") { user[:uid] = newuid } trans = assert_events([:user_changed], comp, "user") assert_equal(newuid, user.provider.uid, "UID was not changed") assert_rollback_events(trans, [:user_changed], "user") assert_equal(old, user.provider.uid, "UID was not reverted") end def attrtest_groups(user) Etc.setgrent max = 0 while group = Etc.getgrent if group.gid > max and group.gid < 5000 max = group.gid end end groups = [] main = [] extra = [] 5.times do |i| i += 1 name = "pptstgr%s" % i groups << name if i < 3 main << name else extra << name end end assert(user[:membership] == :minimum, "Membership did not default correctly") assert_nothing_raised { user.retrieve } # Now add some of them to our user assert_nothing_raised { user[:groups] = extra } assert_nothing_raised { user.retrieve } assert_instance_of(String, user.state(:groups).should) # Some tests to verify that groups work correctly startig from nothing # Remove our user user[:ensure] = :absent assert_apply(user) assert_nothing_raised do user.retrieve end # And add it again user[:ensure] = :present assert_apply(user) # Make sure that the groups are a string, not an array assert(user.provider.groups.is_a?(String), "Incorrectly passed an array to groups") user.retrieve assert(user.state(:groups).is, "Did not retrieve group list") list = user.state(:groups).is assert_equal(extra.sort, list.sort, "Group list is not equal") # Now set to our main list of groups assert_nothing_raised { user[:groups] = main } assert_equal((main + extra).sort, user.state(:groups).should.split(",").sort) assert_nothing_raised { user.retrieve } assert(!user.insync?, "User is incorrectly in sync") assert_apply(user) assert_nothing_raised { user.retrieve } # We're not managing inclusively, so it should keep the old group # memberships and add the new ones list = user.state(:groups).is assert_equal((main + extra).sort, list.sort, "Group list is not equal") assert_nothing_raised { user[:membership] = :inclusive } assert_nothing_raised { user.retrieve } assert(!user.insync?, "User is incorrectly in sync") assert_events([:user_changed], user) assert_nothing_raised { user.retrieve } list = user.state(:groups).is assert_equal(main.sort, list.sort, "Group list is not equal") # Set the values a bit differently. user.state(:groups).should = list.sort { |a,b| b <=> a } user.state(:groups).is = list.sort assert(user.state(:groups).insync?, "Groups state did not sort groups") user.delete(:groups) end def test_autorequire 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", :groups => "yayness" ) home = Puppet.type(:file).create( :path => file, :ensure => "directory" ) group = Puppet.type(:group).create( :name => "pptestg" ) ogroup = Puppet.type(:group).create( :name => "yayness" ) comp = newcomp(user, group, home, ogroup) } comp.finalize comp.retrieve assert(user.requires?(group), "User did not require group") assert(user.requires?(home), "User did not require home dir") assert(user.requires?(ogroup), "User did not require other groups") end def test_simpleuser name = "pptest" user = mkuser(name) comp = newcomp("usercomp", user) trans = assert_events([:user_created], comp, "user") assert_equal(user.should(:comment), user.provider.comment, "Comment was not set correctly") assert_rollback_events(trans, [:user_removed], "user") assert(! user.provider.exists?, "User did not get deleted") end def test_allusermodelstates user = nil name = "pptest" user = mkuser(name) assert(! user.provider.exists?, "User %s is present" % name) comp = newcomp("usercomp", user) trans = assert_events([:user_created], comp, "user") user.retrieve assert_equal("Puppet Testing User", user.provider.comment, "Comment was not set") tests = Puppet.type(:user).validstates tests.each { |test| if self.respond_to?("attrtest_%s" % test) self.send("attrtest_%s" % test, user) else Puppet.err "Not testing attr %s of user" % test end } user[:ensure] = :absent assert_apply(user) end end # $Id$ diff --git a/test/types/zone.rb b/test/types/zone.rb index 46f411ef3..b0d02d748 100755 --- a/test/types/zone.rb +++ b/test/types/zone.rb @@ -1,430 +1,430 @@ # Test host job creation, modification, and destruction require 'puppettest' require 'puppet' require 'puppet/type/zone' require 'facter' class TestZone < Test::Unit::TestCase include PuppetTest def test_nothing end # Zones can only be tested on solaris. if Facter["operatingsystem"].value == "Solaris" def setup super @@zones = [] end def teardown current = %x{zoneadm list -cp}.split("\n").inject({}) { |h, line| ary = line.split(":") h[ary[1]] = ary[2] h } Puppet::Type.type(:zone).clear # Get rid of any lingering zones @@zones.each do |zone| next unless current.include? zone obj = Puppet::Type.type(:zone).create(:name => zone) obj[:ensure] = :absent assert_apply(obj) end # We can't delete the temp files until the zones are stopped and removed. super end def mkzone(name) zone = nil base = tempfile() Dir.mkdir(base) File.chmod(0700, base) root = File.join(base, "zonebase") assert_nothing_raised { zone = Puppet::Type.type(:zone).create( :name => name, :path => root, :ensure => "configured" # don't want to install zones automatically ) } @@zones << name return zone end def test_list list = nil assert_nothing_raised { list = Puppet::Type.type(:zone).list } assert(! list.empty?, "Got no zones back") assert(list.find { |z| z[:name] == "global" }, "Could not find global zone") end def test_valueslice zone = mkzone("slicetest") state = zone.state(:ensure) slice = nil assert_nothing_raised { slice = state.class.valueslice(:absent, :installed).collect do |o| o[:name] end } assert_equal([:configured, :installed], slice) assert_nothing_raised { slice = state.class.valueslice(:running, :installed).collect do |o| o[:name] end } assert_equal(slice, [:installed]) end # Make sure the ensure stuff behaves as we expect def test_zoneensure zone = mkzone("ensurezone") state = zone.state(:ensure) assert(state, "Did not get ensure state") assert_nothing_raised { zone.retrieve } assert(! state.insync?, "State is somehow in sync") assert(state.up?, "State incorrectly thinks it is not moving up") zone.is = [:ensure, :configured] zone[:ensure] = :installed assert(state.up?, "State incorrectly thinks it is not moving up") zone[:ensure] = :absent assert(! state.up?, "State incorrectly thinks it is moving up") end # Make sure all mentioned methods actually exist. def test_zonemethods_exist methods = [] zone = mkzone("methodtest") state = zone.state(:ensure) assert_nothing_raised { state.class.valueslice(:absent, :running).each do |st| [:up, :down].each do |m| if st[m] methods << st[m] end end end } methods.each do |m| assert(Puppet::Type.type(:zone).method_defined?(m), "Zones do not define method %s" % m) end end # Make sure our state generates the correct text. def test_inherit_state zone = mkzone("configtesting") zone[:ensure] = :configured assert_nothing_raised { zone[:inherit] = "/usr" } state = zone.state(:inherit) assert(zone, "Did not get 'inherit' state") assert_equal("add inherit-pkg-dir\nset dir=/usr\nend", state.configtext, "Got incorrect config text") state.is = "/usr" assert_equal("", state.configtext, "Got incorrect config text") # Now we want multiple directories state.should = %w{/usr /sbin /lib} # The statements are sorted text = "add inherit-pkg-dir set dir=/lib end add inherit-pkg-dir set dir=/sbin end" assert_equal(text, state.configtext, "Got incorrect config text") state.is = %w{/usr /sbin /lib} state.should = %w{/usr /sbin} text = "remove inherit-pkg-dir dir=/lib" assert_equal(text, state.configtext, "Got incorrect config text") end - if Process.uid == 0 + if Puppet::SUIDManager.uid == 0 # Make sure our ensure process actually works. def test_ensure_sync zone = mkzone("ensuretesting") zone[:ensure] = :configured zone.retrieve assert_apply(zone) zone.retrieve assert(zone.insync?, "Zone is not insync") end def test_getconfig zone = mkzone("configtesting") base = tempfile() zone[:path] = base ip = "192.168.0.1" interface = "bge0" zone[:ip] = "#{interface}:#{ip}" IO.popen("zonecfg -z configtesting -f -", "w") do |f| f.puts %{create -b set zonepath=#{tempfile()} set autoboot=true add inherit-pkg-dir set dir=/lib end add inherit-pkg-dir set dir=/platform end add inherit-pkg-dir set dir=/sbin end add inherit-pkg-dir set dir=/opt/csw end add inherit-pkg-dir set dir=/usr end add net set address=#{ip} set physical=bge0 end } end assert_equal(0, $?, "Did not successfully create zone") #@@zones << "configtesting" assert_nothing_raised { zone.send(:getconfig) } # Now, make sure everything is right. assert_equal(%w{/sbin /usr /opt/csw /lib /platform}.sort, zone.is(:inherit).sort, "Inherited dirs did not get collected correctly." ) assert_equal(["#{interface}:#{ip}"], zone.is(:ip), "IP addresses did not get collected correctly.") assert_equal(:true, zone.is(:autoboot), "Autoboot did not get collected correctly.") end # Make sure we can do all the various and sundry configuring things. def test_configuring_zones zone = mkzone("configtesting") assert_nothing_raised { zone[:inherit] = "/usr" } zone[:ensure] = :configured zone.retrieve assert_apply(zone) zone.retrieve assert(zone.insync?, "Zone is not insync") # Now add a new directory to inherit assert_nothing_raised { zone[:inherit] = ["/sbin", "/usr"] } assert_apply(zone) zone.retrieve assert(zone.insync?, "Zone is not insync") assert(%x{/usr/sbin/zonecfg -z #{zone[:name]} info} =~ /dir: \/sbin/, "sbin was not added") # And then remove it. assert_nothing_raised { zone[:inherit] = "/usr" } assert_apply(zone) zone.retrieve assert(zone.insync?, "Zone is not insync") assert(%x{/usr/sbin/zonecfg -z #{zone[:name]} info} !~ /dir: \/sbin/, "sbin was not removed") # Now add an ip adddress. Fortunately (or not), zonecfg doesn't verify # that the interface exists. zone[:ip] = "hme0:192.168.0.1" zone.retrieve assert(! zone.insync?, "Zone is marked as in sync") assert_apply(zone) zone.retrieve assert(zone.insync?, "Zone is not in sync") assert(%x{/usr/sbin/zonecfg -z #{zone[:name]} info} =~ /192.168.0.1/, "ip was not added") zone[:ip] = ["hme1:192.168.0.2", "hme0:192.168.0.1"] assert_apply(zone) zone.retrieve assert(zone.insync?, "Zone is not in sync") assert(%x{/usr/sbin/zonecfg -z #{zone[:name]} info} =~ /192.168.0.2/, "ip was not added") zone[:ip] = ["hme1:192.168.0.2"] assert_apply(zone) zone.retrieve assert(%x{/usr/sbin/zonecfg -z #{zone[:name]} info} !~ /192.168.0.1/, "ip was not removed") end # Test creating and removing a zone, but only up to the configured state, # so it's faster. def test_smallcreate zone = mkzone("smallcreate") # Include a bunch of stuff so the zone isn't as large dirs = %w{/usr /sbin /lib /platform} %w{/opt/csw}.each do |dir| dirs << dir if FileTest.exists? dir end zone[:inherit] = dirs assert(zone, "Did not make zone") zone[:ensure] = :configured assert(! zone.insync?, "Zone is incorrectly in sync") assert_apply(zone) assert_nothing_raised { zone.retrieve } assert(zone.insync?, "Zone is incorrectly out of sync") zone[:ensure] = :absent assert_apply(zone) zone.retrieve assert_equal(:absent, zone.is(:ensure), "Zone is not absent") end # Just go through each method linearly and make sure it works. def test_each_method zone = mkzone("methodtesting") dirs = %w{/usr /sbin /lib /platform} %w{/opt/csw}.each do |dir| dirs << dir if FileTest.exists? dir end zone[:inherit] = dirs [[:configure, :configured], [:install, :installed], [:start, :running], [:stop, :installed], [:uninstall, :configured], [:unconfigure, :absent] ].each do |method, state| Puppet.info "Testing %s" % method assert_nothing_raised { zone.retrieve } assert_nothing_raised { zone.send(method) } assert_nothing_raised { zone.retrieve } assert_equal(state, zone.is(:ensure), "Method %s did not correctly set state %s" % [method, state]) end end def test_mkzone zone = mkzone("testmaking") # Include a bunch of stuff so the zone isn't as large dirs = %w{/usr /sbin /lib /platform} %w{/opt/csw}.each do |dir| dirs << dir if FileTest.exists? dir end zone[:inherit] = dirs assert(zone, "Did not make zone") [:configured, :installed, :running, :installed, :absent].each do |value| assert_nothing_raised { zone[:ensure] = value } assert(! zone.insync?, "Zone is incorrectly in sync") assert_apply(zone) assert_nothing_raised { zone.retrieve } assert(zone.insync?, "Zone is incorrectly out of sync") end zone.retrieve assert_equal(:absent, zone.is(:ensure), "Zone is not absent") end end end end # $Id$ diff --git a/test/util/utiltest.rb b/test/util/utiltest.rb index b3a356429..f18f16906 100755 --- a/test/util/utiltest.rb +++ b/test/util/utiltest.rb @@ -1,361 +1,361 @@ require 'puppet' require 'puppettest' class TestPuppetUtil < Test::Unit::TestCase include PuppetTest # we're getting corrupt files, probably because multiple processes # are reading or writing the file at once # so we need to test that def test_multiwrite file = tempfile() File.open(file, "w") { |f| f.puts "starting" } value = {:a => :b} threads = [] sync = Sync.new 9.times { |a| threads << Thread.new { 9.times { |b| assert_nothing_raised { sync.synchronize(Sync::SH) { Puppet::Util.readlock(file) { |f| f.read } } sleep 0.01 sync.synchronize(Sync::EX) { Puppet::Util.writelock(file) { |f| f.puts "%s %s" % [a, b] } } } } } } threads.each { |th| th.join } end # First verify we can convert a known user def test_gidbyname %x{groups}.split(" ").each { |group| gid = nil assert_nothing_raised { gid = Puppet::Util.gid(group) } assert(gid, "Could not retrieve gid for %s" % group) assert(Puppet.type(:group)[group], "Util did not create %s" % group) } end # Then verify we can retrieve a known group by gid def test_gidbyid %x{groups}.split(" ").each { |group| obj = Puppet.type(:group).create( :name => group, :check => [:gid] ) obj.retrieve id = obj.is(:gid) gid = nil assert_nothing_raised { gid = Puppet::Util.gid(id) } assert(gid, "Could not retrieve gid for %s" % group) assert_equal(id, gid, "Got mismatched ids") } end # Finally, verify that we can find groups by id even if we don't # know them def test_gidbyunknownid gid = nil - group = Process.gid + group = Puppet::SUIDManager.gid assert_nothing_raised { gid = Puppet::Util.gid(group) } assert(gid, "Could not retrieve gid for %s" % group) assert_equal(group, gid, "Got mismatched ids") end def user require 'etc' unless defined? @user - obj = Etc.getpwuid(Process.uid) + obj = Etc.getpwuid(Puppet::SUIDManager.uid) @user = obj.name end return @user end # And do it all over again for users # First verify we can convert a known user def test_uidbyname user = user() uid = nil assert_nothing_raised { uid = Puppet::Util.uid(user) } assert(uid, "Could not retrieve uid for %s" % user) - assert_equal(Process.uid, uid, "UIDs did not match") + assert_equal(Puppet::SUIDManager.uid, uid, "UIDs did not match") assert(Puppet.type(:user)[user], "Util did not create %s" % user) end # Then verify we can retrieve a known user by uid def test_uidbyid user = user() obj = Puppet.type(:user).create( :name => user, :check => [:uid] ) obj.retrieve id = obj.is(:uid) uid = nil assert_nothing_raised { uid = Puppet::Util.uid(id) } assert(uid, "Could not retrieve uid for %s" % user) assert_equal(id, uid, "Got mismatched ids") end # Finally, verify that we can find users by id even if we don't # know them def test_uidbyunknownid uid = nil - user = Process.uid + user = Puppet::SUIDManager.uid assert_nothing_raised { uid = Puppet::Util.uid(user) } assert(uid, "Could not retrieve uid for %s" % user) assert_equal(user, uid, "Got mismatched ids") end def test_withumask oldmask = File.umask path = tempfile() # FIXME this fails on FreeBSD with a mode of 01777 Puppet::Util.withumask(000) do Dir.mkdir(path, 0777) end assert(File.stat(path).mode & 007777 == 0777, "File has the incorrect mode") assert_equal(oldmask, File.umask, "Umask was not reset") end def test_benchmark path = tempfile() str = "yayness" File.open(path, "w") do |f| f.print "yayness" end # First test it with the normal args assert_nothing_raised do val = nil result = Puppet::Util.benchmark(:notice, "Read file") do val = File.read(path) end assert_equal(str, val) assert_instance_of(Float, result) end # Now test it with a passed object assert_nothing_raised do val = nil Puppet::Util.benchmark(Puppet, :notice, "Read file") do val = File.read(path) end assert_equal(str, val) end end - unless Process.uid == 0 + unless Puppet::SUIDManager.uid == 0 $stderr.puts "Run as root to perform Utility tests" def test_nothing end else def mknverify(file, user, group = nil, id = false) if File.exists?(file) File.unlink(file) end args = [] unless user or group args << nil end if user if id args << user.uid else args << user.name end end if group if id args << group.gid else args << group.name end end gid = nil if group gid = group.gid else - gid = Process.gid + gid = Puppet::SUIDManager.gid end uid = nil if user uid = user.uid else - uid = Process.uid + uid = Puppet::SUIDManager.uid end assert_nothing_raised { - Puppet::Util.asuser(*args) { - assert_equal(Process.euid, uid, "UID is %s instead of %s" % - [Process.euid, uid] + Puppet::SUIDManager.asuser(*args) { + assert_equal(Puppet::SUIDManager.euid, uid, "UID is %s instead of %s" % + [Puppet::SUIDManager.euid, uid] ) - assert_equal(Process.egid, gid, "GID is %s instead of %s" % - [Process.egid, gid] + assert_equal(Puppet::SUIDManager.egid, gid, "GID is %s instead of %s" % + [Puppet::SUIDManager.egid, gid] ) system("touch %s" % file) } } if uid == 0 #Puppet.warning "Not testing user" else #Puppet.warning "Testing user %s" % uid assert(File.exists?(file), "File does not exist") assert_equal(File.stat(file).uid, uid, "File is owned by %s instead of %s" % [File.stat(file).uid, uid] ) #system("ls -l %s" % file) end # I'm skipping these, because it seems so system dependent. #if gid == 0 # #Puppet.warning "Not testing group" #else # Puppet.warning "Testing group %s" % gid.inspect # system("ls -l %s" % file) # assert_equal(gid, File.stat(file).gid, # "File group is %s instead of %s" % # [File.stat(file).gid, gid] # ) #end assert_nothing_raised { File.unlink(file) } end def test_asuser file = File.join(tmpdir, "asusertest") @@tmpfiles << file [ [nil], # Nothing [nonrootuser()], # just user, by name [nonrootuser(), nil, true], # user, by uid [nonrootuser(), nonrootgroup()], # user and group, by name [nonrootuser(), nonrootgroup(), true], # user and group, by id ].each { |ary| mknverify(file, *ary) } end # Verify that we get reset back to the right user def test_asuser_recovery begin Puppet::Util.asuser(nonrootuser()) { raise "an error" } rescue end - assert(Process.euid == 0, "UID did not get reset") + assert(Puppet::SUIDManager.euid == 0, "UID did not get reset") end end def test_proxy klass = Class.new do attr_accessor :hash class << self attr_accessor :ohash end end klass.send(:include, Puppet::Util) klass.ohash = {} inst = klass.new inst.hash = {} assert_nothing_raised do Puppet::Util.proxy klass, :hash, "[]", "[]=", :clear, :delete end assert_nothing_raised do Puppet::Util.classproxy klass, :ohash, "[]", "[]=", :clear, :delete end assert_nothing_raised do inst[:yay] = "boo" inst["cool"] = :yayness end [:yay, "cool"].each do |var| assert_equal(inst.hash[var], inst[var], "Var %s did not take" % var) end assert_nothing_raised do klass[:Yay] = "boo" klass["Cool"] = :yayness end [:Yay, "Cool"].each do |var| assert_equal(inst.hash[var], inst[var], "Var %s did not take" % var) end end def test_symbolize ret = nil assert_nothing_raised { ret = Puppet::Util.symbolize("yayness") } assert_equal(:yayness, ret) assert_nothing_raised { ret = Puppet::Util.symbolize(:yayness) } assert_equal(:yayness, ret) assert_nothing_raised { ret = Puppet::Util.symbolize(43) } assert_equal(43, ret) assert_nothing_raised { ret = Puppet::Util.symbolize(nil) } assert_equal(nil, ret) end end # $Id$