diff options
author | Michael V. O'Brien <michael@reductivelabs.com> | 2007-09-25 12:00:07 -0500 |
---|---|---|
committer | Michael V. O'Brien <michael@reductivelabs.com> | 2007-09-25 12:00:07 -0500 |
commit | ff2828f5dbe68ff1cb06a3503590a3e4bd1b59e3 (patch) | |
tree | 8c8960cac1d7b3e8b48e44163062be3b3f4c201f | |
parent | f8ab62b212788a4591276c95b5f67217f7517e4e (diff) | |
parent | ffaa8ce07979f4db860950fa9be08ca37964206f (diff) | |
download | puppet-ff2828f5dbe68ff1cb06a3503590a3e4bd1b59e3.tar.gz puppet-ff2828f5dbe68ff1cb06a3503590a3e4bd1b59e3.tar.xz puppet-ff2828f5dbe68ff1cb06a3503590a3e4bd1b59e3.zip |
Merge branch 'master' of git://reductivelabs.com/puppet
158 files changed, 5845 insertions, 3937 deletions
diff --git a/bin/filebucket b/bin/filebucket index 2001eaf63..22580cbe3 100755 --- a/bin/filebucket +++ b/bin/filebucket @@ -109,7 +109,7 @@ options = [ ] # Add all of the config parameters as valid options. -Puppet.config.addargs(options) +Puppet.settings.addargs(options) result = GetoptLong.new(*options) @@ -139,7 +139,7 @@ begin when "--remote" options[:remote] = true else - Puppet.config.handlearg(opt, arg) + Puppet.settings.handlearg(opt, arg) end } rescue GetoptLong::InvalidOption => detail diff --git a/bin/puppet b/bin/puppet index 36f0fcd62..deeb65c42 100755 --- a/bin/puppet +++ b/bin/puppet @@ -76,7 +76,7 @@ options = [ ] # Add all of the config parameters as valid options. -Puppet.config.addargs(options) +Puppet.settings.addargs(options) result = GetoptLong.new(*options) @@ -124,7 +124,7 @@ begin $stderr.puts detail.to_s end else - Puppet.config.handlearg(opt, arg) + Puppet.settings.handlearg(opt, arg) end } rescue GetoptLong::InvalidOption => detail @@ -156,7 +156,7 @@ end # Now parse the config if Puppet[:config] and File.exists? Puppet[:config] - Puppet.config.parse(Puppet[:config]) + Puppet.settings.parse(Puppet[:config]) end Puppet.genconfig @@ -194,8 +194,8 @@ begin if Puppet[:parseonly] exit(0) end - client.getconfig - client.apply + config = client.getconfig + config.apply rescue => detail if detail.is_a?(XMLRPC::FaultException) $stderr.puts detail.message diff --git a/bin/puppetca b/bin/puppetca index 3dbd87b89..72b2640a3 100755 --- a/bin/puppetca +++ b/bin/puppetca @@ -104,7 +104,7 @@ options = [ ] # Add all of the config parameters as valid options. -Puppet.config.addargs(options) +Puppet.settings.addargs(options) result = GetoptLong.new(*options) @@ -143,7 +143,7 @@ begin when "--verbose" Puppet::Util::Log.level = :info else - Puppet.config.handlearg(opt, arg) + Puppet.settings.handlearg(opt, arg) end } rescue GetoptLong::InvalidOption => detail diff --git a/bin/puppetd b/bin/puppetd index a03ed8f10..8d112ca3a 100755 --- a/bin/puppetd +++ b/bin/puppetd @@ -178,7 +178,7 @@ options = [ ] # Add all of the config parameters as valid options. -Puppet.config.addargs(options) +Puppet.settings.addargs(options) result = GetoptLong.new(*options) @@ -216,10 +216,10 @@ begin options[:enable] = true when "--test" # Enable all of the most common test options. - Puppet.config.handlearg("--ignorecache") - Puppet.config.handlearg("--no-usecacheonfailure") - Puppet.config.handlearg("--no-splay") - Puppet.config.handlearg("--show_diff") + Puppet.settings.handlearg("--ignorecache") + Puppet.settings.handlearg("--no-usecacheonfailure") + Puppet.settings.handlearg("--no-splay") + Puppet.settings.handlearg("--show_diff") options[:onetime] = true options[:waitforcert] = 0 unless Puppet::Util::Log.level == :debug @@ -264,7 +264,7 @@ begin options[:waitforcert] = arg.to_i explicit_waitforcert = true else - Puppet.config.handlearg(opt, arg) + Puppet.settings.handlearg(opt, arg) end } rescue GetoptLong::InvalidOption => detail diff --git a/bin/puppetmasterd b/bin/puppetmasterd index 4552fd0b8..51c714b15 100755 --- a/bin/puppetmasterd +++ b/bin/puppetmasterd @@ -95,7 +95,7 @@ options = [ ] # Add all of the config parameters as valid options. -Puppet.config.addargs(options) +Puppet.settings.addargs(options) result = GetoptLong.new(*options) @@ -152,7 +152,7 @@ begin when "--verbose" options[:verbose] = true else - Puppet.config.handlearg(opt, arg) + Puppet.settings.handlearg(opt, arg) end } rescue GetoptLong::InvalidOption => detail diff --git a/bin/puppetrun b/bin/puppetrun index cd6b493b9..1aa0b295b 100755 --- a/bin/puppetrun +++ b/bin/puppetrun @@ -199,7 +199,7 @@ flags = [ ] # Add all of the config parameters as valid options. -Puppet.config.addargs(flags) +Puppet.settings.addargs(flags) result = GetoptLong.new(*flags) @@ -259,7 +259,7 @@ begin when "--debug" options[:debug] = true else - Puppet.config.handlearg(opt, arg) + Puppet.settings.handlearg(opt, arg) end } rescue GetoptLong::InvalidOption => detail @@ -278,7 +278,7 @@ config = File.join(Puppet[:confdir], "puppetmasterd.conf") Puppet.parse_config(config) if File.exists? config - Puppet.config.parse(config) + Puppet.settings.parse(config) end if Puppet[:ldapnodes] @@ -99,7 +99,7 @@ options = [ ] # Add all of the config parameters as valid options. -Puppet.config.addargs(options) +Puppet.settings.addargs(options) result = GetoptLong.new(*options) @@ -139,7 +139,7 @@ result.each { |opt,arg| debug = true else # Anything else is handled by the config stuff - Puppet.config.handlearg(opt, arg) + Puppet.settings.handlearg(opt, arg) end } diff --git a/ext/module_puppet b/ext/module_puppet index cb03b6ef2..52f65b094 100755 --- a/ext/module_puppet +++ b/ext/module_puppet @@ -61,7 +61,7 @@ options = [ ] # Add all of the config parameters as valid options. -Puppet.config.addargs(options) +Puppet.settings.addargs(options) result = GetoptLong.new(*options) @@ -108,7 +108,7 @@ begin $stderr.puts detail.to_s end else - Puppet.config.handlearg(opt, arg) + Puppet.settings.handlearg(opt, arg) end } rescue GetoptLong::InvalidOption => detail @@ -121,7 +121,7 @@ end # Now parse the config if Puppet[:config] and File.exists? Puppet[:config] - Puppet.config.parse(Puppet[:config]) + Puppet.settings.parse(Puppet[:config]) end client = nil @@ -191,8 +191,8 @@ if parseonly end begin - client.getconfig - client.apply + config = client.getconfig + config.apply rescue => detail Puppet.err detail exit(1) diff --git a/ext/tools/passwd2puppet b/ext/tools/passwd2puppet deleted file mode 100755 index 29ffdbf95..000000000 --- a/ext/tools/passwd2puppet +++ /dev/null @@ -1,45 +0,0 @@ -#!/usr/bin/ruby -w - -#-------------------- -# Convert a passwd-format file to Puppet users -# - -require 'getoptlong' - -result = GetoptLong.new( - [ "--help", "-h", GetoptLong::NO_ARGUMENT ] -) - -result.each { |opt,arg| - case opt - when "--help" - puts "There is no help yet" - exit - else - raise "Invalid option '#{opt}'" - end -} - -fields = %w{uid gid comment home shell} - -puts "user {" -ARGV.each do |file| - File.open(file) do |of| - of.sort.each do |line| - next if line =~ /^\s*#/ - next if line =~ /^\s*$/ - - ary = line.chomp.split(":") - puts " " + ary.shift + ":" - ary.shift # get rid of that password field - - puts fields.zip(ary).collect { |field, val| - " %s => \"%s\"" % [field, val] - }.join(",\n") + ";" - - end - end -end -puts "}" - -# $Id$ diff --git a/lib/puppet.rb b/lib/puppet.rb index 20d879fdf..c1f31e467 100644 --- a/lib/puppet.rb +++ b/lib/puppet.rb @@ -12,7 +12,7 @@ require 'puppet/external/event-loop' require 'puppet/util' require 'puppet/util/log' require 'puppet/util/autoload' -require 'puppet/util/config' +require 'puppet/util/settings' require 'puppet/util/feature' require 'puppet/util/suidmanager' @@ -45,7 +45,7 @@ module Puppet end # the hash that determines how our system behaves - @@config = Puppet::Util::Config.new + @@settings = Puppet::Util::Settings.new # The services running in this process. @services ||= [] @@ -76,7 +76,7 @@ module Puppet # Store a new default value. def self.setdefaults(section, hash) - @@config.setdefaults(section, hash) + @@settings.setdefaults(section, hash) end # configuration parameter access and stuff @@ -89,17 +89,17 @@ module Puppet return false end else - return @@config[param] + return @@settings[param] end end # configuration parameter access and stuff def self.[]=(param,value) - @@config[param] = value + @@settings[param] = value end def self.clear - @@config.clear + @@settings.clear end def self.debug=(value) @@ -110,8 +110,8 @@ module Puppet end end - def self.config - @@config + def self.settings + @@settings end # Load all of the configuration parameters. @@ -122,7 +122,7 @@ module Puppet val = Puppet[:configprint] if val == "all" hash = {} - Puppet.config.each do |name, obj| + Puppet.settings.each do |name, obj| val = obj.value case val when true, false, "": val = val.inspect @@ -134,7 +134,7 @@ module Puppet end elsif val =~ /,/ val.split(/\s*,\s*/).sort.each do |v| - if Puppet.config.include?(v) + if Puppet.settings.include?(v) puts "%s = %s" % [v, Puppet[v]] else puts "invalid parameter: %s" % v @@ -143,7 +143,7 @@ module Puppet end else val.split(/\s*,\s*/).sort.each do |v| - if Puppet.config.include?(v) + if Puppet.settings.include?(v) puts Puppet[val] else puts "invalid parameter: %s" % v @@ -154,14 +154,14 @@ module Puppet exit(0) end if Puppet[:genconfig] - puts Puppet.config.to_config + puts Puppet.settings.to_config exit(0) end end def self.genmanifest if Puppet[:genmanifest] - puts Puppet.config.to_manifest + puts Puppet.settings.to_manifest exit(0) end end @@ -208,14 +208,14 @@ module Puppet oldconfig ||= File.join(Puppet[:confdir], Puppet[:name].to_s + ".conf") if FileTest.exists?(oldconfig) and Puppet[:name] != "puppet" Puppet.warning "Individual config files are deprecated; remove %s and use puppet.conf" % oldconfig - Puppet.config.old_parse(oldconfig) + Puppet.settings.old_parse(oldconfig) return end # Now check for the normal config. if Puppet[:config] and File.exists? Puppet[:config] Puppet.debug "Parsing %s" % Puppet[:config] - Puppet.config.parse(Puppet[:config]) + Puppet.settings.parse(Puppet[:config]) end end diff --git a/lib/puppet/checksum.rb b/lib/puppet/checksum.rb new file mode 100644 index 000000000..c607953c1 --- /dev/null +++ b/lib/puppet/checksum.rb @@ -0,0 +1,64 @@ +# +# Created by Luke Kanies on 2007-9-22. +# Copyright (c) 2007. All rights reserved. + +require 'puppet' +require 'puppet/indirector' + +# A checksum class to model translating checksums to file paths. This +# is the new filebucket. +class Puppet::Checksum + extend Puppet::Indirector + + indirects :checksum + + attr_reader :algorithm, :content + + def algorithm=(value) + unless respond_to?(value) + raise ArgumentError, "Checksum algorithm %s is not supported" % value + end + value = value.intern if value.is_a?(String) + @algorithm = value + # Reset the checksum so it's forced to be recalculated. + @checksum = nil + end + + # Calculate (if necessary) and return the checksum + def checksum + unless @checksum + @checksum = send(algorithm) + end + @checksum + end + + def initialize(content, algorithm = nil) + raise ArgumentError.new("You must specify the content") unless content + + @content = content + self.algorithm = algorithm || "md5" + + # Init to avoid warnings. + @checksum = nil + end + + # This can't be private, else respond_to? returns false. + def md5 + require 'digest/md5' + Digest::MD5.hexdigest(content) + end + + # This is here so the Indirector::File terminus works correctly. + def name + checksum + end + + def sha1 + require 'digest/sha1' + Digest::SHA1.hexdigest(content) + end + + def to_s + "Checksum<{%s}%s>" % [algorithm, checksum] + end +end diff --git a/lib/puppet/config_stores/rest.rb b/lib/puppet/config_stores/rest.rb index 980968bd8..bb3d937ac 100644 --- a/lib/puppet/config_stores/rest.rb +++ b/lib/puppet/config_stores/rest.rb @@ -1,4 +1,4 @@ -Puppet::Util::ConfigStore.newstore(:rest) do +Puppet::Util::SettingsStore.newstore(:rest) do desc "Store client configurations via a REST web service." require 'net/http' diff --git a/lib/puppet/defaults.rb b/lib/puppet/defaults.rb index 78364e786..f76ae9b84 100644 --- a/lib/puppet/defaults.rb +++ b/lib/puppet/defaults.rb @@ -267,7 +267,7 @@ module Puppet ) # Define the config default. - self.setdefaults(self.config[:name], + self.setdefaults(self.settings[:name], :config => ["$confdir/puppet.conf", "The configuration file for #{Puppet[:name]}."], :pidfile => ["", "The pid file"], @@ -494,14 +494,18 @@ module Puppet "The server through which to send email reports."] ) - self.setdefaults(:facts, - :factstore => ["yaml", - "The backend store to use for client facts."] + # This needs to be in main because it's used too early in the system, such that + # we get an infinite loop otherwise. + self.setdefaults(:main, + :facts_terminus => ["yaml", + "The backend store to use for client facts."], + :checksum_terminus => ["file", + "The backend store to use for storing files by checksum (i.e., filebuckets)."] ) - self.setdefaults(:yamlfacts, - :yamlfactdir => ["$vardir/facts", - "The directory in which client facts are stored when the yaml fact store is used."] + self.setdefaults(:yaml, + :yamldir => ["$vardir/yaml", + "The directory in which YAML data is stored, usually in a subdirectory."] ) self.setdefaults(:rails, @@ -554,7 +558,7 @@ module Puppet setdefaults(:parser, :typecheck => [true, "Whether to validate types during parsing."], :paramcheck => [true, "Whether to validate parameters during parsing."], - :node_source => ["none", "Where to look for node configuration information. + :node_terminus => ["null", "Where to look for node configuration information. The default node source, ``none``, just returns a node with its facts filled in, which is required for normal functionality. See the `NodeSourceReference`:trac: for more information."] diff --git a/lib/puppet/dsl.rb b/lib/puppet/dsl.rb index 793578bca..3696cd9ee 100644 --- a/lib/puppet/dsl.rb +++ b/lib/puppet/dsl.rb @@ -67,11 +67,8 @@ module Puppet def apply bucket = export() - objects = bucket.to_type - master = Puppet::Network::Client.master.new :Master => "whatever" - master.objects = objects - - master.apply + configuration = bucket.to_configuration + configuration.apply end def export @@ -255,8 +252,7 @@ module Puppet def scope unless defined?(@scope) @interp = Puppet::Parser::Interpreter.new :Code => "" - # Load the class, so the node object class is available. - require 'puppet/network/handler/node' + require 'puppet/node' @node = Puppet::Node.new(Facter.value(:hostname)) @node.parameters = Facter.to_hash @interp = Puppet::Parser::Interpreter.new :Code => "" diff --git a/lib/puppet/fact_stores/yaml.rb b/lib/puppet/fact_stores/yaml.rb index a4b12a2e5..b33e162ba 100644 --- a/lib/puppet/fact_stores/yaml.rb +++ b/lib/puppet/fact_stores/yaml.rb @@ -16,7 +16,7 @@ Puppet::Util::FactStore.newstore(:yaml) do end def initialize - Puppet.config.use(:yamlfacts) + Puppet.settings.use(:yamlfacts) end # Store the facts to disk. diff --git a/lib/puppet/indirector.rb b/lib/puppet/indirector.rb index 0ba538355..6ff2de1b4 100644 --- a/lib/puppet/indirector.rb +++ b/lib/puppet/indirector.rb @@ -1,76 +1,51 @@ # Manage indirections to termini. They are organized in terms of indirections - # - e.g., configuration, node, file, certificate -- and each indirection has one -# or more terminus types defined. The indirection must have its preferred terminus -# configured via a 'default' in the form of '<indirection>_terminus'; e.g., -# 'node_terminus = ldap'. +# or more terminus types defined. The indirection is configured via the +# +indirects+ method, which will be called by the class extending itself +# with this module. module Puppet::Indirector - # This manages reading in all of our files for us and then retrieving - # loaded instances. We still have to define the 'newX' method, but this - # does all of the rest -- loading, storing, and retrieving by name. - require 'puppet/util/instance_loader' - include Puppet::Util::InstanceLoader + # LAK:FIXME We need to figure out how to handle documentation for the + # different indirection types. - # Define a new indirection terminus. This method is used by the individual - # termini in their separate files. Again, the autoloader takes care of - # actually loading these files. - def register_terminus(name, options = {}, &block) - genclass(name, :hash => instance_hash(indirection.name), :attributes => options, :block => block) - end - - # Retrieve a terminus class by indirection and name. - def terminus(name) - loaded_instance(name) - end + require 'puppet/indirector/indirection' + require 'puppet/indirector/terminus' # Declare that the including class indirects its methods to # this terminus. The terminus name must be the name of a Puppet # default, not the value -- if it's the value, then it gets # evaluated at parse time, which is before the user has had a chance # to override it. - def indirects(indirection, options) - @indirection = indirection - @indirect_terminus = options[:to] - - # Set up autoloading of the appropriate termini. - autoload "puppet/indirector/%s" % indirection + def indirects(indirection) + raise(ArgumentError, "Already handling indirection for %s; cannot also handle %s" % [@indirection.name, indirection]) if defined?(@indirection) and @indirection + # populate this class with the various new methods + extend ClassMethods + include InstanceMethods + + # instantiate the actual Terminus for that type and this name (:ldap, w/ args :node) + # & hook the instantiated Terminus into this class (Node: @indirection = terminus) + @indirection = Puppet::Indirector::Indirection.new(self, indirection) end - # Define methods for each of the HTTP methods. These just point to the - # termini, with consistent error-handling. Each method is called with - # the first argument being the indirection type and the rest of the - # arguments passed directly on to the indirection terminus. There is - # currently no attempt to standardize around what the rest of the arguments - # should allow or include or whatever. - # There is also no attempt to pre-validate that a given indirection supports - # the method in question. We should probably require that indirections - # declare supported methods, and then verify that termini implement all of - # those methods. - [:get, :post, :put, :delete].each do |method_name| - define_method(method_name) do |*args| - begin - terminus.send(method_name, *args) - rescue NoMethodError - raise ArgumentError, "Indirection category %s does not respond to REST method %s" % [indirection, method_name] - end - end - end + module ClassMethods + attr_reader :indirection + + def find(*args) + indirection.find(*args) + end - private + def destroy(*args) + indirection.destroy(*args) + end - # Create a new terminus instance. - def make_terminus(indirection) - # Load our terminus class. - unless klass = self.class.terminus(indirection, indirection.default) - raise ArgumentError, "Could not find terminus %s for indirection %s" % [indirection.default, indirection] - end - return klass.new + def search(*args) + indirection.search(*args) + end end - # Return the singleton terminus for this indirection. - def terminus - unless terminus = @termini[indirection.name] - terminus = @termini[indirection.name] = make_terminus(indirection) - end - terminus + module InstanceMethods + # these become instance methods + def save(*args) + self.class.indirection.save(self, *args) + end end end diff --git a/lib/puppet/indirector/code.rb b/lib/puppet/indirector/code.rb new file mode 100644 index 000000000..0c0ee146b --- /dev/null +++ b/lib/puppet/indirector/code.rb @@ -0,0 +1,6 @@ +require 'puppet/indirector/terminus' + +# Do nothing, requiring that the back-end terminus do all +# of the work. +class Puppet::Indirector::Code < Puppet::Indirector::Terminus +end diff --git a/lib/puppet/indirector/code/configuration.rb b/lib/puppet/indirector/code/configuration.rb new file mode 100644 index 000000000..6d0317204 --- /dev/null +++ b/lib/puppet/indirector/code/configuration.rb @@ -0,0 +1,171 @@ +require 'puppet/node' +require 'puppet/node/configuration' +require 'puppet/indirector/code' +require 'puppet/parser/interpreter' +require 'yaml' + +class Puppet::Indirector::Code::Configuration < Puppet::Indirector::Code + desc "Puppet's configuration compilation interface. Passed a node name + or other key, retrieves information about the node (using the ``node_source``) + and returns a compiled configuration." + + include Puppet::Util + + attr_accessor :code + + # Compile a node's configuration. + def find(key, client = nil, clientip = nil) + # If we want to use the cert name as our key + if Puppet[:node_name] == 'cert' and client + key = client + end + + # Note that this is reasonable, because either their node source should actually + # know about the node, or they should be using the ``none`` node source, which + # will always return data. + unless node = Puppet::Node.search(key) + raise Puppet::Error, "Could not find node '%s'" % key + end + + # Add any external data to the node. + add_node_data(node) + + configuration = compile(node) + + return configuration + end + + def initialize + set_server_facts + end + + # Create/return our interpreter. + def interpreter + unless defined?(@interpreter) and @interpreter + @interpreter = create_interpreter + end + @interpreter + end + + # Return the configuration version. + def version(client = nil, clientip = nil) + if client and node = Puppet::Node.search(client) + update_node_check(node) + return interpreter.configuration_version(node) + else + # Just return something that will always result in a recompile, because + # this is local. + return (Time.now + 1000).to_i + end + end + + private + + # Add any extra data necessary to the node. + def add_node_data(node) + # Merge in our server-side facts, so they can be used during compilation. + node.merge(@server_facts) + end + + # Compile the actual configuration. + def compile(node) + # Ask the interpreter to compile the configuration. + str = "Compiled configuration for %s" % node.name + if node.environment + str += " in environment %s" % node.environment + end + config = nil + + # LAK:FIXME This should log at :none when our client is + # local, since we don't want 'puppet' (vs. puppetmasterd) to + # log compile times. + benchmark(:notice, "Compiled configuration for %s" % node.name) do + begin + config = interpreter.compile(node) + rescue Puppet::Error => detail + if Puppet[:trace] + puts detail.backtrace + end + unless local? + Puppet.err detail.to_s + end + raise XMLRPC::FaultException.new( + 1, detail.to_s + ) + end + end + + return config + end + + # Create our interpreter object. + def create_interpreter + args = {} + + # Allow specification of a code snippet or of a file + if self.code + args[:Code] = self.code + end + + # LAK:FIXME This needs to be handled somehow. + #if options.include?(:UseNodes) + # args[:UseNodes] = options[:UseNodes] + #elsif @local + # args[:UseNodes] = false + #end + + return Puppet::Parser::Interpreter.new(args) + end + + # Initialize our server fact hash; we add these to each client, and they + # won't change while we're running, so it's safe to cache the values. + def set_server_facts + @server_facts = {} + + # Add our server version to the fact list + @server_facts["serverversion"] = Puppet.version.to_s + + # And then add the server name and IP + {"servername" => "fqdn", + "serverip" => "ipaddress" + }.each do |var, fact| + if value = Facter.value(fact) + @server_facts[var] = value + else + Puppet.warning "Could not retrieve fact %s" % fact + end + end + + if @server_facts["servername"].nil? + host = Facter.value(:hostname) + if domain = Facter.value(:domain) + @server_facts["servername"] = [host, domain].join(".") + else + @server_facts["servername"] = host + end + end + end + + # Translate our configuration appropriately for sending back to a client. + # LAK:FIXME This method should probably be part of the protocol, but it + # shouldn't be here. + def translate(config) + if local? + config + else + CGI.escape(config.to_yaml(:UseBlock => true)) + end + end + + # Mark that the node has checked in. LAK:FIXME this needs to be moved into + # the Node class, or somewhere that's got abstract backends. + def update_node_check(node) + if Puppet.features.rails? and Puppet[:storeconfigs] + Puppet::Rails.connect + + host = Puppet::Rails::Host.find_or_create_by_name(node.name) + host.last_freshcheck = Time.now + host.save + end + end +end diff --git a/lib/puppet/indirector/exec.rb b/lib/puppet/indirector/exec.rb new file mode 100644 index 000000000..7e4ac8d18 --- /dev/null +++ b/lib/puppet/indirector/exec.rb @@ -0,0 +1,57 @@ +require 'puppet/indirector/terminus' +require 'puppet/util' + +class Puppet::Indirector::Exec < Puppet::Indirector::Terminus + # Look for external node definitions. + def find(name) + # Run the command. + unless output = query(name) + return nil + end + + # Translate the output to ruby. + return output + end + + private + + # Proxy the execution, so it's easier to test. + def execute(command) + Puppet::Util.execute(command) + end + + # Call the external command and see if it returns our output. + def query(name) + external_command = command + + # Make sure it's an arry + unless external_command.is_a?(Array) + raise Puppet::DevError, "Exec commands must be an array" + end + + # Make sure it's fully qualified. + unless external_command[0][0] == File::SEPARATOR[0] + raise ArgumentError, "You must set the exec parameter to a fully qualified command" + end + + # Add our name to it. + external_command << name + begin + output = execute(external_command) + rescue Puppet::ExecutionFailure => detail + if $?.exitstatus == 1 + return nil + else + Puppet.err "Could not retrieve external node information for %s: %s" % [name, detail] + end + return nil + end + + if output =~ /\A\s*\Z/ # all whitespace + Puppet.debug "Empty response for %s from exec %s terminus" % [name, self.name] + return nil + else + return output + end + end +end diff --git a/lib/puppet/indirector/exec/node.rb b/lib/puppet/indirector/exec/node.rb new file mode 100644 index 000000000..033afe3f0 --- /dev/null +++ b/lib/puppet/indirector/exec/node.rb @@ -0,0 +1,50 @@ +require 'puppet/indirector/exec' + +class Puppet::Indirector::Exec::Node < Puppet::Indirector::Exec + desc "Call an external program to get node information." + include Puppet::Util + + def command + command = Puppet[:external_nodes] + unless command != "none" + raise ArgumentError, "You must set the 'external_nodes' parameter to use the external node terminus" + end + command.split + end + + # Look for external node definitions. + def find(name) + output = super or return nil + + # Translate the output to ruby. + result = translate(name, output) + + return create_node(name, result) + end + + private + + # Turn our outputted objects into a Puppet::Node instance. + def create_node(name, result) + node = Puppet::Node.new(name) + set = false + [:parameters, :classes].each do |param| + if value = result[param] + node.send(param.to_s + "=", value) + set = true + end + end + + node.fact_merge + return node + end + + # Translate the yaml string into Ruby objects. + def translate(name, output) + begin + YAML.load(output).inject({}) { |hash, data| hash[symbolize(data[0])] = data[1]; hash } + rescue => detail + raise Puppet::Error, "Could not load external node results for %s: %s" % [name, detail] + end + end +end diff --git a/lib/puppet/indirector/file.rb b/lib/puppet/indirector/file.rb new file mode 100644 index 000000000..c2d36c46b --- /dev/null +++ b/lib/puppet/indirector/file.rb @@ -0,0 +1,54 @@ +require 'puppet/indirector/terminus' + +# An empty terminus type, meant to just return empty objects. +class Puppet::Indirector::File < Puppet::Indirector::Terminus + def destroy(file) + if respond_to?(:path) + path = path(file.name) + else + path = file.path + end + raise Puppet::Error.new("File %s does not exist; cannot destroy" % [file]) unless File.exist?(path) + + begin + File.unlink(path) + rescue => detail + raise Puppet::Error, "Could not remove %s: %s" % [file, detail] + end + end + + def find(name) + if respond_to?(:path) + path = path(name) + else + path = name + end + + return nil unless File.exist?(path) + + begin + content = File.read(path) + rescue => detail + raise Puppet::Error, "Could not retrieve path %s: %s" % [path, detail] + end + + return model.new(content) + end + + def save(file) + if respond_to?(:path) + path = path(file.name) + else + path = file.path + end + dir = File.dirname(path) + + raise Puppet::Error.new("Cannot save %s; parent directory %s does not exist" % [file, dir]) unless File.directory?(dir) + + begin + File.open(path, "w") { |f| f.print file.content } + rescue => detail + raise Puppet::Error, "Could not write %s: %s" % [file, detail] + end + end +end diff --git a/lib/puppet/indirector/file/checksum.rb b/lib/puppet/indirector/file/checksum.rb new file mode 100644 index 000000000..2f0974ced --- /dev/null +++ b/lib/puppet/indirector/file/checksum.rb @@ -0,0 +1,33 @@ +require 'puppet/checksum' +require 'puppet/indirector/file' + +class Puppet::Indirector::File::Checksum < Puppet::Indirector::File + desc "Store files in a directory set based on their checksums." + + def initialize + Puppet.settings.use(:filebucket) + end + + def path(checksum) + path = [] + path << Puppet[:bucketdir] # Start with the base directory + path << checksum[0..7].split("").join(File::SEPARATOR) # Add sets of directories based on the checksum + path << checksum # And the full checksum name itself + path << "contents" # And the actual file name + + path.join(File::SEPARATOR) + end + + def save(file) + path = File.dirname(path(file.name)) + + # Make the directories if necessary. + unless FileTest.directory?(path) + Puppet::Util.withumask(0007) do + FileUtils.mkdir_p(path) + end + end + + super + end +end diff --git a/lib/puppet/indirector/indirection.rb b/lib/puppet/indirector/indirection.rb new file mode 100644 index 000000000..8afe0012d --- /dev/null +++ b/lib/puppet/indirector/indirection.rb @@ -0,0 +1,91 @@ +# An actual indirection. +class Puppet::Indirector::Indirection + @@indirections = [] + + # Clear all cached termini from all indirections. + def self.clear_cache + @@indirections.each { |ind| ind.clear_cache } + end + + # Find an indirection by name. This is provided so that Terminus classes + # can specifically hook up with the indirections they are associated with. + def self.instance(name) + @@indirections.find { |i| i.name == name } + end + + attr_accessor :name, :model + + # Clear our cached list of termini. + # This is only used for testing. + def clear_cache + @termini.clear + end + + # This is only used for testing. + def delete + @@indirections.delete(self) if @@indirections.include?(self) + end + + def initialize(model, name, options = {}) + @model = model + @name = name + options.each do |name, value| + begin + send(name.to_s + "=", value) + rescue NoMethodError + raise ArgumentError, "%s is not a valid Indirection parameter" % name + end + end + @termini = {} + @terminus_types = {} + raise(ArgumentError, "Indirection %s is already defined" % @name) if @@indirections.find { |i| i.name == @name } + @@indirections << self + end + + # Return the singleton terminus for this indirection. + def terminus(terminus_name = nil) + # Get the name of the terminus. + unless terminus_name + param_name = "%s_terminus" % self.name + if Puppet.settings.valid?(param_name) + terminus_name = Puppet.settings[param_name] + else + terminus_name = Puppet[:default_terminus] + end + unless terminus_name and terminus_name.to_s != "" + raise ArgumentError, "Invalid terminus name %s" % terminus_name.inspect + end + terminus_name = terminus_name.intern if terminus_name.is_a?(String) + end + + return @termini[terminus_name] ||= make_terminus(terminus_name) + end + + def find(*args) + terminus.find(*args) + end + + def destroy(*args) + terminus.destroy(*args) + end + + def search(*args) + terminus.search(*args) + end + + # these become instance methods + def save(*args) + terminus.save(*args) + end + + private + + # Create a new terminus instance. + def make_terminus(name) + # Load our terminus class. + unless klass = Puppet::Indirector::Terminus.terminus_class(name, self.name) + raise ArgumentError, "Could not find terminus %s for indirection %s" % [name, self.name] + end + return klass.new + end +end diff --git a/lib/puppet/indirector/ldap.rb b/lib/puppet/indirector/ldap.rb new file mode 100644 index 000000000..fb883def6 --- /dev/null +++ b/lib/puppet/indirector/ldap.rb @@ -0,0 +1,90 @@ +require 'puppet/indirector/terminus' + +class Puppet::Indirector::Ldap < Puppet::Indirector::Terminus + # Perform our ldap search and process the result. + def find(name) + # We have to use 'yield' here because the LDAP::Entry objects + # get destroyed outside the scope of the search, strangely. + ldapsearch(name) { |entry| return process(name, entry) } + + # Return nil if we haven't found something. + return nil + end + + # Process the found entry. We assume that we don't just want the + # ldap object. + def process(name, entry) + raise Puppet::DevError, "The 'process' method has not been overridden for the LDAP terminus for %s" % self.name + end + + # Default to all attributes. + def search_attributes + nil + end + + def search_base + Puppet[:ldapbase] + end + + # The ldap search filter to use. + def search_filter(name) + raise Puppet::DevError, "No search string set for LDAP terminus for %s" % self.name + end + + # Find the ldap node, return the class list and parent node specially, + # and everything else in a parameter hash. + def ldapsearch(node) + raise ArgumentError.new("You must pass a block to ldapsearch") unless block_given? + + found = false + count = 0 + + begin + connection.search(search_base, 2, search_filter(node), search_attributes) do |entry| + found = true + yield entry + end + rescue => detail + if count == 0 + # Try reconnecting to ldap if we get an exception and we haven't yet retried. + count += 1 + @connection = nil + Puppet.warning "Retrying LDAP connection" + retry + else + raise Puppet::Error, "LDAP Search failed: %s" % detail + end + end + + return found + end + + private + + # Create an ldap connection. + def connection + unless defined? @connection and @connection + unless Puppet.features.ldap? + raise Puppet::Error, "Could not set up LDAP Connection: Missing ruby/ldap libraries" + end + begin + if Puppet[:ldapssl] + @connection = LDAP::SSLConn.new(Puppet[:ldapserver], Puppet[:ldapport]) + elsif Puppet[:ldaptls] + @connection = LDAP::SSLConn.new( + Puppet[:ldapserver], Puppet[:ldapport], true + ) + else + @connection = LDAP::Conn.new(Puppet[:ldapserver], Puppet[:ldapport]) + end + @connection.set_option(LDAP::LDAP_OPT_PROTOCOL_VERSION, 3) + @connection.set_option(LDAP::LDAP_OPT_REFERRALS, LDAP::LDAP_OPT_ON) + @connection.simple_bind(Puppet[:ldapuser], Puppet[:ldappassword]) + rescue => detail + raise Puppet::Error, "Could not connect to LDAP: %s" % detail + end + end + + return @connection + end +end diff --git a/lib/puppet/indirector/ldap/node.rb b/lib/puppet/indirector/ldap/node.rb new file mode 100644 index 000000000..6f35b575c --- /dev/null +++ b/lib/puppet/indirector/ldap/node.rb @@ -0,0 +1,115 @@ +require 'puppet/indirector/ldap' + +class Puppet::Indirector::Ldap::Node < Puppet::Indirector::Ldap + desc "Search in LDAP for node configuration information." + + # The attributes that Puppet class information is stored in. + def class_attributes + Puppet[:ldapclassattrs].split(/\s*,\s*/) + end + + # Look for our node in ldap. + def find(name) + return nil unless information = super + node = Puppet::Node.new(name) + + parent_info = nil + parent = information[:parent] + parents = [name] + while parent + if parents.include?(parent) + raise ArgumentError, "Found loop in LDAP node parents; %s appears twice" % parent + end + parents << parent + ldapsearch(parent) do |entry| + parent_info = process(parent, entry) + end + information[:classes] += parent_info[:classes] + parent_info[:parameters].each do |param, value| + # Specifically test for whether it's set, so false values are handled + # correctly. + information[:parameters][param] = value unless information[:parameters].include?(param) + end + + parent = parent_info[:parent] + end + + node.classes = information[:classes].uniq unless information[:classes].empty? + node.parameters = information[:parameters] unless information[:parameters].empty? + node.fact_merge + + return node + end + + # The parent attribute, if we have one. + def parent_attribute + if pattr = Puppet[:ldapparentattr] and ! pattr.empty? + pattr + else + nil + end + end + + # Process the found entry. We assume that we don't just want the + # ldap object. + def process(name, entry) + result = {} + if pattr = parent_attribute + if values = entry.vals(pattr) + if values.length > 1 + raise Puppet::Error, + "Node %s has more than one parent: %s" % [name, values.inspect] + end + unless values.empty? + result[:parent] = values.shift + end + end + end + + result[:classes] = [] + class_attributes.each { |attr| + if values = entry.vals(attr) + values.each do |v| result[:classes] << v end + end + } + + result[:parameters] = entry.to_hash.inject({}) do |hash, ary| + if ary[1].length == 1 + hash[ary[0]] = ary[1].shift + else + hash[ary[0]] = ary[1] + end + hash + end + + return result + end + + # Default to all attributes. + def search_attributes + ldapattrs = Puppet[:ldapattrs] + + # results in everything getting returned + return nil if ldapattrs == "all" + + search_attrs = class_attributes + ldapattrs.split(/\s*,\s*/) + + if pattr = parent_attribute + search_attrs << pattr + end + + search_attrs + end + + # The ldap search filter to use. + def search_filter(name) + filter = Puppet[:ldapstring] + + if filter.include? "%s" + # Don't replace the string in-line, since that would hard-code our node + # info. + filter = filter.gsub('%s', name) + end + filter + end +end diff --git a/lib/puppet/indirector/memory.rb b/lib/puppet/indirector/memory.rb new file mode 100644 index 000000000..5bfcec95d --- /dev/null +++ b/lib/puppet/indirector/memory.rb @@ -0,0 +1,21 @@ +require 'puppet/indirector/terminus' + +# Manage a memory-cached list of instances. +class Puppet::Indirector::Memory < Puppet::Indirector::Terminus + def initialize + @instances = {} + end + + def destroy(instance) + raise ArgumentError.new("Could not find %s to destroy" % instance) unless @instances.include?(instance.name) + @instances.delete(instance.name) + end + + def find(name) + @instances[name] + end + + def save(instance) + @instances[instance.name] = instance + end +end diff --git a/lib/puppet/indirector/memory/node.rb b/lib/puppet/indirector/memory/node.rb new file mode 100644 index 000000000..c5000b879 --- /dev/null +++ b/lib/puppet/indirector/memory/node.rb @@ -0,0 +1,8 @@ +require 'puppet/indirector/memory' + +class Puppet::Indirector::Memory::Node < Puppet::Indirector::Memory + desc "Keep track of nodes in memory but nowhere else. This is used for + one-time compiles, such as what the stand-alone ``puppet`` does. + To use this terminus, you must load it with the data you want it + to contain." +end diff --git a/lib/puppet/indirector/null.rb b/lib/puppet/indirector/null.rb new file mode 100644 index 000000000..db2b1db1c --- /dev/null +++ b/lib/puppet/indirector/null.rb @@ -0,0 +1,9 @@ +require 'puppet/indirector/terminus' + +# An empty terminus type, meant to just return empty objects. +class Puppet::Indirector::Null < Puppet::Indirector::Terminus + # Just return nothing. + def find(name) + indirection.model.new(name) + end +end diff --git a/lib/puppet/indirector/null/node.rb b/lib/puppet/indirector/null/node.rb new file mode 100644 index 000000000..eb08f5697 --- /dev/null +++ b/lib/puppet/indirector/null/node.rb @@ -0,0 +1,14 @@ +require 'puppet/indirector/null' + +class Puppet::Indirector::Null::Node < Puppet::Indirector::Null + desc "Always return an empty node object. This is the node source you should + use when you don't have some other, functional source you want to use, + as the compiler will not work without this node information." + + # Just return an empty node. + def find(name) + node = super + node.fact_merge + node + end +end diff --git a/lib/puppet/indirector/terminus.rb b/lib/puppet/indirector/terminus.rb new file mode 100644 index 000000000..bcff08d79 --- /dev/null +++ b/lib/puppet/indirector/terminus.rb @@ -0,0 +1,117 @@ +require 'puppet/indirector' +require 'puppet/indirector/indirection' +require 'puppet/util/instance_loader' + +# A simple class that can function as the base class for indirected types. +class Puppet::Indirector::Terminus + require 'puppet/util/docs' + extend Puppet::Util::Docs + + class << self + include Puppet::Util::InstanceLoader + + attr_accessor :name, :terminus_type + attr_reader :abstract_terminus, :indirection + + # Are we an abstract terminus type, rather than an instance with an + # associated indirection? + def abstract_terminus? + abstract_terminus + end + + # Look up the indirection if we were only provided a name. + def indirection=(name) + if name.is_a?(Puppet::Indirector::Indirection) + @indirection = name + elsif ind = Puppet::Indirector::Indirection.instance(name) + @indirection = ind + else + raise ArgumentError, "Could not find indirection instance %s" % name + end + end + + # Register our subclass with the appropriate indirection. + # This follows the convention that our terminus is named after the + # indirection. + def inherited(subclass) + longname = subclass.to_s + if longname =~ /#<Class/ + raise ArgumentError, "Terminus subclasses must have associated constants" + end + names = longname.split("::") + name = names.pop.downcase.intern + + subclass.name = name + + # Short-circuit the abstract types, which are those that directly subclass + # the Terminus class. + if self == Puppet::Indirector::Terminus + subclass.mark_as_abstract_terminus + return + end + + # Set the terminus type to be the name of the abstract terminus type. + # Yay, class/instance confusion. + subclass.terminus_type = self.name + + # This will throw an exception if the indirection instance cannot be found. + # Do this last, because it also registers the terminus type with the indirection, + # which needs the above information. + subclass.indirection = name + + # And add this instance to the instance hash. + Puppet::Indirector::Terminus.register_terminus_class(subclass) + end + + # Mark that this instance is abstract. + def mark_as_abstract_terminus + @abstract_terminus = true + end + + def model + indirection.model + end + + # Register a class, probably autoloaded. + def register_terminus_class(klass) + setup_instance_loading klass.terminus_type + instance_hash(klass.terminus_type)[klass.name] = klass + end + + # Return a terminus by name, using the autoloader. + def terminus_class(type, name) + setup_instance_loading type + loaded_instance(type, name) + end + + private + + def setup_instance_loading(type) + unless instance_loading?(type) + instance_load type, "puppet/indirector/%s" % type + end + end + end + + def initialize + if self.class.abstract_terminus? + raise Puppet::DevError, "Cannot create instances of abstract terminus types" + end + end + + def terminus_type + self.class.terminus_type + end + + def name + self.class.name + end + + def model + self.class.model + end + + def indirection + self.class.indirection + end +end diff --git a/lib/puppet/indirector/yaml.rb b/lib/puppet/indirector/yaml.rb new file mode 100644 index 000000000..b9ea54f39 --- /dev/null +++ b/lib/puppet/indirector/yaml.rb @@ -0,0 +1,45 @@ +require 'puppet/indirector/terminus' + +# The base class for YAML indirection termini. +class Puppet::Indirector::Yaml < Puppet::Indirector::Terminus + def initialize + # Make sure our base directory exists. + Puppet.settings.use(:yaml) + end + + # Read a given name's file in and convert it from YAML. + def find(name) + raise ArgumentError.new("You must specify the name of the object to retrieve") unless name + file = path(name) + return nil unless FileTest.exist?(file) + + begin + return YAML.load(File.read(file)) + rescue => detail + raise Puppet::Error, "Could not read YAML data for %s(%s): %s" % [indirection.name, name, detail] + end + end + + # Convert our object to YAML and store it to the disk. + def save(object) + raise ArgumentError.new("You can only save objects that respond to :name") unless object.respond_to?(:name) + + file = path(object.name) + + basedir = File.dirname(file) + + # This is quite likely a bad idea, since we're not managing ownership or modes. + unless FileTest.exist?(basedir) + Dir.mkdir(basedir) + end + + File.open(file, "w", 0660) { |f| f.print YAML.dump(object) } + end + + private + + # Return the path to a given node's file. + def path(name) + File.join(Puppet[:yamldir], self.name.to_s, name.to_s + ".yaml") + end +end diff --git a/lib/puppet/indirector/yaml/facts.rb b/lib/puppet/indirector/yaml/facts.rb new file mode 100644 index 000000000..754b0d5a6 --- /dev/null +++ b/lib/puppet/indirector/yaml/facts.rb @@ -0,0 +1,5 @@ +require 'puppet/indirector/yaml' + +class Puppet::Indirector::Yaml::Facts < Puppet::Indirector::Yaml + desc "Store client facts as flat files, serialized using YAML." +end diff --git a/lib/puppet/metatype/closure.rb b/lib/puppet/metatype/closure.rb index efb4712c6..727bc6884 100644 --- a/lib/puppet/metatype/closure.rb +++ b/lib/puppet/metatype/closure.rb @@ -1,19 +1,6 @@ class Puppet::Type attr_writer :implicit - def self.implicitcreate(hash) - unless hash.include?(:implicit) - hash[:implicit] = true - end - if obj = self.create(hash) - obj.implicit = true - - return obj - else - return nil - end - end - # Is this type's name isomorphic with the object? That is, if the # name conflicts, does it necessarily mean that the objects conflict? # Defaults to true. diff --git a/lib/puppet/metatype/container.rb b/lib/puppet/metatype/container.rb index 7c44a7def..7bbccf8a0 100644 --- a/lib/puppet/metatype/container.rb +++ b/lib/puppet/metatype/container.rb @@ -14,14 +14,6 @@ class Puppet::Type self.class.depthfirst? end - def parent=(parent) - if self.parentof?(parent) - devfail "%s[%s] is already the parent of %s[%s]" % - [self.class.name, self.title, parent.class.name, parent.title] - end - @parent = parent - end - # Add a hook for testing for recursion. def parentof?(child) if (self == child) diff --git a/lib/puppet/metatype/instances.rb b/lib/puppet/metatype/instances.rb index f6c2fdd34..4af230b28 100644 --- a/lib/puppet/metatype/instances.rb +++ b/lib/puppet/metatype/instances.rb @@ -79,8 +79,7 @@ class Puppet::Type end # Force users to call this, so that we can merge objects if - # necessary. FIXME This method should be responsible for most of the - # error handling. + # necessary. def self.create(args) # Don't modify the original hash; instead, create a duplicate and modify it. # We have to dup and use the ! so that it stays a TransObject if it is @@ -138,9 +137,8 @@ class Puppet::Type # now pass through and create the new object elsif implicit - Puppet.notice "Ignoring implicit %s" % title - - return retobj + Puppet.debug "Ignoring implicit %s[%s]" % [self.name, title] + return nil else # If only one of the objects is being managed, then merge them if retobj.managed? @@ -308,8 +306,8 @@ class Puppet::Type # Create the path for logging and such. def pathbuilder - if defined? @parent and @parent - [@parent.pathbuilder, self.ref].flatten + if p = parent + [p.pathbuilder, self.ref].flatten else [self.ref] end diff --git a/lib/puppet/module.rb b/lib/puppet/module.rb index 924958bbe..dc30d8167 100644 --- a/lib/puppet/module.rb +++ b/lib/puppet/module.rb @@ -9,7 +9,7 @@ class Puppet::Module # parameter. Only consider paths that are absolute and existing # directories def self.modulepath(environment = nil) - dirs = Puppet.config.value(:modulepath, environment).split(":") + dirs = Puppet.settings.value(:modulepath, environment).split(":") if ENV["PUPPETLIB"] dirs = ENV["PUPPETLIB"].split(":") + dirs else @@ -61,7 +61,7 @@ class Puppet::Module if mod return mod.template(file) else - return File.join(Puppet.config.value(:templatedir, environment), template) + return File.join(Puppet.settings.value(:templatedir, environment), template) end end diff --git a/lib/puppet/network/client/ca.rb b/lib/puppet/network/client/ca.rb index 412c9c59f..46fb9f51f 100644 --- a/lib/puppet/network/client/ca.rb +++ b/lib/puppet/network/client/ca.rb @@ -14,9 +14,9 @@ class Puppet::Network::Client::CA < Puppet::Network::Client end # This client is really only able to request certificates for the - # current host. It uses the Puppet.config settings to figure everything out. + # current host. It uses the Puppet.settings settings to figure everything out. def request_cert - Puppet.config.use(:main, :ssl) + Puppet.settings.use(:main, :ssl) if cert = read_cert return cert @@ -49,8 +49,8 @@ class Puppet::Network::Client::CA < Puppet::Network::Client end # Only write the cert out if it passes validating. - Puppet.config.write(:hostcert) do |f| f.print cert end - Puppet.config.write(:localcacert) do |f| f.print cacert end + Puppet.settings.write(:hostcert) do |f| f.print cert end + Puppet.settings.write(:localcacert) do |f| f.print cacert end return @cert end diff --git a/lib/puppet/network/client/master.rb b/lib/puppet/network/client/master.rb index c6d7cd75d..f950a6059 100644 --- a/lib/puppet/network/client/master.rb +++ b/lib/puppet/network/client/master.rb @@ -7,7 +7,7 @@ class Puppet::Network::Client::Master < Puppet::Network::Client @@sync = Sync.new end - attr_accessor :objects + attr_accessor :configuration attr_reader :compile_time class << self @@ -46,51 +46,7 @@ class Puppet::Network::Client::Master < Puppet::Network::Client # Return the list of dynamic facts as an array of symbols def self.dynamic_facts - Puppet.config[:dynamicfacts].split(/\s*,\s*/).collect { |fact| fact.downcase } - end - - # This method actually applies the configuration. - def apply(tags = nil, ignoreschedules = false) - unless defined? @objects - raise Puppet::Error, "Cannot apply; objects not defined" - end - - transaction = @objects.evaluate - - if tags - transaction.tags = tags - end - - if ignoreschedules - transaction.ignoreschedules = true - end - - transaction.addtimes :config_retrieval => @configtime - - begin - transaction.evaluate - rescue Puppet::Error => detail - Puppet.err "Could not apply complete configuration: %s" % - detail - rescue => detail - Puppet.err "Got an uncaught exception of type %s: %s" % - [detail.class, detail] - if Puppet[:trace] - puts detail.backtrace - end - ensure - Puppet::Util::Storage.store - end - - if Puppet[:report] or Puppet[:summarize] - report(transaction) - end - - return transaction - ensure - if defined? transaction and transaction - transaction.cleanup - end + Puppet.settings[:dynamicfacts].split(/\s*,\s*/).collect { |fact| fact.downcase } end # Cache the config @@ -111,10 +67,10 @@ class Puppet::Network::Client::Master < Puppet::Network::Client end def clear - @objects.remove(true) if @objects + @configuration.clear(true) if @configuration Puppet::Type.allclear mkdefault_objects - @objects = nil + @configuration = nil end # Initialize and load storage @@ -183,15 +139,15 @@ class Puppet::Network::Client::Master < Puppet::Network::Client facts = self.class.facts end - if self.objects or FileTest.exists?(self.cachefile) + if self.configuration or FileTest.exists?(self.cachefile) if self.fresh?(facts) Puppet.info "Config is up to date" - if self.objects + if self.configuration return end if oldtext = self.retrievecache begin - @objects = YAML.load(oldtext).to_type + @configuration = YAML.load(oldtext).to_configuration rescue => detail Puppet.warning "Could not load cached configuration: %s" % detail end @@ -213,7 +169,7 @@ class Puppet::Network::Client::Master < Puppet::Network::Client end unless objects = get_actual_config(facts) - @objects = nil + @configuration = nil return end @@ -225,21 +181,21 @@ class Puppet::Network::Client::Master < Puppet::Network::Client self.setclasses(objects.classes) # Clear all existing objects, so we can recreate our stack. - if self.objects + if self.configuration clear() end - # Now convert the objects to real Puppet objects - @objects = objects.to_type + # Now convert the objects to a puppet configuration graph. + @configuration = objects.to_configuration - if @objects.nil? + if @configuration.nil? raise Puppet::Error, "Configuration could not be processed" end - # and perform any necessary final actions before we evaluate. - @objects.finalize + # Keep the state database up to date. + @configuration.host_config = true - return @objects + return @configuration end # A simple proxy method, so it's easy to test. @@ -249,12 +205,9 @@ class Puppet::Network::Client::Master < Puppet::Network::Client # Just so we can specify that we are "the" instance. def initialize(*args) - Puppet.config.use(:main, :ssl, :puppetd) + Puppet.settings.use(:main, :ssl, :puppetd) super - # This might be nil - @configtime = 0 - self.class.instance = self @running = false @@ -297,7 +250,9 @@ class Puppet::Network::Client::Master < Puppet::Network::Client end # The code that actually runs the configuration. - def run(tags = nil, ignoreschedules = false) + # This just passes any options on to the configuration, + # which accepts :tags and :ignoreschedules. + def run(options = {}) got_lock = false splay Puppet::Util.sync(:puppetrun).synchronize(Sync::EX) do @@ -307,19 +262,20 @@ class Puppet::Network::Client::Master < Puppet::Network::Client else got_lock = true begin - @configtime = thinmark do + duration = thinmark do self.getconfig end rescue => detail Puppet.err "Could not retrieve configuration: %s" % detail end - if defined? @objects and @objects + if defined? @configuration and @configuration + @configuration.retrieval_duration = duration unless @local Puppet.notice "Starting configuration run" end benchmark(:notice, "Finished configuration run") do - self.apply(tags, ignoreschedules) + @configuration.apply(options) end end end @@ -366,9 +322,6 @@ class Puppet::Network::Client::Master < Puppet::Network::Client # Download files from the remote server, returning a list of all # changed files. def self.download(args) - objects = Puppet::Type.type(:component).create( - :name => "#{args[:name]}_collector" - ) hash = { :path => args[:dest], :recurse => true, @@ -383,18 +336,23 @@ class Puppet::Network::Client::Master < Puppet::Network::Client if args[:ignore] hash[:ignore] = args[:ignore].split(/\s+/) end - objects.push Puppet::Type.type(:file).create(hash) + downconfig = Puppet::Node::Configuration.new("downloading") + downconfig.add_resource Puppet::Type.type(:file).create(hash) Puppet.info "Retrieving #{args[:name]}s" noop = Puppet[:noop] Puppet[:noop] = false + files = [] begin - trans = objects.evaluate - trans.ignoretags = true Timeout::timeout(self.timeout) do - trans.evaluate + downconfig.apply do |trans| + trans.changed?.find_all do |resource| + yield resource if block_given? + files << resource[:path] + end + end end rescue Puppet::Error, Timeout::Error => detail if Puppet[:debug] @@ -403,18 +361,10 @@ class Puppet::Network::Client::Master < Puppet::Network::Client Puppet.err "Could not retrieve #{args[:name]}s: %s" % detail end - # Now source all of the changed objects, but only source those - # that are top-level. - files = [] - trans.changed?.find_all do |object| - yield object if block_given? - files << object[:path] - end - trans.cleanup - # Now clean up after ourselves - objects.remove - files + downconfig.clear + + return files ensure # I can't imagine why this is necessary, but apparently at last one person has had problems with noop # being nil here. @@ -427,21 +377,21 @@ class Puppet::Network::Client::Master < Puppet::Network::Client # Retrieve facts from the central server. def self.getfacts - # Clear all existing definitions. - Facter.clear # Download the new facts path = Puppet[:factpath].split(":") files = [] download(:dest => Puppet[:factdest], :source => Puppet[:factsource], - :ignore => Puppet[:factsignore], :name => "fact") do |object| - - next unless path.include?(::File.dirname(object[:path])) + :ignore => Puppet[:factsignore], :name => "fact") do |resource| - files << object[:path] + next unless path.include?(::File.dirname(resource[:path])) + files << resource[:path] end ensure + # Clear all existing definitions. + Facter.clear + # Reload everything. if Facter.respond_to? :loadfacts Facter.loadfacts @@ -461,20 +411,20 @@ class Puppet::Network::Client::Master < Puppet::Network::Client # changed plugins, because Puppet::Type loads plugins on demand. def self.getplugins download(:dest => Puppet[:plugindest], :source => Puppet[:pluginsource], - :ignore => Puppet[:pluginsignore], :name => "plugin") do |object| + :ignore => Puppet[:pluginsignore], :name => "plugin") do |resource| - next if FileTest.directory?(object[:path]) - path = object[:path].sub(Puppet[:plugindest], '').sub(/^\/+/, '') + next if FileTest.directory?(resource[:path]) + path = resource[:path].sub(Puppet[:plugindest], '').sub(/^\/+/, '') unless Puppet::Util::Autoload.loaded?(path) next end begin Puppet.info "Reloading downloaded file %s" % path - load object[:path] + load resource[:path] rescue => detail Puppet.warning "Could not reload downloaded file %s: %s" % - [object[:path], detail] + [resource[:path], detail] end end end @@ -517,42 +467,6 @@ class Puppet::Network::Client::Master < Puppet::Network::Client return timeout end - - # Send off the transaction report. - def report(transaction) - begin - report = transaction.generate_report() - rescue => detail - Puppet.err "Could not generate report: %s" % detail - return - end - - if Puppet[:rrdgraph] == true - report.graph() - end - - if Puppet[:summarize] - puts report.summary - end - - if Puppet[:report] - begin - reportclient().report(report) - rescue => detail - Puppet.err "Reporting failed: %s" % detail - end - end - end - - def reportclient - unless defined? @reportclient - @reportclient = Puppet::Network::Client.report.new( - :Server => Puppet[:reportserver] - ) - end - - @reportclient - end loadfacts() @@ -633,7 +547,7 @@ class Puppet::Network::Client::Master < Puppet::Network::Client Puppet.err "Could not retrieve configuration: %s" % detail unless Puppet[:usecacheonfailure] - @objects = nil + @configuration = nil Puppet.warning "Not using cache on failed configuration" return end diff --git a/lib/puppet/network/handler/ca.rb b/lib/puppet/network/handler/ca.rb index 422b21ae1..052eb5c19 100644 --- a/lib/puppet/network/handler/ca.rb +++ b/lib/puppet/network/handler/ca.rb @@ -60,7 +60,7 @@ class Puppet::Network::Handler end def initialize(hash = {}) - Puppet.config.use(:main, :ssl, :ca) + Puppet.settings.use(:main, :ssl, :ca) if hash.include? :autosign @autosign = hash[:autosign] end diff --git a/lib/puppet/network/handler/configuration.rb b/lib/puppet/network/handler/configuration.rb index 2c72d3d2b..2df1b3ab4 100644 --- a/lib/puppet/network/handler/configuration.rb +++ b/lib/puppet/network/handler/configuration.rb @@ -30,7 +30,7 @@ class Puppet::Network::Handler # Note that this is reasonable, because either their node source should actually # know about the node, or they should be using the ``none`` node source, which # will always return data. - unless node = node_handler.details(key) + unless node = Puppet::Node.search(key) raise Puppet::Error, "Could not find node '%s'" % key end @@ -64,7 +64,7 @@ class Puppet::Network::Handler # Return the configuration version. def version(client = nil, clientip = nil) - if client and node = node_handler.details(client) + if client and node = Puppet::Node.search(client) update_node_check(node) return interpreter.configuration_version(node) else @@ -79,7 +79,7 @@ class Puppet::Network::Handler # Add any extra data necessary to the node. def add_node_data(node) # Merge in our server-side facts, so they can be used during compilation. - node.fact_merge(@server_facts) + node.merge(@server_facts) # Add any specified classes to the node's class list. if classes = @options[:Classes] @@ -159,14 +159,6 @@ class Puppet::Network::Handler @interpreter end - # Create a node handler instance for looking up our nodes. - def node_handler - unless defined?(@node_handler) - @node_handler = Puppet::Network::Handler.handler(:node).create - end - @node_handler - end - # Initialize our server fact hash; we add these to each client, and they # won't change while we're running, so it's safe to cache the values. def set_server_facts diff --git a/lib/puppet/network/handler/facts.rb b/lib/puppet/network/handler/facts.rb deleted file mode 100755 index 4767e8be4..000000000 --- a/lib/puppet/network/handler/facts.rb +++ /dev/null @@ -1,68 +0,0 @@ -require 'yaml' -require 'puppet/util/fact_store' - -class Puppet::Network::Handler - # Receive logs from remote hosts. - class Facts < Handler - desc "An interface for storing and retrieving client facts. Currently only - used internally by Puppet." - - @interface = XMLRPC::Service::Interface.new("facts") { |iface| - iface.add_method("void set(string, string)") - iface.add_method("string get(string)") - iface.add_method("integer store_date(string)") - } - - def initialize(hash = {}) - super - - backend = Puppet[:factstore] - - unless klass = Puppet::Util::FactStore.store(backend) - raise Puppet::Error, "Could not find fact store %s" % backend - end - - @backend = klass.new - end - - # Get the facts from our back end. - def get(node) - if facts = @backend.get(node) - return strip_internal(facts) - else - return nil - end - end - - # Set the facts in the backend. - def set(node, facts) - @backend.set(node, add_internal(facts)) - nil - end - - # Retrieve a client's storage date. - def store_date(node) - if facts = get(node) - facts[:_puppet_timestamp].to_i - else - nil - end - end - - private - - # Add internal data to the facts for storage. - def add_internal(facts) - facts = facts.dup - facts[:_puppet_timestamp] = Time.now - facts - end - - # Strip out that internal data. - def strip_internal(facts) - facts = facts.dup - facts.find_all { |name, value| name.to_s =~ /^_puppet_/ }.each { |name, value| facts.delete(name) } - facts - end - end -end diff --git a/lib/puppet/network/handler/filebucket.rb b/lib/puppet/network/handler/filebucket.rb index bb6a0e6d3..1bf8da854 100755 --- a/lib/puppet/network/handler/filebucket.rb +++ b/lib/puppet/network/handler/filebucket.rb @@ -64,7 +64,7 @@ class Puppet::Network::Handler # :nodoc: end end - Puppet.config.use(:filebucket) + Puppet.settings.use(:filebucket) @name = "Filebucket[#{@path}]" end diff --git a/lib/puppet/network/handler/fileserver.rb b/lib/puppet/network/handler/fileserver.rb index a429412d2..ae0e6553d 100755 --- a/lib/puppet/network/handler/fileserver.rb +++ b/lib/puppet/network/handler/fileserver.rb @@ -243,7 +243,10 @@ class Puppet::Network::Handler # the modules. def modules_mount(module_name, client) # Find our environment, if we have one. - if node = node_handler.details(client || Facter.value("hostname")) + unless hostname = (client || Facter.value("hostname")) + raise ArgumentError, "Could not find hostname" + end + if node = Puppet::Node.find(hostname) env = node.environment else env = nil @@ -258,14 +261,6 @@ class Puppet::Network::Handler end end - # Create a node handler instance for looking up our nodes. - def node_handler - unless defined?(@node_handler) - @node_handler = Puppet::Network::Handler.handler(:node).create - end - @node_handler - end - # Read the configuration file. def readconfig(check = true) return if @noreadconfig @@ -455,7 +450,7 @@ class Puppet::Network::Handler def getfileobject(dir, links) unless FileTest.exists?(dir) - self.notice "File source %s does not exist" % dir + self.debug "File source %s does not exist" % dir return nil end diff --git a/lib/puppet/network/handler/master.rb b/lib/puppet/network/handler/master.rb index c8db277ba..030950c61 100644 --- a/lib/puppet/network/handler/master.rb +++ b/lib/puppet/network/handler/master.rb @@ -74,7 +74,7 @@ class Puppet::Network::Handler client, clientip = clientname(client, clientip, facts) # Pass the facts to the fact handler - fact_handler.set(client, facts) + Puppet::Node::Facts.new(client, facts).save # And get the configuration from the config handler begin @@ -134,13 +134,6 @@ class Puppet::Network::Handler return facts end - def fact_handler - unless defined? @fact_handler - @fact_handler = Puppet::Network::Handler.handler(:facts).new :local => local? - end - @fact_handler - end - # Translate our configuration appropriately for sending back to a client. def translate(config) if local? diff --git a/lib/puppet/network/handler/node.rb b/lib/puppet/network/handler/node.rb deleted file mode 100644 index c6ccc2eb6..000000000 --- a/lib/puppet/network/handler/node.rb +++ /dev/null @@ -1,242 +0,0 @@ -# Created by Luke A. Kanies on 2007-08-13. -# Copyright (c) 2007. All rights reserved. - -require 'puppet/util' -require 'puppet/node' -require 'puppet/util/classgen' -require 'puppet/util/instance_loader' - -# Look up a node, along with all the details about it. -class Puppet::Network::Handler::Node < Puppet::Network::Handler - desc "Retrieve information about nodes." - - # Create a singleton node handler - def self.create - unless @handler - @handler = new - end - @handler - end - - # Add a new node source. - def self.newnode_source(name, options = {}, &block) - name = symbolize(name) - - fact_merge = options[:fact_merge] - mod = genmodule(name, :extend => SourceBase, :hash => instance_hash(:node_source), :block => block) - mod.send(:define_method, :fact_merge?) do - fact_merge - end - mod - end - - # Collect the docs for all of our node sources. - def self.node_source_docs - docs = "" - - # Use this method so they all get loaded - instance_loader(:node_source).loadall - loaded_instances(:node_source).sort { |a,b| a.to_s <=> b.to_s }.each do |name| - mod = self.node_source(name) - docs += "%s\n%s\n" % [name, "-" * name.to_s.length] - - docs += Puppet::Util::Docs.scrub(mod.doc) + "\n\n" - end - - docs - end - - # List each of the node sources. - def self.node_sources - instance_loader(:node_source).loadall - loaded_instances(:node_source) - end - - # Remove a defined node source; basically only used for testing. - def self.rm_node_source(name) - rmclass(name, :hash => instance_hash(:node_source)) - end - - extend Puppet::Util::ClassGen - extend Puppet::Util::InstanceLoader - - # A simple base module we can use for modifying how our node sources work. - module SourceBase - include Puppet::Util::Docs - end - - @interface = XMLRPC::Service::Interface.new("nodes") { |iface| - iface.add_method("string details(key)") - iface.add_method("string parameters(key)") - iface.add_method("string environment(key)") - iface.add_method("string classes(key)") - } - - # Set up autoloading and retrieving of reports. - instance_load :node_source, 'puppet/node_source' - - attr_reader :source - - # Return a given node's classes. - def classes(key) - if node = details(key) - node.classes - else - nil - end - end - - # Return an entire node configuration. This uses the 'nodesearch' method - # defined in the node_source to look for the node. - def details(key, client = nil, clientip = nil) - return nil unless key - if node = cached?(key) - return node - end - facts = node_facts(key) - node = nil - names = node_names(key, facts) - names.each do |name| - name = name.to_s if name.is_a?(Symbol) - if node = nodesearch(name) and @source != "none" - Puppet.info "Found %s in %s" % [name, @source] - break - end - end - - # If they made it this far, we haven't found anything, so look for a - # default node. - unless node or names.include?("default") - if node = nodesearch("default") - Puppet.notice "Using default node for %s" % key - end - end - - if node - node.source = @source - node.names = names - - # Merge the facts into the parameters. - if fact_merge? - node.fact_merge(facts) - end - - cache(node) - - return node - else - return nil - end - end - - # Return a given node's environment. - def environment(key, client = nil, clientip = nil) - if node = details(key) - node.environment - else - nil - end - end - - # Create our node lookup tool. - def initialize(hash = {}) - @source = hash[:Source] || Puppet[:node_source] - - unless mod = self.class.node_source(@source) - raise ArgumentError, "Unknown node source '%s'" % @source - end - - extend(mod) - - super - - # We cache node info for speed - @node_cache = {} - end - - # Try to retrieve a given node's parameters. - def parameters(key, client = nil, clientip = nil) - if node = details(key) - node.parameters - else - nil - end - end - - private - - # Store the node to make things a bit faster. - def cache(node) - @node_cache[node.name] = node - end - - # If the node is cached, return it. - def cached?(name) - # Don't use cache when the filetimeout is set to 0 - return false if [0, "0"].include?(Puppet[:filetimeout]) - - if node = @node_cache[name] and Time.now - node.time < Puppet[:filetimeout] - return node - else - return false - end - end - - # Create/cache a fact handler. - def fact_handler - unless defined?(@fact_handler) - @fact_handler = Puppet::Network::Handler.handler(:facts).new - end - @fact_handler - end - - # Short-hand for creating a new node, so the node sources don't need to - # specify the constant. - def newnode(options) - Puppet::Node.new(options) - end - - # Look up the node facts from our fact handler. - def node_facts(key) - if facts = fact_handler.get(key) - facts - else - {} - end - end - - # Calculate the list of node names we should use for looking - # up our node. - def node_names(key, facts = nil) - facts ||= node_facts(key) - names = [] - - if hostname = facts["hostname"] - unless hostname == key - names << hostname - end - else - hostname = key - end - - if fqdn = facts["fqdn"] - hostname = fqdn - names << fqdn - end - - # Make sure both the fqdn and the short name of the - # host can be used in the manifest - if hostname =~ /\./ - names << hostname.sub(/\..+/,'') - elsif domain = facts['domain'] - names << hostname + "." + domain - end - - # Sort the names inversely by name length. - names.sort! { |a,b| b.length <=> a.length } - - # And make sure the key is first, since that's the most - # likely usage. - ([key] + names).uniq - end -end diff --git a/lib/puppet/network/handler/report.rb b/lib/puppet/network/handler/report.rb index 81aee6a3c..62e9cfdec 100755 --- a/lib/puppet/network/handler/report.rb +++ b/lib/puppet/network/handler/report.rb @@ -71,8 +71,8 @@ class Puppet::Network::Handler def initialize(*args) super - Puppet.config.use(:reporting) - Puppet.config.use(:metrics) + Puppet.settings.use(:reporting) + Puppet.settings.use(:metrics) end # Accept a report from a client. diff --git a/lib/puppet/network/handler/resource.rb b/lib/puppet/network/handler/resource.rb index ac29dce53..7709b85fe 100755 --- a/lib/puppet/network/handler/resource.rb +++ b/lib/puppet/network/handler/resource.rb @@ -39,21 +39,14 @@ class Puppet::Network::Handler end end - component = bucket.to_type - - # Create a client, but specify the remote machine as the server - # because the class requires it, even though it's unused - client = Puppet::Network::Client.client(:Master).new(:Master => client||"localhost") - - # Set the objects - client.objects = component + config = bucket.to_configuration # And then apply the configuration. This way we're reusing all # the code in there. It should probably just be separated out, though. - transaction = client.apply + transaction = config.apply # And then clean up - component.remove + config.clear(true) # It'd be nice to return some kind of report, but... at this point # we have no such facility. diff --git a/lib/puppet/network/handler/runner.rb b/lib/puppet/network/handler/runner.rb index c41e83608..4b9ccab75 100755 --- a/lib/puppet/network/handler/runner.rb +++ b/lib/puppet/network/handler/runner.rb @@ -50,10 +50,10 @@ class Puppet::Network::Handler # And then we need to tell it to run, with this extra info. if fg - master.run(tags, ignoreschedules) + master.run(:tags => tags, :ignoreschedules => ignoreschedules) else Puppet.newthread do - master.run(tags, ignoreschedules) + master.run(:tags => tags, :ignoreschedules => ignoreschedules) end end diff --git a/lib/puppet/network/server/webrick.rb b/lib/puppet/network/server/webrick.rb index 3af0cfd3f..f24411ab3 100644 --- a/lib/puppet/network/server/webrick.rb +++ b/lib/puppet/network/server/webrick.rb @@ -48,7 +48,7 @@ module Puppet # yuck; separate http logs file = nil - Puppet.config.use(:main, :ssl, Puppet[:name]) + Puppet.settings.use(:main, :ssl, Puppet[:name]) if Puppet[:name] == "puppetmasterd" file = Puppet[:masterhttplog] else diff --git a/lib/puppet/node.rb b/lib/puppet/node.rb index 2d3ac712e..d71bd507e 100644 --- a/lib/puppet/node.rb +++ b/lib/puppet/node.rb @@ -1,10 +1,27 @@ +require 'puppet/indirector' + # A simplistic class for managing the node information itself. class Puppet::Node + require 'puppet/node/facts' + + # Set up indirection, so that nodes can be looked for in + # the node sources. + extend Puppet::Indirector + + # Use the node source as the indirection terminus. + indirects :node + + # Add the node-searching methods. This is what people will actually + # interact with that will find the node with the list of names or + # will search for a default node. + require 'puppet/node/searching' + extend Puppet::Node::Searching + attr_accessor :name, :classes, :parameters, :source, :ipaddress, :names attr_reader :time attr_writer :environment - # Do not return environments tha are empty string, and use + # Do not return environments that are the empty string, and use # explicitly set environments, then facts, then a central env # value. def environment @@ -21,6 +38,9 @@ class Puppet::Node end def initialize(name, options = {}) + unless name + raise ArgumentError, "Node names cannot be nil" + end @name = name # Provide a default value. @@ -52,9 +72,15 @@ class Puppet::Node end # Merge the node facts with parameters from the node source. - # This is only called if the node source has 'fact_merge' set to true. - def fact_merge(facts) - facts.each do |name, value| + def fact_merge + if facts = Puppet::Node::Facts.find(name) + merge(facts.values) + end + end + + # Merge any random parameters into our parameter list. + def merge(params) + params.each do |name, value| @parameters[name] = value unless @parameters.include?(name) end end diff --git a/lib/puppet/node/configuration.rb b/lib/puppet/node/configuration.rb index 4f93fdbe5..53f63d003 100644 --- a/lib/puppet/node/configuration.rb +++ b/lib/puppet/node/configuration.rb @@ -1,13 +1,34 @@ +require 'puppet/indirector' require 'puppet/external/gratr/digraph' # This class models a node configuration. It is the thing # meant to be passed from server to client, and it contains all # of the information in the configuration, including the resources # and the relationships between them. -class Puppet::Node::Configuration < GRATR::Digraph - attr_accessor :name, :version +class Puppet::Node::Configuration < Puppet::PGraph + extend Puppet::Indirector + indirects :configuration + + # The host name this is a configuration for. + attr_accessor :name + + # The configuration version. Used for testing whether a configuration + # is up to date. + attr_accessor :version + + # How long this configuration took to retrieve. Used for reporting stats. + attr_accessor :retrieval_duration + + # How we should extract the configuration for sending to the client. attr_reader :extraction_format + # Whether this is a host configuration, which behaves very differently. + # In particular, reports are sent, graphs are made, and state is + # stored in the state database. If this is set incorrectly, then you often + # end up in infinite loops, because configurations are used to make things + # that the host configuration needs. + attr_accessor :host_config + # Add classes to our class list. def add_class(*classes) classes.each do |klass| @@ -18,10 +39,133 @@ class Puppet::Node::Configuration < GRATR::Digraph tag(*classes) end + # Add one or more resources to our graph and to our resource table. + def add_resource(*resources) + resources.each do |resource| + unless resource.respond_to?(:ref) + raise ArgumentError, "Can only add objects that respond to :ref" + end + + ref = resource.ref + if @resource_table.include?(ref) + raise ArgumentError, "Resource %s is already defined" % ref + else + @resource_table[ref] = resource + end + resource.configuration = self + add_vertex!(resource) + end + end + + # Apply our configuration to the local host. Valid options + # are: + # :tags - set the tags that restrict what resources run + # during the transaction + # :ignoreschedules - tell the transaction to ignore schedules + # when determining the resources to run + def apply(options = {}) + @applying = true + + Puppet::Util::Storage.load if host_config? + transaction = Puppet::Transaction.new(self) + + transaction.tags = options[:tags] if options[:tags] + transaction.ignoreschedules = true if options[:ignoreschedules] + + transaction.addtimes :config_retrieval => @retrieval_duration + + begin + transaction.evaluate + rescue Puppet::Error => detail + Puppet.err "Could not apply complete configuration: %s" % detail + rescue => detail + if Puppet[:trace] + puts detail.backtrace + end + Puppet.err "Got an uncaught exception of type %s: %s" % [detail.class, detail] + ensure + # Don't try to store state unless we're a host config + # too recursive. + Puppet::Util::Storage.store if host_config? + end + + if block_given? + yield transaction + end + + if host_config and (Puppet[:report] or Puppet[:summarize]) + transaction.send_report + end + + return transaction + ensure + @applying = false + cleanup() + if defined? transaction and transaction + transaction.cleanup + end + end + + # Are we in the middle of applying the configuration? + def applying? + @applying + end + + def clear(remove_resources = true) + super() + # We have to do this so that the resources clean themselves up. + @resource_table.values.each { |resource| resource.remove } if remove_resources + @resource_table.clear + + if defined?(@relationship_graph) and @relationship_graph + @relationship_graph.clear(false) + @relationship_graph = nil + end + end + def classes @classes.dup end + # Create an implicit resource, meaning that it will lose out + # to any explicitly defined resources. This method often returns + # nil. + # The quirk of this method is that it's not possible to create + # an implicit resource before an explicit resource of the same name, + # because all explicit resources are created before any generate() + # methods are called on the individual resources. Thus, this + # method can safely just check if an explicit resource already exists + # and toss this implicit resource if so. + def create_implicit_resource(type, options) + unless options.include?(:implicit) + options[:implicit] = true + end + + # This will return nil if an equivalent explicit resource already exists. + # When resource classes no longer retain references to resource instances, + # this will need to be modified to catch that conflict and discard + # implicit resources. + if resource = create_resource(type, options) + resource.implicit = true + + return resource + else + return nil + end + end + + # Create a new resource and register it in the configuration. + def create_resource(type, options) + unless klass = Puppet::Type.type(type) + raise ArgumentError, "Unknown resource type %s" % type + end + return unless resource = klass.create(options) + + @transient_resources << resource if applying? + add_resource(resource) + resource + end + # Make sure we support the requested extraction format. def extraction_format=(value) unless respond_to?("extract_to_%s" % value) @@ -94,12 +238,101 @@ class Puppet::Node::Configuration < GRATR::Digraph return result end - def initialize(name) + # Make sure all of our resources are "finished". + def finalize + @resource_table.values.each { |resource| resource.finish } + + write_graph(:resources) + end + + def host_config? + host_config || false + end + + def initialize(name = nil) super() - @name = name + @name = name if name @extraction_format ||= :transportable @tags = [] @classes = [] + @resource_table = {} + @transient_resources = [] + @applying = false + @relationship_graph = nil + + if block_given? + yield(self) + finalize() + end + end + + # Create a graph of all of the relationships in our configuration. + def relationship_graph + unless defined? @relationship_graph and @relationship_graph + # It's important that we assign the graph immediately, because + # the debug messages below use the relationships in the + # relationship graph to determine the path to the resources + # spitting out the messages. If this is not set, + # then we get into an infinite loop. + @relationship_graph = Puppet::Node::Configuration.new + @relationship_graph.host_config = host_config? + + # First create the dependency graph + self.vertices.each do |vertex| + @relationship_graph.add_vertex! vertex + vertex.builddepends.each do |edge| + @relationship_graph.add_edge!(edge) + end + end + + # Lastly, add in any autorequires + @relationship_graph.vertices.each do |vertex| + vertex.autorequire.each do |edge| + unless @relationship_graph.edge?(edge) + unless @relationship_graph.edge?(edge.target, edge.source) + vertex.debug "Autorequiring %s" % [edge.source] + @relationship_graph.add_edge!(edge) + else + vertex.debug "Skipping automatic relationship with %s" % (edge.source == vertex ? edge.target : edge.source) + end + end + end + end + + @relationship_graph.write_graph(:relationships) + + # Then splice in the container information + @relationship_graph.splice!(self, Puppet::Type::Component) + + @relationship_graph.write_graph(:expanded_relationships) + end + @relationship_graph + end + + # Remove the resource from our configuration. Notice that we also call + # 'remove' on the resource, at least until resource classes no longer maintain + # references to the resource instances. + def remove_resource(*resources) + resources.each do |resource| + @resource_table.delete(resource.ref) if @resource_table.include?(resource.ref) + remove_vertex!(resource) if vertex?(resource) + @relationship_graph.remove_vertex!(resource) if @relationship_graph and @relationship_graph.vertex?(resource) + resource.remove + end + end + + # Look a resource up by its reference (e.g., File[/etc/passwd]). + def resource(type, title = nil) + if title + ref = "%s[%s]" % [type.to_s.capitalize, title] + else + ref = type + end + if resource = @resource_table[ref] + return resource + elsif defined?(@relationship_graph) and @relationship_graph + @relationship_graph.resource(ref) + end end # Add a tag. @@ -120,4 +353,29 @@ class Puppet::Node::Configuration < GRATR::Digraph def tags @tags.dup end + + # Produce the graph files if requested. + def write_graph(name) + # We only want to graph the main host configuration. + return unless host_config? + + return unless Puppet[:graph] + + Puppet.settings.use(:graphing) + + file = File.join(Puppet[:graphdir], "%s.dot" % name.to_s) + File.open(file, "w") { |f| + f.puts to_dot("name" => name.to_s.capitalize) + } + end + + private + + def cleanup + unless @transient_resources.empty? + remove_resource(*@transient_resources) + @transient_resources.clear + @relationship_graph = nil + end + end end diff --git a/lib/puppet/node/facts.rb b/lib/puppet/node/facts.rb new file mode 100755 index 000000000..a2e6d9c04 --- /dev/null +++ b/lib/puppet/node/facts.rb @@ -0,0 +1,36 @@ +require 'puppet/node' +require 'puppet/indirector' + +# Manage a given node's facts. This either accepts facts and stores them, or +# returns facts for a given node. +class Puppet::Node::Facts + # Set up indirection, so that nodes can be looked for in + # the node sources. + extend Puppet::Indirector + + # Use the node source as the indirection terminus. + indirects :facts + + attr_accessor :name, :values + + def initialize(name, values = {}) + @name = name + @values = values + + add_internal + end + + private + + # Add internal data to the facts for storage. + def add_internal + self.values[:_timestamp] = Time.now + end + + # Strip out that internal data. + def strip_internal + newvals = values.dup + newvals.find_all { |name, value| name.to_s =~ /^_/ }.each { |name, value| newvals.delete(name) } + newvals + end +end diff --git a/lib/puppet/node/searching.rb b/lib/puppet/node/searching.rb new file mode 100644 index 000000000..10dd588ab --- /dev/null +++ b/lib/puppet/node/searching.rb @@ -0,0 +1,106 @@ +# The module that handles actually searching for nodes. This is only included +# in the Node class, but it's completely stand-alone functionality, so it's +# worth making it a separate module to simplify testing. +module Puppet::Node::Searching + # Retrieve a node from the node source, with some additional munging + # thrown in for kicks. + def search(key) + return nil unless key + if node = cached?(key) + return node + end + facts = node_facts(key) + node = nil + names = node_names(key, facts) + names.each do |name| + name = name.to_s if name.is_a?(Symbol) + if node = find(name) + #Puppet.info "Found %s in %s" % [name, @source] + break + end + end + + # If they made it this far, we haven't found anything, so look for a + # default node. + unless node or names.include?("default") + if node = find("default") + Puppet.notice "Using default node for %s" % key + end + end + + if node + node.names = names + + cache(node) + + return node + else + return nil + end + end + + private + + # Store the node to make things a bit faster. + def cache(node) + @node_cache ||= {} + @node_cache[node.name] = node + end + + # If the node is cached, return it. + def cached?(name) + # Don't use cache when the filetimeout is set to 0 + return false if [0, "0"].include?(Puppet[:filetimeout]) + @node_cache ||= {} + + if node = @node_cache[name] and Time.now - node.time < Puppet[:filetimeout] + return node + else + return false + end + end + + # Look up the node facts from our fact handler. + def node_facts(key) + if facts = Puppet::Node::Facts.find(key) + facts.values + else + {} + end + end + + # Calculate the list of node names we should use for looking + # up our node. + def node_names(key, facts = nil) + facts ||= node_facts(key) + names = [] + + if hostname = facts["hostname"] + unless hostname == key + names << hostname + end + else + hostname = key + end + + if fqdn = facts["fqdn"] + hostname = fqdn + names << fqdn + end + + # Make sure both the fqdn and the short name of the + # host can be used in the manifest + if hostname =~ /\./ + names << hostname.sub(/\..+/,'') + elsif domain = facts['domain'] + names << hostname + "." + domain + end + + # Sort the names inversely by name length. + names.sort! { |a,b| b.length <=> a.length } + + # And make sure the key is first, since that's the most + # likely usage. + ([key] + names).uniq + end +end diff --git a/lib/puppet/parser/ast/component.rb b/lib/puppet/parser/ast/component.rb deleted file mode 100644 index 1d7fe9cdd..000000000 --- a/lib/puppet/parser/ast/component.rb +++ /dev/null @@ -1,224 +0,0 @@ -require 'puppet/parser/ast/branch' - -class Puppet::Parser::AST - # Evaluate the stored parse tree for a given component. This will - # receive the arguments passed to the component and also the type and - # name of the component. - class Component < AST::Branch - include Puppet::Util - include Puppet::Util::Warnings - include Puppet::Util::MethodHelper - class << self - attr_accessor :name - end - - # The class name - @name = :definition - - attr_accessor :classname, :arguments, :code, :scope, :keyword - attr_accessor :exported, :namespace, :parser, :virtual - - # These are retrieved when looking up the superclass - attr_accessor :name - - attr_reader :parentclass - - def child_of?(klass) - false - end - - def evaluate_resource(hash) - origscope = hash[:scope] - title = hash[:title] - args = symbolize_options(hash[:arguments] || {}) - - name = args[:name] || title - - exported = hash[:exported] - virtual = hash[:virtual] - - pscope = origscope - scope = subscope(pscope, title) - - if virtual or origscope.virtual? - scope.virtual = true - end - - if exported or origscope.exported? - scope.exported = true - end - - # Additionally, add a tag for whatever kind of class - # we are - if @classname != "" and ! @classname.nil? - @classname.split(/::/).each { |tag| scope.tag(tag) } - end - - [name, title].each do |str| - unless str.nil? or str =~ /[^\w]/ or str == "" - scope.tag(str) - end - end - - # define all of the arguments in our local scope - if self.arguments - # Verify that all required arguments are either present or - # have been provided with defaults. - self.arguments.each { |arg, default| - arg = symbolize(arg) - unless args.include?(arg) - if defined? default and ! default.nil? - default = default.safeevaluate :scope => scope - args[arg] = default - #Puppet.debug "Got default %s for %s in %s" % - # [default.inspect, arg.inspect, @name.inspect] - else - parsefail "Must pass %s to %s of type %s" % - [arg,title,@classname] - end - end - } - end - - # Set each of the provided arguments as variables in the - # component's scope. - args.each { |arg,value| - unless validattr?(arg) - parsefail "%s does not accept attribute %s" % [@classname, arg] - end - - exceptwrap do - scope.setvar(arg.to_s,args[arg]) - end - } - - unless args.include? :title - scope.setvar("title",title) - end - - unless args.include? :name - scope.setvar("name",name) - end - - if self.code - return self.code.safeevaluate(:scope => scope) - else - return nil - end - end - - def initialize(hash = {}) - @arguments = nil - @parentclass = nil - super - - # Convert the arguments to a hash for ease of later use. - if @arguments - unless @arguments.is_a? Array - @arguments = [@arguments] - end - oldargs = @arguments - @arguments = {} - oldargs.each do |arg, val| - @arguments[arg] = val - end - else - @arguments = {} - end - - # Deal with metaparams in the argument list. - @arguments.each do |arg, defvalue| - next unless Puppet::Type.metaparamclass(arg) - if defvalue - warnonce "%s is a metaparam; this value will inherit to all contained elements" % arg - else - raise Puppet::ParseError, - "%s is a metaparameter; please choose another name" % - name - end - end - end - - def find_parentclass - @parser.findclass(namespace, parentclass) - end - - # Set our parent class, with a little check to avoid some potential - # weirdness. - def parentclass=(name) - if name == self.classname - parsefail "Parent classes must have dissimilar names" - end - - @parentclass = name - end - - # Hunt down our class object. - def parentobj - if @parentclass - # Cache our result, since it should never change. - unless defined?(@parentobj) - unless tmp = find_parentclass - parsefail "Could not find %s %s" % [self.class.name, @parentclass] - end - - if tmp == self - parsefail "Parent classes must have dissimilar names" - end - - @parentobj = tmp - end - @parentobj - else - nil - end - end - - # Create a new subscope in which to evaluate our code. - def subscope(scope, name = nil) - args = { - :type => self.classname, - :keyword => self.keyword, - :namespace => self.namespace - } - - args[:name] = name if name - scope = scope.newscope(args) - scope.source = self - - return scope - end - - def to_s - classname - end - - # Check whether a given argument is valid. Searches up through - # any parent classes that might exist. - def validattr?(param) - param = param.to_s - - if @arguments.include?(param) - # It's a valid arg for us - return true - elsif param == "name" - return true -# elsif defined? @parentclass and @parentclass -# # Else, check any existing parent -# if parent = @scope.lookuptype(@parentclass) and parent != [] -# return parent.validarg?(param) -# elsif builtin = Puppet::Type.type(@parentclass) -# return builtin.validattr?(param) -# else -# raise Puppet::Error, "Could not find parent class %s" % -# @parentclass -# end - elsif Puppet::Type.metaparam?(param) - return true - else - # Or just return false - return false - end - end - end -end diff --git a/lib/puppet/parser/interpreter.rb b/lib/puppet/parser/interpreter.rb index a4ea26572..81867c84b 100644 --- a/lib/puppet/parser/interpreter.rb +++ b/lib/puppet/parser/interpreter.rb @@ -68,7 +68,7 @@ class Puppet::Parser::Interpreter elsif self.file parser.file = self.file else - file = Puppet.config.value(:manifest, environment) + file = Puppet.settings.value(:manifest, environment) parser.file = file end parser.parse diff --git a/lib/puppet/pgraph.rb b/lib/puppet/pgraph.rb index 07bdff4bb..2399d1651 100644 --- a/lib/puppet/pgraph.rb +++ b/lib/puppet/pgraph.rb @@ -84,7 +84,7 @@ class Puppet::PGraph < GRATR::Digraph def matching_edges(events, base = nil) events.collect do |event| source = base || event.source - + unless vertex?(source) Puppet.warning "Got an event from invalid vertex %s" % source.ref next @@ -95,7 +95,7 @@ class Puppet::PGraph < GRATR::Digraph adjacent(source, :direction => :out, :type => :edges).find_all do |edge| edge.match?(event.event) end - end.flatten + end.compact.flatten end # Take container information from another graph and use it @@ -209,5 +209,3 @@ class Puppet::PGraph < GRATR::Digraph end end end - -# $Id$ diff --git a/lib/puppet/provider/maillist/mailman.rb b/lib/puppet/provider/maillist/mailman.rb index b556eecd7..903689178 100755 --- a/lib/puppet/provider/maillist/mailman.rb +++ b/lib/puppet/provider/maillist/mailman.rb @@ -101,9 +101,9 @@ Puppet::Type.type(:maillist).provide(:mailman) do # Pull the current state of the list from the full list. We're # getting some double entendre here.... def query - provider.class.instances.each do |list| + self.class.instances.each do |list| if list.name == self.name or list.name.downcase == self.name - return list.property_hash + return list.properties end end nil diff --git a/lib/puppet/provider/mount.rb b/lib/puppet/provider/mount.rb index 446ed641c..6a05d9826 100644 --- a/lib/puppet/provider/mount.rb +++ b/lib/puppet/provider/mount.rb @@ -50,5 +50,3 @@ module Puppet::Provider::Mount end end end - -# $Id$ diff --git a/lib/puppet/rails.rb b/lib/puppet/rails.rb index 670c622eb..53dafce79 100644 --- a/lib/puppet/rails.rb +++ b/lib/puppet/rails.rb @@ -9,7 +9,7 @@ module Puppet::Rails # This global init does not work for testing, because we remove # the state dir on every test. unless ActiveRecord::Base.connected? - Puppet.config.use(:main, :rails, :puppetmasterd) + Puppet.settings.use(:main, :rails, :puppetmasterd) ActiveRecord::Base.logger = Logger.new(Puppet[:railslog]) begin @@ -103,7 +103,7 @@ module Puppet::Rails raise Puppet::DevError, "No activerecord, cannot init Puppet::Rails" end - Puppet.config.use(:puppetmasterd, :rails) + Puppet.settings.use(:puppetmasterd, :rails) begin ActiveRecord::Base.establish_connection(database_arguments()) diff --git a/lib/puppet/reference/configuration.rb b/lib/puppet/reference/configuration.rb index 65fdbeb7d..4b09002b8 100644 --- a/lib/puppet/reference/configuration.rb +++ b/lib/puppet/reference/configuration.rb @@ -1,6 +1,6 @@ config = Puppet::Util::Reference.newreference(:configuration, :depth => 1, :doc => "A reference for all configuration parameters") do docs = {} - Puppet.config.each do |name, object| + Puppet.settings.each do |name, object| docs[name] = object end diff --git a/lib/puppet/reports/rrdgraph.rb b/lib/puppet/reports/rrdgraph.rb index 7209a8401..1516d331a 100644 --- a/lib/puppet/reports/rrdgraph.rb +++ b/lib/puppet/reports/rrdgraph.rb @@ -102,7 +102,7 @@ Puppet::Network::Handler.report.newreport(:rrdgraph) do unless File.directory?(hostdir) and FileTest.writable?(hostdir) # Some hackishness to create the dir with all of the right modes and ownership - config = Puppet::Util::Config.new + config = Puppet::Util::Settings.new config.setdefaults(:reports, :hostdir => {:default => hostdir, :owner => Puppet[:user], :mode => 0755, :group => Puppet[:group], :desc => "eh"}) # This creates the dir. diff --git a/lib/puppet/reports/store.rb b/lib/puppet/reports/store.rb index 8d6e11379..389de4e6e 100644 --- a/lib/puppet/reports/store.rb +++ b/lib/puppet/reports/store.rb @@ -1,7 +1,7 @@ require 'puppet' Puppet::Network::Handler.report.newreport(:store, :useyaml => true) do - Puppet.config.use(:reporting) + Puppet.settings.use(:reporting) desc "Store the yaml report on disk. Each host sends its report as a YAML dump and this just stores the file on disk, in the ``reportdir`` directory. @@ -11,9 +11,9 @@ Puppet::Network::Handler.report.newreport(:store, :useyaml => true) do default report)." def mkclientdir(client, dir) - config = Puppet::Util::Config.new + config = Puppet::Util::Settings.new config.setdefaults("reportclient-#{client}", - "clientdir-#{client}" => { :default => dir, + "client-#{client}-dir" => { :default => dir, :mode => 0750, :desc => "Client dir for %s" % client, :owner => Puppet[:user], diff --git a/lib/puppet/reports/tagmail.rb b/lib/puppet/reports/tagmail.rb index b62a6d2d3..8c65524ee 100644 --- a/lib/puppet/reports/tagmail.rb +++ b/lib/puppet/reports/tagmail.rb @@ -31,7 +31,7 @@ Puppet::Network::Handler.report.newreport(:tagmail) do " - Puppet.config.use(:tagmail) + Puppet.settings.use(:tagmail) # Find all matching messages. def match(taglists) diff --git a/lib/puppet/sslcertificates/ca.rb b/lib/puppet/sslcertificates/ca.rb index 5e355f7fe..fd416dd5e 100644 --- a/lib/puppet/sslcertificates/ca.rb +++ b/lib/puppet/sslcertificates/ca.rb @@ -53,7 +53,7 @@ class Puppet::SSLCertificates::CA end def initialize(hash = {}) - Puppet.config.use(:main, :ca, :ssl) + Puppet.settings.use(:main, :ca, :ssl) self.setconfig(hash) if Puppet[:capass] @@ -73,7 +73,7 @@ class Puppet::SSLCertificates::CA self.getcert init_crl unless FileTest.exists?(@config[:serial]) - Puppet.config.write(:serial) do |f| + Puppet.settings.write(:serial) do |f| f << "%04X" % 1 end end @@ -85,7 +85,7 @@ class Puppet::SSLCertificates::CA 20.times { pass += (rand(74) + 48).chr } begin - Puppet.config.write(:capass) { |f| f.print pass } + Puppet.settings.write(:capass) { |f| f.print pass } rescue Errno::EACCES => detail raise Puppet::Error, detail.to_s end @@ -163,10 +163,10 @@ class Puppet::SSLCertificates::CA Puppet::Util::SUIDManager.asuser(Puppet[:user], Puppet[:group]) do @cert = cert.mkselfsigned end - Puppet.config.write(:cacert) do |f| + Puppet.settings.write(:cacert) do |f| f.puts @cert.to_pem end - Puppet.config.write(:capub) do |f| + Puppet.settings.write(:capub) do |f| f.puts @cert.public_key end return cert @@ -201,7 +201,7 @@ class Puppet::SSLCertificates::CA # Take the Puppet config and store it locally. def setconfig(hash) @config = {} - Puppet.config.params("ca").each { |param| + Puppet.settings.params("ca").each { |param| param = param.intern if param.is_a? String if hash.include?(param) @config[param] = hash[param] @@ -303,7 +303,7 @@ class Puppet::SSLCertificates::CA raise Puppet::Error, "Certificate request for %s already exists" % host end - Puppet.config.writesub(:csrdir, csrfile) do |f| + Puppet.settings.writesub(:csrdir, csrfile) do |f| f.print csr.to_pem end end @@ -319,7 +319,7 @@ class Puppet::SSLCertificates::CA end Puppet::SSLCertificates::Inventory::add(cert) - Puppet.config.writesub(:signeddir, certfile) do |f| + Puppet.settings.writesub(:signeddir, certfile) do |f| f.print cert.to_pem end end @@ -389,7 +389,7 @@ class Puppet::SSLCertificates::CA @crl.next_update = now + 5 * 365*24*60*60 sign_with_key(@crl) - Puppet.config.write(:cacrl) do |f| + Puppet.settings.write(:cacrl) do |f| f.puts @crl.to_pem end end diff --git a/lib/puppet/sslcertificates/inventory.rb b/lib/puppet/sslcertificates/inventory.rb index d475c1c86..14d2155a8 100644 --- a/lib/puppet/sslcertificates/inventory.rb +++ b/lib/puppet/sslcertificates/inventory.rb @@ -11,7 +11,7 @@ module Puppet::SSLCertificates inited = false end - Puppet.config.write(:cert_inventory, "a") do |f| + Puppet.settings.write(:cert_inventory, "a") do |f| f.puts((inited ? nil : self.init).to_s + format(cert)) end end diff --git a/lib/puppet/sslcertificates/support.rb b/lib/puppet/sslcertificates/support.rb index e37a52038..c8d2e846b 100644 --- a/lib/puppet/sslcertificates/support.rb +++ b/lib/puppet/sslcertificates/support.rb @@ -44,7 +44,7 @@ module Puppet::SSLCertificates::Support unless instance_variable_get(var) unless cert = send(reader) cert = send(maker) - Puppet.config.write(param) { |f| f.puts cert.to_pem } + Puppet.settings.write(param) { |f| f.puts cert.to_pem } end instance_variable_set(var, cert) end @@ -59,7 +59,7 @@ module Puppet::SSLCertificates::Support # Our key meta programming can only handle one file, so we have # to separately write out the public key. - Puppet.config.write(:hostpubkey) do |f| + Puppet.settings.write(:hostpubkey) do |f| f.print key.public_key.to_pem end return key @@ -104,8 +104,8 @@ module Puppet::SSLCertificates::Support if cert.nil? or cert == "" return nil end - Puppet.config.write(:hostcert) do |f| f.print cert end - Puppet.config.write(:localcacert) do |f| f.print cacert end + Puppet.settings.write(:hostcert) do |f| f.print cert end + Puppet.settings.write(:localcacert) do |f| f.print cacert end #File.open(@certfile, "w", 0644) { |f| f.print cert } #File.open(@cacertfile, "w", 0644) { |f| f.print cacert } begin diff --git a/lib/puppet/transaction.rb b/lib/puppet/transaction.rb index 46bac3058..4e2ab702f 100644 --- a/lib/puppet/transaction.rb +++ b/lib/puppet/transaction.rb @@ -6,10 +6,14 @@ require 'puppet/propertychange' module Puppet class Transaction - attr_accessor :component, :resources, :ignoreschedules, :ignoretags - attr_accessor :relgraph, :sorted_resources, :configurator + attr_accessor :component, :configuration, :ignoreschedules + attr_accessor :sorted_resources, :configurator + # The report, once generated. attr_reader :report + + # The list of events generated in this transaction. + attr_reader :events attr_writer :tags @@ -22,13 +26,14 @@ class Transaction end end - # Check to see if we should actually allow deleition. + # Check to see if we should actually allow processing, but this really only + # matters when a resource is getting deleted. def allow_processing?(resource, changes) # If a resource is going to be deleted but it still has # dependencies, then don't delete it unless it's implicit or the # dependency is itself being deleted. if resource.purging? and resource.deleting? - if deps = @relgraph.dependents(resource) and ! deps.empty? and deps.detect { |d| ! d.deleting? } + if deps = relationship_graph.dependents(resource) and ! deps.empty? and deps.detect { |d| ! d.deleting? } resource.warning "%s still depend%s on me -- not purging" % [deps.collect { |r| r.ref }.join(","), deps.length > 1 ? "":"s"] return false @@ -129,6 +134,9 @@ class Transaction # Find all of the changed resources. def changed? @changes.find_all { |change| change.changed }.collect { |change| + unless change.property.resource + raise "No resource for %s" % change.inspect + end change.property.resource }.uniq end @@ -137,14 +145,8 @@ class Transaction # contained resources might never get cleaned up. def cleanup if defined? @generated - @generated.each do |resource| - resource.remove - end + relationship_graph.remove_resource(*@generated) end - if defined? @relgraph - @relgraph.clear - end - @resources.clear end # Copy an important relationships from the parent to the newly-generated @@ -158,10 +160,11 @@ class Transaction else edge = [resource, gen_child] end - unless @relgraph.edge?(edge[1], edge[0]) - @relgraph.add_edge!(*edge) + relationship_graph.add_resource(gen_child) unless relationship_graph.resource(gen_child.ref) + + unless relationship_graph.edge?(edge[1], edge[0]) + relationship_graph.add_edge!(*edge) else - @relgraph.add_vertex!(gen_child) resource.debug "Skipping automatic relationship to %s" % gen_child end end @@ -192,7 +195,8 @@ class Transaction children.each do |child| child.finish # Make sure that the vertex is in the relationship graph. - @relgraph.add_vertex!(child) + relationship_graph.add_resource(child) unless relationship_graph.resource(child.ref) + child.configuration = relationship_graph end @generated += children return children @@ -265,9 +269,12 @@ class Transaction # Collect the targets of any subscriptions to those events. We pass # the parent resource in so it will override the source in the events, # since eval_generated children can't have direct relationships. - @relgraph.matching_edges(events, resource).each do |edge| - edge = edge.dup - label = edge.label + relationship_graph.matching_edges(events, resource).each do |orig_edge| + # We have to dup the label here, else we modify the original edge label, + # which affects whether a given event will match on the next run, which is, + # of course, bad. + edge = orig_edge.class.new(orig_edge.source, orig_edge.target) + label = orig_edge.label.dup label[:event] = events.collect { |e| e.event } edge.label = label set_trigger(edge) @@ -283,8 +290,6 @@ class Transaction def evaluate @count = 0 - graph(@resources, :resources) - # Start logging. Puppet::Util::Log.newdestination(@report) @@ -314,6 +319,7 @@ class Transaction Puppet.debug "Finishing transaction %s with %s changes" % [self.object_id, @count] + @events = allevents allevents end @@ -333,7 +339,7 @@ class Transaction # enough to check the immediate dependencies, which is why we use # a tree from the reversed graph. skip = false - deps = @relgraph.dependencies(resource) + deps = relationship_graph.dependencies(resource) deps.each do |dep| if fails = failed?(dep) resource.notice "Dependency %s[%s] has %s failures" % @@ -347,7 +353,7 @@ class Transaction # Collect any dynamically generated resources. def generate - list = @resources.vertices + list = @configuration.vertices # Store a list of all generated resources, so that we can clean them up # after the transaction closes. @@ -369,7 +375,8 @@ class Transaction end made.uniq! made.each do |res| - @resources.add_vertex!(res) + @configuration.add_resource(res) + res.configuration = configuration newlist << res @generated << res res.finish @@ -410,32 +417,24 @@ class Transaction return @report end - # Produce the graph files if requested. - def graph(gr, name) - # We don't want to graph the configuration process. - return if self.configurator - - return unless Puppet[:graph] - - Puppet.config.use(:graphing) - - file = File.join(Puppet[:graphdir], "%s.dot" % name.to_s) - File.open(file, "w") { |f| - f.puts gr.to_dot("name" => name.to_s.capitalize) - } + # Should we ignore tags? + def ignore_tags? + ! @configuration.host_config? end # this should only be called by a Puppet::Type::Component resource now # and it should only receive an array def initialize(resources) - if resources.is_a?(Puppet::PGraph) - @resources = resources + if resources.is_a?(Puppet::Node::Configuration) + @configuration = resources + elsif resources.is_a?(Puppet::PGraph) + raise "Transactions should get configurations now, not PGraph" else - @resources = resources.to_graph + raise "Transactions require configurations" end @resourcemetrics = { - :total => @resources.vertices.length, + :total => @configuration.vertices.length, :out_of_sync => 0, # The number of resources that had changes :applied => 0, # The number of resources fixed :skipped => 0, # The number of resources skipped @@ -474,7 +473,7 @@ class Transaction # types, just providers. def prefetch prefetchers = {} - @resources.each do |resource| + @configuration.each do |resource| if provider = resource.provider and provider.class.respond_to?(:prefetch) prefetchers[provider.class] ||= {} prefetchers[provider.class][resource.title] = resource @@ -501,48 +500,49 @@ class Transaction # Now add any dynamically generated resources generate() - - # Create a relationship graph from our resource graph - @relgraph = relationship_graph # This will throw an error if there are cycles in the graph. - @sorted_resources = @relgraph.topsort + @sorted_resources = relationship_graph.topsort end - - # Create a graph of all of the relationships in our resource graph. + def relationship_graph - graph = Puppet::PGraph.new - - # First create the dependency graph - @resources.vertices.each do |vertex| - graph.add_vertex!(vertex) - vertex.builddepends.each do |edge| - graph.add_edge!(edge) - end + configuration.relationship_graph + end + + # Send off the transaction report. + def send_report + begin + report = generate_report() + rescue => detail + Puppet.err "Could not generate report: %s" % detail + return + end + + if Puppet[:rrdgraph] == true + report.graph() + end + + if Puppet[:summarize] + puts report.summary end - # Lastly, add in any autorequires - graph.vertices.each do |vertex| - vertex.autorequire.each do |edge| - unless graph.edge?(edge) - unless graph.edge?(edge.target, edge.source) - vertex.debug "Autorequiring %s" % [edge.source] - graph.add_edge!(edge) - else - vertex.debug "Skipping automatic relationship with %s" % (edge.source == vertex ? edge.target : edge.source) - end - end + if Puppet[:report] + begin + reportclient().report(report) + rescue => detail + Puppet.err "Reporting failed: %s" % detail end end - - graph(graph, :relationships) - - # Then splice in the container information - graph.splice!(@resources, Puppet::Type::Component) + end - graph(graph, :expanded_relationships) - - return graph + def reportclient + unless defined? @reportclient + @reportclient = Puppet::Network::Client.report.new( + :Server => Puppet[:reportserver] + ) + end + + @reportclient end # Roll all completed changes back. @@ -572,7 +572,7 @@ class Transaction end # FIXME This won't work right now. - @relgraph.matching_edges(events).each do |edge| + relationship_graph.matching_edges(events).each do |edge| @targets[edge.target] << edge end @@ -606,7 +606,7 @@ class Transaction # Should this resource be skipped? def skip?(resource) skip = false - if ! tagged?(resource) + if missing_tags?(resource) resource.debug "Not tagged with %s" % tags.join(", ") elsif ! scheduled?(resource) resource.debug "Not scheduled" @@ -620,29 +620,22 @@ class Transaction # The tags we should be checking. def tags - # Allow the tags to be overridden unless defined? @tags - @tags = Puppet[:tags] - end - - unless defined? @processed_tags - if @tags.nil? or @tags == "" + tags = Puppet[:tags] + if tags.nil? or tags == "" @tags = [] else - @tags = [@tags] unless @tags.is_a? Array - @tags = @tags.collect do |tag| - tag.split(/\s*,\s*/) - end.flatten + @tags = tags.split(/\s*,\s*/) end - @processed_tags = true end @tags end # Is this resource tagged appropriately? - def tagged?(resource) - self.ignoretags or tags.empty? or resource.tagged?(tags) + def missing_tags?(resource) + return false if self.ignore_tags? or tags.empty? + return true unless resource.tagged?(tags) end # Are there any edges that target this resource? diff --git a/lib/puppet/transportable.rb b/lib/puppet/transportable.rb index aa7eb92f7..ecd179ed7 100644 --- a/lib/puppet/transportable.rb +++ b/lib/puppet/transportable.rb @@ -60,7 +60,18 @@ module Puppet instance_variables end - def to_type(parent = nil) + def to_ref + unless defined? @ref + if self.type and self.name + @ref = "%s[%s]" % [self.type, self.name] + else + @ref = nil + end + end + @ref + end + + def to_type retobj = nil if typeklass = Puppet::Type.type(self.type) # FIXME This should really be done differently, but... @@ -77,10 +88,6 @@ module Puppet raise Puppet::Error.new("Could not find object type %s" % self.type) end - if parent - parent.push retobj - end - return retobj end end @@ -167,12 +174,7 @@ module Puppet end end - str = nil - if self.top - str = "%s" - else - str = "#{@keyword} #{@type} {\n%s\n}" - end + str = "#{@keyword} #{@name} {\n%s\n}" str % @children.collect { |child| child.to_manifest }.collect { |str| @@ -188,7 +190,44 @@ module Puppet instance_variables end - def to_type(parent = nil) + # Create a resource graph from our structure. + def to_configuration + configuration = Puppet::Node::Configuration.new(Facter.value("hostname")) do |config| + delver = proc do |obj| + unless container = config.resource(obj.to_ref) + container = obj.to_type + config.add_resource container + end + obj.each do |child| + unless resource = config.resource(child.to_ref) + next unless resource = child.to_type + config.add_resource resource + end + config.add_edge!(container, resource) + if child.is_a?(self.class) + delver.call(child) + end + end + end + + delver.call(self) + end + + return configuration + end + + def to_ref + unless defined? @ref + if self.type and self.name + @ref = "%s[%s]" % [self.type, self.name] + else + @ref = nil + end + end + @ref + end + + def to_type # this container will contain the equivalent of all objects at # this level #container = Puppet::Component.new(:name => @name, :type => @type) @@ -235,45 +274,16 @@ module Puppet #Puppet.debug "%s[%s] has no parameters" % [@type, @name] end - #if parent - # hash[:parent] = parent - #end container = Puppet::Type::Component.create(hash) end #Puppet.info container.inspect - if parent - parent.push container - end - # unless we successfully created the container, return an error unless container Puppet.warning "Got no container back" return nil end - self.each { |child| - # the fact that we descend here means that we are - # always going to execute depth-first - # which is _probably_ a good thing, but one never knows... - unless child.is_a?(Puppet::TransBucket) or - child.is_a?(Puppet::TransObject) - raise Puppet::DevError, - "TransBucket#to_type cannot handle objects of type %s" % - child.class - end - - # Now just call to_type on them with the container as a parent - begin - child.to_type(container) - rescue => detail - if Puppet[:trace] and ! detail.is_a?(Puppet::Error) - puts detail.backtrace - end - Puppet.err detail.to_s - end - } - # at this point, no objects at are level are still Transportable # objects return container @@ -287,7 +297,5 @@ module Puppet end end - #------------------------------------------------------------ end -# $Id$ diff --git a/lib/puppet/type.rb b/lib/puppet/type.rb index b4b6e3b18..19f676ff4 100644 --- a/lib/puppet/type.rb +++ b/lib/puppet/type.rb @@ -44,7 +44,9 @@ class Type # that it is clear whether it operates on all attributes (thus has 'attr' in # the method name, or whether it operates on a specific type of attributes. attr_accessor :file, :line - attr_reader :parent + + # The configuration that this resource is stored in. + attr_accessor :configuration attr_writer :title attr_writer :noop @@ -310,6 +312,25 @@ class Type return self[:name] end + # Look up our parent in the configuration, if we have one. + def parent + return nil unless configuration + + # This is kinda weird. + if implicit? + parents = configuration.relationship_graph.adjacent(self, :direction => :in) + else + parents = configuration.adjacent(self, :direction => :in) + end + if parents + # We should never have more than one parent, so let's just ignore + # it if we happen to. + return parents.shift + else + return nil + end + end + # Return the "type[name]" style reference. def ref "%s[%s]" % [self.class.name.to_s.capitalize, self.title] diff --git a/lib/puppet/type/component.rb b/lib/puppet/type/component.rb index 5905d85ab..c5842e0e7 100644 --- a/lib/puppet/type/component.rb +++ b/lib/puppet/type/component.rb @@ -52,6 +52,7 @@ Puppet::Type.newtype(:component) do # this is only called on one component over the whole system # this also won't work with scheduling, but eh def evaluate + raise "Component#evaluate is deprecated" self.finalize unless self.finalized? transaction = Puppet::Transaction.new(self) transaction.component = self @@ -127,27 +128,6 @@ Puppet::Type.newtype(:component) do return false end end - - def push(*childs) - unless defined? @children - @children = [] - end - childs.each { |child| - # Make sure we don't have any loops here. - if parentof?(child) - devfail "Already the parent of %s[%s]" % [child.class.name, child.title] - end - unless child.is_a?(Puppet::Type) - self.debug "Got object of type %s" % child.class - self.devfail( - "Containers can only contain Puppet resources, not %s" % - child.class - ) - end - @children.push(child) - child.parent = self - } - end # Component paths are special because they function as containers. def pathbuilder @@ -162,30 +142,15 @@ Puppet::Type.newtype(:component) do else myname = self.title end - if self.parent - return [@parent.pathbuilder, myname] + if p = self.parent + return [p.pathbuilder, myname] else return [myname] end end - # Remove an object. The argument determines whether the object's - # subscriptions get eliminated, too. - def remove(rmdeps = true) - # Our children remove themselves from our @children array (else the object - # we called this on at the top would not be removed), so we duplicate the - # array and iterate over that. If we don't do this, only half of the - # objects get removed. - @children.dup.each { |child| - child.remove(rmdeps) - } - - @children.clear - - # Get rid of params and provider, too. - super - - @parent = nil + def ref + title end # We have a different way of setting the title @@ -203,30 +168,12 @@ Puppet::Type.newtype(:component) do end def refresh - @children.collect { |child| + configuration.adjacent(self).each do |child| if child.respond_to?(:refresh) child.refresh child.log "triggering %s" % :refresh end - } - end - - # Convert to a graph object with all of the container info. - def to_graph - graph = Puppet::PGraph.new - - delver = proc do |obj| - obj.each do |child| - graph.add_edge!(obj, child) - if child.is_a?(self.class) - delver.call(child) - end - end end - - delver.call(self) - - return graph end def to_s @@ -237,5 +184,3 @@ Puppet::Type.newtype(:component) do end end end - -# $Id$ diff --git a/lib/puppet/type/pfile.rb b/lib/puppet/type/pfile.rb index 99b5a7435..5bf9fef78 100644 --- a/lib/puppet/type/pfile.rb +++ b/lib/puppet/type/pfile.rb @@ -574,6 +574,9 @@ module Puppet # Create a new file or directory object as a child to the current # object. def newchild(path, local, hash = {}) + unless configuration + raise Puppet::DevError, "File recursion cannot happen without a configuration" + end # make local copy of arguments args = symbolize_options(@arghash) @@ -608,14 +611,14 @@ module Puppet } child = nil - klass = self.class - + # 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] + if child = configuration.resource(:file, path) unless child.parent.object_id == self.object_id + puts("Parent is %s, I am %s" % [child.parent.ref, self.ref]) if child.parent self.debug "Not managing more explicit file %s" % path return nil @@ -640,28 +643,22 @@ module Puppet #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 - rescue Puppet::Error => detail - self.notice( - "Cannot manage: %s" % - [detail.message] - ) - self.debug args.inspect - child = nil + # This method is used by subclasses of :file, so use the class name rather than hard-coding + # :file. + return nil unless child = configuration.create_implicit_resource(self.class.name, args) rescue => detail - self.notice( - "Cannot manage: %s" % - [detail] - ) - self.debug args.inspect - child = nil + puts detail.backtrace + self.notice "Cannot manage: %s" % [detail] + return nil end end + + # LAK:FIXME This shouldn't be necessary, but as long as we're + # modeling the relationship graph specifically, it is. + configuration.relationship_graph.add_edge! self, child + unless child.parent + raise "Did not set parent of %s" % child.ref + end return child end @@ -669,12 +666,14 @@ module Puppet # path names, rather than including the full parent's title each # time. def pathbuilder - if defined? @parent + # We specifically need to call the method here, so it looks + # up our parent in the configuration graph. + if parent = parent() # We only need to behave specially when our parent is also # a file - if @parent.is_a?(self.class) + if parent.is_a?(self.class) # Remove the parent file name - list = @parent.pathbuilder + list = parent.pathbuilder list.pop # remove the parent's path info return list << self.ref else @@ -695,9 +694,7 @@ module Puppet # files. def recurse # are we at the end of the recursion? - unless self.recurse? - return - end + return unless self.recurse? recurse = self[:recurse] # we might have a string, rather than a number diff --git a/lib/puppet/util/feature.rb b/lib/puppet/util/feature.rb index 30c38e286..2669d1ab1 100644 --- a/lib/puppet/util/feature.rb +++ b/lib/puppet/util/feature.rb @@ -16,8 +16,7 @@ class Puppet::Util::Feature if self.class.respond_to?(method) raise ArgumentError, "Feature %s is already defined" % name end - - result = true + if block_given? begin result = yield @@ -25,33 +24,21 @@ class Puppet::Util::Feature warn "Failed to load feature test for %s: %s" % [name, detail] result = false end - end - - if ary = options[:libs] - ary = [ary] unless ary.is_a?(Array) - - ary.each do |lib| - unless lib.is_a?(String) - raise ArgumentError, "Libraries must be passed as strings not %s" % lib.class - end - - begin - require lib - rescue Exception - Puppet.debug "Failed to load library '%s' for feature '%s'" % [lib, name] - result = false - end - end + @results[name] = result end meta_def(method) do - result + unless @results.include?(name) + @results[name] = test(name, options) + end + @results[name] end end # Create a new feature collection. def initialize(path) @path = path + @results = {} @loader = Puppet::Util::Autoload.new(self, @path) end @@ -71,6 +58,28 @@ class Puppet::Util::Feature return false end end -end -# $Id$ + # Actually test whether the feature is present. We only want to test when + # someone asks for the feature, so we don't unnecessarily load + # files. + def test(name, options) + result = true + if ary = options[:libs] + ary = [ary] unless ary.is_a?(Array) + + ary.each do |lib| + unless lib.is_a?(String) + raise ArgumentError, "Libraries must be passed as strings not %s" % lib.class + end + + begin + require lib + rescue Exception + Puppet.debug "Failed to load library '%s' for feature '%s'" % [lib, name] + result = false + end + end + end + result + end +end diff --git a/lib/puppet/util/instance_loader.rb b/lib/puppet/util/instance_loader.rb index 1a64c9c69..f280014eb 100755 --- a/lib/puppet/util/instance_loader.rb +++ b/lib/puppet/util/instance_loader.rb @@ -5,6 +5,12 @@ require 'puppet/util' # of Puppet::Util::Autoload module Puppet::Util::InstanceLoader include Puppet::Util + + # Are we instance-loading this type? + def instance_loading?(type) + defined?(@autoloaders) and @autoloaders.include?(symbolize(type)) + end + # Define a new type of autoloading. def instance_load(type, path, options = {}) @autoloaders ||= {} @@ -54,7 +60,7 @@ module Puppet::Util::InstanceLoader # Retrieve an alread-loaded instance, or attempt to load our instance. def loaded_instance(type, name) name = symbolize(name) - instances = instance_hash(type) + return nil unless instances = instance_hash(type) unless instances.include? name if instance_loader(type).load(name) unless instances.include? name @@ -70,5 +76,3 @@ module Puppet::Util::InstanceLoader instances[name] end end - -# $Id$ diff --git a/lib/puppet/util/metric.rb b/lib/puppet/util/metric.rb index 13bbc2af1..133aa9c2a 100644 --- a/lib/puppet/util/metric.rb +++ b/lib/puppet/util/metric.rb @@ -21,7 +21,7 @@ class Puppet::Util::Metric end def create(start = nil) - Puppet.config.use(:metrics) + Puppet.settings.use(:metrics) start ||= Time.now.to_i - 5 diff --git a/lib/puppet/util/config.rb b/lib/puppet/util/settings.rb index 9cdb4cfe3..1478cd8a5 100644 --- a/lib/puppet/util/config.rb +++ b/lib/puppet/util/settings.rb @@ -5,7 +5,7 @@ require 'getoptlong' # The class for handling configuration files. -class Puppet::Util::Config +class Puppet::Util::Settings include Enumerable include Puppet::Util @@ -45,15 +45,17 @@ class Puppet::Util::Config end # A simplified equality operator. - def ==(other) - self.each { |myname, myobj| - unless other[myname] == value(myname) - return false - end - } - - return true - end + # LAK: For some reason, this causes mocha to not be able to mock + # the 'value' method, and it's not used anywhere. +# def ==(other) +# self.each { |myname, myobj| +# unless other[myname] == value(myname) +# 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. @@ -69,14 +71,14 @@ class Puppet::Util::Config return options end - # Turn the config into a transaction and apply it + # Turn the config into a Puppet configuration and apply it def apply trans = self.to_transportable begin - comp = trans.to_type - trans = comp.evaluate - trans.evaluate - comp.remove + config = trans.to_configuration + config.store_state = false + config.apply + config.clear rescue => detail if Puppet[:trace] puts detail.backtrace @@ -476,57 +478,15 @@ class Puppet::Util::Config end # Convert a single section into transportable objects. - def section_to_transportable(section, done = nil, includefiles = true) + def section_to_transportable(section, done = nil) done ||= Hash.new { |hash, key| hash[key] = {} } objects = [] persection(section) do |obj| if @config[:mkusers] and value(:mkusers) - [: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 root or wheel - next if %w{root wheel}.include?(name.to_s) - - # 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.property(:ensure) - newobj[:ensure] = "present" - end - newobj.tag(section) - if type == :user - newobj[:comment] ||= "%s user" % name - end - else - newobj = Puppet::TransObject.new(name, type.to_s) - newobj.tags = ["puppet", "configuration", section] - newobj[:ensure] = "present" - if type == :user - newobj[:comment] ||= "%s user" % name - end - # Set the group appropriately for the user - if type == :user - newobj[:gid] = Puppet[:group] - end - done[type][name] = newobj - objects << newobj - end - end - end + objects += add_user_resources(section, obj, done) end + # Only files are convertable to transportable resources. if obj.respond_to? :to_transportable next if value(obj.name) =~ /^\/dev/ transobjects = obj.to_transportable @@ -544,7 +504,8 @@ class Puppet::Util::Config end bucket = Puppet::TransBucket.new - bucket.type = section + bucket.type = "Settings" + bucket.name = section bucket.push(*objects) bucket.keyword = "class" @@ -596,9 +557,9 @@ class Puppet::Util::Config end # Convert our list of objects into a component that can be applied. - def to_component + def to_configuration transport = self.to_transportable - return transport.to_type + return transport.to_configuration end # Convert our list of objects into a configuration file. @@ -634,7 +595,7 @@ Generated on #{Time.now}. end # Convert our configuration into a list of transportable objects. - def to_transportable + def to_transportable(*sections) done = Hash.new { |hash, key| hash[key] = {} } @@ -643,14 +604,20 @@ Generated on #{Time.now}. if defined? @file.file and @file.file topbucket.name = @file.file else - topbucket.name = "configtop" + topbucket.name = "top" end - topbucket.type = "puppetconfig" + topbucket.type = "Settings" topbucket.top = true # Now iterate over each section - eachsection do |section| - topbucket.push section_to_transportable(section, done) + if sections.empty? + eachsection do |section| + sections << section + end + end + sections.each do |section| + obj = section_to_transportable(section, done) + topbucket.push obj end topbucket @@ -683,37 +650,31 @@ Generated on #{Time.now}. } 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.configurator = 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 + bucket = to_transportable(*sections) + + config = bucket.to_configuration + config.host_config = false + config.apply + config.clear + +# tags = nil +# if Puppet[:tags] +# tags = Puppet[:tags] +# Puppet[:tags] = "" +# end +# trans = objects.evaluate +# trans.ignoretags = true +# trans.configurator = 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 @@ -845,6 +806,48 @@ Generated on #{Time.now}. private + # Create the transportable objects for users and groups. + def add_user_resources(section, obj, done) + resources = [] + [: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 root or wheel + next if %w{root wheel}.include?(name.to_s) + + # 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 + else + newobj = Puppet::TransObject.new(name, type.to_s) + newobj.tags = ["puppet", "configuration", section] + newobj[:ensure] = :present + if type == :user + newobj[:comment] ||= "%s user" % name + end + # Set the group appropriately for the user + if type == :user + newobj[:gid] = Puppet[:group] + end + done[type][name] = newobj + resources << newobj + end + end + end + resources + end + # Extract extra setting information for files. def extract_fileinfo(string) result = {} @@ -1105,6 +1108,11 @@ Generated on #{Time.now}. # 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 it's not a fully qualified path... + if value.is_a?(String) and value !~ /^\$/ and value !~ /^\// + # Make it one + value = File.join(Dir.getwd, value) + end if value.to_s =~ /\/$/ @type = :directory return value.sub(/\/$/, '') @@ -1127,9 +1135,6 @@ Generated on #{Time.now}. 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 @@ -1138,9 +1143,9 @@ Generated on #{Time.now}. objects = [] path = self.value - unless path =~ /^#{File::SEPARATOR}/ - path = File.join(Dir.getwd, path) - end + + # Skip plain files that don't exist, since we won't be managing them anyway. + return nil unless self.name.to_s =~ /dir$/ or File.exist?(path) or self.create obj = Puppet::TransObject.new(path, "file") # Only create directories, or files that are specifically marked to @@ -1157,7 +1162,7 @@ Generated on #{Time.now}. } # Only chown or chgrp when root - if Puppet::Util::SUIDManager.uid == 0 + if Puppet.features.root? [:group, :owner].each { |var| if value = self.send(var) obj[var] = value @@ -1187,7 +1192,7 @@ Generated on #{Time.now}. name = $1 unless @parent.include?(name) raise ArgumentError, - "Configuration parameter '%s' is undefined" % + "Settings parameter '%s' is undefined" % name end } @@ -1218,5 +1223,3 @@ Generated on #{Time.now}. end end end - -# $Id$ diff --git a/lib/puppet/util/storage.rb b/lib/puppet/util/storage.rb index a10183615..9e99057d9 100644 --- a/lib/puppet/util/storage.rb +++ b/lib/puppet/util/storage.rb @@ -46,7 +46,7 @@ class Puppet::Util::Storage self.init def self.load - Puppet.config.use(:main) + Puppet.settings.use(:main) unless File.exists?(Puppet[:statefile]) unless defined? @@state and ! @@state.nil? @@ -99,5 +99,3 @@ class Puppet::Util::Storage end end end - -# $Id$ diff --git a/spec/Rakefile b/spec/Rakefile index 40d107312..bb2a75de5 100644 --- a/spec/Rakefile +++ b/spec/Rakefile @@ -2,9 +2,15 @@ require File.join(File.dirname(__FILE__), "spec_helper.rb") require 'rake' require 'spec/rake/spectask' +basedir = File.dirname(__FILE__) +puppetlibdir = File.join(basedir, "../lib") +puppettestlibdir = File.join(basedir, "../test/lib") +speclibdir = File.join(basedir, "lib") + desc "Run all spec unit tests" Spec::Rake::SpecTask.new('unit') do |t| t.spec_files = FileList['unit/**/*.rb'] + t.libs = [puppetlibdir, puppettestlibdir, speclibdir] end task :default => [:unit] diff --git a/spec/bin/spec b/spec/bin/spec index a7e6ce0cb..aaf320f34 100755 --- a/spec/bin/spec +++ b/spec/bin/spec @@ -1,3 +1,4 @@ +#!/usr/bin/env ruby $LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__) + "/../lib")) require 'spec' ::Spec::Runner::CommandLine.run(ARGV, STDERR, STDOUT, true, true) diff --git a/spec/integration/checksum.rb b/spec/integration/checksum.rb new file mode 100755 index 000000000..f112f7502 --- /dev/null +++ b/spec/integration/checksum.rb @@ -0,0 +1,48 @@ +#!/usr/bin/env ruby +# +# Created by Luke Kanies on 2007-9-22. +# Copyright (c) 2007. All rights reserved. + +require File.dirname(__FILE__) + '/../spec_helper' + +require 'puppet/checksum' + +describe Puppet::Checksum, " when using the file terminus" do + before do + Puppet[:checksum_terminus] = "file" + + @content = "this is some content" + @sum = Puppet::Checksum.new(@content) + + @file = Puppet::Checksum.indirection.terminus.path(@sum.checksum) + end + + it "should store content at a path determined by its checksum" do + File.stubs(:directory?).returns(true) + filehandle = mock 'filehandle' + filehandle.expects(:print).with(@content) + File.expects(:open).with(@file, "w").yields(filehandle) + + @sum.save + end + + it "should retrieve stored content when the checksum is provided as the key" do + File.stubs(:exist?).returns(true) + File.expects(:read).with(@file).returns(@content) + + newsum = Puppet::Checksum.find(@sum.checksum) + + newsum.content.should == @content + end + + it "should remove specified files when asked" do + File.stubs(:exist?).returns(true) + File.expects(:unlink).with(@file) + + Puppet::Checksum.destroy(@sum) + end + + after do + Puppet.settings.clear + end +end diff --git a/spec/integration/node.rb b/spec/integration/node.rb new file mode 100755 index 000000000..8bc641bae --- /dev/null +++ b/spec/integration/node.rb @@ -0,0 +1,46 @@ +#!/usr/bin/env ruby +# +# Created by Luke Kanies on 2007-9-23. +# Copyright (c) 2007. All rights reserved. + +require File.dirname(__FILE__) + '/../spec_helper' + +require 'puppet/node' + +describe Puppet::Node, " when using the memory terminus" do + before do + @name = "me" + @node = Puppet::Node.new(@name) + Puppet[:node_terminus] = "memory" + end + + it "should find no nodes by default" do + Puppet::Node.find(@name).should be_nil + end + + it "should be able to find nodes that were previously saved" do + @node.save + Puppet::Node.find(@name).should equal(@node) + end + + it "should replace existing saved nodes when a new node with the same name is saved" do + @node.save + two = Puppet::Node.new(@name) + two.save + Puppet::Node.find(@name).should equal(two) + end + + it "should be able to remove previously saved nodes" do + @node.save + Puppet::Node.destroy(@node) + Puppet::Node.find(@name).should be_nil + end + + it "should fail when asked to destroy a node that does not exist" do + proc { Puppet::Node.destroy(@node) }.should raise_error(ArgumentError) + end + + after do + Puppet.settings.clear + end +end diff --git a/spec/lib/spec/dsl/behaviour.rb b/spec/lib/spec/dsl/behaviour.rb index 5158bb673..159a0ba7e 100644 --- a/spec/lib/spec/dsl/behaviour.rb +++ b/spec/lib/spec/dsl/behaviour.rb @@ -1,6 +1,10 @@ +require(File.expand_path(File.dirname(__FILE__) + '../../../../../test/lib/puppettest/runnable_test.rb')) + module Spec module DSL - class EvalModule < Module; end + class EvalModule < Module; + include PuppetTest::RunnableTest + end class Behaviour extend BehaviourCallbacks diff --git a/spec/lib/spec/runner/behaviour_runner.rb b/spec/lib/spec/runner/behaviour_runner.rb index 1ac891f3c..078490e92 100644 --- a/spec/lib/spec/runner/behaviour_runner.rb +++ b/spec/lib/spec/runner/behaviour_runner.rb @@ -55,6 +55,8 @@ module Spec def run_behaviours @behaviours.each do |behaviour| + # LAK:NOTE: this 'runnable' test is Puppet-specific. + next unless behaviour.runnable? behaviour.run(@options.reporter, @options.dry_run, @options.reverse, @options.timeout) end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index aa56fd93e..3017f272a 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,13 +1,21 @@ -dir = File.dirname(__FILE__) -$:.unshift("#{dir}/lib").unshift("#{dir}/../lib") +dir = File.expand_path(File.dirname(__FILE__)) +$:.unshift("#{dir}/lib") +$:.unshift("#{dir}/../lib") # Add the old test dir, so that we can still find mocha and spec $:.unshift("#{dir}/../test/lib") require 'mocha' -require 'spec' require 'puppettest' +require 'spec' Spec::Runner.configure do |config| config.mock_with :mocha + config.prepend_before :each do + setup() if respond_to? :setup + end + + config.prepend_after :each do + teardown() if respond_to? :teardown + end end diff --git a/spec/unit/indirector/code.rb b/spec/unit/indirector/code.rb new file mode 100755 index 000000000..f34dcf402 --- /dev/null +++ b/spec/unit/indirector/code.rb @@ -0,0 +1,33 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../spec_helper' +require 'puppet/indirector/code' + +describe Puppet::Indirector::Code do + before do + Puppet::Indirector::Terminus.stubs(:register_terminus_class) + @model = mock 'model' + @indirection = stub 'indirection', :name => :mystuff, :register_terminus_type => nil, :model => @model + Puppet::Indirector::Indirection.stubs(:instance).returns(@indirection) + + @code_class = Class.new(Puppet::Indirector::Code) do + def self.to_s + "Testing" + end + end + + @searcher = @code_class.new + end + + it "should not have a find() method defined" do + @searcher.should_not respond_to(:find) + end + + it "should not have a save() method defined" do + @searcher.should_not respond_to(:save) + end + + it "should not have a destroy() method defined" do + @searcher.should_not respond_to(:destroy) + end +end diff --git a/spec/unit/indirector/code/configuration.rb b/spec/unit/indirector/code/configuration.rb new file mode 100755 index 000000000..8652f342d --- /dev/null +++ b/spec/unit/indirector/code/configuration.rb @@ -0,0 +1,158 @@ +#!/usr/bin/env ruby +# +# Created by Luke Kanies on 2007-9-23. +# Copyright (c) 2007. All rights reserved. + +require File.dirname(__FILE__) + '/../../../spec_helper' + +require 'puppet/indirector/code/configuration' + +describe Puppet::Indirector::Code::Configuration do + # LAK:TODO I have no idea how to do this, or even if it should be in this class or test or what. + # This is used for determining if the client should recompile its configuration, so it's not sufficient + # to recompile and compare versions. + # It might be that the right solution is to require configuration caching, and then compare the cached + # configuration version to the current version, via some querying mechanism (i.e., the client asks for just + # the configuration's 'up-to-date' attribute, rather than the whole configuration). + it "should provide a mechanism for determining if the client's configuration is up to date" +end + +describe Puppet::Indirector::Code::Configuration do + before do + Puppet.expects(:version).returns(1) + Facter.expects(:value).with('fqdn').returns("my.server.com") + Facter.expects(:value).with('ipaddress').returns("my.ip.address") + end + + it "should gather data about itself" do + Puppet::Indirector::Code::Configuration.new + end + + it "should cache the server metadata and reuse it" do + compiler = Puppet::Indirector::Code::Configuration.new + node1 = stub 'node1', :merge => nil + node2 = stub 'node2', :merge => nil + compiler.stubs(:compile) + Puppet::Node.stubs(:search).with('node1').returns(node1) + Puppet::Node.stubs(:search).with('node2').returns(node2) + + compiler.find('node1') + compiler.find('node2') + end +end + +describe Puppet::Indirector::Code::Configuration, " when creating the interpreter" do + before do + @compiler = Puppet::Indirector::Code::Configuration.new + end + + it "should not create the interpreter until it is asked for the first time" do + interp = mock 'interp' + Puppet::Parser::Interpreter.expects(:new).with({}).returns(interp) + @compiler.interpreter.should equal(interp) + end + + it "should use the same interpreter for all compiles" do + interp = mock 'interp' + Puppet::Parser::Interpreter.expects(:new).with({}).returns(interp) + @compiler.interpreter.should equal(interp) + @compiler.interpreter.should equal(interp) + end + + it "should provide a mechanism for setting the code to pass to the interpreter" do + @compiler.should respond_to(:code=) + end + + it "should pass any specified code on to the interpreter when it is being initialized" do + code = "some code" + @compiler.code = code + interp = mock 'interp' + Puppet::Parser::Interpreter.expects(:new).with(:Code => code).returns(interp) + @compiler.send(:interpreter).should equal(interp) + end + +end + +describe Puppet::Indirector::Code::Configuration, " when finding nodes" do + before do + @compiler = Puppet::Indirector::Code::Configuration.new + @name = "me" + @node = mock 'node' + @compiler.stubs(:compile) + end + + it "should look node information up via the Node class with the provided key" do + @node.stubs :merge + Puppet::Node.expects(:search).with(@name).returns(@node) + @compiler.find(@name) + end + + it "should fail if it cannot find the node" do + @node.stubs :merge + Puppet::Node.expects(:search).with(@name).returns(nil) + proc { @compiler.find(@name) }.should raise_error(Puppet::Error) + end +end + +describe Puppet::Indirector::Code::Configuration, " after finding nodes" do + before do + Puppet.expects(:version).returns(1) + Puppet.settings.stubs(:value).with(:node_name).returns("cert") + Facter.expects(:value).with('fqdn').returns("my.server.com") + Facter.expects(:value).with('ipaddress').returns("my.ip.address") + @compiler = Puppet::Indirector::Code::Configuration.new + @name = "me" + @node = mock 'node' + @compiler.stubs(:compile) + Puppet::Node.stubs(:search).with(@name).returns(@node) + end + + it "should add the server's Puppet version to the node's parameters as 'serverversion'" do + @node.expects(:merge).with { |args| args["serverversion"] == "1" } + @compiler.find(@name) + end + + it "should add the server's fqdn to the node's parameters as 'servername'" do + @node.expects(:merge).with { |args| args["servername"] == "my.server.com" } + @compiler.find(@name) + end + + it "should add the server's IP address to the node's parameters as 'serverip'" do + @node.expects(:merge).with { |args| args["serverip"] == "my.ip.address" } + @compiler.find(@name) + end + + # LAK:TODO This is going to be difficult, because this whole process is so + # far removed from the actual connection that the certificate information + # will be quite hard to come by, dum by, gum by. + it "should search for the name using the client certificate's DN if the :node_name setting is set to 'cert'" +end + +describe Puppet::Indirector::Code::Configuration, " when creating configurations" do + before do + @compiler = Puppet::Indirector::Code::Configuration.new + @name = "me" + @node = stub 'node', :merge => nil, :name => @name, :environment => "yay" + Puppet::Node.stubs(:search).with(@name).returns(@node) + end + + it "should pass the found node to the interpreter for compiling" do + config = mock 'config' + @compiler.interpreter.expects(:compile).with(@node) + @compiler.find(@name) + end + + it "should return the results of compiling as the configuration" do + config = mock 'config' + @compiler.interpreter.expects(:compile).with(@node).returns(:configuration) + @compiler.find(@name).should == :configuration + end + + it "should benchmark the compile process" do + @compiler.expects(:benchmark).with do |level, message| + level == :notice and message =~ /^Compiled configuration/ + end + @compiler.interpreter.stubs(:compile).with(@node) + @compiler.find(@name) + end +end diff --git a/spec/unit/indirector/exec.rb b/spec/unit/indirector/exec.rb new file mode 100755 index 000000000..42fbe0955 --- /dev/null +++ b/spec/unit/indirector/exec.rb @@ -0,0 +1,49 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../spec_helper' + +require 'puppet/indirector/exec' + +describe Puppet::Indirector::Exec do + before do + @indirection = mock 'indirection' + Puppet::Indirector::Indirection.expects(:instance).with(:testing).returns(@indirection) + @exec_class = Class.new(Puppet::Indirector::Exec) do + def self.to_s + "Testing" + end + + attr_accessor :command + end + + @searcher = @exec_class.new + @searcher.command = ["/echo"] + end + + it "should throw an exception if the command is not an array" do + @searcher.command = "/usr/bin/echo" + proc { @searcher.find("foo") }.should raise_error(Puppet::DevError) + end + + it "should throw an exception if the command is not fully qualified" do + @searcher.command = ["mycommand"] + proc { @searcher.find("foo") }.should raise_error(ArgumentError) + end + + it "should execute the command with the object name as the only argument" do + @searcher.expects(:execute).with(%w{/echo yay}) + @searcher.find("yay") + end + + it "should return the output of the script" do + @searcher.expects(:execute).with(%w{/echo yay}).returns("whatever") + @searcher.find("yay").should == "whatever" + end + + it "should return nil when the command produces no output" do + @searcher.expects(:execute).with(%w{/echo yay}).returns(nil) + @searcher.find("yay").should be_nil + end + + it "should be able to execute commands with multiple arguments" +end diff --git a/spec/unit/indirector/exec/node.rb b/spec/unit/indirector/exec/node.rb new file mode 100755 index 000000000..47f4ce7a5 --- /dev/null +++ b/spec/unit/indirector/exec/node.rb @@ -0,0 +1,62 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../../spec_helper' + +require 'puppet/indirector/exec/node' + +describe Puppet::Indirector::Exec::Node, " when constructing the command to run" do + before do + @indirection = mock 'indirection' + Puppet.settings.stubs(:value).with(:external_nodes).returns("/echo") + @searcher = Puppet::Indirector::Exec::Node.new + end + + it "should use the external_node script as the command" do + Puppet.expects(:[]).with(:external_nodes).returns("/bin/echo") + @searcher.command.should == %w{/bin/echo} + end + + it "should throw an exception if no external node command is set" do + Puppet.expects(:[]).with(:external_nodes).returns("none") + proc { @searcher.find("foo") }.should raise_error(ArgumentError) + end +end + +describe Puppet::Indirector::Exec::Node, " when handling the results of the command" do + before do + @indirection = mock 'indirection' + Puppet.settings.stubs(:value).with(:external_nodes).returns("/echo") + @searcher = Puppet::Indirector::Exec::Node.new + @node = stub 'node', :fact_merge => nil + @name = "yay" + Puppet::Node.expects(:new).with(@name).returns(@node) + @result = {} + # Use a local variable so the reference is usable in the execute() definition. + result = @result + @searcher.meta_def(:execute) do |command| + return YAML.dump(result) + end + end + + it "should translate the YAML into a Node instance" do + # Use an empty hash + @searcher.find(@name).should equal(@node) + end + + it "should set the resulting parameters as the node parameters" do + @result[:parameters] = {"a" => "b", "c" => "d"} + @node.expects(:parameters=).with "a" => "b", "c" => "d" + @searcher.find(@name) + end + + it "should set the resulting classes as the node classes" do + @result[:classes] = %w{one two} + @node.expects(:classes=).with %w{one two} + @searcher.find(@name) + end + + it "should merge the node's facts with its parameters" do + @node.expects(:fact_merge) + @searcher.find(@name) + end +end diff --git a/spec/unit/indirector/file.rb b/spec/unit/indirector/file.rb new file mode 100755 index 000000000..cc86f9fa9 --- /dev/null +++ b/spec/unit/indirector/file.rb @@ -0,0 +1,160 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../spec_helper' +require 'puppet/indirector/file' + +module FileTerminusTesting + def setup + Puppet::Indirector::Terminus.stubs(:register_terminus_class) + @model = mock 'model' + @indirection = stub 'indirection', :name => :mystuff, :register_terminus_type => nil, :model => @model + Puppet::Indirector::Indirection.stubs(:instance).returns(@indirection) + + @file_class = Class.new(Puppet::Indirector::File) do + def self.to_s + "Testing" + end + end + + @searcher = @file_class.new + + @path = "/my/file" + @dir = "/my" + end +end + +describe Puppet::Indirector::File, " when finding files" do + include FileTerminusTesting + + it "should provide a method to return file contents at a specified path" do + @searcher.should respond_to(:find) + end + + it "should return file contents as an instance of the model" do + content = "my content" + + file = mock 'file' + @model.expects(:new).with(content).returns(file) + + File.expects(:exist?).with(@path).returns(true) + File.expects(:read).with(@path).returns(content) + @searcher.find(@path) + end + + it "should create the model instance with the content as the only argument to initialization" do + content = "my content" + + file = mock 'file' + @model.expects(:new).with(content).returns(file) + + File.expects(:exist?).with(@path).returns(true) + File.expects(:read).with(@path).returns(content) + @searcher.find(@path).should equal(file) + end + + it "should return nil if no file is found" do + File.expects(:exist?).with(@path).returns(false) + @searcher.find(@path).should be_nil + end + + it "should fail intelligently if a found file cannot be read" do + File.expects(:exist?).with(@path).returns(true) + File.expects(:read).with(@path).raises(RuntimeError) + proc { @searcher.find(@path) }.should raise_error(Puppet::Error) + end + + it "should use the path() method to calculate the path if it exists" do + @searcher.meta_def(:path) do |name| + name.upcase + end + + File.expects(:exist?).with(@path.upcase).returns(false) + @searcher.find(@path) + end +end + +describe Puppet::Indirector::File, " when saving files" do + include FileTerminusTesting + + it "should provide a method to save file contents at a specified path" do + filehandle = mock 'file' + content = "my content" + File.expects(:directory?).with(@dir).returns(true) + File.expects(:open).with(@path, "w").yields(filehandle) + filehandle.expects(:print).with(content) + + file = stub 'file', :content => content, :path => @path, :name => @path + + @searcher.save(file) + end + + it "should fail intelligently if the file's parent directory does not exist" do + File.expects(:directory?).with(@dir).returns(false) + + file = stub 'file', :path => @path, :name => @path + + proc { @searcher.save(file) }.should raise_error(Puppet::Error) + end + + it "should fail intelligently if a file cannot be written" do + filehandle = mock 'file' + content = "my content" + File.expects(:directory?).with(@dir).returns(true) + File.expects(:open).with(@path, "w").yields(filehandle) + filehandle.expects(:print).with(content).raises(ArgumentError) + + file = stub 'file', :content => content, :path => @path, :name => @path + + proc { @searcher.save(file) }.should raise_error(Puppet::Error) + end + + it "should use the path() method to calculate the path if it exists" do + @searcher.meta_def(:path) do |name| + name.upcase + end + + file = stub 'file', :name => "/yay" + + File.expects(:open).with("/YAY", "w") + @searcher.save(file) + end +end + +describe Puppet::Indirector::File, " when removing files" do + include FileTerminusTesting + + it "should provide a method to remove files at a specified path" do + file = stub 'file', :path => @path, :name => @path + File.expects(:exist?).with(@path).returns(true) + File.expects(:unlink).with(@path) + + @searcher.destroy(file) + end + + it "should throw an exception if the file is not found" do + file = stub 'file', :path => @path, :name => @path + File.expects(:exist?).with(@path).returns(false) + + proc { @searcher.destroy(file) }.should raise_error(Puppet::Error) + end + + it "should fail intelligently if the file cannot be removed" do + file = stub 'file', :path => @path, :name => @path + File.expects(:exist?).with(@path).returns(true) + File.expects(:unlink).with(@path).raises(ArgumentError) + + proc { @searcher.destroy(file) }.should raise_error(Puppet::Error) + end + + it "should use the path() method to calculate the path if it exists" do + @searcher.meta_def(:path) do |name| + name.upcase + end + + file = stub 'file', :name => "/yay" + File.expects(:exist?).with("/YAY").returns(true) + File.expects(:unlink).with("/YAY") + + @searcher.destroy(file) + end +end diff --git a/spec/unit/indirector/file/checksum.rb b/spec/unit/indirector/file/checksum.rb new file mode 100755 index 000000000..539bb973c --- /dev/null +++ b/spec/unit/indirector/file/checksum.rb @@ -0,0 +1,142 @@ +#!/usr/bin/env ruby +# +# Created by Luke Kanies on 2007-9-22. +# Copyright (c) 2007. All rights reserved. + +require File.dirname(__FILE__) + '/../../../spec_helper' + +require 'puppet/indirector/file/checksum' + +module FileChecksumTesting + def setup + Puppet.settings.stubs(:use) + @store = Puppet::Indirector::File::Checksum.new + + @value = "70924d6fa4b2d745185fa4660703a5c0" + @sum = stub 'sum', :name => @value + + @dir = "/what/ever" + + Puppet.stubs(:[]).with(:bucketdir).returns(@dir) + + @path = @store.path(@value) + end +end + +describe Puppet::Indirector::File::Checksum do + it "should be a subclass of the File terminus class" do + Puppet::Indirector::File::Checksum.superclass.should equal(Puppet::Indirector::File) + end + + it "should have documentation" do + Puppet::Indirector::File::Checksum.doc.should be_instance_of(String) + end +end + +describe Puppet::Indirector::File::Checksum, " when initializing" do + it "should use the filebucket settings section" do + Puppet.settings.expects(:use).with(:filebucket) + Puppet::Indirector::File::Checksum.new + end +end + +describe Puppet::Indirector::File::Checksum, " when determining file paths" do + include FileChecksumTesting + + # I was previously passing the object in. + it "should use the value passed in to path() as the checksum" do + @value.expects(:name).never + @store.path(@value) + end + + it "should use the value of the :bucketdir setting as the root directory" do + @path.should =~ %r{^#{@dir}} + end + + it "should choose a path 8 directories deep with each directory name being the respective character in the checksum" do + dirs = @value[0..7].split("").join(File::SEPARATOR) + @path.should be_include(dirs) + end + + it "should use the full checksum as the final directory name" do + File.basename(File.dirname(@path)).should == @value + end + + it "should use 'contents' as the actual file name" do + File.basename(@path).should == "contents" + end + + it "should use the bucketdir, the 8 sum character directories, the full checksum, and 'contents' as the full file name" do + @path.should == [@dir, @value[0..7].split(""), @value, "contents"].flatten.join(File::SEPARATOR) + end +end + +describe Puppet::Indirector::File::Checksum, " when retrieving files" do + include FileChecksumTesting + + # The smallest test that will use the calculated path + it "should look for the calculated path" do + File.expects(:exist?).with(@path).returns(false) + @store.find(@value) + end + + it "should return an instance of Puppet::Checksum created with the content if the file exists" do + content = "my content" + sum = stub 'file' + Puppet::Checksum.expects(:new).with(content).returns(sum) + + File.expects(:exist?).with(@path).returns(true) + File.expects(:read).with(@path).returns(content) + + @store.find(@value).should equal(sum) + end + + it "should return nil if no file is found" do + File.expects(:exist?).with(@path).returns(false) + @store.find(@value).should be_nil + end + + it "should fail intelligently if a found file cannot be read" do + File.expects(:exist?).with(@path).returns(true) + File.expects(:read).with(@path).raises(RuntimeError) + proc { @store.find(@value) }.should raise_error(Puppet::Error) + end +end + +describe Puppet::Indirector::File::Checksum, " when saving files" do + include FileChecksumTesting + + # LAK:FIXME I don't know how to include in the spec the fact that we're + # using the superclass's save() method and thus are acquiring all of + # it's behaviours. + it "should save the content to the calculated path" do + File.stubs(:directory?).with(File.dirname(@path)).returns(true) + File.expects(:open).with(@path, "w") + + file = stub 'file', :name => @value + @store.save(file) + end + + it "should make any directories necessary for storage" do + FileUtils.expects(:mkdir_p).with do |arg| + File.umask == 0007 and arg == File.dirname(@path) + end + File.expects(:directory?).with(File.dirname(@path)).returns(true) + File.expects(:open).with(@path, "w") + + file = stub 'file', :name => @value + @store.save(file) + end +end + +describe Puppet::Indirector::File::Checksum, " when deleting files" do + include FileChecksumTesting + + it "should remove the file at the calculated path" do + File.expects(:exist?).with(@path).returns(true) + File.expects(:unlink).with(@path) + + file = stub 'file', :name => @value + @store.destroy(file) + end +end diff --git a/spec/unit/indirector/indirection.rb b/spec/unit/indirector/indirection.rb new file mode 100755 index 000000000..326b6b470 --- /dev/null +++ b/spec/unit/indirector/indirection.rb @@ -0,0 +1,160 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../spec_helper' + +require 'puppet/indirector' + +describe Puppet::Indirector::Indirection, " when initializing" do + it "should keep a reference to the indirecting model" do + model = mock 'model' + @indirection = Puppet::Indirector::Indirection.new(model, :myind) + @indirection.model.should equal(model) + end + + it "should set the name" do + @indirection = Puppet::Indirector::Indirection.new(mock('model'), :myind) + @indirection.name.should == :myind + end + + it "should require indirections to have unique names" do + @indirection = Puppet::Indirector::Indirection.new(mock('model'), :test) + proc { Puppet::Indirector::Indirection.new(:test) }.should raise_error(ArgumentError) + end + + after do + @indirection.delete if defined? @indirection + end +end + +describe Puppet::Indirector::Indirection, " when managing indirection instances" do + it "should allow an indirection to be retrieved by name" do + @indirection = Puppet::Indirector::Indirection.new(mock('model'), :test) + Puppet::Indirector::Indirection.instance(:test).should equal(@indirection) + end + + it "should return nil when the named indirection has not been created" do + Puppet::Indirector::Indirection.instance(:test).should be_nil + end + + after do + @indirection.delete if defined? @indirection + end +end + +describe Puppet::Indirector::Indirection, " when choosing terminus types" do + before do + @indirection = Puppet::Indirector::Indirection.new(mock('model'), :test) + @terminus = mock 'terminus' + @terminus_class = stub 'terminus class', :new => @terminus + end + + it "should follow a convention on using per-model configuration parameters to determine the terminus class" do + Puppet.settings.expects(:valid?).with('test_terminus').returns(true) + Puppet.settings.expects(:value).with('test_terminus').returns(:foo) + Puppet::Indirector::Terminus.expects(:terminus_class).with(:foo, :test).returns(@terminus_class) + @indirection.terminus.should equal(@terminus) + end + + it "should use a default system-wide configuration parameter parameter to determine the terminus class when no + per-model configuration parameter is available" do + Puppet.settings.expects(:valid?).with('test_terminus').returns(false) + Puppet.settings.expects(:value).with(:default_terminus).returns(:foo) + Puppet::Indirector::Terminus.expects(:terminus_class).with(:foo, :test).returns(@terminus_class) + @indirection.terminus.should equal(@terminus) + end + + it "should select the specified terminus class if a name is provided" do + Puppet::Indirector::Terminus.expects(:terminus_class).with(:foo, :test).returns(@terminus_class) + @indirection.terminus(:foo).should equal(@terminus) + end + + it "should fail when the terminus class name is an empty string" do + proc { @indirection.terminus("") }.should raise_error(ArgumentError) + end + + it "should fail when the terminus class name is nil" do + proc { @indirection.terminus(nil) }.should raise_error(ArgumentError) + end + + it "should fail when the specified terminus class cannot be found" do + Puppet::Indirector::Terminus.expects(:terminus_class).with(:foo, :test).returns(nil) + proc { @indirection.terminus(:foo) }.should raise_error(ArgumentError) + end + + after do + @indirection.delete if defined? @indirection + end +end + +describe Puppet::Indirector::Indirection, " when managing terminus instances" do + before do + @indirection = Puppet::Indirector::Indirection.new(mock('model'), :test) + @terminus = mock 'terminus' + @terminus_class = mock 'terminus class' + Puppet::Indirector::Terminus.stubs(:terminus_class).with(:foo, :test).returns(@terminus_class) + end + + it "should create an instance of the chosen terminus class" do + @terminus_class.stubs(:new).returns(@terminus) + @indirection.terminus(:foo).should equal(@terminus) + end + + it "should allow the clearance of cached terminus instances" do + terminus1 = mock 'terminus1' + terminus2 = mock 'terminus2' + @terminus_class.stubs(:new).returns(terminus1, terminus2, ArgumentError) + @indirection.terminus(:foo).should equal(terminus1) + @indirection.class.clear_cache + @indirection.terminus(:foo).should equal(terminus2) + end + + # Make sure it caches the terminus. + it "should return the same terminus instance each time for a given name" do + @terminus_class.stubs(:new).returns(@terminus) + @indirection.terminus(:foo).should equal(@terminus) + @indirection.terminus(:foo).should equal(@terminus) + end + + it "should not create a terminus instance until one is actually needed" do + Puppet::Indirector.expects(:terminus).never + indirection = Puppet::Indirector::Indirection.new(mock('model'), :lazytest) + end + + after do + @indirection.delete + Puppet::Indirector::Indirection.clear_cache + end +end + +describe Puppet::Indirector::Indirection do + before do + @indirection = Puppet::Indirector::Indirection.new(mock('model'), :test) + @terminus = mock 'terminus' + @indirection.stubs(:terminus).returns(@terminus) + end + + it "should handle lookups of a model instance by letting the appropriate terminus perform the lookup" do + @terminus.expects(:find).with(:mything).returns(:whev) + @indirection.find(:mything).should == :whev + end + + it "should handle removing model instances from a terminus letting the appropriate terminus remove the instance" do + @terminus.expects(:destroy).with(:mything).returns(:whev) + @indirection.destroy(:mything).should == :whev + end + + it "should handle searching for model instances by letting the appropriate terminus find the matching instances" do + @terminus.expects(:search).with(:mything).returns(:whev) + @indirection.search(:mything).should == :whev + end + + it "should handle storing a model instance by letting the appropriate terminus store the instance" do + @terminus.expects(:save).with(:mything).returns(:whev) + @indirection.save(:mything).should == :whev + end + + after do + @indirection.delete + Puppet::Indirector::Indirection.clear_cache + end +end diff --git a/spec/unit/indirector/indirector.rb b/spec/unit/indirector/indirector.rb new file mode 100755 index 000000000..1702bf51f --- /dev/null +++ b/spec/unit/indirector/indirector.rb @@ -0,0 +1,86 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../spec_helper' + +require 'puppet/defaults' +require 'puppet/indirector' + +describe Puppet::Indirector, " when available to a model" do + before do + @thingie = Class.new do + extend Puppet::Indirector + end + end + + it "should provide a way for the model to register an indirection under a name" do + @thingie.should respond_to(:indirects) + end +end + +describe Puppet::Indirector, "when registering an indirection" do + before do + @thingie = Class.new do + extend Puppet::Indirector + end + end + + it "should require a name when registering a model" do + Proc.new {@thingie.send(:indirects) }.should raise_error(ArgumentError) + end + + it "should create an indirection instance to manage each indirecting model" do + @indirection = @thingie.indirects(:test) + @indirection.should be_instance_of(Puppet::Indirector::Indirection) + end + + it "should not allow a model to register under multiple names" do + # Keep track of the indirection instance so we can delete it on cleanup + @indirection = @thingie.indirects :first + Proc.new { @thingie.indirects :second }.should raise_error(ArgumentError) + end + + it "should make the indirection available via an accessor" do + @indirection = @thingie.indirects :first + @thingie.indirection.should equal(@indirection) + end + + after do + @indirection.delete if @indirection + end +end + +describe Puppet::Indirector, " when redirecting a model" do + before do + @thingie = Class.new do + extend Puppet::Indirector + end + @mock_terminus = mock('Terminus') + @indirection = @thingie.send(:indirects, :test) + @thingie.expects(:indirection).returns(@mock_terminus) + end + + it "should give the model the ability to lookup a model instance by letting the indirection perform the lookup" do + @mock_terminus.expects(:find) + @thingie.find + end + + it "should give the model the ability to remove model instances from a terminus by letting the indirection remove the instance" do + @mock_terminus.expects(:destroy) + @thingie.destroy + end + + it "should give the model the ability to search for model instances by letting the indirection find the matching instances" do + @mock_terminus.expects(:search) + @thingie.search + end + + it "should give the model the ability to store a model instance by letting the indirection store the instance" do + thing = @thingie.new + @mock_terminus.expects(:save).with(thing) + thing.save + end + + after do + @indirection.delete + end +end diff --git a/spec/unit/indirector/ldap.rb b/spec/unit/indirector/ldap.rb new file mode 100755 index 000000000..fe9408986 --- /dev/null +++ b/spec/unit/indirector/ldap.rb @@ -0,0 +1,147 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../spec_helper' + +require 'puppet/indirector/ldap' + +describe Puppet::Indirector::Ldap, " when searching ldap" do + before do + @indirection = mock 'indirection' + Puppet::Indirector::Indirection.stubs(:instance).returns(@indirection) + @ldap_class = Class.new(Puppet::Indirector::Ldap) do + def self.to_s + "Testing" + end + end + + @connection = mock 'ldap' + + @searcher = @ldap_class.new + + # Stub everything, and we can selectively replace with an expect as + # we need to for testing. + @searcher.stubs(:connection).returns(@connection) + @searcher.stubs(:search_filter).returns(:filter) + @searcher.stubs(:search_base).returns(:base) + @searcher.stubs(:process) + end + + it "should call the ldapsearch method with the name being searched for" do + @searcher.expects(:ldapsearch).with("yay") + @searcher.find "yay" + end + + it "should fail if no block is passed to the ldapsearch method" do + proc { @searcher.ldapsearch("blah") }.should raise_error(ArgumentError) + end + + it "should use the results of the ldapbase method as the ldap search base" do + @searcher.stubs(:search_base).returns("mybase") + @connection.expects(:search).with do |*args| + args[0].should == "mybase" + true + end + @searcher.find "yay" + end + + it "should default to the value of the :search_base setting as the result of the ldapbase method" do + Puppet.expects(:[]).with(:ldapbase).returns("myldapbase") + searcher = @ldap_class.new + searcher.search_base.should == "myldapbase" + end + + it "should use the results of the :search_attributes method as the list of attributes to return" do + @searcher.stubs(:search_attributes).returns(:myattrs) + @connection.expects(:search).with do |*args| + args[3].should == :myattrs + true + end + @searcher.find "yay" + end + + it "should use the results of the :search_filter method as the search filter" do + @searcher.stubs(:search_filter).with("yay").returns("yay's filter") + @connection.expects(:search).with do |*args| + args[2].should == "yay's filter" + true + end + @searcher.find "yay" + end + + it "should use depth 2 when searching" do + @connection.expects(:search).with do |*args| + args[1].should == 2 + true + end + @searcher.find "yay" + end + + it "should call process() on the first found entry" do + @connection.expects(:search).yields("myresult") + @searcher.expects(:process).with("yay", "myresult") + @searcher.find "yay" + end + + it "should reconnect and retry the search if there is a failure" do + run = false + @connection.stubs(:search).with do |*args| + if run + true + else + run = true + raise "failed" + end + end.yields("myresult") + @searcher.expects(:process).with("yay", "myresult") + + @searcher.find "yay" + end + + it "should not reconnect on failure more than once" do + count = 0 + @connection.stubs(:search).with do |*args| + count += 1 + raise ArgumentError, "yay" + end + proc { @searcher.find("whatever") }.should raise_error(Puppet::Error) + count.should == 2 + end + + it "should return true if an entry is found" do + @connection.expects(:search).yields("result") + @searcher.ldapsearch("whatever") { |r| }.should be_true + end +end + +describe Puppet::Indirector::Ldap, " when connecting to ldap" do + confine "LDAP is not available" => Puppet.features.ldap? + confine "No LDAP test data for networks other than Luke's" => Facter.value(:domain) == "madstop.com" + + it "should only create the ldap connection when asked for it the first time" + + it "should throw an exception if it cannot connect to LDAP" + + it "should use SSL when the :ldapssl setting is true" + + it "should connect to the server specified in the :ldapserver setting" + + it "should use the port specified in the :ldapport setting" + + it "should use protocol version 3" + + it "should follow referrals" + + it "should use the user specified in the :ldapuser setting" + + it "should use the password specified in the :ldappassord setting" + + it "should have an ldap method that returns an LDAP connection object" + + it "should fail when LDAP support is missing" +end + +describe Puppet::Indirector::Ldap, " when reconnecting to ldap" do + confine "Not running on culain as root" => (Puppet::Util::SUIDManager.uid == 0 and Facter.value("hostname") == "culain") + + it "should reconnect to ldap when connections are lost" +end diff --git a/spec/unit/indirector/ldap/node.rb b/spec/unit/indirector/ldap/node.rb new file mode 100755 index 000000000..37953db19 --- /dev/null +++ b/spec/unit/indirector/ldap/node.rb @@ -0,0 +1,240 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../../spec_helper' + +require 'puppet/indirector/ldap/node' + +module LdapNodeSearching + def setup + @searcher = Puppet::Indirector::Ldap::Node.new + @entries = {} + entries = @entries + + @connection = mock 'connection' + @entry = mock 'entry' + @connection.stubs(:search).yields(@entry) + @searcher.stubs(:connection).returns(@connection) + @searcher.stubs(:class_attributes).returns([]) + @searcher.stubs(:parent_attribute).returns(nil) + @searcher.stubs(:search_base).returns(:yay) + @searcher.stubs(:search_filter).returns(:filter) + + @node = mock 'node' + @node.stubs(:fact_merge) + @name = "mynode" + Puppet::Node.stubs(:new).with(@name).returns(@node) + end +end + +describe Puppet::Indirector::Ldap::Node, " when searching for nodes" do + include LdapNodeSearching + + it "should return nil if no results are found in ldap" do + @connection.stubs(:search) + @searcher.find("mynode").should be_nil + end + + it "should return a node object if results are found in ldap" do + @entry.stubs(:to_hash).returns({}) + @searcher.find("mynode").should equal(@node) + end + + it "should deduplicate class values" do + @entry.stubs(:to_hash).returns({}) + @searcher.stubs(:class_attributes).returns(%w{one two}) + @entry.stubs(:vals).with("one").returns(%w{a b}) + @entry.stubs(:vals).with("two").returns(%w{b c}) + @node.expects(:classes=).with(%w{a b c}) + @searcher.find("mynode") + end + + it "should add any values stored in the class_attributes attributes to the node classes" do + @entry.stubs(:to_hash).returns({}) + @searcher.stubs(:class_attributes).returns(%w{one two}) + @entry.stubs(:vals).with("one").returns(%w{a b}) + @entry.stubs(:vals).with("two").returns(%w{c d}) + @node.expects(:classes=).with(%w{a b c d}) + @searcher.find("mynode") + end + + it "should add all entry attributes as node parameters" do + @entry.stubs(:to_hash).returns("one" => ["two"], "three" => ["four"]) + @node.expects(:parameters=).with("one" => "two", "three" => "four") + @searcher.find("mynode") + end + + it "should retain false parameter values" do + @entry.stubs(:to_hash).returns("one" => [false]) + @node.expects(:parameters=).with("one" => false) + @searcher.find("mynode") + end + + it "should turn single-value parameter value arrays into single non-arrays" do + @entry.stubs(:to_hash).returns("one" => ["a"]) + @node.expects(:parameters=).with("one" => "a") + @searcher.find("mynode") + end + + it "should keep multi-valued parametes as arrays" do + @entry.stubs(:to_hash).returns("one" => ["a", "b"]) + @node.expects(:parameters=).with("one" => ["a", "b"]) + @searcher.find("mynode") + end +end + +describe Puppet::Indirector::Ldap::Node, " when a parent node exists" do + include LdapNodeSearching + + before do + @parent = mock 'parent' + @parent_parent = mock 'parent_parent' + + @searcher.meta_def(:search_filter) do |name| + return name + end + @connection.stubs(:search).with { |*args| args[2] == @name }.yields(@entry) + @connection.stubs(:search).with { |*args| args[2] == 'parent' }.yields(@parent) + @connection.stubs(:search).with { |*args| args[2] == 'parent_parent' }.yields(@parent_parent) + + @searcher.stubs(:parent_attribute).returns(:parent) + end + + it "should add any parent classes to the node's classes" do + @entry.stubs(:to_hash).returns({}) + @entry.stubs(:vals).with(:parent).returns(%w{parent}) + @entry.stubs(:vals).with("one").returns(%w{a b}) + + @parent.stubs(:to_hash).returns({}) + @parent.stubs(:vals).with("one").returns(%w{c d}) + @parent.stubs(:vals).with(:parent).returns(nil) + + @searcher.stubs(:class_attributes).returns(%w{one}) + @node.expects(:classes=).with(%w{a b c d}) + @searcher.find("mynode") + end + + it "should add any parent parameters to the node's parameters" do + @entry.stubs(:to_hash).returns("one" => "two") + @entry.stubs(:vals).with(:parent).returns(%w{parent}) + + @parent.stubs(:to_hash).returns("three" => "four") + @parent.stubs(:vals).with(:parent).returns(nil) + + @node.expects(:parameters=).with("one" => "two", "three" => "four") + @searcher.find("mynode") + end + + it "should prefer node parameters over parent parameters" do + @entry.stubs(:to_hash).returns("one" => "two") + @entry.stubs(:vals).with(:parent).returns(%w{parent}) + + @parent.stubs(:to_hash).returns("one" => "three") + @parent.stubs(:vals).with(:parent).returns(nil) + + @node.expects(:parameters=).with("one" => "two") + @searcher.find("mynode") + end + + it "should recursively look up parent information" do + @entry.stubs(:to_hash).returns("one" => "two") + @entry.stubs(:vals).with(:parent).returns(%w{parent}) + + @parent.stubs(:to_hash).returns("three" => "four") + @parent.stubs(:vals).with(:parent).returns(['parent_parent']) + + @parent_parent.stubs(:to_hash).returns("five" => "six") + @parent_parent.stubs(:vals).with(:parent).returns(nil) + @parent_parent.stubs(:vals).with(:parent).returns(nil) + + @node.expects(:parameters=).with("one" => "two", "three" => "four", "five" => "six") + @searcher.find("mynode") + end + + it "should not allow loops in parent declarations" do + @entry.stubs(:to_hash).returns("one" => "two") + @entry.stubs(:vals).with(:parent).returns(%w{parent}) + + @parent.stubs(:to_hash).returns("three" => "four") + @parent.stubs(:vals).with(:parent).returns([@name]) + proc { @searcher.find("mynode") }.should raise_error(ArgumentError) + end +end + +describe Puppet::Indirector::Ldap::Node, " when developing the search query" do + before do + @searcher = Puppet::Indirector::Ldap::Node.new + end + + it "should return the value of the :ldapclassattrs split on commas as the class attributes" do + Puppet.stubs(:[]).with(:ldapclassattrs).returns("one,two") + @searcher.class_attributes.should == %w{one two} + end + + it "should return nil as the parent attribute if the :ldapparentattr is set to an empty string" do + Puppet.stubs(:[]).with(:ldapparentattr).returns("") + @searcher.parent_attribute.should be_nil + end + + it "should return the value of the :ldapparentattr as the parent attribute" do + Puppet.stubs(:[]).with(:ldapparentattr).returns("pere") + @searcher.parent_attribute.should == "pere" + end + + it "should use the value of the :ldapstring as the search filter" do + Puppet.stubs(:[]).with(:ldapstring).returns("mystring") + @searcher.search_filter("testing").should == "mystring" + end + + it "should replace '%s' with the node name in the search filter if it is present" do + Puppet.stubs(:[]).with(:ldapstring).returns("my%sstring") + @searcher.search_filter("testing").should == "mytestingstring" + end + + it "should not modify the global :ldapstring when replacing '%s' in the search filter" do + filter = mock 'filter' + filter.expects(:include?).with("%s").returns(true) + filter.expects(:gsub).with("%s", "testing").returns("mynewstring") + Puppet.stubs(:[]).with(:ldapstring).returns(filter) + @searcher.search_filter("testing").should == "mynewstring" + end +end + +describe Puppet::Indirector::Ldap::Node, " when deciding attributes to search for" do + before do + @searcher = Puppet::Indirector::Ldap::Node.new + end + + it "should use 'nil' if the :ldapattrs setting is 'all'" do + Puppet.stubs(:[]).with(:ldapattrs).returns("all") + @searcher.search_attributes.should be_nil + end + + it "should split the value of :ldapattrs on commas and use the result as the attribute list" do + Puppet.stubs(:[]).with(:ldapattrs).returns("one,two") + @searcher.stubs(:class_attributes).returns([]) + @searcher.stubs(:parent_attribute).returns(nil) + @searcher.search_attributes.should == %w{one two} + end + + it "should add the class attributes to the search attributes if not returning all attributes" do + Puppet.stubs(:[]).with(:ldapattrs).returns("one,two") + @searcher.stubs(:class_attributes).returns(%w{three four}) + @searcher.stubs(:parent_attribute).returns(nil) + # Sort them so i don't have to care about return order + @searcher.search_attributes.sort.should == %w{one two three four}.sort + end + + it "should add the parent attribute to the search attributes if not returning all attributes" do + Puppet.stubs(:[]).with(:ldapattrs).returns("one,two") + @searcher.stubs(:class_attributes).returns([]) + @searcher.stubs(:parent_attribute).returns("parent") + @searcher.search_attributes.sort.should == %w{one two parent}.sort + end + + it "should not add nil parent attributes to the search attributes" do + Puppet.stubs(:[]).with(:ldapattrs).returns("one,two") + @searcher.stubs(:class_attributes).returns([]) + @searcher.stubs(:parent_attribute).returns(nil) + @searcher.search_attributes.should == %w{one two} + end +end diff --git a/spec/unit/indirector/memory.rb b/spec/unit/indirector/memory.rb new file mode 100755 index 000000000..ac6f055ce --- /dev/null +++ b/spec/unit/indirector/memory.rb @@ -0,0 +1,53 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../spec_helper' +require 'puppet/indirector/memory' + +describe "A Memory Terminus", :shared => true do + it "should find no instances by default" do + @searcher.find(@name).should be_nil + end + + it "should be able to find instances that were previously saved" do + @searcher.save(@instance) + @searcher.find(@name).should equal(@instance) + end + + it "should replace existing saved instances when a new instance with the same name is saved" do + @searcher.save(@instance) + two = stub 'second', :name => @name + @searcher.save(two) + @searcher.find(@name).should equal(two) + end + + it "should be able to remove previously saved instances" do + @searcher.save(@instance) + @searcher.destroy(@instance) + @searcher.find(@name).should be_nil + end + + it "should fail when asked to destroy an instance that does not exist" do + proc { @searcher.destroy(@instance) }.should raise_error(ArgumentError) + end +end + +describe Puppet::Indirector::Memory do + it_should_behave_like "A Memory Terminus" + + before do + Puppet::Indirector::Terminus.stubs(:register_terminus_class) + @model = mock 'model' + @indirection = stub 'indirection', :name => :mystuff, :register_terminus_type => nil, :model => @model + Puppet::Indirector::Indirection.stubs(:instance).returns(@indirection) + + @memory_class = Class.new(Puppet::Indirector::Memory) do + def self.to_s + "Testing" + end + end + + @searcher = @memory_class.new + @name = "me" + @instance = stub 'instance', :name => @name + end +end diff --git a/spec/unit/indirector/memory/node.rb b/spec/unit/indirector/memory/node.rb new file mode 100755 index 000000000..cba4af53a --- /dev/null +++ b/spec/unit/indirector/memory/node.rb @@ -0,0 +1,19 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../../spec_helper' + +require 'puppet/indirector/memory/node' + +# All of our behaviour is described here, so we always have to +# include it. +require 'unit/indirector/memory' + +describe Puppet::Indirector::Memory::Node do + before do + @name = "me" + @searcher = Puppet::Indirector::Memory::Node.new + @instance = stub 'instance', :name => @name + end + + it_should_behave_like "A Memory Terminus" +end diff --git a/spec/unit/indirector/null.rb b/spec/unit/indirector/null.rb new file mode 100755 index 000000000..9e1dcb07c --- /dev/null +++ b/spec/unit/indirector/null.rb @@ -0,0 +1,27 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../spec_helper' +require 'puppet/indirector/null' + +describe Puppet::Indirector::Null do + before do + Puppet::Indirector::Terminus.stubs(:register_terminus_class) + @model = mock 'model' + @indirection = stub 'indirection', :name => :mystuff, :register_terminus_type => nil, :model => @model + Puppet::Indirector::Indirection.stubs(:instance).returns(@indirection) + + @null_class = Class.new(Puppet::Indirector::Null) do + def self.to_s + "Testing" + end + end + + @searcher = @null_class.new + end + + it "should return return an instance of the indirected model" do + object = mock 'object' + @model.expects(:new).with("yay").returns object + @searcher.find("yay").should equal(object) + end +end diff --git a/spec/unit/indirector/null/node.rb b/spec/unit/indirector/null/node.rb new file mode 100755 index 000000000..c589e5820 --- /dev/null +++ b/spec/unit/indirector/null/node.rb @@ -0,0 +1,18 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../../spec_helper' + +require 'puppet/indirector/null/node' + +describe Puppet::Indirector::Null::Node do + before do + @searcher = Puppet::Indirector::Null::Node.new + end + + it "should call node_merge() on the returned node" do + node = mock 'node' + Puppet::Node.expects(:new).with("mynode").returns(node) + node.expects(:fact_merge) + @searcher.find("mynode") + end +end diff --git a/spec/unit/indirector/terminus.rb b/spec/unit/indirector/terminus.rb new file mode 100755 index 000000000..dc86cf315 --- /dev/null +++ b/spec/unit/indirector/terminus.rb @@ -0,0 +1,226 @@ +require File.dirname(__FILE__) + '/../../spec_helper' +require 'puppet/defaults' +require 'puppet/indirector' + +describe Puppet::Indirector::Terminus do + before do + Puppet::Indirector::Terminus.stubs(:register_terminus_class) + + @indirection = stub 'indirection', :name => :mystuff, :register_terminus_type => nil + Puppet::Indirector::Indirection.stubs(:instance).with(:mystuff).returns(@indirection) + @abstract_terminus = Class.new(Puppet::Indirector::Terminus) do + def self.to_s + "Abstract" + end + end + @terminus = Class.new(@abstract_terminus) do + def self.to_s + "Terminus::Type::MyStuff" + end + end + end + + it "should provide a method for setting terminus class documentation" do + @terminus.should respond_to(:desc) + end + + it "should support a class-level name attribute" do + @terminus.should respond_to(:name) + end + + it "should support a class-level indirection attribute" do + @terminus.should respond_to(:indirection) + end + + it "should support a class-level terminus-type attribute" do + @terminus.should respond_to(:terminus_type) + end + + it "should support a class-level model attribute" do + @terminus.should respond_to(:model) + end + + it "should accept indirection instances as its indirection" do + indirection = stub 'indirection', :is_a? => true, :register_terminus_type => nil + proc { @terminus.indirection = indirection }.should_not raise_error + @terminus.indirection.should equal(indirection) + end + + it "should look up indirection instances when only a name has been provided" do + indirection = mock 'indirection' + Puppet::Indirector::Indirection.expects(:instance).with(:myind).returns(indirection) + @terminus.indirection = :myind + @terminus.indirection.should equal(indirection) + end + + it "should fail when provided a name that does not resolve to an indirection" do + Puppet::Indirector::Indirection.expects(:instance).with(:myind).returns(nil) + proc { @terminus.indirection = :myind }.should raise_error(ArgumentError) + + # It shouldn't overwrite our existing one (or, more normally, it shouldn't set + # anything). + @terminus.indirection.should equal(@indirection) + end +end + +# LAK: This could reasonably be in the Indirection instances, too. It doesn't make +# a whole heckuva lot of difference, except that with the instance loading in +# the Terminus base class, we have to have a check to see if we're already +# instance-loading a given terminus class type. +describe Puppet::Indirector::Terminus, " when managing terminus classes" do + it "should provide a method for registering terminus classes" do + Puppet::Indirector::Terminus.should respond_to(:register_terminus_class) + end + + it "should provide a method for returning terminus classes by name and type" do + terminus = stub 'terminus_type', :terminus_type => :abstract, :name => :whatever + Puppet::Indirector::Terminus.register_terminus_class(terminus) + Puppet::Indirector::Terminus.terminus_class(:abstract, :whatever).should equal(terminus) + end + + it "should set up autoloading for any terminus class types requested" do + Puppet::Indirector::Terminus.expects(:instance_load).with(:test2, "puppet/indirector/test2") + Puppet::Indirector::Terminus.terminus_class(:test2, :whatever) + end + + it "should load terminus classes that are not found" do + # Set up instance loading; it would normally happen automatically + Puppet::Indirector::Terminus.instance_load :test1, "puppet/indirector/test1" + Puppet::Indirector::Terminus.instance_loader(:test1).expects(:load).with(:yay) + Puppet::Indirector::Terminus.terminus_class(:test1, :yay) + end + + it "should fail when no indirection can be found" do + Puppet::Indirector::Indirection.expects(:instance).with(:myindirection).returns(nil) + + @abstract_terminus = Class.new(Puppet::Indirector::Terminus) do + def self.to_s + "Abstract" + end + end + proc { + @terminus = Class.new(@abstract_terminus) do + def self.to_s + "MyIndirection" + end + end + }.should raise_error(ArgumentError) + end + + it "should register the terminus class with the terminus base class" do + Puppet::Indirector::Terminus.expects(:register_terminus_class).with do |type| + type.terminus_type == :abstract and type.name == :myindirection + end + @indirection = stub 'indirection', :name => :myind, :register_terminus_type => nil + Puppet::Indirector::Indirection.expects(:instance).with(:myindirection).returns(@indirection) + + @abstract_terminus = Class.new(Puppet::Indirector::Terminus) do + def self.to_s + "Abstract" + end + end + + @terminus = Class.new(@abstract_terminus) do + def self.to_s + "MyIndirection" + end + end + end +end + +describe Puppet::Indirector::Terminus, " when creating terminus class types" do + before do + Puppet::Indirector::Terminus.stubs(:register_terminus_class) + @subclass = Class.new(Puppet::Indirector::Terminus) do + def self.to_s + "Puppet::Indirector::Terminus::MyTermType" + end + end + end + + it "should set the name of the abstract subclass to be its class constant" do + @subclass.name.should equal(:mytermtype) + end + + it "should mark abstract terminus types as such" do + @subclass.should be_abstract_terminus + end + + it "should not allow instances of abstract subclasses to be created" do + proc { @subclass.new }.should raise_error(Puppet::DevError) + end +end + +describe Puppet::Indirector::Terminus, " when creating terminus classes" do + before do + Puppet::Indirector::Terminus.stubs(:register_terminus_class) + + @indirection = stub 'indirection', :name => :myind, :register_terminus_type => nil + Puppet::Indirector::Indirection.expects(:instance).with(:myindirection).returns(@indirection) + + @abstract_terminus = Class.new(Puppet::Indirector::Terminus) do + def self.to_s + "Abstract" + end + end + @terminus = Class.new(@abstract_terminus) do + def self.to_s + "MyIndirection" + end + end + end + + it "should associate the subclass with an indirection based on the subclass constant" do + @terminus.indirection.should equal(@indirection) + end + + it "should set the subclass's type to the abstract terminus name" do + @terminus.terminus_type.should == :abstract + end + + it "should set the subclass's name to the indirection name" do + @terminus.name.should == :myindirection + end + + it "should set the subclass's model to the indirection model" do + @indirection.expects(:model).returns :yay + @terminus.model.should == :yay + end +end + +describe Puppet::Indirector::Terminus, " when a terminus instance" do + before do + Puppet::Indirector::Terminus.stubs(:register_terminus_class) + @indirection = stub 'indirection', :name => :myyaml, :register_terminus_type => nil + Puppet::Indirector::Indirection.stubs(:instance).with(:mystuff).returns(@indirection) + @abstract_terminus = Class.new(Puppet::Indirector::Terminus) do + def self.to_s + "Abstract" + end + end + @terminus_class = Class.new(@abstract_terminus) do + def self.to_s + "MyStuff" + end + end + @terminus_class.name = :test + @terminus = @terminus_class.new + end + + it "should return the class's name as its name" do + @terminus.name.should == :test + end + + it "should return the class's indirection as its indirection" do + @terminus.indirection.should equal(@indirection) + end + + it "should set the instances's type to the abstract terminus type's name" do + @terminus.terminus_type.should == :abstract + end + + it "should set the instances's model to the indirection's model" do + @indirection.expects(:model).returns :yay + @terminus.model.should == :yay + end +end diff --git a/spec/unit/indirector/yaml.rb b/spec/unit/indirector/yaml.rb new file mode 100755 index 000000000..9e1d65e49 --- /dev/null +++ b/spec/unit/indirector/yaml.rb @@ -0,0 +1,104 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../spec_helper' + +require 'puppet/indirector/yaml' + +module YamlTesting + def setup + @indirection = stub 'indirection', :name => :myyaml, :register_terminus_type => nil + Puppet::Indirector::Indirection.stubs(:instance).with(:myyaml).returns(@indirection) + @store_class = Class.new(Puppet::Indirector::Yaml) do + def self.to_s + "MyYaml" + end + end + @store = @store_class.new + + @subject = Object.new + @subject.metaclass.send(:attr_accessor, :name) + @subject.name = :me + + @dir = "/what/ever" + Puppet.settings.stubs(:use) + Puppet.settings.stubs(:value).with(:yamldir).returns(@dir) + end +end + +describe Puppet::Indirector::Yaml, " when choosing file location" do + include YamlTesting + + it "should store all files in a single file root set in the Puppet defaults" do + @store.send(:path, :me).should =~ %r{^#{@dir}} + end + + it "should use the terminus name for choosing the subdirectory" do + @store.send(:path, :me).should =~ %r{^#{@dir}/myyaml} + end + + it "should use the object's name to determine the file name" do + @store.send(:path, :me).should =~ %r{me.yaml$} + end +end + +describe Puppet::Indirector::Yaml, " when storing objects as YAML" do + include YamlTesting + + it "should only store objects that respond to :name" do + proc { @store.save(Object.new) }.should raise_error(ArgumentError) + end + + it "should convert Ruby objects to YAML and write them to disk" do + yaml = @subject.to_yaml + file = mock 'file' + path = @store.send(:path, @subject.name) + FileTest.expects(:exist?).with(File.dirname(path)).returns(true) + File.expects(:open).with(path, "w", 0660).yields(file) + file.expects(:print).with(yaml) + + @store.save(@subject) + end + + it "should create the indirection subdirectory if it does not exist" do + yaml = @subject.to_yaml + file = mock 'file' + path = @store.send(:path, @subject.name) + dir = File.dirname(path) + FileTest.expects(:exist?).with(dir).returns(false) + Dir.expects(:mkdir).with(dir) + File.expects(:open).with(path, "w", 0660).yields(file) + file.expects(:print).with(yaml) + + @store.save(@subject) + end +end + +describe Puppet::Indirector::Yaml, " when retrieving YAML" do + include YamlTesting + + it "should require the name of the object to retrieve" do + proc { @store.find(nil) }.should raise_error(ArgumentError) + end + + it "should read YAML in from disk and convert it to Ruby objects" do + path = @store.send(:path, @subject.name) + + yaml = @subject.to_yaml + FileTest.expects(:exist?).with(path).returns(true) + File.expects(:read).with(path).returns(yaml) + + @store.find(@subject.name).instance_variable_get("@name").should == :me + end + + it "should fail coherently when the stored YAML is invalid" do + path = @store.send(:path, @subject.name) + + # Something that will fail in yaml + yaml = "--- !ruby/object:Hash" + + FileTest.expects(:exist?).with(path).returns(true) + File.expects(:read).with(path).returns(yaml) + + proc { @store.find(@subject.name) }.should raise_error(Puppet::Error) + end +end diff --git a/spec/unit/indirector/yaml/facts.rb b/spec/unit/indirector/yaml/facts.rb new file mode 100755 index 000000000..f1256cfa4 --- /dev/null +++ b/spec/unit/indirector/yaml/facts.rb @@ -0,0 +1,26 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../../spec_helper' + +require 'puppet/node/facts' +require 'puppet/indirector/yaml/facts' + +describe Puppet::Indirector::Yaml::Facts do + it "should be a subclass of the Yaml terminus" do + Puppet::Indirector::Yaml::Facts.superclass.should equal(Puppet::Indirector::Yaml) + end + + + it "should have documentation" do + Puppet::Indirector::Yaml::Facts.doc.should_not be_nil + end + + it "should be registered with the facts indirection" do + indirection = Puppet::Indirector::Indirection.instance(:facts) + Puppet::Indirector::Yaml::Facts.indirection.should equal(indirection) + end + + it "should have its name set to :facts" do + Puppet::Indirector::Yaml::Facts.name.should == :facts + end +end diff --git a/spec/unit/node/configuration.rb b/spec/unit/node/configuration.rb index 4429fe3a3..8ba55f50c 100755 --- a/spec/unit/node/configuration.rb +++ b/spec/unit/node/configuration.rb @@ -133,3 +133,340 @@ describe Puppet::Node::Configuration, " when extracting transobjects" do botarray.include?(:botres).should be_true end end + +describe Puppet::Node::Configuration, " when functioning as a resource container" do + before do + @config = Puppet::Node::Configuration.new("host") + @one = stub 'resource1', :ref => "Me[you]", :configuration= => nil + @two = stub 'resource2', :ref => "Me[him]", :configuration= => nil + @dupe = stub 'resource3', :ref => "Me[you]", :configuration= => nil + end + + it "should provide a method to add one or more resources" do + @config.add_resource @one, @two + @config.resource(@one.ref).should equal(@one) + @config.resource(@two.ref).should equal(@two) + end + + it "should make all vertices available by resource reference" do + @config.add_resource(@one) + @config.resource(@one.ref).should equal(@one) + @config.vertices.find { |r| r.ref == @one.ref }.should equal(@one) + end + + it "should not allow two resources with the same resource reference" do + @config.add_resource(@one) + proc { @config.add_resource(@dupe) }.should raise_error(ArgumentError) + end + + it "should not store objects that do not respond to :ref" do + proc { @config.add_resource("thing") }.should raise_error(ArgumentError) + end + + it "should remove all resources when asked" do + @config.add_resource @one + @config.add_resource @two + @one.expects :remove + @two.expects :remove + @config.clear(true) + end + + it "should support a mechanism for finishing resources" do + @one.expects :finish + @two.expects :finish + @config.add_resource @one + @config.add_resource @two + + @config.finalize + end + + it "should optionally support an initialization block and should finalize after such blocks" do + @one.expects :finish + @two.expects :finish + config = Puppet::Node::Configuration.new("host") do |conf| + conf.add_resource @one + conf.add_resource @two + end + end + + it "should inform the resource that it is the resource's configuration" do + @one.expects(:configuration=).with(@config) + @config.add_resource @one + end + + it "should be able to find resources by reference" do + @config.add_resource @one + @config.resource(@one.ref).should equal(@one) + end + + it "should be able to find resources by reference or by type/title tuple" do + @config.add_resource @one + @config.resource("me", "you").should equal(@one) + end + + it "should have a mechanism for removing resources" do + @config.add_resource @one + @one.expects :remove + @config.remove_resource(@one) + @config.resource(@one.ref).should be_nil + @config.vertex?(@one).should be_false + end +end + +module ApplyingConfigurations + def setup + @config = Puppet::Node::Configuration.new("host") + + @config.retrieval_duration = Time.now + @transaction = mock 'transaction' + Puppet::Transaction.stubs(:new).returns(@transaction) + @transaction.stubs(:evaluate) + @transaction.stubs(:cleanup) + @transaction.stubs(:addtimes) + end +end + +describe Puppet::Node::Configuration, " when applying" do + include ApplyingConfigurations + + it "should create and evaluate a transaction" do + @transaction.expects(:evaluate) + @config.apply + end + + it "should provide the configuration time to the transaction" do + @transaction.expects(:addtimes).with do |arg| + arg[:config_retrieval].should be_instance_of(Time) + true + end + @config.apply + end + + it "should clean up the transaction" do + @transaction.expects :cleanup + @config.apply + end + + it "should return the transaction" do + @config.apply.should equal(@transaction) + end + + it "should yield the transaction if a block is provided" do + @config.apply do |trans| + trans.should equal(@transaction) + end + end + + it "should default to not being a host configuration" do + @config.host_config.should be_nil + end + + it "should pass supplied tags on to the transaction" do + @transaction.expects(:tags=).with(%w{one two}) + @config.apply(:tags => %w{one two}) + end + + it "should set ignoreschedules on the transaction if specified in apply()" do + @transaction.expects(:ignoreschedules=).with(true) + @config.apply(:ignoreschedules => true) + end +end + +describe Puppet::Node::Configuration, " when applying host configurations" do + include ApplyingConfigurations + + # super() doesn't work in the setup method for some reason + before do + @config.host_config = true + end + + it "should send a report if reporting is enabled" do + Puppet[:report] = true + @transaction.expects :send_report + @config.apply + end + + it "should send a report if report summaries are enabled" do + Puppet[:summarize] = true + @transaction.expects :send_report + @config.apply + end + + it "should initialize the state database before applying a configuration" do + Puppet::Util::Storage.expects(:load) + + # Short-circuit the apply, so we know we're loading before the transaction + Puppet::Transaction.expects(:new).raises ArgumentError + proc { @config.apply }.should raise_error(ArgumentError) + end + + it "should sync the state database after applying" do + Puppet::Util::Storage.expects(:store) + @config.apply + end + + after { Puppet.settings.clear } +end + +describe Puppet::Node::Configuration, " when applying non-host configurations" do + include ApplyingConfigurations + + before do + @config.host_config = false + end + + it "should never send reports" do + Puppet[:report] = true + Puppet[:summarize] = true + @transaction.expects(:send_report).never + @config.apply + end + + it "should never modify the state database" do + Puppet::Util::Storage.expects(:load).never + Puppet::Util::Storage.expects(:store).never + @config.apply + end + + after { Puppet.settings.clear } +end + +describe Puppet::Node::Configuration, " when creating a relationship graph" do + before do + @config = Puppet::Node::Configuration.new("host") + @compone = Puppet::Type::Component.create :name => "one" + @comptwo = Puppet::Type::Component.create :name => "two", :require => ["class", "one"] + @file = Puppet::Type.type(:file) + @one = @file.create :path => "/one" + @two = @file.create :path => "/two" + @config.add_edge! @compone, @one + @config.add_edge! @comptwo, @two + + @three = @file.create :path => "/three" + @four = @file.create :path => "/four", :require => ["file", "/three"] + @five = @file.create :path => "/five" + @config.add_resource @compone, @comptwo, @one, @two, @three, @four, @five + @relationships = @config.relationship_graph + end + + it "should be able to create a relationship graph" do + @relationships.should be_instance_of(Puppet::Node::Configuration) + end + + it "should copy its host_config setting to the relationship graph" do + config = Puppet::Node::Configuration.new + config.host_config = true + config.relationship_graph.host_config.should be_true + end + + it "should not have any components" do + @relationships.vertices.find { |r| r.instance_of?(Puppet::Type::Component) }.should be_nil + end + + it "should have all non-component resources from the configuration" do + # The failures print out too much info, so i just do a class comparison + @relationships.vertex?(@five).should be_true + end + + it "should have all resource relationships set as edges" do + @relationships.edge?(@three, @four).should be_true + end + + it "should copy component relationships to all contained resources" do + @relationships.edge?(@one, @two).should be_true + end + + it "should get removed when the configuration is cleaned up" do + @relationships.expects(:clear).with(false) + @config.clear + @config.instance_variable_get("@relationship_graph").should be_nil + end + + it "should create a new relationship graph after clearing the old one" do + @relationships.expects(:clear).with(false) + @config.clear + @config.relationship_graph.should be_instance_of(Puppet::Node::Configuration) + end + + it "should look up resources in the relationship graph if not found in the main configuration" do + five = stub 'five', :ref => "File[five]", :configuration= => nil + @relationships.add_resource five + @config.resource(five.ref).should equal(five) + end + + it "should provide a method to create additional resources that also registers the resource" do + args = {:name => "/yay", :ensure => :file} + resource = stub 'file', :ref => "File[/yay]", :configuration= => @config + Puppet::Type.type(:file).expects(:create).with(args).returns(resource) + @config.create_resource :file, args + @config.resource("File[/yay]").should equal(resource) + end + + it "should provide a mechanism for creating implicit resources" do + args = {:name => "/yay", :ensure => :file} + resource = stub 'file', :ref => "File[/yay]", :configuration= => @config + Puppet::Type.type(:file).expects(:create).with(args).returns(resource) + resource.expects(:implicit=).with(true) + @config.create_implicit_resource :file, args + @config.resource("File[/yay]").should equal(resource) + end + + it "should remove resources created mid-transaction" do + args = {:name => "/yay", :ensure => :file} + resource = stub 'file', :ref => "File[/yay]", :configuration= => @config + @transaction = mock 'transaction' + Puppet::Transaction.stubs(:new).returns(@transaction) + @transaction.stubs(:evaluate) + @transaction.stubs(:cleanup) + @transaction.stubs(:addtimes) + Puppet::Type.type(:file).expects(:create).with(args).returns(resource) + resource.expects :remove + @config.apply do |trans| + @config.create_resource :file, args + @config.resource("File[/yay]").should equal(resource) + end + @config.resource("File[/yay]").should be_nil + end + + it "should remove resources from the relationship graph if it exists" do + @config.remove_resource(@one) + @config.relationship_graph.vertex?(@one).should be_false + end + + after do + Puppet::Type.allclear + end +end + +describe Puppet::Node::Configuration, " when writing dot files" do + before do + @config = Puppet::Node::Configuration.new("host") + @name = :test + @file = File.join(Puppet[:graphdir], @name.to_s + ".dot") + end + it "should only write when it is a host configuration" do + File.expects(:open).with(@file).never + @config.host_config = false + Puppet[:graph] = true + @config.write_graph(@name) + end + + it "should only write when graphing is enabled" do + File.expects(:open).with(@file).never + @config.host_config = true + Puppet[:graph] = false + @config.write_graph(@name) + end + + it "should write a dot file based on the passed name" do + File.expects(:open).with(@file, "w").yields(stub("file", :puts => nil)) + @config.expects(:to_dot).with("name" => @name.to_s.capitalize) + @config.host_config = true + Puppet[:graph] = true + @config.write_graph(@name) + end + + after do + Puppet.settings.clear + end +end diff --git a/spec/unit/node/facts.rb b/spec/unit/node/facts.rb new file mode 100755 index 000000000..61f05a2b2 --- /dev/null +++ b/spec/unit/node/facts.rb @@ -0,0 +1,39 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../spec_helper' + +require 'puppet/node/facts' + +describe Puppet::Node::Facts, " when indirecting" do + before do + @terminus = mock 'terminus' + Puppet::Node::Facts.stubs(:indirection).returns(@terminus) + + # We have to clear the cache so that the facts ask for our terminus stub, + # instead of anything that might be cached. + Puppet::Indirector::Indirection.clear_cache + @facts = Puppet::Node::Facts.new("me", "one" => "two") + end + + it "should redirect to the specified fact store for retrieval" do + @terminus.expects(:find).with(:my_facts) + Puppet::Node::Facts.find(:my_facts) + end + + it "should redirect to the specified fact store for storage" do + @terminus.expects(:save).with(@facts) + @facts.save + end + + after do + mocha_verify + Puppet::Indirector::Indirection.clear_cache + end +end + +describe Puppet::Node::Facts, " when storing and retrieving" do + it "should add metadata to the facts" do + facts = Puppet::Node::Facts.new("me", "one" => "two", "three" => "four") + facts.values[:_timestamp].should be_instance_of(Time) + end +end diff --git a/spec/unit/other/node.rb b/spec/unit/node/node.rb index 66d5ba9d7..fe5d2be8b 100755 --- a/spec/unit/other/node.rb +++ b/spec/unit/node/node.rb @@ -11,6 +11,10 @@ describe Puppet::Node, " when initializing" do @node.name.should == "testnode" end + it "should not allow nil node names" do + proc { Puppet::Node.new(nil) }.should raise_error(ArgumentError) + end + it "should default to an empty parameter hash" do @node.parameters.should == {} end @@ -62,7 +66,7 @@ describe Puppet::Node, " when returning the environment" do end it "should return the central environment if there is no environment fact nor explicit environment" do - Puppet.config.expects(:[]).with(:environment).returns(:centralenv) + Puppet.settings.expects(:[]).with(:environment).returns(:centralenv) @node.environment.should == :centralenv end @@ -77,7 +81,7 @@ describe Puppet::Node, " when returning the environment" do end it "should not use an explicit environment that is an empty string" do - Puppet.config.expects(:[]).with(:environment).returns(nil) + Puppet.settings.expects(:[]).with(:environment).returns(nil) @node.environment.should be_nil end end @@ -85,17 +89,47 @@ end describe Puppet::Node, " when merging facts" do before do @node = Puppet::Node.new("testnode") + Puppet::Node::Facts.stubs(:find).with(@node.name).returns(Puppet::Node::Facts.new(@node.name, "one" => "c", "two" => "b")) end it "should prefer parameters already set on the node over facts from the node" do @node.parameters = {"one" => "a"} - @node.fact_merge("one" => "c") + @node.fact_merge @node.parameters["one"].should == "a" end it "should add passed parameters to the parameter list" do @node.parameters = {"one" => "a"} - @node.fact_merge("two" => "b") + @node.fact_merge @node.parameters["two"].should == "b" end + + it "should accept arbitrary parameters to merge into its parameters" do + @node.parameters = {"one" => "a"} + @node.merge "two" => "three" + @node.parameters["two"].should == "three" + end +end + +describe Puppet::Node, " when indirecting" do + before do + @terminus = mock 'terminus' + Puppet::Node.stubs(:indirection).returns(@terminus) + end + + it "should redirect to the specified node source" do + @terminus.expects(:find).with(:my_node.to_s) + Puppet::Node.find(:my_node.to_s) + end + + after do + Puppet::Indirector::Indirection.clear_cache + end +end + +describe Puppet::Node do + # LAK:NOTE This is used to keep track of when a given node has connected, + # so we can report on nodes that do not appear to connecting to the + # central server. + it "should provide a method for noting that the node has connected" end diff --git a/spec/unit/node/searching.rb b/spec/unit/node/searching.rb new file mode 100755 index 000000000..e747996e4 --- /dev/null +++ b/spec/unit/node/searching.rb @@ -0,0 +1,79 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../spec_helper' +require 'puppet/node/searching' +require 'puppet/node/facts' + +describe Puppet::Node::Searching, " when searching for nodes" do + before do + @searcher = Object.new + @searcher.extend(Puppet::Node::Searching) + @facts = Puppet::Node::Facts.new("foo", "hostname" => "yay", "domain" => "domain.com") + @node = Puppet::Node.new("foo") + Puppet::Node::Facts.stubs(:find).with("foo").returns(@facts) + end + + it "should search for the node by its key first" do + names = [] + @searcher.expects(:find).with do |name| + names << name + names == %w{foo} + end.returns(@node) + @searcher.search("foo").should equal(@node) + end + + it "should return the first node found using the generated list of names" do + names = [] + @searcher.expects(:find).with("foo").returns(nil) + @searcher.expects(:find).with("yay.domain.com").returns(@node) + @searcher.search("foo").should equal(@node) + end + + it "should search for the rest of the names inversely by length" do + names = [] + @facts.values["fqdn"] = "longer.than.the.normal.fqdn.com" + @searcher.stubs(:find).with do |name| + names << name + end + @searcher.search("foo") + # Strip off the key + names.shift + + # And the 'default' + names.pop + + length = 100 + names.each do |name| + (name.length < length).should be_true + length = name.length + end + end + + it "should attempt to find a default node if no names are found" do + names = [] + @searcher.stubs(:find).with do |name| + names << name + end.returns(nil) + @searcher.search("foo") + names[-1].should == "default" + end + + it "should cache the nodes" do + @searcher.expects(:find).with("foo").returns(@node) + @searcher.search("foo").should equal(@node) + @searcher.search("foo").should equal(@node) + end + + it "should flush the node cache using the :filetimeout parameter" do + node2 = Puppet::Node.new("foo2") + Puppet[:filetimeout] = -1 + # I couldn't get this to work with :expects + @searcher.stubs(:find).returns(@node, node2).then.raises(ArgumentError) + @searcher.search("foo").should equal(@node) + @searcher.search("foo").should equal(node2) + end + + after do + Puppet.settings.clear + end +end diff --git a/spec/unit/other/checksum.rb b/spec/unit/other/checksum.rb new file mode 100755 index 000000000..6a63e833d --- /dev/null +++ b/spec/unit/other/checksum.rb @@ -0,0 +1,92 @@ +#!/usr/bin/env ruby +# +# Created by Luke Kanies on 2007-9-22. +# Copyright (c) 2007. All rights reserved. + +require File.dirname(__FILE__) + '/../../spec_helper' + +require 'puppet/checksum' + +describe Puppet::Checksum do + it "should have 'Checksum' and the checksum algorithm when converted to a string" do + inst = Puppet::Checksum.new("whatever", "md5") + inst.to_s.should == "Checksum<{md5}#{inst.checksum}>" + end + + it "should convert algorithm names to symbols when they are set after checksum creation" do + sum = Puppet::Checksum.new("whatever") + sum.algorithm = "md5" + sum.algorithm.should == :md5 + end + + it "should return the checksum as the name" do + sum = Puppet::Checksum.new("whatever") + sum.checksum.should == sum.name + end +end + +describe Puppet::Checksum, " when initializing" do + before do + @content = "this is some content" + @sum = Puppet::Checksum.new(@content) + end + + it "should require content" do + proc { Puppet::Checksum.new(nil) }.should raise_error(ArgumentError) + end + + it "should set the content appropriately" do + @sum.content.should == @content + end + + it "should calculate the checksum" do + require 'digest/md5' + Digest::MD5.expects(:hexdigest).with(@content).returns(:mychecksum) + @sum.checksum.should == :mychecksum + end + + it "should not calculate the checksum until it is asked for" do + require 'digest/md5' + Digest::MD5.expects(:hexdigest).never + sum = Puppet::Checksum.new(@content, :md5) + end + + it "should remove the old checksum value if the algorithm is changed" do + Digest::MD5.expects(:hexdigest).with(@content).returns(:oldsum) + oldsum = @sum.checksum + @sum.algorithm = :sha1 + Digest::SHA1.expects(:hexdigest).with(@content).returns(:newsum) + @sum.checksum.should == :newsum + end + + it "should default to 'md5' as the checksum algorithm if the algorithm is not in the name" do + @sum.algorithm.should == :md5 + end + + it "should support specifying the algorithm during initialization" do + sum = Puppet::Checksum.new(@content, :sha1) + sum.algorithm.should == :sha1 + end + + it "should fail when an unsupported algorithm is used" do + proc { Puppet::Checksum.new(@content, :nope) }.should raise_error(ArgumentError) + end +end + +describe Puppet::Checksum, " when using back-ends" do + it "should redirect using Puppet::Indirector" do + Puppet::Indirector::Indirection.instance(:checksum).model.should equal(Puppet::Checksum) + end + + it "should have a :save instance method" do + Puppet::Checksum.new("mysum").should respond_to(:save) + end + + it "should respond to :find" do + Puppet::Checksum.should respond_to(:find) + end + + it "should respond to :destroy" do + Puppet::Checksum.should respond_to(:destroy) + end +end diff --git a/spec/unit/other/modules.rb b/spec/unit/other/modules.rb index 0ab37aa9e..f53c43e89 100755 --- a/spec/unit/other/modules.rb +++ b/spec/unit/other/modules.rb @@ -5,10 +5,10 @@ require File.dirname(__FILE__) + '/../../spec_helper' describe Puppet::Module, " when building its search path" do include PuppetTest - it "should ignore unqualified paths in the search path" do + it "should fully qualify unqualified paths in the search path" do Puppet[:modulepath] = "something:/my/something" File.stubs(:directory?).returns(true) - Puppet::Module.modulepath.should == %w{/my/something} + Puppet::Module.modulepath.should == [File.join(Dir.getwd, 'something'), "/my/something"] end it "should ignore paths that do not exist" do @@ -26,7 +26,7 @@ describe Puppet::Module, " when building its search path" do end it "should use the environment-specific search path when a node environment is provided" do - Puppet.config.expects(:value).with(:modulepath, "myenv").returns("/mone:/mtwo") + Puppet.settings.expects(:value).with(:modulepath, "myenv").returns("/mone:/mtwo") File.stubs(:directory?).returns(true) Puppet::Module.modulepath("myenv").should == %w{/mone /mtwo} end @@ -82,30 +82,30 @@ describe Puppet::Module, " when searching for templates" do end it "should use the main templatedir if no module is found" do - Puppet.config.expects(:value).with(:templatedir, nil).returns("/my/templates") + Puppet.settings.expects(:value).with(:templatedir, nil).returns("/my/templates") Puppet::Module.expects(:find).with("mymod", nil).returns(nil) Puppet::Module.find_template("mymod/mytemplate").should == "/my/templates/mymod/mytemplate" end it "should return unqualified templates directly in the template dir" do - Puppet.config.expects(:value).with(:templatedir, nil).returns("/my/templates") + Puppet.settings.expects(:value).with(:templatedir, nil).returns("/my/templates") Puppet::Module.expects(:find).never Puppet::Module.find_template("mytemplate").should == "/my/templates/mytemplate" end it "should use the environment templatedir if no module is found and an environment is specified" do - Puppet.config.expects(:value).with(:templatedir, "myenv").returns("/myenv/templates") + Puppet.settings.expects(:value).with(:templatedir, "myenv").returns("/myenv/templates") Puppet::Module.expects(:find).with("mymod", "myenv").returns(nil) Puppet::Module.find_template("mymod/mytemplate", "myenv").should == "/myenv/templates/mymod/mytemplate" end it "should use the node environment if specified" do - Puppet.config.expects(:value).with(:modulepath, "myenv").returns("/my/templates") + Puppet.settings.expects(:value).with(:modulepath, "myenv").returns("/my/templates") File.stubs(:directory?).returns(true) Puppet::Module.find_template("mymod/envtemplate", "myenv").should == "/my/templates/mymod/templates/envtemplate" end - after { Puppet.config.clear } + after { Puppet.settings.clear } end describe Puppet::Module, " when searching for manifests" do @@ -125,27 +125,27 @@ describe Puppet::Module, " when searching for manifests" do end it "should use the node environment if specified" do - Puppet.config.expects(:value).with(:modulepath, "myenv").returns("/env/modules") + Puppet.settings.expects(:value).with(:modulepath, "myenv").returns("/env/modules") File.stubs(:directory?).returns(true) Dir.expects(:glob).with("/env/modules/mymod/manifests/envmanifest.pp").returns(%w{/env/modules/mymod/manifests/envmanifest.pp}) Puppet::Module.find_manifests("mymod/envmanifest.pp", :environment => "myenv").should == ["/env/modules/mymod/manifests/envmanifest.pp"] end it "should return all manifests matching the glob pattern" do - Puppet.config.expects(:value).with(:modulepath, nil).returns("/my/modules") + Puppet.settings.expects(:value).with(:modulepath, nil).returns("/my/modules") File.stubs(:directory?).returns(true) Dir.expects(:glob).with("/my/modules/mymod/manifests/yay/*.pp").returns(%w{/one /two}) Puppet::Module.find_manifests("mymod/yay/*.pp").should == %w{/one /two} end it "should default to the 'init.pp' file in the manifests directory" do - Puppet.config.expects(:value).with(:modulepath, nil).returns("/my/modules") + Puppet.settings.expects(:value).with(:modulepath, nil).returns("/my/modules") File.stubs(:directory?).returns(true) Dir.expects(:glob).with("/my/modules/mymod/manifests/init.pp").returns(%w{my manifest}) Puppet::Module.find_manifests("mymod").should == %w{my manifest} end - after { Puppet.config.clear } + after { Puppet.settings.clear } end describe Puppet::Module, " when returning files" do diff --git a/spec/unit/other/pgraph.rb b/spec/unit/other/pgraph.rb new file mode 100755 index 000000000..19809ac1e --- /dev/null +++ b/spec/unit/other/pgraph.rb @@ -0,0 +1,285 @@ +#!/usr/bin/env ruby +# +# Created by Luke Kanies on 2007-9-12. +# Copyright (c) 2006. All rights reserved. + +require File.dirname(__FILE__) + '/../../spec_helper' + +require 'puppet/util/graph' + +class Container + include Puppet::Util::Graph + include Enumerable + attr_accessor :name + def each + @children.each do |c| yield c end + end + + def initialize(name, ary) + @name = name + @children = ary + end + + def push(*ary) + ary.each { |c| @children.push(c)} + end + + def to_s + @name + end +end + +describe Puppet::PGraph do + before do + @graph = Puppet::PGraph.new + end + + it "should correctly clear vertices and edges when asked" do + @graph.add_edge!("a", "b") + @graph.add_vertex! "c" + @graph.clear + @graph.vertices.should be_empty + @graph.edges.should be_empty + end +end + +describe Puppet::PGraph, " when matching edges" do + before do + @graph = Puppet::PGraph.new + @event = Puppet::Event.new(:source => "a", :event => :yay) + @none = Puppet::Event.new(:source => "a", :event => :NONE) + + @edges = {} + @edges["a/b"] = Puppet::Relationship["a", "b", {:event => :yay, :callback => :refresh}] + @edges["a/c"] = Puppet::Relationship["a", "c", {:event => :yay, :callback => :refresh}] + @graph.add_edge!(@edges["a/b"]) + end + + it "should match edges whose source matches the source of the event" do + @graph.matching_edges([@event]).should == [@edges["a/b"]] + end + + it "should match always match nothing when the event is :NONE" do + @graph.matching_edges([@none]).should be_empty + end + + it "should match multiple edges" do + @graph.add_edge!(@edges["a/c"]) + @graph.matching_edges([@event]).sort.should == [@edges["a/b"], @edges["a/c"]].sort + end +end + +describe Puppet::PGraph, " when determining dependencies" do + before do + @graph = Puppet::PGraph.new + + @graph.add_edge!("a", "b") + @graph.add_edge!("a", "c") + @graph.add_edge!("b", "d") + end + + it "should find all dependents when they are on multiple levels" do + @graph.dependents("a").sort.should == %w{b c d}.sort + end + + it "should find single dependents" do + @graph.dependents("b").sort.should == %w{d}.sort + end + + it "should return an empty array when there are no dependents" do + @graph.dependents("c").sort.should == [].sort + end + + it "should find all dependencies when they are on multiple levels" do + @graph.dependencies("d").sort.should == %w{a b} + end + + it "should find single dependencies" do + @graph.dependencies("c").sort.should == %w{a} + end + + it "should return an empty array when there are no dependencies" do + @graph.dependencies("a").sort.should == [] + end +end + +describe Puppet::PGraph, " when splicing the relationship graph" do + def container_graph + @one = Container.new("one", %w{a b}) + @two = Container.new("two", ["c", "d"]) + @three = Container.new("three", ["i", "j"]) + @middle = Container.new("middle", ["e", "f", @two]) + @top = Container.new("top", ["g", "h", @middle, @one, @three]) + @empty = Container.new("empty", []) + + @contgraph = @top.to_graph + + # We have to add the container to the main graph, else it won't + # be spliced in the dependency graph. + @contgraph.add_vertex!(@empty) + end + + def dependency_graph + @depgraph = Puppet::PGraph.new + @contgraph.vertices.each do |v| + @depgraph.add_vertex(v) + end + + # We have to specify a relationship to our empty container, else it + # never makes it into the dep graph in the first place. + {@one => @two, "f" => "c", "h" => @middle, "c" => @empty}.each do |source, target| + @depgraph.add_edge!(source, target, :callback => :refresh) + end + end + + def splice + @depgraph.splice!(@contgraph, Container) + end + + before do + container_graph + dependency_graph + splice + end + + it "should not create a cyclic graph" do + @depgraph.should_not be_cyclic + end + + # This is the real heart of splicing -- replacing all containers in + # our relationship and exploding their relationships so that each + # relationship to a container gets copied to all of its children. + it "should remove all Container objects from the dependency graph" do + @depgraph.vertices.find_all { |v| v.is_a?(Container) }.should be_empty + end + + it "should add container relationships to contained objects" do + @contgraph.leaves(@middle).each do |leaf| + @depgraph.should be_edge("h", leaf) + end + end + + it "should explode container-to-container relationships, making edges between all respective contained objects" do + @one.each do |oobj| + @two.each do |tobj| + @depgraph.should be_edge(oobj, tobj) + end + end + end + + it "should no longer contain anything but the non-container objects" do + @depgraph.vertices.find_all { |v| ! v.is_a?(String) }.should be_empty + end + + it "should copy labels" do + @depgraph.edges.each do |edge| + edge.label.should == {:callback => :refresh} + end + end + + it "should not add labels to edges that have none" do + @depgraph.add_edge!(@two, @three) + splice + @depgraph.edge_label("c", "i").should == {} + end + + it "should copy labels over edges that have none" do + @depgraph.add_edge!("c", @three, {:callback => :refresh}) + splice + # And make sure the label got copied. + @depgraph.edge_label("c", "i").should == {:callback => :refresh} + end + + it "should not replace a label with a nil label" do + # Lastly, add some new label-less edges and make sure the label stays. + @depgraph.add_edge!(@middle, @three) + @depgraph.add_edge!("c", @three, {:callback => :refresh}) + splice + @depgraph.edge_label("c", "i").should == {:callback => :refresh} + end + + it "should copy labels to all created edges" do + @depgraph.add_edge!(@middle, @three) + @depgraph.add_edge!("c", @three, {:callback => :refresh}) + splice + @three.each do |child| + edge = @depgraph.edge_class.new("c", child) + @depgraph.should be_edge(edge) + @depgraph[edge].should == {:callback => :refresh} + end + end +end + +# Labels in this graph are used for managing relationships, +# including callbacks, so they're quite important. +describe Puppet::PGraph, " when managing labels" do + before do + @graph = Puppet::PGraph.new + @label = {:callback => :yay} + end + + it "should return nil for edges with no label" do + @graph.add_edge!(:a, :b) + @graph.edge_label(:a, :b).should be_nil + end + + it "should just return empty label hashes" do + @graph.add_edge!(:a, :b, {}) + @graph.edge_label(:a, :b).should == {} + end + + it "should consider empty label hashes to be nil when copying" do + @graph.add_edge!(:a, :b) + @graph.copy_label(:a, :b, {}) + @graph.edge_label(:a, :b).should be_nil + end + + it "should return label hashes" do + @graph.add_edge!(:a, :b, @label) + @graph.edge_label(:a, :b).should == @label + end + + it "should replace nil labels with real labels" do + @graph.add_edge!(:a, :b) + @graph.copy_label(:a, :b, @label) + @graph.edge_label(:a, :b).should == @label + end + + it "should not replace labels with nil labels" do + @graph.add_edge!(:a, :b, @label) + @graph.copy_label(:a, :b, {}) + @graph.edge_label(:a, :b).should == @label + end +end + +describe Puppet::PGraph, " when sorting the graph" do + before do + @graph = Puppet::PGraph.new + end + + def add_edges(hash) + hash.each do |a,b| + @graph.add_edge!(a, b) + end + end + + it "should fail on two-vertex loops" do + add_edges :a => :b, :b => :a + proc { @graph.topsort }.should raise_error(Puppet::Error) + end + + it "should fail on multi-vertex loops" do + add_edges :a => :b, :b => :c, :c => :a + proc { @graph.topsort }.should raise_error(Puppet::Error) + end + + it "should fail when a larger tree contains a small cycle" do + add_edges :a => :b, :b => :a, :c => :a, :d => :c + proc { @graph.topsort }.should raise_error(Puppet::Error) + end + + it "should succeed on trees with no cycles" do + add_edges :a => :b, :b => :e, :c => :a, :d => :c + proc { @graph.topsort }.should_not raise_error + end +end diff --git a/spec/unit/other/transaction.rb b/spec/unit/other/transaction.rb new file mode 100755 index 000000000..7990d2eef --- /dev/null +++ b/spec/unit/other/transaction.rb @@ -0,0 +1,26 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../spec_helper' + +describe Puppet::Transaction, " when determining tags" do + before do + @config = Puppet::Node::Configuration.new + @transaction = Puppet::Transaction.new(@config) + end + + it "should default to the tags specified in the :tags setting" do + Puppet.expects(:[]).with(:tags).returns("one") + @transaction.tags.should == %w{one} + end + + it "should split tags based on ','" do + Puppet.expects(:[]).with(:tags).returns("one,two") + @transaction.tags.should == %w{one two} + end + + it "should use any tags set after creation" do + Puppet.expects(:[]).with(:tags).never + @transaction.tags = %w{one two} + @transaction.tags.should == %w{one two} + end +end diff --git a/spec/unit/other/transbucket.rb b/spec/unit/other/transbucket.rb new file mode 100755 index 000000000..8cb9abaa4 --- /dev/null +++ b/spec/unit/other/transbucket.rb @@ -0,0 +1,133 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../spec_helper' + +describe Puppet::TransBucket do + before do + @bucket = Puppet::TransBucket.new + end + + it "should be able to produce a RAL component" do + @bucket.name = "luke" + @bucket.type = "user" + + resource = nil + proc { resource = @bucket.to_type }.should_not raise_error + resource.should be_instance_of(Puppet::Type::Component) + resource.title.should == "user[luke]" + end + + it "should accept TransObjects into its children list" do + object = Puppet::TransObject.new("luke", "user") + proc { @bucket.push(object) }.should_not raise_error + @bucket.each do |o| + o.should equal(object) + end + end + + it "should accept TransBuckets into its children list" do + object = Puppet::TransBucket.new() + proc { @bucket.push(object) }.should_not raise_error + @bucket.each do |o| + o.should equal(object) + end + end + + it "should refuse to accept any children that are not TransObjects or TransBuckets" do + proc { @bucket.push "a test" }.should raise_error + end + + it "should return nil as its reference when type or name is missing" do + @bucket.to_ref.should be_nil + end + + it "should return the title as its reference" do + @bucket.name = "luke" + @bucket.type = "user" + @bucket.to_ref.should == "user[luke]" + end +end + +describe Puppet::TransBucket, " when generating a configuration" do + before do + @bottom = Puppet::TransBucket.new + @bottom.type = "fake" + @bottom.name = "bottom" + @bottomobj = Puppet::TransObject.new("bottom", "user") + @bottom.push @bottomobj + + @middle = Puppet::TransBucket.new + @middle.type = "fake" + @middle.name = "middle" + @middleobj = Puppet::TransObject.new("middle", "user") + @middle.push(@middleobj) + @middle.push(@bottom) + + @top = Puppet::TransBucket.new + @top.type = "fake" + @top.name = "top" + @topobj = Puppet::TransObject.new("top", "user") + @top.push(@topobj) + @top.push(@middle) + + @config = @top.to_configuration + + @users = %w{top middle bottom} + @fakes = %w{fake[bottom] fake[middle] fake[top]} + end + + it "should convert all transportable objects to RAL resources" do + @users.each do |name| + @config.vertices.find { |r| r.class.name == :user and r.title == name }.should be_instance_of(Puppet::Type.type(:user)) + end + end + + it "should convert all transportable buckets to RAL components" do + @fakes.each do |name| + @config.vertices.find { |r| r.class.name == :component and r.title == name }.should be_instance_of(Puppet::Type.type(:component)) + end + end + + it "should add all resources to the graph's resource table" do + @config.resource("fake[top]").should equal(@top) + end + + it "should finalize all resources" do + @config.vertices.each do |vertex| vertex.should be_finalized end + end + + it "should only call to_type on each resource once" do + @topobj.expects(:to_type) + @bottomobj.expects(:to_type) + @top.to_configuration + end + + after do + Puppet::Type.allclear + end +end + +describe Puppet::TransBucket, " when serializing" do + before do + @bucket = Puppet::TransBucket.new(%w{one two}) + @bucket.name = "one" + @bucket.type = "two" + end + + it "should be able to be dumped to yaml" do + proc { YAML.dump(@bucket) }.should_not raise_error + end + + it "should dump YAML that produces an equivalent object" do + result = YAML.dump(@bucket) + + newobj = YAML.load(result) + newobj.name.should == "one" + newobj.type.should == "two" + children = [] + newobj.each do |o| + children << o + end + children.should == %w{one two} + end +end diff --git a/spec/unit/other/transobject.rb b/spec/unit/other/transobject.rb new file mode 100755 index 000000000..07c9dc761 --- /dev/null +++ b/spec/unit/other/transobject.rb @@ -0,0 +1,116 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../spec_helper' + +describe Puppet::TransObject, " when building its search path" do +end + +describe Puppet::TransObject, " when building its search path" do +end +#!/usr/bin/env ruby + +$:.unshift("../lib").unshift("../../lib") if __FILE__ =~ /\.rb$/ + +require 'puppet' +require 'puppet/transportable' +require 'puppettest' +require 'puppettest/parsertesting' +require 'yaml' + +class TestTransportable < Test::Unit::TestCase + include PuppetTest::ParserTesting + + def test_yamldumpobject + obj = mk_transobject + obj.to_yaml_properties + str = nil + assert_nothing_raised { + str = YAML.dump(obj) + } + + newobj = nil + assert_nothing_raised { + newobj = YAML.load(str) + } + + assert(newobj.name, "Object has no name") + assert(newobj.type, "Object has no type") + end + + def test_yamldumpbucket + objects = %w{/etc/passwd /etc /tmp /var /dev}.collect { |d| + mk_transobject(d) + } + bucket = mk_transbucket(*objects) + str = nil + assert_nothing_raised { + str = YAML.dump(bucket) + } + + newobj = nil + assert_nothing_raised { + newobj = YAML.load(str) + } + + assert(newobj.name, "Bucket has no name") + assert(newobj.type, "Bucket has no type") + end + + # Verify that we correctly strip out collectable objects, since they should + # not be sent to the client. + def test_collectstrip + top = mk_transtree do |object, depth, width| + if width % 2 == 1 + object.collectable = true + end + end + + assert(top.flatten.find_all { |o| o.collectable }.length > 0, + "Could not find any collectable objects") + + # Now strip out the collectable objects + top.collectstrip! + + # And make sure they're actually gone + assert_equal(0, top.flatten.find_all { |o| o.collectable }.length, + "Still found collectable objects") + end + + # Make sure our 'delve' command is working + def test_delve + top = mk_transtree do |object, depth, width| + if width % 2 == 1 + object.collectable = true + end + end + + objects = [] + buckets = [] + collectable = [] + + count = 0 + assert_nothing_raised { + top.delve do |object| + count += 1 + if object.is_a? Puppet::TransBucket + buckets << object + else + objects << object + if object.collectable + collectable << object + end + end + end + } + + top.flatten.each do |obj| + assert(objects.include?(obj), "Missing obj %s[%s]" % [obj.type, obj.name]) + end + + assert_equal(collectable.length, + top.flatten.find_all { |o| o.collectable }.length, + "Found incorrect number of collectable objects") + end +end + +# $Id$ diff --git a/spec/unit/parser/interpreter.rb b/spec/unit/parser/interpreter.rb index c0f9d54b3..a79267b52 100755 --- a/spec/unit/parser/interpreter.rb +++ b/spec/unit/parser/interpreter.rb @@ -77,8 +77,8 @@ describe Puppet::Parser::Interpreter, " when creating parser instances" do file = mock 'file' file.stubs(:changed?).returns(true) file.stubs(:file).returns("/whatever") - Puppet.config.stubs(:read_file).with(file).returns(text) - Puppet.config.parse(file) + Puppet.settings.stubs(:read_file).with(file).returns(text) + Puppet.settings.parse(file) parser1 = mock 'parser1' Puppet::Parser::Parser.expects(:new).with(:environment => :env1).returns(parser1) diff --git a/spec/unit/ral/type.rb b/spec/unit/ral/type.rb new file mode 100755 index 000000000..c8bf8c9b4 --- /dev/null +++ b/spec/unit/ral/type.rb @@ -0,0 +1,25 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../spec_helper' + +describe Puppet::Type, " when in a configuration" do + before do + @configuration = Puppet::Node::Configuration.new + @container = Puppet::Type.type(:component).create(:name => "container") + @one = Puppet::Type.type(:file).create(:path => "/file/one") + @two = Puppet::Type.type(:file).create(:path => "/file/two") + @configuration.add_resource @container + @configuration.add_resource @one + @configuration.add_resource @two + @configuration.add_edge! @container, @one + @configuration.add_edge! @container, @two + end + + it "should have no parent if there is no in edge" do + @container.parent.should be_nil + end + + it "should set its parent to its in edge" do + @one.parent.ref.should equal(@container.ref) + end +end diff --git a/spec/unit/util/config.rb b/spec/unit/util/config.rb deleted file mode 100755 index 348a54893..000000000 --- a/spec/unit/util/config.rb +++ /dev/null @@ -1,408 +0,0 @@ -#!/usr/bin/env ruby - -require File.dirname(__FILE__) + '/../../spec_helper' - -describe Puppet::Util::Config, " when specifying defaults" do - before do - @config = Puppet::Util::Config.new - end - - it "should start with no defined parameters" do - @config.params.length.should == 0 - end - - it "should allow specification of default values associated with a section as an array" do - @config.setdefaults(:section, :myvalue => ["defaultval", "my description"]) - end - - it "should not allow duplicate parameter specifications" do - @config.setdefaults(:section, :myvalue => ["a", "b"]) - lambda { @config.setdefaults(:section, :myvalue => ["c", "d"]) }.should raise_error(ArgumentError) - end - - it "should allow specification of default values associated with a section as a hash" do - @config.setdefaults(:section, :myvalue => {:default => "defaultval", :desc => "my description"}) - end - - it "should consider defined parameters to be valid" do - @config.setdefaults(:section, :myvalue => ["defaultval", "my description"]) - @config.valid?(:myvalue).should be_true - end - - it "should require a description when defaults are specified with an array" do - lambda { @config.setdefaults(:section, :myvalue => ["a value"]) }.should raise_error(ArgumentError) - end - - it "should require a description when defaults are specified with a hash" do - lambda { @config.setdefaults(:section, :myvalue => {:default => "a value"}) }.should raise_error(ArgumentError) - end - - it "should support specifying owner, group, and mode when specifying files" do - @config.setdefaults(:section, :myvalue => {:default => "/some/file", :owner => "blah", :mode => "boo", :group => "yay", :desc => "whatever"}) - end - - it "should support specifying a short name" do - @config.setdefaults(:section, :myvalue => {:default => "w", :desc => "b", :short => "m"}) - end - - it "should fail when short names conflict" do - @config.setdefaults(:section, :myvalue => {:default => "w", :desc => "b", :short => "m"}) - lambda { @config.setdefaults(:section, :myvalue => {:default => "w", :desc => "b", :short => "m"}) }.should raise_error(ArgumentError) - end -end - -describe Puppet::Util::Config, " when setting values" do - before do - @config = Puppet::Util::Config.new - @config.setdefaults :main, :myval => ["val", "desc"] - @config.setdefaults :main, :bool => [true, "desc"] - end - - it "should provide a method for setting values from other objects" do - @config[:myval] = "something else" - @config[:myval].should == "something else" - end - - it "should support a getopt-specific mechanism for setting values" do - @config.handlearg("--myval", "newval") - @config[:myval].should == "newval" - end - - it "should support a getopt-specific mechanism for turning booleans off" do - @config.handlearg("--no-bool") - @config[:bool].should == false - end - - it "should support a getopt-specific mechanism for turning booleans on" do - # Turn it off first - @config[:bool] = false - @config.handlearg("--bool") - @config[:bool].should == true - end - - it "should clear the cache when setting getopt-specific values" do - @config.setdefaults :mysection, :one => ["whah", "yay"], :two => ["$one yay", "bah"] - @config[:two].should == "whah yay" - @config.handlearg("--one", "else") - @config[:two].should == "else yay" - end - - it "should not clear other values when setting getopt-specific values" do - @config[:myval] = "yay" - @config.handlearg("--no-bool") - @config[:myval].should == "yay" - end - - it "should call passed blocks when values are set" do - values = [] - @config.setdefaults(:section, :hooker => {:default => "yay", :desc => "boo", :hook => lambda { |v| values << v }}) - values.should == [] - - @config[:hooker] = "something" - values.should == %w{something} - end - - it "should munge values using the element-specific methods" do - @config[:bool] = "false" - @config[:bool].should == false - end - - it "should prefer cli values to values set in Ruby code" do - @config.handlearg("--myval", "cliarg") - @config[:myval] = "memarg" - @config[:myval].should == "cliarg" - end -end - -describe Puppet::Util::Config, " when returning values" do - before do - @config = Puppet::Util::Config.new - @config.setdefaults :section, :one => ["ONE", "a"], :two => ["$one TWO", "b"], :three => ["$one $two THREE", "c"], :four => ["$two $three FOUR", "d"] - end - - it "should provide a mechanism for returning set values" do - @config[:one] = "other" - @config[:one].should == "other" - end - - it "should interpolate default values for other parameters into returned parameter values" do - @config[:one].should == "ONE" - @config[:two].should == "ONE TWO" - @config[:three].should == "ONE ONE TWO THREE" - end - - it "should interpolate default values that themselves need to be interpolated" do - @config[:four].should == "ONE TWO ONE ONE TWO THREE FOUR" - end - - it "should interpolate set values for other parameters into returned parameter values" do - @config[:one] = "on3" - @config[:two] = "$one tw0" - @config[:three] = "$one $two thr33" - @config[:four] = "$one $two $three f0ur" - @config[:one].should == "on3" - @config[:two].should == "on3 tw0" - @config[:three].should == "on3 on3 tw0 thr33" - @config[:four].should == "on3 on3 tw0 on3 on3 tw0 thr33 f0ur" - end - - it "should not cache interpolated values such that stale information is returned" do - @config[:two].should == "ONE TWO" - @config[:one] = "one" - @config[:two].should == "one TWO" - end - - it "should not cache values such that information from one environment is returned for another environment" do - text = "[env1]\none = oneval\n[env2]\none = twoval\n" - file = mock 'file' - file.stubs(:changed?).returns(true) - file.stubs(:file).returns("/whatever") - @config.stubs(:read_file).with(file).returns(text) - @config.parse(file) - - @config.value(:one, "env1").should == "oneval" - @config.value(:one, "env2").should == "twoval" - end - - it "should have a name determined by the 'name' parameter" do - @config.setdefaults(:whatever, :name => ["something", "yayness"]) - @config.name.should == :something - @config[:name] = :other - @config.name.should == :other - end -end - -describe Puppet::Util::Config, " when choosing which value to return" do - before do - @config = Puppet::Util::Config.new - @config.setdefaults :section, - :one => ["ONE", "a"], - :name => ["myname", "w"] - end - - it "should return default values if no values have been set" do - @config[:one].should == "ONE" - end - - it "should return values set on the cli before values set in the configuration file" do - text = "[main]\none = fileval\n" - file = mock 'file' - file.stubs(:changed?).returns(true) - file.stubs(:file).returns("/whatever") - @config.stubs(:parse_file).returns(text) - @config.handlearg("--one", "clival") - @config.parse(file) - - @config[:one].should == "clival" - end - - it "should return values set on the cli before values set in Ruby" do - @config[:one] = "rubyval" - @config.handlearg("--one", "clival") - @config[:one].should == "clival" - end - - it "should return values set in the executable-specific section before values set in the main section" do - text = "[main]\none = mainval\n[myname]\none = nameval\n" - file = mock 'file' - file.stubs(:changed?).returns(true) - file.stubs(:file).returns("/whatever") - @config.stubs(:read_file).with(file).returns(text) - @config.parse(file) - - @config[:one].should == "nameval" - end - - it "should not return values outside of its search path" do - text = "[other]\none = oval\n" - file = "/some/file" - file = mock 'file' - file.stubs(:changed?).returns(true) - file.stubs(:file).returns("/whatever") - @config.stubs(:read_file).with(file).returns(text) - @config.parse(file) - @config[:one].should == "ONE" - end - - it "should return values in a specified environment" do - text = "[env]\none = envval\n" - file = "/some/file" - file = mock 'file' - file.stubs(:changed?).returns(true) - file.stubs(:file).returns("/whatever") - @config.stubs(:read_file).with(file).returns(text) - @config.parse(file) - @config.value(:one, "env").should == "envval" - end - - it "should return values in a specified environment before values in the main or name sections" do - text = "[env]\none = envval\n[main]\none = mainval\n[myname]\none = nameval\n" - file = "/some/file" - file = mock 'file' - file.stubs(:changed?).returns(true) - file.stubs(:file).returns("/whatever") - @config.stubs(:read_file).with(file).returns(text) - @config.parse(file) - @config.value(:one, "env").should == "envval" - end -end - -describe Puppet::Util::Config, " when parsing its configuration" do - before do - @config = Puppet::Util::Config.new - @config.setdefaults :section, :one => ["ONE", "a"], :two => ["$one TWO", "b"], :three => ["$one $two THREE", "c"] - end - - it "should return values set in the configuration file" do - text = "[main] - one = fileval - " - file = "/some/file" - @config.expects(:read_file).with(file).returns(text) - @config.parse(file) - @config[:one].should == "fileval" - end - - #484 - this should probably be in the regression area - it "should not throw an exception on unknown parameters" do - text = "[main]\nnosuchparam = mval\n" - file = "/some/file" - @config.expects(:read_file).with(file).returns(text) - lambda { @config.parse(file) }.should_not raise_error - end - - it "should support an old parse method when per-executable configuration files still exist" do - # I'm not going to bother testing this method. - @config.should respond_to(:old_parse) - end - - it "should convert booleans in the configuration file into Ruby booleans" do - text = "[main] - one = true - two = false - " - file = "/some/file" - @config.expects(:read_file).with(file).returns(text) - @config.parse(file) - @config[:one].should == true - @config[:two].should == false - end - - it "should convert integers in the configuration file into Ruby Integers" do - text = "[main] - one = 65 - " - file = "/some/file" - @config.expects(:read_file).with(file).returns(text) - @config.parse(file) - @config[:one].should == 65 - end - - it "should support specifying file all metadata (owner, group, mode) in the configuration file" do - @config.setdefaults :section, :myfile => ["/my/file", "a"] - - text = "[main] - myfile = /other/file {owner = luke, group = luke, mode = 644} - " - file = "/some/file" - @config.expects(:read_file).with(file).returns(text) - @config.parse(file) - @config[:myfile].should == "/other/file" - @config.metadata(:myfile).should == {:owner => "luke", :group => "luke", :mode => "644"} - end - - it "should support specifying file a single piece of metadata (owner, group, or mode) in the configuration file" do - @config.setdefaults :section, :myfile => ["/my/file", "a"] - - text = "[main] - myfile = /other/file {owner = luke} - " - file = "/some/file" - @config.expects(:read_file).with(file).returns(text) - @config.parse(file) - @config[:myfile].should == "/other/file" - @config.metadata(:myfile).should == {:owner => "luke"} - end -end - -describe Puppet::Util::Config, " when reparsing its configuration" do - before do - @config = Puppet::Util::Config.new - @config.setdefaults :section, :one => ["ONE", "a"], :two => ["$one TWO", "b"], :three => ["$one $two THREE", "c"] - end - - it "should replace in-memory values with on-file values" do - # Init the value - text = "[main]\none = disk-init\n" - file = mock 'file' - file.stubs(:changed?).returns(true) - file.stubs(:file).returns("/test/file") - @config[:one] = "init" - @config.file = file - - # Now replace the value - text = "[main]\none = disk-replace\n" - - # This is kinda ridiculous - the reason it parses twice is that - # it goes to parse again when we ask for the value, because the - # mock always says it should get reparsed. - @config.expects(:read_file).with(file).returns(text).times(2) - @config.reparse - @config[:one].should == "disk-replace" - end - - it "should retain parameters set by cli when configuration files are reparsed" do - @config.handlearg("--one", "clival") - - text = "[main]\none = on-disk\n" - file = mock 'file' - file.stubs(:file).returns("/test/file") - @config.stubs(:read_file).with(file).returns(text) - @config.parse(file) - - @config[:one].should == "clival" - end - - it "should remove in-memory values that are no longer set in the file" do - # Init the value - text = "[main]\none = disk-init\n" - file = mock 'file' - file.stubs(:changed?).returns(true) - file.stubs(:file).returns("/test/file") - @config.expects(:read_file).with(file).returns(text) - @config.parse(file) - @config[:one].should == "disk-init" - - # Now replace the value - text = "[main]\ntwo = disk-replace\n" - @config.expects(:read_file).with(file).returns(text) - @config.parse(file) - #@config.reparse - - # The originally-overridden value should be replaced with the default - @config[:one].should == "ONE" - - # and we should now have the new value in memory - @config[:two].should == "disk-replace" - end -end - -#describe Puppet::Util::Config, " when being used to manage the host machine" do -# it "should provide a method that writes files with the correct modes" -# -# it "should provide a method that creates directories with the correct modes" -# -# it "should provide a method to declare what directories should exist" -# -# it "should provide a method to trigger enforcing of file modes on existing files and directories" -# -# it "should provide a method to convert the file mode enforcement into a Puppet manifest" -# -# it "should provide an option to create needed users and groups" -# -# it "should provide a method to print out the current configuration" -# -# it "should be able to provide all of its parameters in a format compatible with GetOpt::Long" -# -# it "should not attempt to manage files within /dev" -#end diff --git a/spec/unit/util/settings.rb b/spec/unit/util/settings.rb new file mode 100755 index 000000000..8d11737b3 --- /dev/null +++ b/spec/unit/util/settings.rb @@ -0,0 +1,535 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../spec_helper' + +describe Puppet::Util::Settings, " when specifying defaults" do + before do + @settings = Puppet::Util::Settings.new + end + + it "should start with no defined parameters" do + @settings.params.length.should == 0 + end + + it "should allow specification of default values associated with a section as an array" do + @settings.setdefaults(:section, :myvalue => ["defaultval", "my description"]) + end + + it "should not allow duplicate parameter specifications" do + @settings.setdefaults(:section, :myvalue => ["a", "b"]) + lambda { @settings.setdefaults(:section, :myvalue => ["c", "d"]) }.should raise_error(ArgumentError) + end + + it "should allow specification of default values associated with a section as a hash" do + @settings.setdefaults(:section, :myvalue => {:default => "defaultval", :desc => "my description"}) + end + + it "should consider defined parameters to be valid" do + @settings.setdefaults(:section, :myvalue => ["defaultval", "my description"]) + @settings.valid?(:myvalue).should be_true + end + + it "should require a description when defaults are specified with an array" do + lambda { @settings.setdefaults(:section, :myvalue => ["a value"]) }.should raise_error(ArgumentError) + end + + it "should require a description when defaults are specified with a hash" do + lambda { @settings.setdefaults(:section, :myvalue => {:default => "a value"}) }.should raise_error(ArgumentError) + end + + it "should support specifying owner, group, and mode when specifying files" do + @settings.setdefaults(:section, :myvalue => {:default => "/some/file", :owner => "blah", :mode => "boo", :group => "yay", :desc => "whatever"}) + end + + it "should support specifying a short name" do + @settings.setdefaults(:section, :myvalue => {:default => "w", :desc => "b", :short => "m"}) + end + + it "should fail when short names conflict" do + @settings.setdefaults(:section, :myvalue => {:default => "w", :desc => "b", :short => "m"}) + lambda { @settings.setdefaults(:section, :myvalue => {:default => "w", :desc => "b", :short => "m"}) }.should raise_error(ArgumentError) + end +end + +describe Puppet::Util::Settings, " when setting values" do + before do + @settings = Puppet::Util::Settings.new + @settings.setdefaults :main, :myval => ["val", "desc"] + @settings.setdefaults :main, :bool => [true, "desc"] + end + + it "should provide a method for setting values from other objects" do + @settings[:myval] = "something else" + @settings[:myval].should == "something else" + end + + it "should support a getopt-specific mechanism for setting values" do + @settings.handlearg("--myval", "newval") + @settings[:myval].should == "newval" + end + + it "should support a getopt-specific mechanism for turning booleans off" do + @settings.handlearg("--no-bool") + @settings[:bool].should == false + end + + it "should support a getopt-specific mechanism for turning booleans on" do + # Turn it off first + @settings[:bool] = false + @settings.handlearg("--bool") + @settings[:bool].should == true + end + + it "should clear the cache when setting getopt-specific values" do + @settings.setdefaults :mysection, :one => ["whah", "yay"], :two => ["$one yay", "bah"] + @settings[:two].should == "whah yay" + @settings.handlearg("--one", "else") + @settings[:two].should == "else yay" + end + + it "should not clear other values when setting getopt-specific values" do + @settings[:myval] = "yay" + @settings.handlearg("--no-bool") + @settings[:myval].should == "yay" + end + + it "should call passed blocks when values are set" do + values = [] + @settings.setdefaults(:section, :hooker => {:default => "yay", :desc => "boo", :hook => lambda { |v| values << v }}) + values.should == [] + + @settings[:hooker] = "something" + values.should == %w{something} + end + + it "should munge values using the element-specific methods" do + @settings[:bool] = "false" + @settings[:bool].should == false + end + + it "should prefer cli values to values set in Ruby code" do + @settings.handlearg("--myval", "cliarg") + @settings[:myval] = "memarg" + @settings[:myval].should == "cliarg" + end +end + +describe Puppet::Util::Settings, " when returning values" do + before do + @settings = Puppet::Util::Settings.new + @settings.setdefaults :section, :one => ["ONE", "a"], :two => ["$one TWO", "b"], :three => ["$one $two THREE", "c"], :four => ["$two $three FOUR", "d"] + end + + it "should provide a mechanism for returning set values" do + @settings[:one] = "other" + @settings[:one].should == "other" + end + + it "should interpolate default values for other parameters into returned parameter values" do + @settings[:one].should == "ONE" + @settings[:two].should == "ONE TWO" + @settings[:three].should == "ONE ONE TWO THREE" + end + + it "should interpolate default values that themselves need to be interpolated" do + @settings[:four].should == "ONE TWO ONE ONE TWO THREE FOUR" + end + + it "should interpolate set values for other parameters into returned parameter values" do + @settings[:one] = "on3" + @settings[:two] = "$one tw0" + @settings[:three] = "$one $two thr33" + @settings[:four] = "$one $two $three f0ur" + @settings[:one].should == "on3" + @settings[:two].should == "on3 tw0" + @settings[:three].should == "on3 on3 tw0 thr33" + @settings[:four].should == "on3 on3 tw0 on3 on3 tw0 thr33 f0ur" + end + + it "should not cache interpolated values such that stale information is returned" do + @settings[:two].should == "ONE TWO" + @settings[:one] = "one" + @settings[:two].should == "one TWO" + end + + it "should not cache values such that information from one environment is returned for another environment" do + text = "[env1]\none = oneval\n[env2]\none = twoval\n" + file = mock 'file' + file.stubs(:changed?).returns(true) + file.stubs(:file).returns("/whatever") + @settings.stubs(:read_file).with(file).returns(text) + @settings.parse(file) + + @settings.value(:one, "env1").should == "oneval" + @settings.value(:one, "env2").should == "twoval" + end + + it "should have a name determined by the 'name' parameter" do + @settings.setdefaults(:whatever, :name => ["something", "yayness"]) + @settings.name.should == :something + @settings[:name] = :other + @settings.name.should == :other + end +end + +describe Puppet::Util::Settings, " when choosing which value to return" do + before do + @settings = Puppet::Util::Settings.new + @settings.setdefaults :section, + :one => ["ONE", "a"], + :name => ["myname", "w"] + end + + it "should return default values if no values have been set" do + @settings[:one].should == "ONE" + end + + it "should return values set on the cli before values set in the configuration file" do + text = "[main]\none = fileval\n" + file = mock 'file' + file.stubs(:changed?).returns(true) + file.stubs(:file).returns("/whatever") + @settings.stubs(:parse_file).returns(text) + @settings.handlearg("--one", "clival") + @settings.parse(file) + + @settings[:one].should == "clival" + end + + it "should return values set on the cli before values set in Ruby" do + @settings[:one] = "rubyval" + @settings.handlearg("--one", "clival") + @settings[:one].should == "clival" + end + + it "should return values set in the executable-specific section before values set in the main section" do + text = "[main]\none = mainval\n[myname]\none = nameval\n" + file = mock 'file' + file.stubs(:changed?).returns(true) + file.stubs(:file).returns("/whatever") + @settings.stubs(:read_file).with(file).returns(text) + @settings.parse(file) + + @settings[:one].should == "nameval" + end + + it "should not return values outside of its search path" do + text = "[other]\none = oval\n" + file = "/some/file" + file = mock 'file' + file.stubs(:changed?).returns(true) + file.stubs(:file).returns("/whatever") + @settings.stubs(:read_file).with(file).returns(text) + @settings.parse(file) + @settings[:one].should == "ONE" + end + + it "should return values in a specified environment" do + text = "[env]\none = envval\n" + file = "/some/file" + file = mock 'file' + file.stubs(:changed?).returns(true) + file.stubs(:file).returns("/whatever") + @settings.stubs(:read_file).with(file).returns(text) + @settings.parse(file) + @settings.value(:one, "env").should == "envval" + end + + it "should return values in a specified environment before values in the main or name sections" do + text = "[env]\none = envval\n[main]\none = mainval\n[myname]\none = nameval\n" + file = "/some/file" + file = mock 'file' + file.stubs(:changed?).returns(true) + file.stubs(:file).returns("/whatever") + @settings.stubs(:read_file).with(file).returns(text) + @settings.parse(file) + @settings.value(:one, "env").should == "envval" + end +end + +describe Puppet::Util::Settings, " when parsing its configuration" do + before do + @settings = Puppet::Util::Settings.new + @settings.setdefaults :section, :one => ["ONE", "a"], :two => ["$one TWO", "b"], :three => ["$one $two THREE", "c"] + end + + it "should return values set in the configuration file" do + text = "[main] + one = fileval + " + file = "/some/file" + @settings.expects(:read_file).with(file).returns(text) + @settings.parse(file) + @settings[:one].should == "fileval" + end + + #484 - this should probably be in the regression area + it "should not throw an exception on unknown parameters" do + text = "[main]\nnosuchparam = mval\n" + file = "/some/file" + @settings.expects(:read_file).with(file).returns(text) + lambda { @settings.parse(file) }.should_not raise_error + end + + it "should support an old parse method when per-executable configuration files still exist" do + # I'm not going to bother testing this method. + @settings.should respond_to(:old_parse) + end + + it "should convert booleans in the configuration file into Ruby booleans" do + text = "[main] + one = true + two = false + " + file = "/some/file" + @settings.expects(:read_file).with(file).returns(text) + @settings.parse(file) + @settings[:one].should == true + @settings[:two].should == false + end + + it "should convert integers in the configuration file into Ruby Integers" do + text = "[main] + one = 65 + " + file = "/some/file" + @settings.expects(:read_file).with(file).returns(text) + @settings.parse(file) + @settings[:one].should == 65 + end + + it "should support specifying file all metadata (owner, group, mode) in the configuration file" do + @settings.setdefaults :section, :myfile => ["/myfile", "a"] + + text = "[main] + myfile = /other/file {owner = luke, group = luke, mode = 644} + " + file = "/some/file" + @settings.expects(:read_file).with(file).returns(text) + @settings.parse(file) + @settings[:myfile].should == "/other/file" + @settings.metadata(:myfile).should == {:owner => "luke", :group => "luke", :mode => "644"} + end + + it "should support specifying file a single piece of metadata (owner, group, or mode) in the configuration file" do + @settings.setdefaults :section, :myfile => ["/myfile", "a"] + + text = "[main] + myfile = /other/file {owner = luke} + " + file = "/some/file" + @settings.expects(:read_file).with(file).returns(text) + @settings.parse(file) + @settings[:myfile].should == "/other/file" + @settings.metadata(:myfile).should == {:owner => "luke"} + end +end + +describe Puppet::Util::Settings, " when reparsing its configuration" do + before do + @settings = Puppet::Util::Settings.new + @settings.setdefaults :section, :one => ["ONE", "a"], :two => ["$one TWO", "b"], :three => ["$one $two THREE", "c"] + end + + it "should replace in-memory values with on-file values" do + # Init the value + text = "[main]\none = disk-init\n" + file = mock 'file' + file.stubs(:changed?).returns(true) + file.stubs(:file).returns("/test/file") + @settings[:one] = "init" + @settings.file = file + + # Now replace the value + text = "[main]\none = disk-replace\n" + + # This is kinda ridiculous - the reason it parses twice is that + # it goes to parse again when we ask for the value, because the + # mock always says it should get reparsed. + @settings.expects(:read_file).with(file).returns(text).times(2) + @settings.reparse + @settings[:one].should == "disk-replace" + end + + it "should retain parameters set by cli when configuration files are reparsed" do + @settings.handlearg("--one", "clival") + + text = "[main]\none = on-disk\n" + file = mock 'file' + file.stubs(:file).returns("/test/file") + @settings.stubs(:read_file).with(file).returns(text) + @settings.parse(file) + + @settings[:one].should == "clival" + end + + it "should remove in-memory values that are no longer set in the file" do + # Init the value + text = "[main]\none = disk-init\n" + file = mock 'file' + file.stubs(:changed?).returns(true) + file.stubs(:file).returns("/test/file") + @settings.expects(:read_file).with(file).returns(text) + @settings.parse(file) + @settings[:one].should == "disk-init" + + # Now replace the value + text = "[main]\ntwo = disk-replace\n" + @settings.expects(:read_file).with(file).returns(text) + @settings.parse(file) + #@settings.reparse + + # The originally-overridden value should be replaced with the default + @settings[:one].should == "ONE" + + # and we should now have the new value in memory + @settings[:two].should == "disk-replace" + end +end + +describe Puppet::Util::Settings, " when being used to manage the host machine" do + before do + @settings = Puppet::Util::Settings.new + @settings.setdefaults :main, :maindir => ["/maindir", "a"], :seconddir => ["/seconddir", "a"] + @settings.setdefaults :other, :otherdir => {:default => "/otherdir", :desc => "a", :owner => "luke", :group => "johnny", :mode => 0755} + @settings.setdefaults :files, :myfile => {:default => "/myfile", :desc => "a", :mode => 0755} + end + + it "should provide a method that writes files with the correct modes" do + pending "Not converted from test/unit yet" + end + + it "should provide a method that creates directories with the correct modes" do + Puppet::Util::SUIDManager.expects(:asuser).with("luke", "johnny").yields + Dir.expects(:mkdir).with("/otherdir", 0755) + @settings.mkdir(:otherdir) + end + + it "should be able to create needed directories in a single section" do + Dir.expects(:mkdir).with("/maindir") + Dir.expects(:mkdir).with("/seconddir") + @settings.use(:main) + end + + it "should be able to create needed directories in multiple sections" do + Dir.expects(:mkdir).with("/maindir") + Dir.expects(:mkdir).with("/otherdir", 0755) + Dir.expects(:mkdir).with("/seconddir") + @settings.use(:main, :other) + end + + it "should provide a method to trigger enforcing of file modes on existing files and directories" do + pending "Not converted from test/unit yet" + end + + it "should provide a method to convert the file mode enforcement into a Puppet manifest" do + pending "Not converted from test/unit yet" + end + + it "should create files when configured to do so with the :create parameter" + + it "should provide a method to convert the file mode enforcement into transportable resources" do + # Make it think we're root so it tries to manage user and group. + Puppet.features.stubs(:root?).returns(true) + File.stubs(:exist?).with("/myfile").returns(true) + trans = nil + trans = @settings.to_transportable + resources = [] + trans.delve { |obj| resources << obj if obj.is_a? Puppet::TransObject } + %w{/maindir /seconddir /otherdir /myfile}.each do |path| + obj = resources.find { |r| r.type == "file" and r.name == path } + if path.include?("dir") + obj[:ensure].should == :directory + else + # Do not create the file, just manage mode + obj[:ensure].should be_nil + end + obj.should be_instance_of(Puppet::TransObject) + case path + when "/otherdir": + obj[:owner].should == "luke" + obj[:group].should == "johnny" + obj[:mode].should == 0755 + when "/myfile": + obj[:mode].should == 0755 + end + end + end + + it "should not try to manage user or group when not running as root" do + Puppet.features.stubs(:root?).returns(false) + trans = nil + trans = @settings.to_transportable(:other) + trans.delve do |obj| + next unless obj.is_a?(Puppet::TransObject) + obj[:owner].should be_nil + obj[:group].should be_nil + end + end + + it "should add needed users and groups to the manifest when asked" do + # This is how we enable user/group management + @settings.setdefaults :main, :mkusers => [true, "w"] + Puppet.features.stubs(:root?).returns(false) + trans = nil + trans = @settings.to_transportable(:other) + resources = [] + trans.delve { |obj| resources << obj if obj.is_a? Puppet::TransObject and obj.type != "file" } + + user = resources.find { |r| r.type == "user" } + user.should be_instance_of(Puppet::TransObject) + user.name.should == "luke" + user[:ensure].should == :present + + # This should maybe be a separate test, but... + group = resources.find { |r| r.type == "group" } + group.should be_instance_of(Puppet::TransObject) + group.name.should == "johnny" + group[:ensure].should == :present + end + + it "should ignore tags and schedules when creating files and directories" + + it "should apply all resources in debug mode to reduce logging" + + it "should not try to manage absent files" do + # Make it think we're root so it tries to manage user and group. + Puppet.features.stubs(:root?).returns(true) + trans = nil + trans = @settings.to_transportable + file = nil + trans.delve { |obj| file = obj if obj.name == "/myfile" } + file.should be_nil + end + + it "should be able to turn the current configuration into a parseable manifest" + + it "should convert octal numbers correctly when producing a manifest" + + it "should be able to provide all of its parameters in a format compatible with GetOpt::Long" do + pending "Not converted from test/unit yet" + end + + it "should not attempt to manage files within /dev" do + pending "Not converted from test/unit yet" + end + + it "should not modify the stored state database when managing resources" do + Puppet::Util::Storage.expects(:store).never + Puppet::Util::Storage.expects(:load).never + Dir.expects(:mkdir).with("/maindir") + @settings.use(:main) + end + + it "should convert all relative paths to fully-qualified paths (#795)" do + @settings[:myfile] = "unqualified" + dir = Dir.getwd + @settings[:myfile].should == File.join(dir, "unqualified") + end + + it "should support a method for re-using all currently used sections" do + Dir.expects(:mkdir).with(@settings[:otherdir], 0755).times(2) + @settings.use(:other) + @settings.reuse + end +end diff --git a/test/certmgr/certmgr.rb b/test/certmgr/certmgr.rb index fb1611d7f..3d863dc27 100755 --- a/test/certmgr/certmgr.rb +++ b/test/certmgr/certmgr.rb @@ -239,13 +239,13 @@ class TestCertMgr < Test::Unit::TestCase ca.revoke(h1.serial) - oldcert = File.read(Puppet.config[:cacert]) - oldserial = File.read(Puppet.config[:serial]) + oldcert = File.read(Puppet.settings[:cacert]) + oldserial = File.read(Puppet.settings[:serial]) # Recreate the CA from disk ca = mkCA() - newcert = File.read(Puppet.config[:cacert]) - newserial = File.read(Puppet.config[:serial]) + newcert = File.read(Puppet.settings[:cacert]) + newserial = File.read(Puppet.settings[:serial]) assert_equal(oldcert, newcert, "The certs are not equal after making a new CA.") assert_equal(oldserial, newserial, "The serials are not equal after making a new CA.") store = mkStore(ca) diff --git a/test/certmgr/inventory.rb b/test/certmgr/inventory.rb index 15d3e5217..9efcb0c09 100755 --- a/test/certmgr/inventory.rb +++ b/test/certmgr/inventory.rb @@ -56,8 +56,8 @@ class TestCertInventory < Test::Unit::TestCase file.expects(:puts).with do |written| written.include? cert.subject.to_s end - Puppet::Util::Config.any_instance.stubs(:write) - Puppet::Util::Config.any_instance.expects(:write). + Puppet::Util::Settings.any_instance.stubs(:write) + Puppet::Util::Settings.any_instance.expects(:write). with(:cert_inventory, 'a').yields(file) Puppet::SSLCertificates::Inventory.add(cert) diff --git a/test/data/snippets/failmissingexecpath.pp b/test/data/snippets/failmissingexecpath.pp deleted file mode 100644 index b03875547..000000000 --- a/test/data/snippets/failmissingexecpath.pp +++ /dev/null @@ -1,14 +0,0 @@ -define distloc($path) { - file { "/tmp/exectesting1": - ensure => file - } - exec { "exectest": - command => "touch $path", - subscribe => File["/tmp/exectesting1"], - refreshonly => true - } -} - -distloc { yay: - path => "/tmp/execdisttesting", -} diff --git a/test/language/snippets.rb b/test/language/snippets.rb index 2c74543e7..7168a81d8 100755 --- a/test/language/snippets.rb +++ b/test/language/snippets.rb @@ -197,7 +197,7 @@ class TestSnippets < Test::Unit::TestCase def snippet_classpathtest path = "/tmp/classtest" - file = @file[path] + file = @configuration.resource(:file, path) assert(file, "did not create file %s" % path) assert_nothing_raised { @@ -271,14 +271,6 @@ class TestSnippets < Test::Unit::TestCase assert_mode_equal(0755, file) end - def snippet_failmissingexecpath - file = "/tmp/exectesting1" - execfile = "/tmp/execdisttesting" - assert_file(file) - - assert_nil(Puppet::Type.type(:exec)["exectest"], "invalid exec was created") - end - def snippet_selectorvalues nums = %w{1 2 3 4 5} files = nums.collect { |n| @@ -472,13 +464,14 @@ class TestSnippets < Test::Unit::TestCase :Manifest => snippet(file), :Local => true ) - server.send(:fact_handler).stubs(:set) - server.send(:fact_handler).stubs(:get).returns(facts) + facts = Puppet::Node::Facts.new("testhost", facts) + Puppet::Node::Facts.stubs(:save) + Puppet::Node::Facts.stubs(:find).returns(facts) client = Puppet::Network::Client.master.new( :Master => server, :Cache => false ) - client.class.stubs(:facts).returns(facts) + client.class.stubs(:facts).returns(facts.values) assert(client.local) assert_nothing_raised { @@ -507,6 +500,7 @@ class TestSnippets < Test::Unit::TestCase assert(obj.name) } } + @configuration = client.configuration assert_nothing_raised { self.send(mname) } diff --git a/test/lib/puppettest.rb b/test/lib/puppettest.rb index b56bc563e..021d6d1de 100755 --- a/test/lib/puppettest.rb +++ b/test/lib/puppettest.rb @@ -5,7 +5,12 @@ $LOAD_PATH.unshift(File.expand_path(File.join(File.dirname(__FILE__), '../../lib require 'puppet' require 'mocha' -require 'test/unit' + +# Only load the test/unit class if we're not in the spec directory. +# Else we get the bogus 'no tests, no failures' message. +unless Dir.getwd =~ /spec/ + require 'test/unit' +end # Yay; hackish but it works if ARGV.include?("-d") @@ -141,7 +146,7 @@ module PuppetTest end @configpath = File.join(tmpdir, - self.class.to_s + "configdir" + @@testcount.to_s + "/" + "configdir" + @@testcount.to_s + "/" ) unless defined? $user and $group @@ -149,7 +154,7 @@ module PuppetTest $group = nonrootgroup().gid.to_s end - Puppet.config.clear + Puppet.settings.clear Puppet[:user] = $user Puppet[:group] = $group @@ -197,8 +202,7 @@ module PuppetTest @@tmpfilenum = 1 end - f = File.join(self.tmpdir(), self.class.to_s + "_" + @method_name.to_s + - @@tmpfilenum.to_s) + f = File.join(self.tmpdir(), "tempfile_" + @@tmpfilenum.to_s) @@tmpfiles << f return f end @@ -223,11 +227,11 @@ module PuppetTest when "Darwin": "/private/tmp" when "SunOS": "/var/tmp" else - "/tmp" + "/tmp" end - @tmpdir = File.join(@tmpdir, "puppettesting") + @tmpdir = File.join(@tmpdir, "puppettesting" + Process.pid.to_s) unless File.exists?(@tmpdir) FileUtils.mkdir_p(@tmpdir) @@ -260,6 +264,8 @@ module PuppetTest Puppet::Type.allclear Puppet::Util::Storage.clear Puppet.clear + Puppet.settings.clear + Puppet::Indirector::Indirection.clear_cache @memoryatend = Puppet::Util.memory diff = @memoryatend - @memoryatstart diff --git a/test/lib/puppettest/graph.rb b/test/lib/puppettest/graph.rb deleted file mode 100644 index db77889bd..000000000 --- a/test/lib/puppettest/graph.rb +++ /dev/null @@ -1,41 +0,0 @@ -#!/usr/bin/env ruby -# -# Created by Luke A. Kanies on 2006-11-24. -# Copyright (c) 2006. All rights reserved. - -require 'puppet/util/graph' - -class Container - include Puppet::Util::Graph - include Enumerable - attr_accessor :name - def each - @children.each do |c| yield c end - end - - def initialize(name, ary) - @name = name - @children = ary - end - - def push(*ary) - ary.each { |c| @children.push(c)} - end - - def to_s - @name - end -end - -module PuppetTest::Graph - def build_tree - one = Container.new("one", %w{a b}) - two = Container.new("two", ["c", "d"]) - three = Container.new("three", ["i", "j"]) - middle = Container.new("middle", ["e", "f", two]) - top = Container.new("top", ["g", "h", middle, one, three]) - return one, two, three, middle, top - end -end - -# $Id$ diff --git a/test/lib/puppettest/parsertesting.rb b/test/lib/puppettest/parsertesting.rb index eef0cd8bc..62fa7213e 100644 --- a/test/lib/puppettest/parsertesting.rb +++ b/test/lib/puppettest/parsertesting.rb @@ -42,7 +42,6 @@ module PuppetTest::ParserTesting end def mkcompile(parser = nil) - require 'puppet/network/handler/node' parser ||= mkparser node = mknode return Compile.new(node, parser) @@ -309,17 +308,17 @@ module PuppetTest::ParserTesting ) } - config = nil + trans = nil assert_nothing_raised { - config = interp.compile(mknode) + trans = interp.compile(mknode) } - comp = nil + config = nil assert_nothing_raised { - comp = config.extract.to_type + config = trans.extract.to_configuration } - assert_apply(comp) + config.apply files.each do |file| assert(FileTest.exists?(file), "Did not create %s" % file) diff --git a/test/lib/puppettest/runnable_test.rb b/test/lib/puppettest/runnable_test.rb new file mode 100644 index 000000000..e4b0f9033 --- /dev/null +++ b/test/lib/puppettest/runnable_test.rb @@ -0,0 +1,30 @@ +# Manage whether a test is runnable. +module PuppetTest + module RunnableTest + # Confine this test based on specified criteria. The keys of the + # hash should be the message to use if the test is not suitable, + # and the values should be either 'true' or 'false'; true values + # mean the test is suitable. + def confine(hash) + @confines ||= {} + hash.each do |message, result| + @confines[message] = result + end + end + + # Evaluate all of our tests to see if any of them are false + # and thus whether this test is considered not runnable. + def runnable? + @messages ||= [] + return false unless @messages.empty? + return true unless defined? @confines + @confines.find_all do |message, result| + ! result + end.each do |message, result| + @messages << message + end + + return @messages.empty? + end + end +end diff --git a/test/lib/puppettest/support/assertions.rb b/test/lib/puppettest/support/assertions.rb index 9369f17e7..7e3e5ca2b 100644 --- a/test/lib/puppettest/support/assertions.rb +++ b/test/lib/puppettest/support/assertions.rb @@ -38,7 +38,7 @@ module PuppetTest run_events(:rollback, trans, events, msg) end - def assert_events(events, *items) + def assert_events(events, *resources) trans = nil comp = nil msg = nil @@ -46,56 +46,26 @@ module PuppetTest unless events.is_a? Array raise Puppet::DevError, "Incorrect call of assert_events" end - if items[-1].is_a? String - msg = items.pop + if resources[-1].is_a? String + msg = resources.pop end - remove_comp = false - # They either passed a comp or a list of items. - if items[0].is_a? Puppet.type(:component) - comp = items.shift - else - comp = newcomp(items[0].title, *items) - remove_comp = true - end - msg ||= comp.title - assert_nothing_raised("Component %s failed" % [msg]) { - trans = comp.evaluate - } + config = resources2config(*resources) + transaction = Puppet::Transaction.new(config) - run_events(:evaluate, trans, events, msg) + run_events(:evaluate, transaction, events, msg) - if remove_comp - Puppet.type(:component).delete(comp) - end - - return trans + return transaction end # A simpler method that just applies what we have. - def assert_apply(*objects) - if objects[0].is_a?(Puppet.type(:component)) - comp = objects.shift - unless objects.empty? - objects.each { |o| comp.push o } - end - else - comp = newcomp(*objects) - end - trans = nil - - assert_nothing_raised("Failed to create transaction") { - trans = comp.evaluate - } + def assert_apply(*resources) + config = resources2config(*resources) events = nil - assert_nothing_raised("Failed to evaluate transaction") { - events = trans.evaluate.collect { |e| e.event } + assert_nothing_raised("Failed to evaluate") { + events = config.apply.events } - trans.cleanup - Puppet.type(:component).delete(comp) events end end - -# $Id$ diff --git a/test/lib/puppettest/support/resources.rb b/test/lib/puppettest/support/resources.rb index 45d89c5fb..18d7caa77 100755 --- a/test/lib/puppettest/support/resources.rb +++ b/test/lib/puppettest/support/resources.rb @@ -4,34 +4,34 @@ # Copyright (c) 2006. All rights reserved. module PuppetTest::Support::Resources - def treefile(name) - Puppet::Type.type(:file).create :path => "/tmp/#{name}", :mode => 0755 + def tree_resource(name) + Puppet::Type.type(:file).create :title => name, :path => "/tmp/#{name}", :mode => 0755 end - def treecomp(name) + def tree_container(name) Puppet::Type::Component.create :name => name, :type => "yay" end - def treenode(name, *children) - comp = treecomp name - children.each do |c| - if c.is_a?(String) - comp.push treefile(c) - else - comp.push c + def treenode(config, name, *resources) + comp = tree_container name + resources.each do |resource| + if resource.is_a?(String) + resource = tree_resource(resource) end + config.add_edge!(comp, resource) + config.add_resource resource unless config.resource(resource.ref) end return comp end def mktree - one = treenode("one", "a", "b") - two = treenode("two", "c", "d") - middle = treenode("middle", "e", "f", two) - top = treenode("top", "g", "h", middle, one) + configuration = Puppet::Node::Configuration.new do |config| + one = treenode(config, "one", "a", "b") + two = treenode(config, "two", "c", "d") + middle = treenode(config, "middle", "e", "f", two) + top = treenode(config, "top", "g", "h", middle, one) + end - return one, two, middle, top + return configuration end end - -# $Id$
\ No newline at end of file diff --git a/test/lib/puppettest/support/utils.rb b/test/lib/puppettest/support/utils.rb index c7d54d5e6..7f4260e31 100644 --- a/test/lib/puppettest/support/utils.rb +++ b/test/lib/puppettest/support/utils.rb @@ -19,6 +19,25 @@ module PuppetTest } end + # Turn a list of resources, or possibly a configuration and some resources, + # into a configuration object. + def resources2config(*resources) + if resources[0].is_a?(Puppet::Node::Configuration) + config = resources.shift + unless resources.empty? + resources.each { |r| config.add_resource r } + end + elsif resources[0].is_a?(Puppet.type(:component)) + raise ArgumentError, "resource2config() no longer accpts components" + comp = resources.shift + comp.delve + else + config = Puppet::Node::Configuration.new + resources.each { |res| config.add_resource res } + end + return config + end + # stop any services that might be hanging around def stopservices if stype = Puppet::Type.type(:service) @@ -127,20 +146,17 @@ module PuppetTest } end - def newcomp(*ary) - name = nil - if ary[0].is_a?(String) - name = ary.shift + def mk_configuration(*resources) + if resources[0].is_a?(String) + name = resources.shift else - name = ary[0].title + name = :testing + end + config = Puppet::Node::Configuration.new :testing do |conf| + resources.each { |resource| conf.add_resource resource } end - comp = Puppet.type(:component).create(:name => name) - ary.each { |item| - comp.push item - } - - return comp + return config end def setme diff --git a/test/lib/puppettest/testcase.rb b/test/lib/puppettest/testcase.rb index cfedeee26..15c835854 100644 --- a/test/lib/puppettest/testcase.rb +++ b/test/lib/puppettest/testcase.rb @@ -4,28 +4,11 @@ # Copyright (c) 2007. All rights reserved. require 'puppettest' +require 'puppettest/runnable_test' class PuppetTest::TestCase < Test::Unit::TestCase include PuppetTest - def self.confine(hash) - @confines ||= {} - hash.each do |message, result| - @confines[message] = result - end - end - - def self.runnable? - @messages ||= [] - return false unless @messages.empty? - return true unless defined? @confines - @confines.find_all do |message, result| - ! result - end.each do |message, result| - @messages << message - end - - return @messages.empty? - end + extend PuppetTest::RunnableTest def self.suite # Always skip this parent class. It'd be nice if there were a @@ -44,5 +27,3 @@ class PuppetTest::TestCase < Test::Unit::TestCase end end end - -# $Id$ diff --git a/test/network/client/ca.rb b/test/network/client/ca.rb index 00ed7413a..511b6fcaa 100755 --- a/test/network/client/ca.rb +++ b/test/network/client/ca.rb @@ -23,7 +23,7 @@ class TestClientCA < Test::Unit::TestCase end [:hostprivkey, :hostcert, :localcacert].each do |name| - assert(FileTest.exists?(Puppet.config[name]), + assert(FileTest.exists?(Puppet.settings[name]), "Did not create cert %s" % name) end end diff --git a/test/network/client/client.rb b/test/network/client/client.rb index 93c63d637..382cd55cf 100755 --- a/test/network/client/client.rb +++ b/test/network/client/client.rb @@ -113,9 +113,9 @@ class TestClient < Test::Unit::TestCase # Create a new ssl root. confdir = tempfile() Puppet[:ssldir] = confdir - Puppet.config.mkdir(:ssldir) - Puppet.config.clearused - Puppet.config.use(:ssl, :ca) + Puppet.settings.mkdir(:ssldir) + Puppet.settings.clearused + Puppet.settings.use(:ssl, :ca) mkserver diff --git a/test/network/client/master.rb b/test/network/client/master.rb index a29254d16..169a1de5f 100755 --- a/test/network/client/master.rb +++ b/test/network/client/master.rb @@ -88,51 +88,6 @@ class TestMasterClient < Test::Unit::TestCase return master, objects end - def test_apply - master, objects = mk_fake_client - - check = Proc.new do |hash| - assert(objects.trans, "transaction was not created") - trans = objects.trans - hash[:yes].each do |m| - assert_equal(1, trans.send(m.to_s + "?"), "did not call #{m} enough times") - end - hash[:no].each do |m| - assert_equal(0, trans.send(m.to_s + "?"), "called #{m} too many times") - end - end - - # First try it with no arguments - assert_nothing_raised do - master.apply - end - check.call :yes => %w{evaluate cleanup addtimes}, :no => %w{report tags ignoreschedules} - assert_equal(0, master.reported, "master sent report with reports disabled") - - - # Now enable reporting and make sure the report method gets called - Puppet[:report] = true - assert_nothing_raised do - master.apply - end - check.call :yes => %w{evaluate cleanup addtimes}, :no => %w{tags ignoreschedules} - assert_equal(1, master.reported, "master did not send report") - - # Now try it with tags enabled - assert_nothing_raised do - master.apply("tags") - end - check.call :yes => %w{evaluate cleanup tags addtimes}, :no => %w{ignoreschedules} - assert_equal(2, master.reported, "master did not send report") - - # and ignoreschedules - assert_nothing_raised do - master.apply("tags", true) - end - check.call :yes => %w{evaluate cleanup tags ignoreschedules addtimes}, :no => %w{} - assert_equal(3, master.reported, "master did not send report") - end - def test_getconfig client = mkclient @@ -167,9 +122,8 @@ class TestMasterClient < Test::Unit::TestCase [:getplugins, :get_actual_config].each do |method| assert($methodsrun.include?(method), "method %s was not run" % method) end - - objects = client.objects - assert(objects.finalized?, "objects were not finalized") + + assert_instance_of(Puppet::Node::Configuration, client.configuration, "Configuration was not created") end def test_disable @@ -233,7 +187,7 @@ class TestMasterClient < Test::Unit::TestCase } end - # This method is supposed + # This method downloads files, and yields each file object if a block is given. def test_download source = tempfile() dest = tempfile() @@ -726,7 +680,7 @@ end client.apply # Make sure the config is not cached. - config = Puppet.config[:localconfig] + ".yaml" + config = Puppet.settings[:localconfig] + ".yaml" assert(! File.exists?(config), "Cached an invalid configuration") end end diff --git a/test/network/handler/configuration.rb b/test/network/handler/configuration.rb index 072fdc053..29a393769 100755 --- a/test/network/handler/configuration.rb +++ b/test/network/handler/configuration.rb @@ -20,19 +20,6 @@ class TestHandlerConfiguration < Test::Unit::TestCase assert(config.local?, "Config is not considered local after being started that way") end - # Make sure we create the node handler when necessary. - def test_node_handler - config = Config.new - handler = nil - assert_nothing_raised("Could not create node handler") do - handler = config.send(:node_handler) - end - assert_instance_of(Puppet::Network::Handler.handler(:node), handler, "Did not create node handler") - - # Now make sure we get the same object back again - assert_equal(handler.object_id, config.send(:node_handler).object_id, "Did not cache node handler") - end - # Test creation/returning of the interpreter def test_interpreter config = Config.new @@ -76,14 +63,14 @@ class TestHandlerConfiguration < Test::Unit::TestCase fakenode = Object.new # Set the server facts to something config.instance_variable_set("@server_facts", :facts) - fakenode.expects(:fact_merge).with(:facts) + fakenode.expects(:merge).with(:facts) config.send(:add_node_data, fakenode) # Now try it with classes. config.instance_variable_set("@options", {:Classes => %w{a b}}) list = [] fakenode = Object.new - fakenode.expects(:fact_merge).with(:facts) + fakenode.expects(:merge).with(:facts) fakenode.expects(:classes).returns(list).times(2) config.send(:add_node_data, fakenode) assert_equal(%w{a b}, list, "Did not add classes to node") @@ -170,19 +157,14 @@ class TestHandlerConfiguration < Test::Unit::TestCase def test_version # First try the case where we can't look up the node config = Config.new - handler = Object.new - handler.expects(:details).with(:client).returns(false) - config.expects(:node_handler).returns(handler) + node = Object.new + Puppet::Node.stubs(:search).with(:client).returns(false, node) interp = Object.new assert_instance_of(Bignum, config.version(:client), "Did not return configuration version") # And then when we find the node. config = Config.new - node = Object.new - handler = Object.new - handler.expects(:details).with(:client).returns(node) config.expects(:update_node_check).with(node) - config.expects(:node_handler).returns(handler) interp = Object.new interp.expects(:configuration_version).returns(:version) config.expects(:interpreter).returns(interp) diff --git a/test/network/handler/facts.rb b/test/network/handler/facts.rb deleted file mode 100755 index 03327b8c4..000000000 --- a/test/network/handler/facts.rb +++ /dev/null @@ -1,112 +0,0 @@ -#!/usr/bin/env ruby - -$:.unshift("../../lib") if __FILE__ =~ /\.rb$/ - -require 'puppettest' -require 'mocha' -require 'puppet/network/handler/facts' - -class TestFactsHandler < Test::Unit::TestCase - include PuppetTest::ServerTest - - def setup - super - - @class = Puppet::Network::Handler.handler(:facts) - - @@client_facts = {} - - unless Puppet::Util::FactStore.store(:testing) - Puppet::Util::FactStore.newstore(:testing) do - def get(node) - @@client_facts[node] - end - - def set(node, facts) - @@client_facts[node] = facts - end - end - end - - Puppet[:factstore] = :testing - - @handler = @class.new - - @facts = {:a => :b, :c => :d} - @name = "foo" - - @backend = @handler.instance_variable_get("@backend") - end - - def teardown - @@client_facts.clear - end - - def test_strip_internal - @facts[:_puppet_one] = "yay" - @facts[:_puppet_two] = "boo" - @facts[:_puppetthree] = "foo" - - newfacts = nil - assert_nothing_raised("Could not call strip_internal") do - newfacts = @handler.send(:strip_internal, @facts) - end - - [:_puppet_one, :_puppet_two, :_puppetthree].each do |name| - assert(@facts.include?(name), "%s was removed in strip_internal from original hash" % name) - end - [:_puppet_one, :_puppet_two].each do |name| - assert(! newfacts.include?(name), "%s was not removed in strip_internal" % name) - end - assert_equal("foo", newfacts[:_puppetthree], "_puppetthree was removed in strip_internal") - end - - def test_add_internal - newfacts = nil - assert_nothing_raised("Could not call strip_internal") do - newfacts = @handler.send(:add_internal, @facts) - end - - assert_instance_of(Time, newfacts[:_puppet_timestamp], "Did not set timestamp in add_internal") - assert(! @facts.include?(:_puppet_timestamp), "Modified original hash in add_internal") - end - - def test_set - newfacts = @facts.dup - newfacts[:_puppet_timestamp] = Time.now - @handler.expects(:add_internal).with(@facts).returns(newfacts) - @backend.expects(:set).with(@name, newfacts).returns(nil) - - assert_nothing_raised("Could not set facts") do - assert_nil(@handler.set(@name, @facts), "handler.set did not return nil") - end - end - - def test_get - prefacts = @facts.dup - prefacts[:_puppet_timestamp] = Time.now - @@client_facts[@name] = prefacts - @handler.expects(:strip_internal).with(prefacts).returns(@facts) - @backend.expects(:get).with(@name).returns(prefacts) - - assert_nothing_raised("Could not retrieve facts") do - assert_equal(@facts, @handler.get(@name), "did not get correct answer from handler.get") - end - - @handler = @class.new - assert_nothing_raised("Failed to call 'get' with no stored facts") do - @handler.get("nosuchname") - end - end - - def test_store_date - time = Time.now - @facts[:_puppet_timestamp] = time - - @handler.expects(:get).with(@name).returns(@facts) - - assert_equal(time.to_i, @handler.store_date(@name), "Did not retrieve timestamp correctly") - end -end - -# $Id$ diff --git a/test/network/handler/master.rb b/test/network/handler/master.rb index a976726ef..4f8e7fab2 100755 --- a/test/network/handler/master.rb +++ b/test/network/handler/master.rb @@ -8,6 +8,11 @@ require 'puppet/network/handler/master' class TestMaster < Test::Unit::TestCase include PuppetTest::ServerTest + def teardown + super + Puppet::Indirector::Indirection.clear_cache + end + def test_defaultmanifest textfiles { |file| Puppet[:manifest] = file @@ -70,10 +75,11 @@ class TestMaster < Test::Unit::TestCase assert(! client.fresh?(facts), "Client is incorrectly up to date") - Puppet.config.use(:main) + Puppet.settings.use(:main) + config = nil assert_nothing_raised { - client.getconfig - client.apply + config = client.getconfig + config.apply } # Now it should be up to date @@ -108,8 +114,8 @@ class TestMaster < Test::Unit::TestCase # Retrieve and apply the new config assert_nothing_raised { - client.getconfig - client.apply + config = client.getconfig + config.apply } assert(client.fresh?(facts), "Client is not up to date") diff --git a/test/network/handler/node.rb b/test/network/handler/node.rb deleted file mode 100755 index 6b8ab9290..000000000 --- a/test/network/handler/node.rb +++ /dev/null @@ -1,640 +0,0 @@ -#!/usr/bin/env ruby - -$:.unshift("../lib").unshift("../../lib") if __FILE__ =~ /\.rb$/ - -require 'mocha' -require 'puppettest' -require 'puppettest/resourcetesting' -require 'puppettest/parsertesting' -require 'puppettest/servertest' -require 'puppet/network/handler/node' - -module NodeTesting - include PuppetTest - Node = Puppet::Network::Handler::Node - SimpleNode = Puppet::Node - - def mk_node_mapper - # First, make sure our nodesearch command works as we expect - # Make a nodemapper - mapper = tempfile() - ruby = %x{which ruby}.chomp - File.open(mapper, "w") { |f| - f.puts "#!#{ruby} - require 'yaml' - name = ARGV.last.chomp - result = {} - - if name =~ /a/ - result[:parameters] = {'one' => ARGV.last + '1', 'two' => ARGV.last + '2'} - end - - if name =~ /p/ - result['classes'] = [1,2,3].collect { |n| ARGV.last + n.to_s } - end - - puts YAML.dump(result) - " - } - File.chmod(0755, mapper) - mapper - end - - def mk_searcher(name) - searcher = Object.new - searcher.extend(Node.node_source(name)) - searcher.meta_def(:newnode) do |name, *args| - SimpleNode.new(name, *args) - end - searcher - end - - def mk_node_source - @node_info = {} - @node_source = Node.newnode_source(:testing, :fact_merge => true) do - def nodesearch(key) - if info = @node_info[key] - SimpleNode.new(info) - else - nil - end - end - end - Puppet[:node_source] = "testing" - - cleanup { Node.rm_node_source(:testing) } - end -end - -class TestNodeHandler < Test::Unit::TestCase - include NodeTesting - - def setup - super - mk_node_source - end - - # Make sure that the handler includes the appropriate - # node source. - def test_initialize - # First try it when passing in the node source - handler = nil - assert_nothing_raised("Could not specify a node source") do - handler = Node.new(:Source => :testing) - end - assert(handler.metaclass.included_modules.include?(@node_source), "Handler did not include node source") - - # Now use the Puppet[:node_source] - Puppet[:node_source] = "testing" - assert_nothing_raised("Could not specify a node source") do - handler = Node.new() - end - assert(handler.metaclass.included_modules.include?(@node_source), "Handler did not include node source") - - # And make sure we throw an exception when an invalid node source is used - assert_raise(ArgumentError, "Accepted an invalid node source") do - handler = Node.new(:Source => "invalid") - end - end - - # Make sure we can find and we cache a fact handler. - def test_fact_handler - handler = Node.new - fhandler = nil - assert_nothing_raised("Could not retrieve the fact handler") do - fhandler = handler.send(:fact_handler) - end - assert_instance_of(Puppet::Network::Handler::Facts, fhandler, "Did not get a fact handler back") - - # Now call it again, making sure we're caching the value. - fhandler2 = nil - assert_nothing_raised("Could not retrieve the fact handler") do - fhandler2 = handler.send(:fact_handler) - end - assert_instance_of(Puppet::Network::Handler::Facts, fhandler2, "Did not get a fact handler on the second run") - assert_equal(fhandler.object_id, fhandler2.object_id, "Did not cache fact handler") - end - - # Make sure we can get node facts from the fact handler. - def test_node_facts - # Check the case where we find the node. - handler = Node.new - fhandler = handler.send(:fact_handler) - fhandler.expects(:get).with("present").returns("a" => "b") - - result = nil - assert_nothing_raised("Could not get facts from fact handler") do - result = handler.send(:node_facts, "present") - end - assert_equal({"a" => "b"}, result, "Did not get correct facts back") - - # Now try the case where the fact handler knows nothing about our host - fhandler.expects(:get).with('missing').returns(nil) - result = nil - assert_nothing_raised("Could not get facts from fact handler when host is missing") do - result = handler.send(:node_facts, "missing") - end - assert_equal({}, result, "Did not get empty hash when no facts are known") - end - - # Test our simple shorthand - def test_newnode - SimpleNode.expects(:new).with("stuff") - handler = Node.new - handler.send(:newnode, "stuff") - end - - # Make sure we can build up the correct node names to search for - def test_node_names - handler = Node.new - - # Verify that the handler asks for the facts if we don't pass them in - handler.expects(:node_facts).with("testing").returns({}) - handler.send(:node_names, "testing") - - handler = Node.new - # Test it first with no parameters - assert_equal(%w{testing}, handler.send(:node_names, "testing"), "Node names did not default to an array including just the node name") - - # Now test it with a fully qualified name - assert_equal(%w{testing.domain.com testing}, handler.send(:node_names, "testing.domain.com"), - "Fully qualified names did not get turned into multiple names, longest first") - - # And try it with a short name + domain fact - assert_equal(%w{testing host.domain.com host}, handler.send(:node_names, "testing", "domain" => "domain.com", "hostname" => "host"), - "The domain fact was not used to build up an fqdn") - - # And with an fqdn - assert_equal(%w{testing host.domain.com host}, handler.send(:node_names, "testing", "fqdn" => "host.domain.com"), - "The fqdn was not used") - - # And make sure the fqdn beats the domain - assert_equal(%w{testing host.other.com host}, handler.send(:node_names, "testing", "domain" => "domain.com", "fqdn" => "host.other.com"), - "The domain was used in preference to the fqdn") - end - - # Make sure we can retrieve a whole node by name. - def test_details_when_we_find_nodes - handler = Node.new - - # Make sure we get the facts first - handler.expects(:node_facts).with("host").returns(:facts) - - # Find the node names - handler.expects(:node_names).with("host", :facts).returns(%w{a b c}) - - # Iterate across them - handler.expects(:nodesearch).with("a").returns(nil) - handler.expects(:nodesearch).with("b").returns(nil) - - # Create an example node to return - node = SimpleNode.new("host") - - # Make sure its source is set - node.expects(:source=).with(handler.source) - - # And that the names are retained - node.expects(:names=).with(%w{a b c}) - - # And make sure we actually get it back - handler.expects(:nodesearch).with("c").returns(node) - - handler.expects(:fact_merge?).returns(true) - - # Make sure we merge the facts with the node's parameters. - node.expects(:fact_merge).with(:facts) - - # Now call the method - result = nil - assert_nothing_raised("could not call 'details'") do - result = handler.details("host") - end - assert_equal(node, result, "Did not get correct node back") - end - - # But make sure we pass through to creating default nodes when appropriate. - def test_details_using_default_node - handler = Node.new - - # Make sure we get the facts first - handler.expects(:node_facts).with("host").returns(:facts) - - # Find the node names - handler.expects(:node_names).with("host", :facts).returns([]) - - # Create an example node to return - node = SimpleNode.new("host") - - # Make sure its source is set - node.expects(:source=).with(handler.source) - - # And make sure we actually get it back - handler.expects(:nodesearch).with("default").returns(node) - - # This time, have it return false - handler.expects(:fact_merge?).returns(false) - - # And because fact_merge was false, we don't merge them. - node.expects(:fact_merge).never - - # Now call the method - result = nil - assert_nothing_raised("could not call 'details'") do - result = handler.details("host") - end - assert_equal(node, result, "Did not get correct node back") - end - - # Make sure our handler behaves rationally when it comes to getting environment data. - def test_environment - # What happens when we can't find the node - handler = Node.new - handler.expects(:details).with("fake").returns(nil) - - result = nil - assert_nothing_raised("Could not call 'Node.environment'") do - result = handler.environment("fake") - end - assert_nil(result, "Got an environment for a node we could not find") - - # Now for nodes we can find - handler = Node.new - node = SimpleNode.new("fake") - handler.expects(:details).with("fake").returns(node) - node.expects(:environment).returns("dev") - - result = nil - assert_nothing_raised("Could not call 'Node.environment'") do - result = handler.environment("fake") - end - assert_equal("dev", result, "Did not get environment back") - end - - # Make sure our handler behaves rationally when it comes to getting parameter data. - def test_parameters - # What happens when we can't find the node - handler = Node.new - handler.expects(:details).with("fake").returns(nil) - - result = nil - assert_nothing_raised("Could not call 'Node.parameters'") do - result = handler.parameters("fake") - end - assert_nil(result, "Got parameters for a node we could not find") - - # Now for nodes we can find - handler = Node.new - node = SimpleNode.new("fake") - handler.expects(:details).with("fake").returns(node) - node.expects(:parameters).returns({"a" => "b"}) - - result = nil - assert_nothing_raised("Could not call 'Node.parameters'") do - result = handler.parameters("fake") - end - assert_equal({"a" => "b"}, result, "Did not get parameters back") - end - - def test_classes - # What happens when we can't find the node - handler = Node.new - handler.expects(:details).with("fake").returns(nil) - - result = nil - assert_nothing_raised("Could not call 'Node.classes'") do - result = handler.classes("fake") - end - assert_nil(result, "Got classes for a node we could not find") - - # Now for nodes we can find - handler = Node.new - node = SimpleNode.new("fake") - handler.expects(:details).with("fake").returns(node) - node.expects(:classes).returns(%w{yay foo}) - - result = nil - assert_nothing_raised("Could not call 'Node.classes'") do - result = handler.classes("fake") - end - assert_equal(%w{yay foo}, result, "Did not get classes back") - end - - # We reuse the filetimeout for the node caching timeout. - def test_node_caching - handler = Node.new - - node = Object.new - node.metaclass.instance_eval do - attr_accessor :time, :name - end - node.time = Time.now - node.name = "yay" - - # Make sure caching works normally - assert_nothing_raised("Could not cache node") do - handler.send(:cache, node) - end - assert_equal(node.object_id, handler.send(:cached?, "yay").object_id, "Did not get node back from the cache") - - # And that it's returned if we ask for it, instead of creating a new node. - assert_equal(node.object_id, handler.details("yay").object_id, "Did not use cached node") - - # Now set the node's time to be a long time ago - node.time = Time.now - 50000 - assert(! handler.send(:cached?, "yay"), "Timed-out node was returned from cache") - end -end - -# Test our configuration object. -class TestNodeSources < Test::Unit::TestCase - include NodeTesting - - def test_node_sources - mod = nil - assert_nothing_raised("Could not add new search type") do - mod = Node.newnode_source(:testing) do - def nodesearch(name) - end - end - end - assert_equal(mod, Node.node_source(:testing), "Did not get node_source back") - - cleanup do - Node.rm_node_source(:testing) - assert(! Node.const_defined?("Testing"), "Did not remove constant") - end - end - - def test_external_node_source - source = Node.node_source(:external) - assert(source, "Could not find external node source") - mapper = mk_node_mapper - searcher = mk_searcher(:external) - assert(searcher.fact_merge?, "External node source does not merge facts") - - # Make sure it gives the right response - assert_equal({'classes' => %w{apple1 apple2 apple3}, :parameters => {"one" => "apple1", "two" => "apple2"}}, - YAML.load(%x{#{mapper} apple})) - - # First make sure we get nil back by default - assert_nothing_raised { - assert_nil(searcher.nodesearch("apple"), - "Interp#nodesearch_external defaulted to a non-nil response") - } - assert_nothing_raised { Puppet[:external_nodes] = mapper } - - node = nil - # Both 'a' and 'p', so we get classes and parameters - assert_nothing_raised { node = searcher.nodesearch("apple") } - assert_equal("apple", node.name, "node name was not set correctly for apple") - assert_equal(%w{apple1 apple2 apple3}, node.classes, "node classes were not set correctly for apple") - assert_equal( {"one" => "apple1", "two" => "apple2"}, node.parameters, "node parameters were not set correctly for apple") - - # A 'p' but no 'a', so we only get classes - assert_nothing_raised { node = searcher.nodesearch("plum") } - assert_equal("plum", node.name, "node name was not set correctly for plum") - assert_equal(%w{plum1 plum2 plum3}, node.classes, "node classes were not set correctly for plum") - assert_equal({}, node.parameters, "node parameters were not set correctly for plum") - - # An 'a' but no 'p', so we only get parameters. - assert_nothing_raised { node = searcher.nodesearch("guava")} # no p's, thus no classes - assert_equal("guava", node.name, "node name was not set correctly for guava") - assert_equal([], node.classes, "node classes were not set correctly for guava") - assert_equal({"one" => "guava1", "two" => "guava2"}, node.parameters, "node parameters were not set correctly for guava") - - assert_nothing_raised { node = searcher.nodesearch("honeydew")} # neither, thus nil - assert_nil(node) - end - - # Make sure a nodesearch with arguments works - def test_nodesearch_external_arguments - mapper = mk_node_mapper - Puppet[:external_nodes] = "#{mapper} -s something -p somethingelse" - searcher = mk_searcher(:external) - node = nil - assert_nothing_raised do - node = searcher.nodesearch("apple") - end - assert_instance_of(SimpleNode, node, "did not create node") - end - - # A wrapper test, to make sure we're correctly calling the external search method. - def test_nodesearch_external_functional - mapper = mk_node_mapper - searcher = mk_searcher(:external) - - Puppet[:external_nodes] = mapper - - node = nil - assert_nothing_raised do - node = searcher.nodesearch("apple") - end - assert_instance_of(SimpleNode, node, "did not create node") - end - - # This can stay in the main test suite because it doesn't actually use ldapsearch, - # it just overrides the method so it behaves as though it were hitting ldap. - def test_ldap_nodesearch - source = Node.node_source(:ldap) - assert(source, "Could not find ldap node source") - searcher = mk_searcher(:ldap) - assert(searcher.fact_merge?, "LDAP node source does not merge facts") - - nodetable = {} - - # Override the ldapsearch definition, so we don't have to actually set it up. - searcher.meta_def(:ldapsearch) do |name| - nodetable[name] - end - - # Make sure we get nothing for nonexistent hosts - node = nil - assert_nothing_raised do - node = searcher.nodesearch("nosuchhost") - end - - assert_nil(node, "Got a node for a non-existent host") - - # Now add a base node with some classes and parameters - nodetable["base"] = [nil, %w{one two}, {"base" => "true"}] - - assert_nothing_raised do - node = searcher.nodesearch("base") - end - - assert_instance_of(SimpleNode, node, "Did not get node from ldap nodesearch") - assert_equal("base", node.name, "node name was not set") - - assert_equal(%w{one two}, node.classes, "node classes were not set") - assert_equal({"base" => "true"}, node.parameters, "node parameters were not set") - - # Now use a different with this as the base - nodetable["middle"] = ["base", %w{three}, {"center" => "boo"}] - assert_nothing_raised do - node = searcher.nodesearch("middle") - end - - assert_instance_of(SimpleNode, node, "Did not get node from ldap nodesearch") - assert_equal("middle", node.name, "node name was not set") - - assert_equal(%w{one two three}.sort, node.classes.sort, "node classes were not set correctly with a parent node") - assert_equal({"base" => "true", "center" => "boo"}, node.parameters, "node parameters were not set correctly with a parent node") - - # And one further, to make sure we fully recurse - nodetable["top"] = ["middle", %w{four five}, {"master" => "far"}] - assert_nothing_raised do - node = searcher.nodesearch("top") - end - - assert_instance_of(SimpleNode, node, "Did not get node from ldap nodesearch") - assert_equal("top", node.name, "node name was not set") - - assert_equal(%w{one two three four five}.sort, node.classes.sort, "node classes were not set correctly with the top node") - assert_equal({"base" => "true", "center" => "boo", "master" => "far"}, node.parameters, "node parameters were not set correctly with the top node") - end - - # Make sure we always get a node back from the 'none' nodesource. - def test_nodesource_none - source = Node.node_source(:none) - assert(source, "Could not find 'none' node source") - searcher = mk_searcher(:none) - assert(searcher.fact_merge?, "'none' node source does not merge facts") - - # Run a couple of node names through it - node = nil - %w{192.168.0.1 0:0:0:3:a:f host host.domain.com}.each do |name| - assert_nothing_raised("Could not create an empty node with name '%s'" % name) do - node = searcher.nodesearch(name) - end - assert_instance_of(SimpleNode, node, "Did not get a simple node back for %s" % name) - assert_equal(name, node.name, "Name was not set correctly") - end - end -end - -class LdapNodeTest < PuppetTest::TestCase - include NodeTesting - include PuppetTest::ServerTest - include PuppetTest::ParserTesting - include PuppetTest::ResourceTesting - AST = Puppet::Parser::AST - confine "LDAP is not available" => Puppet.features.ldap? - confine "No LDAP test data for networks other than Luke's" => Facter.value(:domain) == "madstop.com" - - 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(name) - node = Puppet::Node.new(name) - parent = nil - found = false - @ldap.search( "ou=hosts, dc=madstop, dc=com", 2, - "(&(objectclass=puppetclient)(cn=%s))" % name - ) do |entry| - node.classes = entry.vals("puppetclass") || [] - node.parameters = entry.to_hash.inject({}) do |hash, ary| - if ary[1].length == 1 - hash[ary[0]] = ary[1].shift - else - hash[ary[0]] = ary[1] - end - hash - end - parent = node.parameters["parentnode"] - found = true - end - raise "Could not find node %s" % name unless found - - return node, parent - end - - def test_ldapsearch - Puppet[:ldapbase] = "ou=hosts, dc=madstop, dc=com" - Puppet[:ldapnodes] = true - - searcher = Object.new - searcher.extend(Node.node_source(:ldap)) - - ldapconnect() - - # Make sure we get nil and nil back when we search for something missing - parent, classes, parameters = nil - assert_nothing_raised do - parent, classes, parameters = searcher.ldapsearch("nosuchhost") - end - - assert_nil(parent, "Got a parent for a non-existent host") - assert_nil(classes, "Got classes for a non-existent host") - - # Make sure we can find 'culain' in ldap - assert_nothing_raised do - parent, classes, parameters = searcher.ldapsearch("culain") - end - - node, realparent = ldaphost("culain") - assert_equal(realparent, parent, "did not get correct parent node from ldap") - assert_equal(node.classes, classes, "did not get correct ldap classes from ldap") - assert_equal(node.parameters, parameters, "did not get correct ldap parameters from ldap") - - # Now compare when we specify the attributes to get. - Puppet[:ldapattrs] = "cn" - assert_nothing_raised do - parent, classes, parameters = searcher.ldapsearch("culain") - end - assert_equal(realparent, parent, "did not get correct parent node from ldap") - assert_equal(node.classes, classes, "did not get correct ldap classes from ldap") - - list = %w{cn puppetclass parentnode dn} - should = node.parameters.inject({}) { |h, a| h[a[0]] = a[1] if list.include?(a[0]); h } - assert_equal(should, parameters, "did not get correct ldap parameters from ldap") - end -end - -class LdapReconnectTests < PuppetTest::TestCase - include NodeTesting - include PuppetTest::ServerTest - include PuppetTest::ParserTesting - include PuppetTest::ResourceTesting - AST = Puppet::Parser::AST - confine "Not running on culain as root" => (Puppet::Util::SUIDManager.uid == 0 and Facter.value("hostname") == "culain") - - def test_ldapreconnect - Puppet[:ldapbase] = "ou=hosts, dc=madstop, dc=com" - Puppet[:ldapnodes] = true - - searcher = Object.new - searcher.extend(Node.node_source(:ldap)) - hostname = "culain.madstop.com" - - # look for our host - assert_nothing_raised { - parent, classes = searcher.nodesearch(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 = searcher.nodesearch(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 = searcher.nodesearch(hostname) - } - end -end diff --git a/test/other/dsl.rb b/test/other/dsl.rb index f1fd1a1e9..59610cd0f 100755 --- a/test/other/dsl.rb +++ b/test/other/dsl.rb @@ -214,5 +214,3 @@ class TestDSL < Test::Unit::TestCase assert_instance_of(Puppet::Parser::Resource, file) end end - -# $Id$ diff --git a/test/other/events.rb b/test/other/events.rb index 802a701a3..b67ea05a1 100755 --- a/test/other/events.rb +++ b/test/other/events.rb @@ -23,7 +23,7 @@ class TestEvents < Test::Unit::TestCase :subscribe => [[file.class.name, file.name]] ) - comp = newcomp("eventtesting", file, exec) + comp = mk_configuration("eventtesting", file, exec) trans = assert_events([:file_created, :triggered], comp) @@ -44,56 +44,16 @@ class TestEvents < Test::Unit::TestCase ) - comp = Puppet.type(:component).create( - :name => "eventtesting" - ) - comp.push exec - trans = comp.evaluate - events = nil - assert_nothing_raised { - events = trans.evaluate - } + config = mk_configuration + config.add_resource file + config.add_resource exec + trans = config.apply - assert_equal(1, events.length) + assert_equal(1, trans.events.length) assert_equal(0, trans.triggered?(exec, :refresh)) end - # Verify that one component can subscribe to another component and the "right" - # thing happens - def test_ladderrequire - comps = {} - objects = {} - fname = tempfile() - file = Puppet.type(:file).create( - :name => tempfile(), - :ensure => "file" - ) - - exec = Puppet.type(:exec).create( - :name => "touch %s" % fname, - :path => "/usr/bin:/bin", - :refreshonly => true - ) - - fcomp = newcomp(file) - ecomp = newcomp(exec) - comp = newcomp("laddercomp", fcomp, ecomp) - - ecomp[:subscribe] = [[fcomp.class.name, fcomp.name]] - - comp.finalize - - trans = comp.evaluate - events = nil - assert_nothing_raised { - events = trans.evaluate - } - - assert(FileTest.exists?(fname), "#{fname} does not exist") - #assert_equal(events.length, trans.triggered?(objects[:b], :refresh)) - end - def test_multiplerefreshes files = [] @@ -115,7 +75,7 @@ class TestEvents < Test::Unit::TestCase ["file", f.name] } - comp = newcomp(exec, *files) + comp = mk_configuration(exec, *files) assert_apply(comp) assert(FileTest.exists?(fname), "Exec file did not get created") @@ -147,17 +107,16 @@ class TestEvents < Test::Unit::TestCase ) execs = [exec1, exec2, exec3] - comp = newcomp(exec1,exec2,exec3) + config = mk_configuration(exec1,exec2,exec3) - trans = comp.evaluate - execs.each do |e| assert(trans.resources.vertex?(e), "%s is not in graph" % e.title) end + trans = Puppet::Transaction.new(config) + execs.each do |e| assert(config.vertex?(e), "%s is not in graph" % e.title) end trans.prepare - execs.each do |e| assert(trans.relgraph.vertex?(e), "%s is not in relgraph" % e.title) end - reverse = trans.relgraph.reversal + execs.each do |e| assert(config.vertex?(e), "%s is not in relgraph" % e.title) end + reverse = trans.relationship_graph.reversal execs.each do |e| assert(reverse.vertex?(e), "%s is not in reversed graph" % e.title) end - - assert_apply(comp) + config.apply assert(FileTest.exists?(file), "File does not exist") diff --git a/test/other/overrides.rb b/test/other/overrides.rb index 2bc443980..9a7c4b8ba 100755 --- a/test/other/overrides.rb +++ b/test/other/overrides.rb @@ -90,12 +90,10 @@ class TestOverrides < Test::Unit::TestCase } } - comp = newcomp("overrides", baseobj) - children.each { |child| comp.push child } + config = mk_configuration(baseobj, *children) assert_nothing_raised("Could not eval component") { - trans = comp.evaluate - trans.evaluate + config.apply } files.each { |path, mode| diff --git a/test/other/pgraph.rb b/test/other/pgraph.rb deleted file mode 100755 index 34ba0e18c..000000000 --- a/test/other/pgraph.rb +++ /dev/null @@ -1,290 +0,0 @@ -#!/usr/bin/env ruby -# -# Created by Luke Kanies on 2006-11-16. -# Copyright (c) 2006. All rights reserved. - -$:.unshift("../lib").unshift("../../lib") if __FILE__ =~ /\.rb$/ - -require 'puppettest' -require 'puppettest/graph' - -class TestPGraph < Test::Unit::TestCase - include PuppetTest - include PuppetTest::Graph - - Edge = Puppet::Relationship - - def test_clear - graph = Puppet::PGraph.new - graph.add_edge!("a", "b") - graph.add_vertex! "c" - assert_nothing_raised do - graph.clear - end - assert(graph.vertices.empty?, "Still have vertices after clear") - assert(graph.edges.empty?, "still have edges after clear") - end - - - def test_matching_edges - graph = Puppet::PGraph.new - - event = Puppet::Event.new(:source => "a", :event => :yay) - none = Puppet::Event.new(:source => "a", :event => :NONE) - - edges = {} - - edges["a/b"] = Edge["a", "b", {:event => :yay, :callback => :refresh}] - edges["a/c"] = Edge["a", "c", {:event => :yay, :callback => :refresh}] - - graph.add_edge!(edges["a/b"]) - - # Try it for the trivial case of one target and a matching event - assert_equal([edges["a/b"]], graph.matching_edges([event])) - - # Make sure we get nothing with a different event - assert_equal([], graph.matching_edges([none])) - - # Set up multiple targets and make sure we get them all back - graph.add_edge!(edges["a/c"]) - assert_equal([edges["a/b"], edges["a/c"]].sort, graph.matching_edges([event]).sort) - assert_equal([], graph.matching_edges([none])) - end - - def test_dependencies - graph = Puppet::PGraph.new - - graph.add_edge!("a", "b") - graph.add_edge!("a", "c") - graph.add_edge!("b", "d") - - assert_equal(%w{b c d}.sort, graph.dependents("a").sort) - assert_equal(%w{d}.sort, graph.dependents("b").sort) - assert_equal([].sort, graph.dependents("c").sort) - - assert_equal(%w{a b}, graph.dependencies("d").sort) - assert_equal(%w{a}, graph.dependencies("b").sort) - assert_equal(%w{a}, graph.dependencies("c").sort) - assert_equal([], graph.dependencies("a").sort) - - end - - # Test that we can take a containment graph and rearrange it by dependencies - def test_splice - one, two, three, middle, top = build_tree - empty = Container.new("empty", []) - # Also, add an empty container to top - top.push empty - - contgraph = top.to_graph - - # Now add a couple of child files, so that we can test whether all - # containers get spliced, rather than just components. - - # Now make a dependency graph - deps = Puppet::PGraph.new - - contgraph.vertices.each do |v| - deps.add_vertex(v) - end - - # We have to specify a relationship to our empty container, else it - # never makes it into the dep graph in the first place. - {one => two, "f" => "c", "h" => middle, "c" => empty}.each do |source, target| - deps.add_edge!(source, target, :callback => :refresh) - end - - #contgraph.to_jpg(File.expand_path("~/Desktop/pics"), "main") - #deps.to_jpg(File.expand_path("~/Desktop/pics"), "before") - assert_nothing_raised { deps.splice!(contgraph, Container) } - - assert(! deps.cyclic?, "Created a cyclic graph") - - # Make sure there are no container objects remaining - #deps.to_jpg(File.expand_path("~/Desktop/pics"), "after") - c = deps.vertices.find_all { |v| v.is_a?(Container) } - assert(c.empty?, "Still have containers %s" % c.inspect) - - # Now make sure the containers got spliced correctly. - contgraph.leaves(middle).each do |leaf| - assert(deps.edge?("h", leaf), "no edge for h => %s" % leaf) - end - one.each do |oobj| - two.each do |tobj| - assert(deps.edge?(oobj, tobj), "no %s => %s edge" % [oobj, tobj]) - end - end - - nons = deps.vertices.find_all { |v| ! v.is_a?(String) } - assert(nons.empty?, - "still contain non-strings %s" % nons.inspect) - - deps.edges.each do |edge| - assert_equal({:callback => :refresh}, edge.label, - "Label was not copied for %s => %s" % [edge.source, edge.target]) - end - - # Now add some relationships to three, but only add labels to one of - # the relationships. - - # Add a simple, label-less relationship - deps.add_edge!(two, three) - assert_nothing_raised { deps.splice!(contgraph, Container) } - - # And make sure it stuck, with no labels. - assert_equal({}, deps.edge_label("c", "i"), - "label was created for c => i") - - # Now add some edges with labels, in a way that should overwrite - deps.add_edge!("c", three, {:callback => :refresh}) - assert_nothing_raised { deps.splice!(contgraph, Container) } - - # And make sure the label got copied. - assert_equal({:callback => :refresh}, deps.edge_label("c", "i"), - "label was not copied for c => i") - - # Lastly, add some new label-less edges and make sure the label stays. - deps.add_edge!(middle, three) - assert_nothing_raised { deps.splice!(contgraph, Container) } - assert_equal({:callback => :refresh}, deps.edge_label("c", "i"), - "label was lost for c => i") - - # Now make sure the 'three' edges all have the label we've used. - # Note that this will not work when we support more than one type of - # subscription. - three.each do |child| - edge = deps.edge_class.new("c", child) - assert(deps.edge?(edge), "no c => %s edge" % child) - assert_equal({:callback => :refresh}, deps[edge], - "label was not retained for c => %s" % child) - end - end - - def test_copy_label - graph = Puppet::PGraph.new - - # First make an edge with no label - graph.add_edge!(:a, :b) - assert_nil(graph.edge_label(:a, :b), "Created a label") - - # Now try to copy an empty label in. - graph.copy_label(:a, :b, {}) - - # It should just do nothing, since we copied an empty label. - assert_nil(graph.edge_label(:a, :b), "Created a label") - - # Now copy in a real label. - graph.copy_label(:a, :b, {:callback => :yay}) - assert_equal({:callback => :yay}, - graph.edge_label(:a, :b), "Did not copy label") - - # Now copy in a nil label - graph.copy_label(:a, :b, nil) - assert_equal({:callback => :yay}, - graph.edge_label(:a, :b), "lost label") - - # And an empty one. - graph.copy_label(:a, :b, {}) - assert_equal({:callback => :yay}, - graph.edge_label(:a, :b), "lost label") - end - - def test_fail_on_cycle - { - {:a => :b, :b => :a, :c => :a, :d => :c} => true, # larger tree involving a smaller cycle - {:a => :b, :b => :c, :c => :a} => true, - {:a => :b, :b => :a, :c => :d, :d => :c} => true, - {:a => :b, :b => :c} => false, - }.each do |hash, result| - graph = Puppet::PGraph.new - hash.each do |a,b| - graph.add_edge!(a, b) - end - - if result - assert_raise(Puppet::Error, "%s did not fail" % hash.inspect) do - graph.topsort - end - else - assert_nothing_raised("%s failed" % hash.inspect) do - graph.topsort - end - end - end - end - - # This isn't really a unit test, it's just a way to do some graphing with - # tons of relationships so we can see how it performs. - def disabled_test_lots_of_relationships - containers = Puppet::PGraph.new - relationships = Puppet::PGraph.new - labels = %w{a b c d e} - conts = {} - vertices = {} - labels.each do |label| - vertices[label] = [] - end - num = 100 - num.times do |i| - labels.each do |label| - vertices[label] << ("%s%s" % [label, i]) - end - end - labels.each do |label| - conts[label] = Container.new(label, vertices[label]) - end - - conts.each do |label, cont| - cont.each do |child| - containers.add_edge!(cont, child) - end - end - prev = nil - labels.inject(nil) do |prev, label| - if prev - containers.add_edge!(conts[prev], conts[label]) - end - label - end - - containers.to_jpg(File.expand_path("~/Desktop/pics/lots"), "start") - - # Now create the relationship graph - - # Make everything in both b and c require d1 - %w{b c}.each do |label| - conts[label].each do |v| - relationships.add_edge!(v, "d1") - #relationships.add_edge!(v, conts["d"]) - end - end - - # Make most in b also require the appropriate thing in c - conts["b"].each do |v| - i = v.split('')[1] - - relationships.add_edge!(v, "c%s" % i) - end - - # And make d1 require most of e - num.times do |i| - relationships.add_edge!("d1", "e%s" % i) - end - - containers.vertices.each do |v| - relationships.add_vertex!(v) - end - relationships.to_jpg(File.expand_path("~/Desktop/pics/lots"), "relationships") - - time = Benchmark.realtime do - relationships.splice!(containers, Container) - end - relationships.to_jpg(File.expand_path("~/Desktop/pics/lots"), "final") - puts time - time = Benchmark.realtime do - relationships.topsort - end - end -end - -# $Id$ diff --git a/test/other/relationships.rb b/test/other/relationships.rb index a5c9db5dc..771b119ee 100755 --- a/test/other/relationships.rb +++ b/test/other/relationships.rb @@ -172,15 +172,12 @@ class TestRelationships < Test::Unit::TestCase end assert_equal([Puppet::Relationship[file, exec]], reqs) - # Now make sure that these relationships are added to the transaction's - # relgraph - trans = Puppet::Transaction.new(newcomp(file, exec)) - assert_nothing_raised do - trans.evaluate + # Now make sure that these relationships are added to the + # relationship graph + config = mk_configuration(file, exec) + config.apply do |trans| + assert(config.relationship_graph.edge?(file, exec), "autorequire edge was not created") end - - graph = trans.relgraph - assert(graph.edge?(file, exec), "autorequire edge was not created") end def test_requires? diff --git a/test/other/report.rb b/test/other/report.rb index c59881f72..3de2dfbee 100755 --- a/test/other/report.rb +++ b/test/other/report.rb @@ -26,16 +26,10 @@ class TestReports < Test::Unit::TestCase ) end - comp = newcomp(*objects) - - trans = nil - assert_nothing_raised("Failed to create transaction") { - trans = comp.evaluate - } - - assert_nothing_raised("Failed to evaluate transaction") { - trans.evaluate - } + config = mk_configuration(*objects) + # So the report works out. + config.retrieval_duration = 0.001 + trans = config.apply return trans.generate_report end @@ -92,7 +86,7 @@ class TestReports < Test::Unit::TestCase # We have to reuse reporting here because of something going on in the # server/report.rb file - Puppet.config.use(:reporting) + Puppet.settings.use(:reporting) 3.times { |i| log = Puppet.warning("Report test message %s" % i) @@ -119,7 +113,7 @@ class TestReports < Test::Unit::TestCase if Puppet.features.rrd? def test_rrdgraph_report - Puppet.config.use(:metrics) + Puppet.settings.use(:metrics) report = mkreport assert(! report.metrics.empty?, "Did not receive any metrics") diff --git a/test/other/transactions.rb b/test/other/transactions.rb index bf5f65084..7d17a92e7 100755 --- a/test/other/transactions.rb +++ b/test/other/transactions.rb @@ -7,8 +7,6 @@ require 'puppettest' require 'mocha' require 'puppettest/support/resources' -# $Id$ - class TestTransactions < Test::Unit::TestCase include PuppetTest::FileTesting include PuppetTest::Support::Resources @@ -133,7 +131,7 @@ class TestTransactions < Test::Unit::TestCase inst = type.create :name => "yay" # Create a transaction - trans = Puppet::Transaction.new(newcomp(inst)) + trans = Puppet::Transaction.new(mk_configuration(inst)) # Make sure prefetch works assert_nothing_raised do @@ -255,7 +253,7 @@ class TestTransactions < Test::Unit::TestCase } - component = newcomp("file",file) + component = mk_configuration("file",file) require 'etc' groupname = Etc.getgrgid(File.stat(file.name).gid).name assert_nothing_raised() { @@ -295,12 +293,11 @@ class TestTransactions < Test::Unit::TestCase file[:check] = check file[:group] = @groups[0] - assert_apply(file) + config = mk_configuration(file) + config.apply @@tmpfiles << execfile - component = newcomp("both",file,exec) - # 'subscribe' expects an array of arrays exec[:subscribe] = [[file.class.name,file.name]] exec[:refreshonly] = true @@ -317,7 +314,11 @@ class TestTransactions < Test::Unit::TestCase file[:mode] = "755" } - trans = assert_events([:file_changed, :triggered], component) + # Make a new configuration so the resource relationships get + # set up. + config = mk_configuration(file, exec) + + trans = assert_events([:file_changed, :triggered], config) assert(FileTest.exists?(execfile), "Execfile does not exist") File.unlink(execfile) @@ -325,7 +326,7 @@ class TestTransactions < Test::Unit::TestCase file[:group] = @groups[1] } - trans = assert_events([:file_changed, :triggered], component) + trans = assert_events([:file_changed, :triggered], config) assert(FileTest.exists?(execfile), "Execfile does not exist") end @@ -343,24 +344,30 @@ class TestTransactions < Test::Unit::TestCase file[:group] = @groups[0] assert_apply(file) - fcomp = newcomp("file",file) - ecomp = newcomp("exec",exec) + config = Puppet::Node::Configuration.new + fcomp = Puppet::Type.type(:component).create(:name => "file") + config.add_resource fcomp + config.add_resource file + config.add_edge!(fcomp, file) - component = newcomp("both",fcomp,ecomp) + ecomp = Puppet::Type.type(:component).create(:name => "exec") + config.add_resource ecomp + config.add_resource exec + config.add_edge!(ecomp, exec) # 'subscribe' expects an array of arrays #component[:require] = [[file.class.name,file.name]] ecomp[:subscribe] = fcomp exec[:refreshonly] = true - trans = assert_events([], component) + trans = assert_events([], config) assert_nothing_raised() { file[:group] = @groups[1] file[:mode] = "755" } - trans = assert_events([:file_changed, :file_changed, :triggered], component) + trans = assert_events([:file_changed, :file_changed, :triggered], config) end # Make sure that multiple subscriptions get triggered. @@ -426,7 +433,8 @@ class TestTransactions < Test::Unit::TestCase Puppet[:ignoreschedules] = false file = Puppet.type(:file).create( :name => tempfile(), - :ensure => "file" + :ensure => "file", + :backup => false ) fname = tempfile() @@ -437,11 +445,10 @@ class TestTransactions < Test::Unit::TestCase :subscribe => ["file", file.name] ) - comp = newcomp(file,exec) - comp.finalize + config = mk_configuration(file, exec) # Run it once - assert_apply(comp) + assert_apply(config) assert(FileTest.exists?(fname), "File did not get created") assert(!exec.scheduled?, "Exec is somehow scheduled") @@ -451,7 +458,8 @@ class TestTransactions < Test::Unit::TestCase file[:content] = "some content" - assert_events([:file_changed, :triggered], comp) + assert_events([:file_changed, :triggered], config) + assert(FileTest.exists?(fname), "File did not get recreated") # Now remove it, so it can get created again @@ -469,7 +477,7 @@ class TestTransactions < Test::Unit::TestCase assert(! file.insync?(file.retrieve), "Uh, file is in sync?") - assert_events([:file_changed, :triggered], comp) + assert_events([:file_changed, :triggered], config) assert(FileTest.exists?(fname), "File did not get recreated") end @@ -493,11 +501,9 @@ class TestTransactions < Test::Unit::TestCase :ensure => :file ) - comp = newcomp(exec, file1, file2) - - comp.finalize + config = mk_configuration(exec, file1, file2) - assert_apply(comp) + assert_apply(config) assert(! FileTest.exists?(file1[:path]), "File got created even tho its dependency failed") @@ -506,31 +512,27 @@ class TestTransactions < Test::Unit::TestCase end end - def f(n) - Puppet::Type.type(:file)["/tmp/#{n.to_s}"] - end - def test_relationship_graph - one, two, middle, top = mktree + config = mktree + + config.meta_def(:f) do |name| + self.resource("File[%s]" % name) + end - {one => two, "f" => "c", "h" => middle}.each do |source, target| - if source.is_a?(String) - source = f(source) - end - if target.is_a?(String) - target = f(target) - end + {"one" => "two", "File[f]" => "File[c]", "File[h]" => "middle"}.each do |source_ref, target_ref| + source = config.resource(source_ref) or raise "Missing %s" % source_ref + target = config.resource(target_ref) or raise "Missing %s" % target_ref target[:require] = source end - trans = Puppet::Transaction.new(top) + trans = Puppet::Transaction.new(config) graph = nil assert_nothing_raised do graph = trans.relationship_graph end - - assert_instance_of(Puppet::PGraph, graph, + + assert_instance_of(Puppet::Node::Configuration, graph, "Did not get relationship graph") # Make sure all of the components are gone @@ -544,13 +546,13 @@ class TestTransactions < Test::Unit::TestCase sorted = graph.topsort.reverse # Now make sure the appropriate edges are there and are in the right order - assert(graph.dependents(f(:f)).include?(f(:c)), + assert(graph.dependents(config.f(:f)).include?(config.f(:c)), "c not marked a dep of f") - assert(sorted.index(f(:c)) < sorted.index(f(:f)), + assert(sorted.index(config.f(:c)) < sorted.index(config.f(:f)), "c is not before f") - one.each do |o| - two.each do |t| + config.resource("one").each do |o| + config.resource("two").each do |t| assert(graph.dependents(o).include?(t), "%s not marked a dep of %s" % [t.ref, o.ref]) assert(sorted.index(t) < sorted.index(o), @@ -558,22 +560,22 @@ class TestTransactions < Test::Unit::TestCase end end - trans.resources.leaves(middle).each do |child| - assert(graph.dependents(f(:h)).include?(child), + trans.configuration.leaves(config.resource("middle")).each do |child| + assert(graph.dependents(config.f(:h)).include?(child), "%s not marked a dep of h" % [child.ref]) - assert(sorted.index(child) < sorted.index(f(:h)), + assert(sorted.index(child) < sorted.index(config.f(:h)), "%s is not before h" % child.ref) end # Lastly, make sure our 'g' vertex made it into the relationship # graph, since it's not involved in any relationships. - assert(graph.vertex?(f(:g)), + assert(graph.vertex?(config.f(:g)), "Lost vertexes with no relations") # Now make the reversal graph and make sure all of the vertices made it into that reverse = graph.reversal %w{a b c d e f g h}.each do |letter| - file = f(letter) + file = config.f(letter) assert(reverse.vertex?(file), "%s did not make it into reversal" % letter) end end @@ -594,15 +596,15 @@ class TestTransactions < Test::Unit::TestCase yay = Puppet::Type.newgenerator :title => "yay" rah = Puppet::Type.newgenerator :title => "rah" - comp = newcomp(yay, rah) - trans = comp.evaluate + config = mk_configuration(yay, rah) + trans = Puppet::Transaction.new(config) assert_nothing_raised do trans.generate end %w{ya ra y r}.each do |name| - assert(trans.resources.vertex?(Puppet::Type.type(:generator)[name]), + assert(trans.configuration.vertex?(Puppet::Type.type(:generator)[name]), "Generated %s was not a vertex" % name) assert($finished.include?(name), "%s was not finished" % name) end @@ -613,7 +615,7 @@ class TestTransactions < Test::Unit::TestCase end %w{ya ra y r}.each do |name| - assert(!trans.resources.vertex?(Puppet::Type.type(:generator)[name]), + assert(!trans.configuration.vertex?(Puppet::Type.type(:generator)[name]), "Generated vertex %s was not removed from graph" % name) assert_nil(Puppet::Type.type(:generator)[name], "Generated vertex %s was not removed from class" % name) @@ -633,8 +635,8 @@ class TestTransactions < Test::Unit::TestCase yay = Puppet::Type.newgenerator :title => "yay" rah = Puppet::Type.newgenerator :title => "rah", :subscribe => yay - comp = newcomp(yay, rah) - trans = comp.evaluate + config = mk_configuration(yay, rah) + trans = Puppet::Transaction.new(config) trans.prepare @@ -645,13 +647,13 @@ class TestTransactions < Test::Unit::TestCase end ya = type["ya"] assert(ya, "Did not generate ya") - assert(trans.relgraph.vertex?(ya), + assert(trans.relationship_graph.vertex?(ya), "Did not add ya to rel_graph") # Now make sure the appropriate relationships were added - assert(trans.relgraph.edge?(yay, ya), + assert(trans.relationship_graph.edge?(yay, ya), "parent was not required by child") - assert(! trans.relgraph.edge?(ya, rah), + assert(! trans.relationship_graph.edge?(ya, rah), "generated child ya inherited depencency on rah") # Now make sure it in turn eval_generates appropriately @@ -662,7 +664,7 @@ class TestTransactions < Test::Unit::TestCase %w{y}.each do |name| res = type[name] assert(res, "Did not generate %s" % name) - assert(trans.relgraph.vertex?(res), + assert(trans.relationship_graph.vertex?(res), "Did not add %s to rel_graph" % name) assert($finished.include?("y"), "y was not finished") end @@ -670,7 +672,7 @@ class TestTransactions < Test::Unit::TestCase assert_nothing_raised("failed to eval_generate with nil response") do trans.eval_resource(type["y"]) end - assert(trans.relgraph.edge?(yay, ya), "no edge was created for ya => yay") + assert(trans.relationship_graph.edge?(yay, ya), "no edge was created for ya => yay") assert_nothing_raised("failed to apply rah") do trans.eval_resource(rah) @@ -678,15 +680,15 @@ class TestTransactions < Test::Unit::TestCase ra = type["ra"] assert(ra, "Did not generate ra") - assert(trans.relgraph.vertex?(ra), + assert(trans.relationship_graph.vertex?(ra), "Did not add ra to rel_graph" % name) assert($finished.include?("ra"), "y was not finished") # Now make sure this generated resource has the same relationships as # the generating resource - assert(! trans.relgraph.edge?(yay, ra), + assert(! trans.relationship_graph.edge?(yay, ra), "rah passed its dependencies on to its children") - assert(! trans.relgraph.edge?(ya, ra), + assert(! trans.relationship_graph.edge?(ya, ra), "children have a direct relationship") # Now make sure that cleanup gets rid of those generated types. @@ -695,14 +697,14 @@ class TestTransactions < Test::Unit::TestCase end %w{ya ra y r}.each do |name| - assert(!trans.relgraph.vertex?(type[name]), + assert(!trans.relationship_graph.vertex?(type[name]), "Generated vertex %s was not removed from graph" % name) assert_nil(type[name], "Generated vertex %s was not removed from class" % name) end # Now, start over and make sure that everything gets evaluated. - trans = comp.evaluate + trans = Puppet::Transaction.new(config) $evaluated.clear assert_nothing_raised do trans.evaluate @@ -711,54 +713,41 @@ class TestTransactions < Test::Unit::TestCase assert_equal(%w{yay ya y rah ra r}, $evaluated, "Not all resources were evaluated or not in the right order") end - - def test_tags - res = Puppet::Type.newfile :path => tempfile() - comp = newcomp(res) - - # Make sure they default to none - assert_equal([], comp.evaluate.tags) - - # Make sure we get the main tags - Puppet[:tags] = %w{this is some tags} - assert_equal(%w{this is some tags}, comp.evaluate.tags) - - # And make sure they get processed correctly - Puppet[:tags] = ["one", "two,three", "four"] - assert_equal(%w{one two three four}, comp.evaluate.tags) - - # lastly, make sure we can override them - trans = comp.evaluate - trans.tags = ["one", "two,three", "four"] - assert_equal(%w{one two three four}, comp.evaluate.tags) + + def test_ignore_tags? + config = Puppet::Node::Configuration.new + config.host_config = true + transaction = Puppet::Transaction.new(config) + assert(! transaction.ignore_tags?, "Ignoring tags when applying a host configuration") + + config.host_config = false + transaction = Puppet::Transaction.new(config) + assert(transaction.ignore_tags?, "Not ignoring tags when applying a non-host configuration") end - def test_tagged? - res = Puppet::Type.newfile :path => tempfile() - comp = newcomp(res) - trans = comp.evaluate - - assert(trans.tagged?(res), "tagged? defaulted to false") - - # Now set some tags - trans.tags = %w{some tags} - - # And make sure it's false - assert(! trans.tagged?(res), "matched invalid tags") - - # Set ignoretags and make sure it sticks - trans.ignoretags = true - assert(trans.tagged?(res), "tags were not ignored") - - # Now make sure we actually correctly match tags - res[:tag] = "mytag" - trans.ignoretags = false - trans.tags = %w{notag} - - assert(! trans.tagged?(res), "tags incorrectly matched") - - trans.tags = %w{mytag yaytag} - assert(trans.tagged?(res), "tags should have matched") + def test_missing_tags? + resource = stub 'resource', :tagged? => true + config = Puppet::Node::Configuration.new + + # Mark it as a host config so we don't care which test is first + config.host_config = true + transaction = Puppet::Transaction.new(config) + assert(! transaction.missing_tags?(resource), "Considered a resource to be missing tags when none are set") + + # host configurations pay attention to tags, no one else does. + Puppet[:tags] = "three,four" + config.host_config = false + transaction = Puppet::Transaction.new(config) + assert(! transaction.missing_tags?(resource), "Considered a resource to be missing tags when not running a host configuration") + + # + config.host_config = true + transaction = Puppet::Transaction.new(config) + assert(! transaction.missing_tags?(resource), "Considered a resource to be missing tags when running a host configuration and all tags are present") + + transaction = Puppet::Transaction.new(config) + resource.stubs :tagged? => false + assert(transaction.missing_tags?(resource), "Considered a resource not to be missing tags when running a host configuration and tags are missing") end # Make sure changes generated by eval_generated resources have proxies @@ -772,8 +761,8 @@ class TestTransactions < Test::Unit::TestCase end resource = type.create :name => "test" - comp = newcomp(resource) - trans = comp.evaluate + config = mk_configuration(resource) + trans = Puppet::Transaction.new(config) trans.prepare assert_nothing_raised do @@ -831,7 +820,7 @@ class TestTransactions < Test::Unit::TestCase end # Make a graph with some stuff in it. - graph = Puppet::PGraph.new + graph = Puppet::Node::Configuration.new # Add a non-triggering edge. a = trigger.new(:a) @@ -883,52 +872,12 @@ class TestTransactions < Test::Unit::TestCase assert(trans.triggered?(c, :refresh), "Transaction did not store the trigger") end - - def test_graph - Puppet.config.use(:main) - # Make a graph - graph = Puppet::PGraph.new - graph.add_edge!("a", "b") - - # Create our transaction - trans = Puppet::Transaction.new(graph) - - assert_nothing_raised do - trans.graph(graph, :testing) - end - - dotfile = File.join(Puppet[:graphdir], "testing.dot") - assert(! FileTest.exists?(dotfile), "Enabled graphing even tho disabled") - - # Now enable graphing - Puppet[:graph] = true - - assert_nothing_raised do - trans.graph(graph, :testing) - end - assert(FileTest.exists?(dotfile), "Did not create graph.") - end - - def test_created_graphs - FileUtils.mkdir_p(Puppet[:graphdir]) - file = Puppet::Type.newfile(:path => tempfile, :content => "yay") - exec = Puppet::Type.type(:exec).create(:command => "echo yay", :path => ENV['PATH'], - :require => file) - - Puppet[:graph] = true - assert_apply(file, exec) - - %w{resources relationships expanded_relationships}.each do |name| - file = File.join(Puppet[:graphdir], "%s.dot" % name) - assert(FileTest.exists?(file), "graph for %s was not created" % name) - end - end def test_set_target file = Puppet::Type.newfile(:path => tempfile(), :content => "yay") exec1 = Puppet::Type.type(:exec).create :command => "/bin/echo exec1" exec2 = Puppet::Type.type(:exec).create :command => "/bin/echo exec2" - trans = Puppet::Transaction.new(newcomp(file, exec1, exec2)) + trans = Puppet::Transaction.new(mk_configuration(file, exec1, exec2)) # First try it with an edge that has no callback edge = Puppet::Relationship.new(file, exec1) @@ -975,7 +924,8 @@ class TestTransactions < Test::Unit::TestCase one[:require] = two two[:require] = one - trans = newcomp(one, two).evaluate + config = mk_configuration(one, two) + trans = Puppet::Transaction.new(config) assert_raise(Puppet::Error) do trans.prepare end @@ -1042,15 +992,15 @@ class TestTransactions < Test::Unit::TestCase rels[dir] = file rels.each do |after, before| - comp = newcomp(before, after) - trans = comp.evaluate + config = mk_configuration(before, after) + trans = Puppet::Transaction.new(config) str = "from %s to %s" % [before, after] assert_nothing_raised("Failed to create graph %s" % str) do trans.prepare end - graph = trans.relgraph + graph = trans.relationship_graph assert(graph.edge?(before, after), "did not create manual relationship %s" % str) assert(! graph.edge?(after, before), "created automatic relationship %s" % str) end @@ -1063,7 +1013,7 @@ class TestTransactions < Test::Unit::TestCase one[:require] = two one[:subscribe] = two - comp = newcomp(one, two) + comp = mk_configuration(one, two) trans = Puppet::Transaction.new(comp) graph = trans.relationship_graph @@ -1188,5 +1138,3 @@ class TestTransactions < Test::Unit::TestCase assert_equal(1, $flushed, "object was flushed in noop") end end - -# $Id$ diff --git a/test/puppet/conffiles.rb b/test/puppet/conffiles.rb index 3dfa53a13..1800c80f6 100755 --- a/test/puppet/conffiles.rb +++ b/test/puppet/conffiles.rb @@ -3,7 +3,6 @@ $:.unshift("../lib").unshift("../../lib") if __FILE__ =~ /\.rb$/ require 'puppettest' -require 'puppet/util/config' class TestConfFiles < Test::Unit::TestCase include PuppetTest @@ -72,7 +71,7 @@ class TestConfFiles < Test::Unit::TestCase path = tempfile() sampledata { |data| - config = Puppet::Util::Config.new + config = Puppet::Util::Settings.new data.each { |section, hash| hash.each { |param, value| config.setdefaults(section, param => [value, value]) @@ -100,7 +99,7 @@ class TestConfFiles < Test::Unit::TestCase # that the default config is free of simple typos etc. def test_genconfig assert_nothing_raised { - Puppet::config::to_config + Puppet::settings::to_config } end diff --git a/test/ral/manager/type.rb b/test/ral/manager/type.rb index 534c35759..b2c111e60 100755 --- a/test/ral/manager/type.rb +++ b/test/ral/manager/type.rb @@ -174,7 +174,7 @@ class TestType < Test::Unit::TestCase ) } - comp = newcomp(twoobj, oneobj) + comp = mk_configuration(twoobj, oneobj) assert_nothing_raised { comp.finalize @@ -246,76 +246,6 @@ class TestType < Test::Unit::TestCase # and make sure managed objects start with them assert(user.property(:ensure), "User did not get an ensure property") end - - # Make sure removal works - def test_remove - objects = {} - top = Puppet.type(:component).create(:name => "top") - objects[top.class] = top - - base = tempfile() - - # now make a two-tier, 5 piece tree - %w{a b}.each do |letter| - name = "comp%s" % letter - comp = Puppet.type(:component).create(:name => name) - top.push comp - objects[comp.class] = comp - - 5.times do |i| - file = base + letter + i.to_s - - obj = Puppet.type(:file).create(:name => file, :ensure => "file") - - comp.push obj - objects[obj.class] = obj - end - end - - assert_nothing_raised do - top.remove - end - - objects.each do |klass, obj| - assert_nil(klass[obj.name], "object %s was not removed" % obj.name) - end - end - - # Verify that objects can't be their own children. - def test_object_recursion - comp = Puppet.type(:component).create(:name => "top") - - file = Puppet.type(:component).create(:name => "middle") - - assert_raise(Puppet::DevError) do - comp.push(comp) - end - - assert_raise(Puppet::DevError) do - file.push(file) - end - - assert_raise(Puppet::DevError) do - comp.parent = comp - end - - assert_raise(Puppet::DevError) do - file.parent = file - end - - assert_nothing_raised { - comp.push(file) - } - - assert_raise(Puppet::DevError) do - file.push(comp) - end - - assert_raise(Puppet::DevError) do - comp.parent = file - end - end - def test_newtype_methods assert_nothing_raised { Puppet::Type.newtype(:mytype) do @@ -732,12 +662,22 @@ class TestType < Test::Unit::TestCase end def test_path + config = mk_configuration + # Check that our paths are built correctly. Just pick a random, "normal" type. type = Puppet::Type.type(:exec) mk = Proc.new do |i, hash| hash[:title] = "exec%s" % i hash[:command] = "/bin/echo" - type.create(hash) + if parent = hash[:parent] + hash.delete(:parent) + end + res = type.create(hash) + config.add_resource res + if parent + config.add_edge!(parent, res) + end + res end exec = mk.call(1, {}) @@ -745,25 +685,31 @@ class TestType < Test::Unit::TestCase assert_equal("/Exec[exec1]", exec.path) comp = Puppet::Type.newcomponent :title => "My[component]", :type => "Yay" + config.add_resource comp exec = mk.call(2, :parent => comp) assert_equal("/My[component]/Exec[exec2]", exec.path) comp = Puppet::Type.newcomponent :name => "Other[thing]" + config.add_resource comp exec = mk.call(3, :parent => comp) assert_equal("/Other[thing]/Exec[exec3]", exec.path) comp = Puppet::Type.newcomponent :type => "server", :name => "server" + config.add_resource comp exec = mk.call(4, :parent => comp) assert_equal("/server/Exec[exec4]", exec.path) comp = Puppet::Type.newcomponent :type => "whatever", :name => "class[main]" + config.add_resource comp exec = mk.call(5, :parent => comp) assert_equal("//Exec[exec5]", exec.path) - comp = Puppet::Type.newcomponent :type => "yay", :name => "Good[bad]", :parent => comp - exec = mk.call(6, :parent => comp) + newcomp = Puppet::Type.newcomponent :type => "yay", :name => "Good[bad]" + config.add_resource newcomp + config.add_edge! comp, newcomp + exec = mk.call(6, :parent => newcomp) assert_equal("//Good[bad]/Exec[exec6]", exec.path) end diff --git a/test/ral/providers/service.rb b/test/ral/providers/service.rb index d21298162..5a4a6c1c2 100755 --- a/test/ral/providers/service.rb +++ b/test/ral/providers/service.rb @@ -54,7 +54,7 @@ class TestLocalService < Test::Unit::TestCase service.retrieve } - comp = newcomp("servicetst", service) + comp = mk_configuration("servicetst", service) service[:ensure] = :running Puppet.info "Starting %s" % service.name @@ -105,7 +105,7 @@ class TestLocalService < Test::Unit::TestCase service.retrieve } - comp = newcomp("servicetst", service) + comp = mk_configuration("servicetst", service) service[:enable] = true Puppet.info "Enabling %s" % service.name diff --git a/test/ral/types/basic.rb b/test/ral/types/basic.rb index 5d09a5183..2802f3440 100755 --- a/test/ral/types/basic.rb +++ b/test/ral/types/basic.rb @@ -35,12 +35,9 @@ class TestBasic < Test::Unit::TestCase :path => ENV["PATH"] ) } - assert_nothing_raised() { - @component.push( - @configfile, - @command - ) - } + @config = mk_configuration(@component, @configfile, @command) + @config.add_edge! @component, @configfile + @config.add_edge! @component, @command end def teardown @@ -86,5 +83,3 @@ class TestBasic < Test::Unit::TestCase } end end - -# $Id$ diff --git a/test/ral/types/component.rb b/test/ral/types/component.rb deleted file mode 100755 index 06c32dd01..000000000 --- a/test/ral/types/component.rb +++ /dev/null @@ -1,113 +0,0 @@ -#!/usr/bin/env ruby - -$:.unshift("../../lib") if __FILE__ =~ /\.rb$/ - -require 'puppettest' -require 'puppettest/support/resources' - -# $Id$ - -class TestComponent < Test::Unit::TestCase - include PuppetTest - include PuppetTest::Support::Resources - def setup - super - @@used = {} - @type = Puppet::Type::Component - @file = Puppet::Type.type(:file) - end - - def randnum(limit) - num = nil - looped = 0 - loop do - looped += 1 - if looped > 2000 - raise "Reached limit of looping" - break - end - num = rand(limit) - unless @@used.include?(num) - @@used[num] = true - break - end - end - - num - end - - def mkfile(num = nil) - unless num - num = randnum(1000) - end - name = tempfile() + num.to_s - - file = Puppet.type(:file).create( - :path => name, - :checksum => "md5" - ) - @@tmpfiles << name - file - end - - def mkcomp - Puppet.type(:component).create(:name => "component_" + randnum(1000).to_s) - end - - def mkrandcomp(numfiles, numdivs) - comp = mkcomp - hash = {} - found = 0 - - divs = {} - - numdivs.times { |i| - num = i + 2 - divs[num] = nil - } - while found < numfiles - num = randnum(numfiles) - found += 1 - f = mkfile(num) - hash[f.name] = f - reqd = [] - divs.each { |n,obj| - if rand(50) % n == 0 - if obj - unless reqd.include?(obj.object_id) - f[:require] = [[obj.class.name, obj.name]] - reqd << obj.object_id - end - end - end - - divs[n] = f - } - end - - hash.each { |name, obj| - comp.push obj - } - - comp.finalize - comp - end - - def test_to_graph - one, two, middle, top = mktree - - graph = nil - assert_nothing_raised do - graph = top.to_graph - end - - assert(graph.is_a?(Puppet::PGraph), "result is not a pgraph") - - [one, two, middle, top].each do |comp| - comp.each do |child| - assert(graph.edge?(comp, child), - "Did not create edge from %s => %s" % [comp.name, child.name]) - end - end - end -end diff --git a/test/ral/types/cron.rb b/test/ral/types/cron.rb index 7b2e770f0..1695befac 100755 --- a/test/ral/types/cron.rb +++ b/test/ral/types/cron.rb @@ -94,7 +94,7 @@ class TestCron < Test::Unit::TestCase text = obj.read name = cron.name - comp = newcomp(name, cron) + comp = mk_configuration(name, cron) assert_events([:cron_created], comp) cron.provider.class.prefetch @@ -157,7 +157,7 @@ class TestCron < Test::Unit::TestCase def test_makeandretrievecron %w{storeandretrieve a-name another-name more_naming SomeName}.each do |name| cron = mkcron(name) - comp = newcomp(name, cron) + comp = mk_configuration(name, cron) trans = assert_events([:cron_created], comp, name) cron.provider.class.prefetch diff --git a/test/ral/types/exec.rb b/test/ral/types/exec.rb index ede6361cd..0c7cc1d90 100755 --- a/test/ral/types/exec.rb +++ b/test/ral/types/exec.rb @@ -179,7 +179,7 @@ class TestExec < Test::Unit::TestCase ) } - comp = newcomp("createstest", exec) + comp = mk_configuration("createstest", exec) assert_events([:executed_command], comp, "creates") assert_events([], comp, "creates") end @@ -202,7 +202,7 @@ class TestExec < Test::Unit::TestCase :require => [:file, oexe] ) - comp = newcomp("Testing", file, exec) + comp = mk_configuration("Testing", file, exec) assert_events([:file_created, :executed_command], comp) end @@ -299,7 +299,7 @@ class TestExec < Test::Unit::TestCase :path => ENV['PATH'] ) } - comp = newcomp(exec) + comp = mk_configuration(exec) assert_events([:executed_command], comp) assert_events([:executed_command], comp) @@ -344,7 +344,7 @@ class TestExec < Test::Unit::TestCase exec = Puppet.type(:exec).create(args) } - comp = newcomp("usertest", exec) + comp = mk_configuration("usertest", exec) assert_events([:executed_command], comp, "usertest") assert(FileTest.exists?(file), "File does not exist") @@ -425,7 +425,7 @@ class TestExec < Test::Unit::TestCase ) } - comp = newcomp(file, exec) + comp = mk_configuration(file, exec) comp.finalize assert_events([:executed_command, :file_changed], comp) diff --git a/test/ral/types/file.rb b/test/ral/types/file.rb index f3e1a562d..0c16d6dff 100755 --- a/test/ral/types/file.rb +++ b/test/ral/types/file.rb @@ -122,7 +122,7 @@ class TestFile < Test::Unit::TestCase ) } - comp = newcomp("createusertest", file) + comp = mk_configuration("createusertest", file) assert_events([:file_created], comp) end @@ -397,6 +397,8 @@ class TestFile < Test::Unit::TestCase events = assert_apply(file) + assert(events) + assert(! events.include?(:file_changed), "File incorrectly changed") assert_events([], file) @@ -493,6 +495,7 @@ class TestFile < Test::Unit::TestCase # Create a test directory path = tempfile() dir = @file.create :path => path, :mode => 0755, :recurse => true + config = mk_configuration(dir) Dir.mkdir(path) @@ -673,6 +676,7 @@ class TestFile < Test::Unit::TestCase :check => %w{owner mode group} ) } + config = mk_configuration dir children = nil @@ -683,19 +687,22 @@ class TestFile < Test::Unit::TestCase assert_equal([subdir], children.collect {|c| c.title }, "Incorrect generated children") - dir.class[subdir].remove + # Remove our subdir resource, + subdir_resource = config.resource(:file, subdir) + config.remove_resource(subdir_resource) + # Create the test file File.open(tmpfile, "w") { |f| f.puts "yayness" } assert_nothing_raised { children = dir.eval_generate } + # And make sure we get both resources back. assert_equal([subdir, tmpfile].sort, children.collect {|c| c.title }.sort, - "Incorrect generated children") + "Incorrect generated children when recurse == %s" % value.inspect) File.unlink(tmpfile) - #system("rm -rf %s" % basedir) Puppet.type(:file).clear end end @@ -754,6 +761,7 @@ class TestFile < Test::Unit::TestCase :check => %w{owner mode group} ) } + mk_configuration dir assert_nothing_raised { dir.eval_generate @@ -796,6 +804,7 @@ class TestFile < Test::Unit::TestCase :check => %w{mode owner group} ) } + mk_configuration dirobj assert_nothing_raised { dirobj.eval_generate @@ -884,7 +893,7 @@ class TestFile < Test::Unit::TestCase ) } - comp = newcomp("yay", file) + comp = mk_configuration("yay", file) comp.finalize assert_apply(comp) #assert_events([:directory_created], comp) @@ -1274,26 +1283,27 @@ class TestFile < Test::Unit::TestCase lfobj = Puppet::Type.newfile( :title => "localfile", :path => localfile, - :content => "rahtest" + :content => "rahtest", + :backup => false ) - destobj = Puppet::Type.newfile(:title => "destdir", :path => destdir, :source => sourcedir, + :backup => false, :recurse => true) - comp = newcomp(lfobj, destobj) - assert_apply(comp) + config = mk_configuration(lfobj, destobj) + config.apply assert(FileTest.exists?(dsourcefile), "File did not get copied") assert(FileTest.exists?(localfile), "File did not get created") assert(FileTest.exists?(purgee), "File got prematurely purged") assert_nothing_raised { destobj[:purge] = true } - assert_apply(comp) + config.apply - assert(FileTest.exists?(dsourcefile), "Source file got purged") assert(FileTest.exists?(localfile), "Local file got purged") + assert(FileTest.exists?(dsourcefile), "Source file got purged") assert(! FileTest.exists?(purgee), "File did not get purged") end @@ -1333,7 +1343,7 @@ class TestFile < Test::Unit::TestCase group = Puppet.type(:group).create( :name => "pptestg" ) - comp = newcomp(user, group, home) + comp = mk_configuration(user, group, home) } # Now make sure we get a relationship for each of these @@ -1629,6 +1639,7 @@ class TestFile < Test::Unit::TestCase file = File.join(dir, "file") File.open(file, "w") { |f| f.puts "" } obj = Puppet::Type.newfile :path => dir, :recurse => true, :mode => 0755 + mk_configuration obj assert_equal("/%s" % obj.ref, obj.path) @@ -1739,6 +1750,7 @@ class TestFile < Test::Unit::TestCase File.open(file, "w") { |f| f.puts "yay" } File.chmod(0644, file) obj = Puppet::Type.newfile(:path => dir, :mode => 0750, :recurse => "2") + config = mk_configuration(obj) children = nil assert_nothing_raised("Failure when recursing") do @@ -1747,6 +1759,7 @@ class TestFile < Test::Unit::TestCase assert(obj.class[subdir], "did not create subdir object") children.each do |c| assert_nothing_raised("Failure when recursing on %s" % c) do + c.configuration = config others = c.eval_generate end end @@ -1780,6 +1793,7 @@ class TestFile < Test::Unit::TestCase obj = Puppet::Type.newfile(:path => dir, :ensure => :directory, :recurse => true) + config = mk_configuration(obj) children = nil assert_nothing_raised do children = obj.eval_generate diff --git a/test/ral/types/file/target.rb b/test/ral/types/file/target.rb index 1e62f07ac..c4597cedf 100755 --- a/test/ral/types/file/target.rb +++ b/test/ral/types/file/target.rb @@ -44,6 +44,7 @@ class TestFileTarget < Test::Unit::TestCase def test_linkrecurse dest = tempfile() link = @file.create :path => tempfile(), :recurse => true, :ensure => dest + mk_configuration link ret = nil @@ -317,9 +318,8 @@ class TestFileTarget < Test::Unit::TestCase :source => dirs["source"], :recurse => true ) - - - trans = assert_apply(obj) + config = mk_configuration obj + config.apply newfile = File.join(dirs["target"], "sourcefile") diff --git a/test/ral/types/fileignoresource.rb b/test/ral/types/fileignoresource.rb index fa01aecdc..153946770 100755 --- a/test/ral/types/fileignoresource.rb +++ b/test/ral/types/fileignoresource.rb @@ -73,19 +73,8 @@ class TestFileIgnoreSources < Test::Unit::TestCase ) } - #make a component and adds the file - comp = Puppet.type(:component).create( - :name => "component" - ) - comp.push tofile - - #make, evaluate transaction and sync the component - assert_nothing_raised { - trans = comp.evaluate - } - assert_nothing_raised { - trans.evaluate - } + config = mk_configuration(tofile) + config.apply #topath should exist as a directory with sourcedir as a directory @@ -150,19 +139,8 @@ class TestFileIgnoreSources < Test::Unit::TestCase ) } - #make a component and adds the file - comp = Puppet.type(:component).create( - :name => "component" - ) - comp.push tofile - - #make, evaluate transaction and sync the component - assert_nothing_raised { - trans = comp.evaluate - } - assert_nothing_raised { - trans.evaluate - } + config = mk_configuration(tofile) + config.apply #topath should exist as a directory with sourcedir as a directory @@ -170,6 +148,7 @@ class TestFileIgnoreSources < Test::Unit::TestCase assert(FileTest.exists?(File.join(topath,sourcefile1))) assert(FileTest.exists?(File.join(topath,subdir))) assert(FileTest.exists?(File.join(File.join(topath,subdir),sourcefile1))) + #This file should not assert(!(FileTest.exists?(File.join(topath,sourcefile2)))) assert(!(FileTest.exists?(File.join(topath,subdir2)))) @@ -235,19 +214,8 @@ class TestFileIgnoreSources < Test::Unit::TestCase ) } - #make a component and adds the file - comp = Puppet.type(:component).create( - :name => "component" - ) - comp.push tofile - - #make, evaluate transaction and sync the component - assert_nothing_raised { - trans = comp.evaluate - } - assert_nothing_raised { - trans.evaluate - } + config = mk_configuration(tofile) + config.apply #topath should exist as a directory with sourcedir as a directory @@ -273,5 +241,3 @@ class TestFileIgnoreSources < Test::Unit::TestCase end end - -# $Id$ diff --git a/test/ral/types/filesources.rb b/test/ral/types/filesources.rb index b257fd935..01d9766db 100755 --- a/test/ral/types/filesources.rb +++ b/test/ral/types/filesources.rb @@ -62,6 +62,7 @@ class TestFileSources < Test::Unit::TestCase :name => path ) } + config = mk_configuration(file) child = nil assert_nothing_raised { child = file.newchild("childtest", true) @@ -275,6 +276,7 @@ class TestFileSources < Test::Unit::TestCase # The sourcerecurse method will only ever get called when we're # recursing, so we go ahead and set it. obj = Puppet::Type.newfile :source => source, :path => dest, :recurse => true + config = mk_configuration(obj) result = nil sourced = nil @@ -288,7 +290,7 @@ class TestFileSources < Test::Unit::TestCase assert_equal([dfileobj], result) # Clean this up so it can be recreated - dfileobj.remove + config.remove_resource(dfileobj) # Make sure we correctly iterate over the sources nosource = tempfile() @@ -577,51 +579,6 @@ class TestFileSources < Test::Unit::TestCase } end - def test_networkSourcesWithoutService - server = nil - - Puppet[:autosign] = true - Puppet[:masterport] = 8765 - - serverpid = nil - assert_nothing_raised() { - server = Puppet::Network::Server::WEBrick.new( - :Handlers => { - :CA => {}, # so that certs autogenerate - } - ) - - } - serverpid = fork { - assert_nothing_raised() { - #trap(:INT) { server.shutdown; Kernel.exit! } - trap(:INT) { server.shutdown } - server.start - } - } - @@tmppids << serverpid - - sleep(1) - - name = File.join(tmpdir(), "nosourcefile") - file = Puppet.type(:file).create( - :source => "puppet://localhost/dist/file", - :name => name - ) - - assert_nothing_raised { - file.retrieve - } - - comp = newcomp("nosource", file) - - assert_nothing_raised { - comp.evaluate - } - - assert(!FileTest.exists?(name), "File with no source exists anyway") - end - def test_unmountedNetworkSources server = nil mounts = { @@ -669,11 +626,8 @@ class TestFileSources < Test::Unit::TestCase file.retrieve } - comp = newcomp("nosource", file) - - assert_nothing_raised { - comp.evaluate - } + comp = mk_configuration(file) + comp.apply assert(!FileTest.exists?(name), "File with no source exists anyway") end @@ -722,7 +676,7 @@ class TestFileSources < Test::Unit::TestCase ) } - comp = newcomp(file) + comp = mk_configuration(file) assert_events([:file_created], comp) assert(File.exists?(to), "File does not exist") @@ -808,9 +762,8 @@ class TestFileSources < Test::Unit::TestCase trans = nil assert_nothing_raised { file[:links] = :manage - comp = newcomp(file) - trans = comp.evaluate - trans.evaluate + comp = mk_configuration(file) + trans = comp.apply } assert(trans.failed?(file), "Object did not fail to copy links") diff --git a/test/ral/types/group.rb b/test/ral/types/group.rb index 9870d533a..5189c63a1 100755 --- a/test/ral/types/group.rb +++ b/test/ral/types/group.rb @@ -65,7 +65,7 @@ class TestGroup < Test::Unit::TestCase def attrtest_ensure(group) group[:ensure] = :absent - comp = newcomp("ensuretest", group) + comp = mk_configuration("ensuretest", group) assert_apply(comp) assert_equal(:absent, group.provider.ensure, "Group is still present") group[:ensure] = :present @@ -91,7 +91,7 @@ class TestGroup < Test::Unit::TestCase assert_equal(15, group.should(:gid), "Did not convert gid to number") - comp = newcomp(group) + comp = mk_configuration(group) trans = assert_events([:group_modified], comp, "group") assert_equal(15, group.provider.gid, "GID was not changed") diff --git a/test/ral/types/tidy.rb b/test/ral/types/tidy.rb index b8d576b9a..8fada1adb 100755 --- a/test/ral/types/tidy.rb +++ b/test/ral/types/tidy.rb @@ -55,7 +55,7 @@ class TestTidy < Test::Unit::TestCase assert_nothing_raised { link = newlink(:target => source, :recurse => true) } - comp = newcomp("linktest",link) + comp = mk_configuration("linktest",link) cycle(comp) path = link.name diff --git a/test/ral/types/user.rb b/test/ral/types/user.rb index 121ac9cf0..9b24cc74a 100755 --- a/test/ral/types/user.rb +++ b/test/ral/types/user.rb @@ -82,7 +82,7 @@ class TestUser < Test::Unit::TestCase old = user.provider.ensure user[:ensure] = :absent - comp = newcomp("ensuretest", user) + comp = mk_configuration("ensuretest", user) assert_apply(user) assert(!user.provider.exists?, "User is still present") user[:ensure] = :present @@ -102,7 +102,7 @@ class TestUser < Test::Unit::TestCase old = user.provider.comment user[:comment] = "A different comment" - comp = newcomp("commenttest", user) + comp = mk_configuration("commenttest", user) trans = assert_events([:user_changed], comp, "user") @@ -117,7 +117,7 @@ class TestUser < Test::Unit::TestCase def attrtest_home(user) obj = nil - comp = newcomp("hometest", user) + comp = mk_configuration("hometest", user) old = user.provider.home user[:home] = old @@ -137,7 +137,7 @@ class TestUser < Test::Unit::TestCase def attrtest_shell(user) old = user.provider.shell - comp = newcomp("shelltest", user) + comp = mk_configuration("shelltest", user) user[:shell] = old @@ -167,7 +167,7 @@ class TestUser < Test::Unit::TestCase def attrtest_gid(user) obj = nil old = user.provider.gid - comp = newcomp("gidtest", user) + comp = mk_configuration("gidtest", user) user.retrieve @@ -216,7 +216,7 @@ class TestUser < Test::Unit::TestCase def attrtest_uid(user) obj = nil - comp = newcomp("uidtest", user) + comp = mk_configuration("uidtest", user) user.provider.uid = 1 @@ -387,7 +387,7 @@ class TestUser < Test::Unit::TestCase ogroup = Puppet.type(:group).create( :name => "yayness" ) - comp = newcomp(user, group, home, ogroup) + comp = mk_configuration(user, group, home, ogroup) } rels = nil @@ -404,7 +404,7 @@ class TestUser < Test::Unit::TestCase user = mkuser(name) - comp = newcomp("usercomp", user) + comp = mk_configuration("usercomp", user) trans = assert_events([:user_created], comp, "user") @@ -424,7 +424,7 @@ class TestUser < Test::Unit::TestCase assert(! user.provider.exists?, "User %s is present" % name) - comp = newcomp("usercomp", user) + comp = mk_configuration("usercomp", user) trans = assert_events([:user_created], comp, "user") diff --git a/test/util/fact_store.rb b/test/util/fact_store.rb deleted file mode 100755 index 5b04d1374..000000000 --- a/test/util/fact_store.rb +++ /dev/null @@ -1,67 +0,0 @@ -#!/usr/bin/env ruby -# -# Created by Luke Kanies on 2007-05-02. -# Copyright (c) 2007. All rights reserved. - -$:.unshift("../lib").unshift("../../lib") if __FILE__ =~ /\.rb$/ - -require 'puppettest' -require 'puppet/util/fact_store' - -class TestFactStore < Test::Unit::TestCase - include PuppetTest - - def test_new_fact_store - klass = nil - assert_nothing_raised("Could not create fact store") do - klass = Puppet::Util::FactStore.newstore(:yay) do - end - end - - assert_equal(klass, Puppet::Util::FactStore.store(:yay), "Did not get created store back by name") - end - - def test_yaml_store - yaml = Puppet::Util::FactStore.store(:yaml) - assert(yaml, "Could not retrieve yaml store") - - name = "node" - facts = {"a" => :b, :c => "d", :e => :f, "g" => "h"} - - store = nil - assert_nothing_raised("Could not create YAML store instance") do - store = yaml.new - end - - assert_nothing_raised("Could not store host facts") do - store.set(name, facts) - end - - dir = Puppet[:yamlfactdir] - - file = File.join(dir, name + ".yaml") - assert(FileTest.exists?(file), "Did not create yaml file for node") - - text = File.read(file) - newfacts = nil - assert_nothing_raised("Could not deserialize yaml") do - newfacts = YAML::load(text) - end - - # Don't directly compare the hashes, because there might be extra - # data stored in the client hash - facts.each do |var, value| - assert_equal(value, newfacts[var], "Value for %s changed during storage" % var) - end - - # Now make sure the facts get retrieved correctly - assert_nothing_raised("Could not retrieve facts") do - newfacts = store.get(name) - end - - # Now make sure the hashes are equal, since internal facts should not be returned. - assert_equal(facts, newfacts, "Retrieved facts are not equal") - end -end - -# $Id$ diff --git a/test/util/features.rb b/test/util/features.rb index 14e93c537..1e5858877 100755 --- a/test/util/features.rb +++ b/test/util/features.rb @@ -93,5 +93,3 @@ class TestFeatures < Test::Unit::TestCase end end end - -# $Id$ diff --git a/test/util/graph.rb b/test/util/graph.rb deleted file mode 100755 index 875fd0ec3..000000000 --- a/test/util/graph.rb +++ /dev/null @@ -1,108 +0,0 @@ -#!/usr/bin/env ruby -# -# Created by Luke Kanies on 2006-11-16. -# Copyright (c) 2006. All rights reserved. - -$:.unshift("../lib").unshift("../../lib") if __FILE__ =~ /\.rb$/ - -require 'puppettest' -require 'puppettest/graph' -require 'puppet/util/graph' - -class TestUtilGraph < Test::Unit::TestCase - include PuppetTest - include PuppetTest::Graph - - def test_to_graph - children = %w{a b c d} - list = Container.new("yay", children) - - graph = nil - assert_nothing_raised do - graph = list.to_graph - end - - assert(graph.vertices.include?(list), "wtf?") - - ([list] + children).each do |thing| - assert(graph.vertex?(thing), "%s is not a vertex" % thing) - end - children.each do |child| - assert(graph.edge?(list, child), - "%s/%s was not added as an edge" % ["yay", child]) - end - end - - def test_recursive_to_graph - one, two, three, middle, top = build_tree - - graph = nil - assert_nothing_raised do - graph = top.to_graph - end - - (%w{a b c d e f g h} + [one, two, middle, top]).each do |v| - assert(graph.vertex?(v), "%s is not a vertex" % v) - end - - [one, two, middle, top].each do |con| - con.each do |child| - assert(graph.edge?(con, child), "%s/%s is not an edge" % [con, child]) - end - end - - # Now make sure we correctly retrieve the leaves from each container - {top => %w{a b c d e f g h i j}, - one => %w{a b}, - two => %w{c d}, - three => %w{i j}, - middle => %w{c d e f}}.each do |cont, list| - leaves = nil - assert_nothing_raised do - leaves = graph.leaves(cont) - end - leaves = leaves.sort - assert_equal(list.sort, leaves.sort, - "Got incorrect leaf list for %s" % cont.name) - %w{a b c d e f g h}.each do |letter| - unless list.include?(letter) - assert(!leaves.include?(letter), - "incorrectly got %s as a leaf of %s" % - [letter, cont.to_s]) - end - end - end - end - - def test_to_graph_with_block - middle = Container.new "middle", ["c", "d", 3, 4] - top = Container.new "top", ["a", "b", middle, 1, 2] - - graph = nil - assert_nothing_raised() { - graph = top.to_graph { |c| c.is_a?(String) or c.is_a?(Container) } - } - - %w{a b c d}.each do |child| - assert(graph.vertex?(child), "%s was not added as a vertex" % child) - end - - [1, 2, 3, 4].each do |child| - assert(! graph.vertex?(child), "%s is a vertex" % child) - end - end - - def test_cyclic_graphs - one = Container.new "one", %w{a b} - two = Container.new "two", %w{c d} - - one.push(two) - two.push(one) - - assert_raise(Puppet::Error, "did not fail on cyclic graph") do - one.to_graph - end - end -end - -# $Id$ diff --git a/test/util/config.rb b/test/util/settings.rb index f99ad54b4..62f34fda6 100755 --- a/test/util/config.rb +++ b/test/util/settings.rb @@ -4,14 +4,14 @@ $:.unshift("../lib").unshift("../../lib") if __FILE__ =~ /\.rb$/ require 'mocha' require 'puppettest' -require 'puppet/util/config' +require 'puppet/util/settings' require 'puppettest/parsertesting' -class TestConfig < Test::Unit::TestCase +class TestSettings < Test::Unit::TestCase include PuppetTest include PuppetTest::ParserTesting - CElement = Puppet::Util::Config::CElement - CBoolean = Puppet::Util::Config::CBoolean + CElement = Puppet::Util::Settings::CElement + CBoolean = Puppet::Util::Settings::CBoolean def setup super @@ -59,57 +59,6 @@ class TestConfig < Test::Unit::TestCase } end - # #795 - when --config=relative, we want to fully expand file paths. - def test_relative_paths_when_to_transportable - config = mkconfig - config.setdefaults :yay, :transtest => ["/what/ever", "yo"] - file = config.element(:transtest) - - # Now override it with a relative path name - config[:transtest] = "here" - - should = File.join(Dir.getwd, "here") - - object = file.to_transportable[0] - assert_equal(should, object.name, "Did not translate relative pathnames to full path names") - end - - def test_to_manifest - set_configs - manifest = nil - assert_nothing_raised("Could not convert to a manifest") { - manifest = @config.to_manifest - } - - Puppet[:parseonly] = true - - interp = nil - assert_nothing_raised do - interp = mkinterp :Code => manifest, :UseNodes => false - end - - trans = nil - node = Puppet::Node.new("node") - assert_nothing_raised do - trans = interp.compile(node) - end - assert_nothing_raised("Could not instantiate objects") { - trans.extract.to_type - } - end - - def test_to_comp - set_configs - comp = nil - assert_nothing_raised("Could not convert to a component") { - comp = @config.to_component - } - - assert_nothing_raised("Could not retrieve component") { - comp.retrieve - } - end - def test_to_config set_configs @@ -146,7 +95,7 @@ class TestConfig < Test::Unit::TestCase def mkconfig c = nil assert_nothing_raised { - c = Puppet::Util::Config.new + c = Puppet::Util::Settings.new } return c end @@ -668,91 +617,6 @@ yay = /a/path assert_equal("/my/file", @config[:b], "Values are not equal") end - def test_reuse - c = mkconfig - - file = tempfile() - section = "testing" - assert_nothing_raised { - @config.setdefaults(section, - :myfile => {:default => file, :create => true, :desc => "yay"} - ) - } - - assert_nothing_raised("Could not use a section") { - @config.use(section) - } - - assert(FileTest.exists?(file), "Did not create file") - - assert(! Puppet::Type.type(:file)[file], "File obj still exists") - - File.unlink(file) - - @config.reuse - assert(FileTest.exists?(file), "Did not create file") - end - - def test_mkusers - c = mkconfig - - file = tempfile() - section = "testing" - assert_nothing_raised { - @config.setdefaults(section, - :mkusers => [false, "yay"], - :myfile => { - :default => file, - :owner => "pptest", - :group => "pptest", - :desc => "yay", - :create => true - } - ) - } - - comp = nil - assert_nothing_raised { - comp = @config.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 } - - @config[:mkusers] = true - - assert_nothing_raised { - @config.to_component - } - - user = Puppet.type(:user)["pptest"] - assert(user, "User object did not get created") - assert(user.managed?, "User object is not managed.") - assert(user.should(:comment), "user does not have a comment set") - - group = Puppet.type(:group)["pptest"] - assert(group, "Group object did not get created") - assert(group.managed?, - "Group object is not managed." - ) - - if Process.uid == 0 - cleanup do - user[:ensure] = :absent - group[:ensure] = :absent - assert_apply(user, group) - end - - assert_apply(user, group) - end - end - def test_notmanagingdev c = mkconfig path = "/dev/testing" @@ -764,11 +628,9 @@ yay = /a/path } ) - assert_nothing_raised { - @config.to_component - } + config = @config.to_configuration - assert(! Puppet.type(:file)["/dev/testing"], "Created dev file") + assert(! config.resource(:file, "/dev/testing"), "Created dev file") end def test_groupsetting @@ -906,15 +768,15 @@ yay = /a/path } assert_equal("http://yayness/rahness", val, - "Config got messed up") + "Settings got messed up") end def test_correct_type_assumptions config = mkconfig - file = Puppet::Util::Config::CFile - element = Puppet::Util::Config::CElement - bool = Puppet::Util::Config::CBoolean + file = Puppet::Util::Settings::CFile + element = Puppet::Util::Settings::CElement + bool = Puppet::Util::Settings::CBoolean # We have to keep these ordered, unfortunately. [ @@ -1119,12 +981,12 @@ yay = /a/path # Now enable it so they'll be added config[:mkusers] = true - comp = config.to_component + comp = config.to_configuration - Puppet::Type.type(:user).each do |u| + comp.vertices.find_all { |r| r.class.name == :user }.each do |u| assert(u.name != "root", "Tried to manage root user") end - Puppet::Type.type(:group).each do |u| + comp.vertices.find_all { |r| r.class.name == :group }.each do |u| assert(u.name != "root", "Tried to manage root group") assert(u.name != "wheel", "Tried to manage wheel group") end |