diff --git a/bin/puppetmasterd b/bin/puppetmasterd index 33e4f436d..b4733e604 100755 --- a/bin/puppetmasterd +++ b/bin/puppetmasterd @@ -1,285 +1,288 @@ #!/usr/bin/env ruby # # = Synopsis # # The central puppet server. Functions as a certificate authority by default. # # = Usage # # puppetmasterd [-D|--daemonize|--no-daemonize] [-d|--debug] [-h|--help] # [-l|--logdest |console|syslog] [--nobucket] [--nonodes] # [-v|--verbose] [-V|--version] # # = Description # # This is the puppet central daemon. # # = Options # # Note that any configuration parameter that's valid in the configuration file # is also a valid long argument. For example, 'ssldir' is a valid configuration # parameter, so you can specify '--ssldir ' as an argument. # # See the configuration file documentation at # http://reductivelabs.com/projects/puppet/reference/configref.html for # the full list of acceptable parameters. A commented list of all # configuration options can also be generated by running puppetmasterdd with # '--genconfig'. # # daemonize:: # Send the process into the background. This is the default. # # no-daemonize:: # Do not send the process into the background. # # debug:: # Enable full debugging. # # help:: # Print this help message. # # logdest:: # Where to send messages. Choose between syslog, the console, and a log file. # Defaults to sending messages to syslog, or the console # if debugging or verbosity is enabled. # # nobucket:: # Do not function as a file bucket. # # nonodes:: # Do not use individual node designations; each node will receive the result # of evaluating the entire configuration. # # noreports:: # Do not start the reports server. # # verbose:: # Enable verbosity. # # version:: # Print the puppet version number and exit. # # = Example # # puppetmasterd # # = Author # # Luke Kanies # # = Copyright # # Copyright (c) 2005 Reductive Labs, LLC # Licensed under the GNU Public License # Do an initial trap, so that cancels don't get a stack trace. trap(:INT) do $stderr.puts "Cancelling startup" exit(0) end require 'getoptlong' require 'puppet' require 'puppet/network/handler' require 'puppet/sslcertificates' options = [ [ "--debug", "-d", GetoptLong::NO_ARGUMENT ], [ "--help", "-h", GetoptLong::NO_ARGUMENT ], [ "--logdest", "-l", GetoptLong::REQUIRED_ARGUMENT ], [ "--nobucket", GetoptLong::NO_ARGUMENT ], [ "--noreports", GetoptLong::NO_ARGUMENT ], [ "--nonodes", GetoptLong::NO_ARGUMENT ], [ "--verbose", "-v", GetoptLong::NO_ARGUMENT ], [ "--version", "-V", GetoptLong::NO_ARGUMENT ] ] # Add all of the config parameters as valid options. Puppet.settings.addargs(options) result = GetoptLong.new(*options) master = {} ca = {} report = {} bucket = {} options = { :havereport => true, :havebucket => true, :havemaster => true, :setdest => false, :verbose => false, :debug => false } begin result.each { |opt,arg| case opt # First check to see if the argument is a valid configuration parameter; # if so, set it. NOTE: there is a catch-all at the bottom for defaults.rb when "--debug" options[:debug] = true when "--help" if Puppet.features.usage? RDoc::usage && exit else puts "No help available unless you have RDoc::usage installed" exit end when "--noreports" options[:havereport] = false when "--nomaster" options[:havemaster] = false when "--nobucket" options[:havebucket] = false when "--nonodes" master[:UseNodes] = false when "--logdest" begin Puppet::Util::Log.newdestination(arg) options[:setdest] = true rescue => detail if Puppet[:debug] puts detail.backtrace end $stderr.puts detail.to_s end when "--version" puts "%s" % Puppet.version exit when "--verbose" options[:verbose] = true else Puppet.settings.handlearg(opt, arg) end } rescue GetoptLong::InvalidOption => detail $stderr.puts "Try '#{$0} --help'" #$stderr.puts detail exit(1) end # Now parse the config Puppet.parse_config # Handle the logging settings. if options[:debug] or options[:verbose] if options[:debug] Puppet::Util::Log.level = :debug else Puppet::Util::Log.level = :info end unless Puppet[:daemonize] Puppet::Util::Log.newdestination(:console) options[:setdest] = true end end unless options[:setdest] Puppet::Util::Log.newdestination(:syslog) end Puppet.genconfig Puppet.genmanifest # A temporary solution, to at least make the master work for now. Puppet::Node::Facts.terminus_class = :yaml +# Cache our nodes in yaml. Currently not configurable. +Puppet::Node.cache_class = :yaml + require 'etc' handlers = { :Status => {}, :FileServer => {} } if options[:havemaster] handlers[:Master] = master end if options[:havereport] handlers[:Report] = report end if Puppet[:ca] handlers[:CA] = ca end if options[:havebucket] handlers[:FileBucket] = bucket end if Puppet[:parseonly] begin Puppet::Network::Handler.master.new(master) rescue => detail if Puppet[:trace] puts detail.backtrace end $stderr.puts detail exit(32) end # we would have already exited if the file weren't syntactically correct exit(0) end webserver = server = nil begin case Puppet[:servertype] when "webrick" # use the default, um, everything require 'puppet/network/http_server/webrick' webserver = server = Puppet::Network::HTTPServer::WEBrick.new(:Handlers => handlers) when "mongrel": require 'puppet/network/http_server/mongrel' server = Puppet::Network::HTTPServer::Mongrel.new(handlers) addr = Puppet[:bindaddress] if addr == "" addr = "127.0.0.1" end webserver = Mongrel::HttpServer.new(addr, Puppet[:masterport]) webserver.register("/", server) else Puppet.err "Invalid server type %s" % Puppet[:servertype] exit(45) end rescue => detail if Puppet[:trace] puts detail.backtrace end $stderr.puts detail exit(1) end if Process.uid == 0 begin Puppet::Util.chuser rescue => detail if Puppet[:debug] puts detail.backtrace end $stderr.puts "Could not change user to %s: %s" % [Puppet[:user], detail] exit(39) end end # Mongrel doesn't shut down like webrick; we really need to write plugins for it. if Puppet[:servertype] == "webrick" Puppet.newservice(server) end Puppet.settraps if Puppet[:daemonize] server.daemonize end Puppet.notice "Starting Puppet server version %s" % [Puppet.version] case Puppet[:servertype] when "webrick" Puppet.start when "mongrel": webserver.run.join end diff --git a/lib/puppet/node.rb b/lib/puppet/node.rb index c39f364bc..c0628ecdc 100644 --- a/lib/puppet/node.rb +++ b/lib/puppet/node.rb @@ -1,165 +1,165 @@ require 'puppet/indirector' # A class for managing nodes, including their facts and environment. class Puppet::Node require 'puppet/node/facts' require 'puppet/node/environment' # 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, :terminus_setting => :node_terminus, :cache_class => :yaml, :doc => "Where to find node information. + indirects :node, :terminus_setting => :node_terminus, :doc => "Where to find node information. A node is composed of its name, its facts, and its environment." # Retrieve a node from the node source, with some additional munging # thrown in for kicks. def self.find_by_any_name(key) return nil unless key facts = node_facts(key) node = nil names = node_names(key, facts) names.each do |name| name = name.to_s if name.is_a?(Symbol) break if node = find(name) 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 return node else return nil end end private # Look up the node facts so we can generate the node names to use. def self.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 self.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 public attr_accessor :name, :classes, :parameters, :source, :ipaddress, :names attr_reader :time # Set the environment, making sure that it's valid. def environment=(value) raise(ArgumentError, "Invalid environment %s" % value) unless Puppet::Node::Environment.valid?(value) @environment = value end # Do not return environments that are the empty string, and use # explicitly set environments, then facts, then a central env # value. def environment unless @environment if env = parameters["environment"] raise(ArgumentError, "Invalid environment %s from parameters" % env) unless Puppet::Node::Environment.valid?(env) @environment = env else @environment = Puppet::Node::Environment.new.name.to_s end end @environment end def initialize(name, options = {}) unless name raise ArgumentError, "Node names cannot be nil" end @name = name # Provide a default value. if names = options[:names] if names.is_a?(String) @names = [names] else @names = names end else @names = [name] end if classes = options[:classes] if classes.is_a?(String) @classes = [classes] else @classes = classes end else @classes = [] end @parameters = options[:parameters] || {} self.environment = options[:environment] if options[:environment] @time = Time.now end # Merge the node facts with parameters from the node source. 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 end diff --git a/spec/unit/node.rb b/spec/unit/node.rb index 4861cb9e3..e62bd5d07 100755 --- a/spec/unit/node.rb +++ b/spec/unit/node.rb @@ -1,210 +1,210 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../spec_helper' describe Puppet::Node, " when initializing" do before do @node = Puppet::Node.new("testnode") end it "should set the node name" do @node.name.should == "testnode" end it "should not allow nil node names" do proc { Puppet::Node.new(nil) }.should raise_error(ArgumentError) end it "should default to an empty parameter hash" do @node.parameters.should == {} end it "should default to an empty class array" do @node.classes.should == [] end it "should note its creation time" do @node.time.should be_instance_of(Time) end it "should accept parameters passed in during initialization" do params = {"a" => "b"} @node = Puppet::Node.new("testing", :parameters => params) @node.parameters.should == params end it "should accept classes passed in during initialization" do classes = %w{one two} @node = Puppet::Node.new("testing", :classes => classes) @node.classes.should == classes end it "should always return classes as an array" do @node = Puppet::Node.new("testing", :classes => "myclass") @node.classes.should == ["myclass"] end it "should accept an environment value" do Puppet.settings.stubs(:value).with(:environments).returns("myenv") @node = Puppet::Node.new("testing", :environment => "myenv") @node.environment.should == "myenv" end it "should validate the environment" do Puppet.settings.stubs(:value).with(:environments).returns("myenv") proc { Puppet::Node.new("testing", :environment => "other") }.should raise_error(ArgumentError) end it "should accept names passed in" do @node = Puppet::Node.new("testing", :names => ["myenv"]) @node.names.should == ["myenv"] end end describe Puppet::Node, " when returning the environment" do before do Puppet.settings.stubs(:value).with(:environments).returns("one,two") Puppet.settings.stubs(:value).with(:environment).returns("one") @node = Puppet::Node.new("testnode") end it "should return the 'environment' fact if present and there is no explicit environment" do @node.parameters = {"environment" => "two"} @node.environment.should == "two" end it "should use the default environment if there is no environment fact nor explicit environment" do env = mock 'environment', :name => :myenv Puppet::Node::Environment.expects(:new).returns(env) @node.environment.should == "myenv" end it "should fail if the parameter environment is invalid" do @node.parameters = {"environment" => "three"} proc { @node.environment }.should raise_error(ArgumentError) end it "should fail if the parameter environment is invalid" do @node.parameters = {"environment" => "three"} proc { @node.environment }.should raise_error(ArgumentError) end 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 @node.parameters["one"].should == "a" end it "should add passed parameters to the parameter list" do @node.parameters = {"one" => "a"} @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 it "should redirect to the indirection" do @indirection = mock 'indirection' Puppet::Node.stubs(:indirection).returns(@indirection) @indirection.expects(:find).with(:my_node.to_s) Puppet::Node.find(:my_node.to_s) end it "should default to the 'plain' node terminus" do Puppet::Node.indirection.terminus_class.should == :plain end - it "should use yaml for caching" do - Puppet::Node.indirection.cache_class.should == :yaml + it "should not have a cache class defined" do + Puppet::Node.indirection.cache_class.should be_nil 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 describe Puppet::Node, " when searching for nodes" do before do @searcher = Puppet::Node @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 return the first node found using the generated list of names" do @searcher.expects(:find).with("foo").returns(nil) @searcher.expects(:find).with("yay.domain.com").returns(@node) @searcher.find_by_any_name("foo").should equal(@node) 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.find_by_any_name("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.find_by_any_name("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.find_by_any_name("foo") names[-1].should == "default" 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.find_by_any_name("foo").should equal(@node) @searcher.find_by_any_name("foo").should equal(node2) end after do Puppet.settings.clear end end