diff --git a/lib/puppet/application/puppetdoc.rb b/lib/puppet/application/puppetdoc.rb index 5656112d0..0a4e0c3c3 100644 --- a/lib/puppet/application/puppetdoc.rb +++ b/lib/puppet/application/puppetdoc.rb @@ -1,224 +1,224 @@ require 'puppet' require 'puppet/application' require 'puppet/util/reference' require 'puppet/network/handler' require 'puppet/util/rdoc' $tab = " " Reference = Puppet::Util::Reference Puppet::Application.new(:puppetdoc) do should_not_parse_config attr_accessor :unknown_args, :manifest preinit do {:references => [], :mode => :text, :format => :to_rest }.each do |name,value| options[name] = value end @unknown_args = [] @manifest = false end option("--all","-a") option("--outputdir OUTPUTDIR","-o") option("--verbose","-v") option("--debug","-d") option("--format FORMAT", "-f") do |arg| method = "to_%s" % arg if Reference.method_defined?(method) options[:format] = method else raise "Invalid output format %s" % arg end end option("--mode MODE", "-m") do |arg| if Reference.modes.include?(arg) or arg.intern==:rdoc options[:mode] = arg.intern else raise "Invalid output mode %s" % arg end end option("--list", "-l") do |arg| puts Reference.references.collect { |r| Reference.reference(r).doc }.join("\n") exit(0) end option("--reference REFERENCE", "-r") do |arg| options[:references] << arg.intern end unknown do |opt, arg| @unknown_args << {:opt => opt, :arg => arg } true end dispatch do return options[:mode] if [:rdoc, :trac, :markdown].include?(options[:mode]) return :other end command(:rdoc) do exit_code = 0 files = [] unless @manifest env = Puppet::Node::Environment.new files += env.modulepath files += env.manifestdir end files += ARGV Puppet.info "scanning: %s" % files.inspect Puppet.settings.setdefaults("puppetdoc", "document_all" => [false, "Document all resources"] ) Puppet.settings[:document_all] = options[:all] || false begin if @manifest Puppet::Util::RDoc.manifestdoc(files) else options[:outputdir] = "doc" unless options[:outputdir] Puppet::Util::RDoc.rdoc(options[:outputdir], files) end rescue => detail if Puppet[:trace] puts detail.backtrace end $stderr.puts "Could not generate documentation: %s" % detail exit_code = 1 end exit exit_code end command(:trac) do options[:references].each do |name| section = Puppet::Util::Reference.reference(name) or raise "Could not find section %s" % name unless options[:mode] == :pdf section.trac end end end command(:markdown) do text = "" with_contents = false exit_code = 0 options[:references].sort { |a,b| a.to_s <=> b.to_s }.each do |name| raise "Could not find reference %s" % name unless section = Puppet::Util::Reference.reference(name) begin # Add the per-section text, but with no ToC text += section.send(options[:format], with_contents) text += Puppet::Util::Reference.footer text.gsub!(/`\w+\s+([^`]+)`:trac:/) { |m| $1 } Puppet::Util::Reference.markdown(name, text) text = "" rescue => detail puts detail.backtrace $stderr.puts "Could not generate reference %s: %s" % [name, detail] exit_code = 1 next end end exit exit_code end command(:other) do text = "" if options[:references].length > 1 with_contents = false else with_contents = true end exit_code = 0 options[:references].sort { |a,b| a.to_s <=> b.to_s }.each do |name| raise "Could not find reference %s" % name unless section = Puppet::Util::Reference.reference(name) begin # Add the per-section text, but with no ToC text += section.send(options[:format], with_contents) rescue => detail puts detail.backtrace $stderr.puts "Could not generate reference %s: %s" % [name, detail] exit_code = 1 next end end unless with_contents # We've only got one reference text += Puppet::Util::Reference.footer end # Replace the trac links, since they're invalid everywhere else text.gsub!(/`\w+\s+([^`]+)`:trac:/) { |m| $1 } if options[:mode] == :pdf Puppet::Util::Reference.pdf(text) else puts text end exit exit_code end setup do # sole manifest documentation if ARGV.size > 0 options[:mode] = :rdoc @manifest = true end if options[:mode] == :rdoc setup_rdoc else setup_reference end end def setup_reference if options[:all] # Don't add dynamic references to the "all" list. options[:references] = Reference.references.reject do |ref| Reference.reference(ref).dynamic? end end if options[:references].empty? options[:references] << :type end end - def setup_rdoc + def setup_rdoc(dummy_argument=:work_arround_for_ruby_GC_bug) # consume the unknown options # and feed them as settings if @unknown_args.size > 0 @unknown_args.each do |option| # force absolute path for modulepath when passed on commandline if option[:opt]=="--modulepath" or option[:opt] == "--manifestdir" option[:arg] = option[:arg].split(':').collect { |p| File.expand_path(p) }.join(':') end Puppet.settings.handlearg(option[:opt], option[:arg]) end end # hack to get access to puppetmasterd modulepath and manifestdir Puppet[:name] = "puppetmasterd" # 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 Puppet::Util::Log.newdestination(:console) end end end diff --git a/lib/puppet/file_serving/base.rb b/lib/puppet/file_serving/base.rb index 02132e885..a7ab9b66e 100644 --- a/lib/puppet/file_serving/base.rb +++ b/lib/puppet/file_serving/base.rb @@ -1,92 +1,92 @@ # # Created by Luke Kanies on 2007-10-22. # Copyright (c) 2007. All rights reserved. require 'puppet/file_serving' # The base class for Content and Metadata; provides common # functionality like the behaviour around links. class Puppet::FileServing::Base # This is for external consumers to store the source that was used # to retrieve the metadata. attr_accessor :source # Does our file exist? def exist? begin stat return true rescue => detail return false end end # Return the full path to our file. Fails if there's no path set. - def full_path + def full_path(dummy_argument=:work_arround_for_ruby_GC_bug) (if relative_path.nil? or relative_path == "" or relative_path == "." path else File.join(path, relative_path) end).gsub(%r{/+}, "/") end def initialize(path, options = {}) self.path = path @links = :manage options.each do |param, value| begin send param.to_s + "=", value rescue NoMethodError raise ArgumentError, "Invalid option %s for %s" % [param, self.class] end end end # Determine how we deal with links. attr_reader :links def links=(value) value = value.to_sym value = :manage if value == :ignore raise(ArgumentError, ":links can only be set to :manage or :follow") unless [:manage, :follow].include?(value) @links = value end # Set our base path. attr_reader :path def path=(path) raise ArgumentError.new("Paths must be fully qualified") unless path =~ /^#{::File::SEPARATOR}/ @path = path end # Set a relative path; this is used for recursion, and sets # the file's path relative to the initial recursion point. attr_reader :relative_path def relative_path=(path) raise ArgumentError.new("Relative paths must not be fully qualified") if path =~ /^#{::File::SEPARATOR}/ @relative_path = path end # Stat our file, using the appropriate link-sensitive method. def stat unless defined?(@stat_method) @stat_method = self.links == :manage ? :lstat : :stat end File.send(@stat_method, full_path()) end def to_pson_data_hash { # No 'document_type' since we don't send these bare 'data' => { 'path' => @path, 'relative_path' => @relative_path, 'links' => @links }, 'metadata' => { 'api_version' => 1 } } end end diff --git a/lib/puppet/indirector/node/ldap.rb b/lib/puppet/indirector/node/ldap.rb index 4600a0dd5..dd8cebfac 100644 --- a/lib/puppet/indirector/node/ldap.rb +++ b/lib/puppet/indirector/node/ldap.rb @@ -1,263 +1,263 @@ require 'puppet/node' require 'puppet/indirector/ldap' class Puppet::Node::Ldap < Puppet::Indirector::Ldap desc "Search in LDAP for node configuration information. See the `LdapNodes`:trac: page for more information. This will first search for whatever the certificate name is, then (if that name contains a '.') for the short name, then 'default'." # The attributes that Puppet class information is stored in. def class_attributes # LAK:NOTE See http://snurl.com/21zf8 [groups_google_com] x = Puppet[:ldapclassattrs].split(/\s*,\s*/) end # Separate this out so it's relatively atomic. It's tempting to call # process() instead of name2hash() here, but it ends up being # difficult to test because all exceptions get caught by ldapsearch. # LAK:NOTE Unfortunately, the ldap support is too stupid to throw anything # but LDAP::ResultError, even on bad connections, so we are rough handed # with our error handling. def name2hash(name) info = nil ldapsearch(search_filter(name)) { |entry| info = entry2hash(entry) } return info end # Look for our node in ldap. def find(request) names = [request.key] if request.key.include?(".") # we assume it's an fqdn names << request.key.sub(/\..+/, '') end names << "default" node = nil names.each do |name| next unless info = name2hash(name) break if node = info2node(request.key, info) end return node end # Find more than one node. LAK:NOTE This is a bit of a clumsy API, because the 'search' # method currently *requires* a key. It seems appropriate in some cases but not others, # and I don't really know how to get rid of it as a requirement but allow it when desired. def search(request) if classes = request.options[:class] classes = [classes] unless classes.is_a?(Array) filter = "(&(objectclass=puppetClient)(puppetclass=" + classes.join(")(puppetclass=") + "))" else filter = "(objectclass=puppetClient)" end infos = [] ldapsearch(filter) { |entry| infos << entry2hash(entry) } return infos.collect do |info| info2node(info[:name], info) end end # The parent attribute, if we have one. def parent_attribute if pattr = Puppet[:ldapparentattr] and ! pattr.empty? pattr else nil end end # The attributes that Puppet will stack as array over the full # hierarchy. - def stacked_attributes + def stacked_attributes(dummy_argument=:work_arround_for_ruby_GC_bug) Puppet[:ldapstackedattrs].split(/\s*,\s*/) end # Convert the found entry into a simple hash. def entry2hash(entry) result = {} result[:name] = entry.dn.split(',')[0].split("=")[1] result[:parent] = get_parent_from_entry(entry) if parent_attribute result[:classes] = get_classes_from_entry(entry) result[:stacked] = get_stacked_values_from_entry(entry) result[:parameters] = get_parameters_from_entry(entry) result[:environment] = result[:parameters]["environment"] if result[:parameters]["environment"] result[:stacked_parameters] = {} if result[:stacked] result[:stacked].each do |value| param = value.split('=', 2) result[:stacked_parameters][param[0]] = param[1] end end if result[:stacked_parameters] result[:stacked_parameters].each do |param, value| result[:parameters][param] = value unless result[:parameters].include?(param) end end result[:parameters] = convert_parameters(result[:parameters]) 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 private # Add our hash of ldap information to the node instance. def add_to_node(node, information) node.classes = information[:classes].uniq unless information[:classes].nil? or information[:classes].empty? node.parameters = information[:parameters] unless information[:parameters].nil? or information[:parameters].empty? node.environment = information[:environment] if information[:environment] end def convert_parameters(parameters) result = {} parameters.each do |param, value| if value.is_a?(Array) result[param] = value.collect { |v| convert(v) } else result[param] = convert(value) end end result end # Convert any values if necessary. def convert(value) case value when Integer, Fixnum, Bignum; value when "true"; true when "false"; false else value end end # Find information for our parent and merge it into the current info. def find_and_merge_parent(parent, information) unless parent_info = name2hash(parent) raise Puppet::Error.new("Could not find parent node '%s'" % parent) 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 information[:environment] ||= parent_info[:environment] parent_info[:parent] end # Take a name and a hash, and return a node instance. def info2node(name, info) merge_parent(info) if info[:parent] node = Puppet::Node.new(name) add_to_node(node, info) node.fact_merge node end def merge_parent(info) parent_info = nil parent = info[:parent] # Preload the parent array with the node name. parents = [info[:name]] while parent if parents.include?(parent) raise ArgumentError, "Found loop in LDAP node parents; %s appears twice" % parent end parents << parent parent = find_and_merge_parent(parent, info) end return info end def get_classes_from_entry(entry) result = class_attributes.inject([]) do |array, attr| if values = entry.vals(attr) values.each do |v| array << v end end array end result.uniq end def get_parameters_from_entry(entry) stacked_params = stacked_attributes entry.to_hash.inject({}) do |hash, ary| unless stacked_params.include?(ary[0]) # don't add our stacked parameters to the main param list if ary[1].length == 1 hash[ary[0]] = ary[1].shift else hash[ary[0]] = ary[1] end end hash end end def get_parent_from_entry(entry) pattr = parent_attribute return nil unless values = entry.vals(pattr) if values.length > 1 raise Puppet::Error, "Node entry %s specifies more than one parent: %s" % [entry.dn, values.inspect] end return nil if values.empty? return values.shift end def get_stacked_values_from_entry(entry) stacked_attributes.inject([]) do |result, attr| if values = entry.vals(attr) result += values end result end end end diff --git a/lib/puppet/parser/ast/leaf.rb b/lib/puppet/parser/ast/leaf.rb index b73c781e1..c8ac6f7e3 100644 --- a/lib/puppet/parser/ast/leaf.rb +++ b/lib/puppet/parser/ast/leaf.rb @@ -1,195 +1,195 @@ class Puppet::Parser::AST # The base class for all of the leaves of the parse trees. These # basically just have types and values. Both of these parameters # are simple values, not AST objects. class Leaf < AST attr_accessor :value, :type # Return our value. def evaluate(scope) return @value end # evaluate ourselves, and match def evaluate_match(value, scope, options = {}) obj = self.safeevaluate(scope) if ! options[:sensitive] && obj.respond_to?(:downcase) obj = obj.downcase end obj == value end def match(value) @value == value end def to_s return @value.to_s unless @value.nil? end end # The boolean class. True or false. Converts the string it receives # to a Ruby boolean. class Boolean < AST::Leaf # Use the parent method, but then convert to a real boolean. def initialize(hash) super unless @value == true or @value == false raise Puppet::DevError, "'%s' is not a boolean" % @value end @value end def to_s @value ? "true" : "false" end end # The base string class. class String < AST::Leaf # Interpolate the string looking for variables, and then return # the result. def evaluate(scope) return scope.strinterp(@value, file, line) end def to_s "\"#{@value}\"" end end # An uninterpreted string. class FlatString < AST::Leaf def evaluate(scope) return @value end def to_s "\"#{@value}\"" end end # The 'default' option on case statements and selectors. class Default < AST::Leaf; end # Capitalized words; used mostly for type-defaults, but also # get returned by the lexer any other time an unquoted capitalized # word is found. class Type < AST::Leaf; end # Lower-case words. class Name < AST::Leaf; end # double-colon separated class names class ClassName < AST::Leaf; end # undef values; equiv to nil class Undef < AST::Leaf; end # Host names, either fully qualified or just the short name, or even a regex class HostName < AST::Leaf def initialize(hash) super @value = @value.to_s.downcase unless @value.is_a?(Regex) if @value =~ /[^-\w.]/ raise Puppet::DevError, "'%s' is not a valid hostname" % @value end end - def to_classname + def to_classname(dummy_argument=:work_arround_for_ruby_GC_bug) to_s.downcase.gsub(/[^-\w:.]/,'').sub(/^\.+/,'') end # implementing eql? and hash so that when an HostName is stored # in a hash it has the same hashing properties as the underlying value def eql?(value) value = value.value if value.is_a?(HostName) return @value.eql?(value) end def hash return @value.hash end def match(value) return @value.match(value) unless value.is_a?(HostName) if value.regex? and self.regex? # Wow this is some sweet design; maybe a touch of refactoring # in order here. return value.value.value == self.value.value elsif value.regex? # we know if the existing name is not a regex, it won't match a regex return false else # else, we could be either a regex or normal and it doesn't matter return @value.match(value.value) end end def regex? @value.is_a?(Regex) end def to_s @value.to_s end end # A simple variable. This object is only used during interpolation; # the VarDef class is used for assignment. class Variable < Name # Looks up the value of the object in the scope tree (does # not include syntactical constructs, like '$' and '{}'). def evaluate(scope) parsewrap do return scope.lookupvar(@value) end end def to_s "\$#{value}" end end class Regex < AST::Leaf def initialize(hash) super @value = Regexp.new(@value) unless @value.is_a?(Regexp) end # we're returning self here to wrap the regexp and to be used in places # where a string would have been used, without modifying any client code. # For instance, in many places we have the following code snippet: # val = @val.safeevaluate(@scope) # if val.match(otherval) # ... # end # this way, we don't have to modify this test specifically for handling # regexes. def evaluate(scope) return self end def evaluate_match(value, scope, options = {}) value = value.is_a?(String) ? value : value.to_s if matched = @value.match(value) scope.ephemeral_from(matched, options[:file], options[:line]) end matched end def match(value) @value.match(value) end def to_s return "/#{@value.source}/" end end end diff --git a/lib/puppet/provider/service/daemontools.rb b/lib/puppet/provider/service/daemontools.rb index 2c6c6dbc7..3749f9c2c 100644 --- a/lib/puppet/provider/service/daemontools.rb +++ b/lib/puppet/provider/service/daemontools.rb @@ -1,198 +1,198 @@ # Daemontools service management # # author Brice Figureau Puppet::Type.type(:service).provide :daemontools, :parent => :base do desc "Daemontools service management. This provider manages daemons running supervised by D.J.Bernstein daemontools. It tries to detect the service directory, with by order of preference: * /service * /etc/service * /var/lib/svscan The daemon directory should be placed in a directory that can be by default in: * /var/lib/service * /etc or this can be overriden in the service resource parameters:: service { \"myservice\": provider => \"daemontools\", path => \"/path/to/daemons\"; } This provider supports out of the box: * start/stop (mapped to enable/disable) * enable/disable * restart * status If a service has ensure => \"running\", it will link /path/to/daemon to /path/to/service, which will automatically enable the service. If a service has ensure => \"stopped\", it will only down the service, not remove the /path/to/service link. " commands :svc => "/usr/bin/svc", :svstat => "/usr/bin/svstat" class << self attr_writer :defpath # Determine the daemon path. - def defpath + def defpath(dummy_argument=:work_arround_for_ruby_GC_bug) unless defined?(@defpath) and @defpath ["/var/lib/service", "/etc"].each do |path| if FileTest.exist?(path) @defpath = path break end end raise "Could not find the daemon directory (tested [/var/lib/service,/etc])" unless @defpath end @defpath end end attr_writer :servicedir # returns all providers for all existing services in @defpath # ie enabled or not def self.instances path = self.defpath unless FileTest.directory?(path) Puppet.notice "Service path %s does not exist" % path next end # reject entries that aren't either a directory # or don't contain a run file Dir.entries(path).reject { |e| fullpath = File.join(path, e) e =~ /^\./ or ! FileTest.directory?(fullpath) or ! FileTest.exist?(File.join(fullpath,"run")) }.collect do |name| new(:name => name, :path => path) end end # returns the daemon dir on this node def self.daemondir self.defpath end # find the service dir on this node def servicedir unless defined?(@servicedir) and @servicedir ["/service", "/etc/service","/var/lib/svscan"].each do |path| if FileTest.exist?(path) @servicedir = path break end end raise "Could not find service directory" unless @servicedir end @servicedir end # returns the full path of this service when enabled # (ie in the service directory) def service File.join(self.servicedir, resource[:name]) end # returns the full path to the current daemon directory # note that this path can be overriden in the resource # definition def daemon File.join(resource[:path], resource[:name]) end def status begin output = svstat self.service if output =~ /:\s+up \(/ return :running end rescue Puppet::ExecutionFailure => detail raise Puppet::Error.new( "Could not get status for service %s: %s" % [ resource.ref, detail] ) end return :stopped end def setupservice begin if resource[:manifest] Puppet.notice "Configuring %s" % resource[:name] command = [ resource[:manifest], resource[:name] ] #texecute("setupservice", command) rv = system("#{command}") end rescue Puppet::ExecutionFailure => detail raise Puppet::Error.new( "Cannot config %s to enable it: %s" % [ self.service, detail ] ) end end def enabled? case self.status when :running # obviously if the daemon is running then it is enabled return :true else # the service is enabled if it is linked return FileTest.symlink?(self.service) ? :true : :false end end def enable begin if ! FileTest.directory?(self.daemon) Puppet.notice "No daemon dir, calling setupservice for %s" % resource[:name] self.setupservice end if self.daemon if ! FileTest.symlink?(self.service) Puppet.notice "Enabling %s: linking %s -> %s" % [ self.service, self.daemon, self.service ] File.symlink(self.daemon, self.service) end end rescue Puppet::ExecutionFailure => detail raise Puppet::Error.new( "No daemon directory found for %s" % self.service ) end end def disable begin if ! FileTest.directory?(self.daemon) Puppet.notice "No daemon dir, calling setupservice for %s" % resource[:name] self.setupservice end if self.daemon if FileTest.symlink?(self.service) Puppet.notice "Disabling %s: removing link %s -> %s" % [ self.service, self.daemon, self.service ] File.unlink(self.service) end end rescue Puppet::ExecutionFailure => detail raise Puppet::Error.new( "No daemon directory found for %s" % self.service ) end self.stop end def restart svc "-t", self.service end def start enable unless enabled? == :true svc "-u", self.service end def stop svc "-d", self.service end end diff --git a/lib/puppet/provider/service/runit.rb b/lib/puppet/provider/service/runit.rb index b313fc79c..b8b444e34 100644 --- a/lib/puppet/provider/service/runit.rb +++ b/lib/puppet/provider/service/runit.rb @@ -1,103 +1,103 @@ # Daemontools service management # # author Brice Figureau Puppet::Type.type(:service).provide :runit, :parent => :daemontools do desc "Runit service management. This provider manages daemons running supervised by Runit. It tries to detect the service directory, with by order of preference: * /service * /var/service * /etc/service The daemon directory should be placed in a directory that can be by default in: * /etc/sv or this can be overriden in the service resource parameters:: service { \"myservice\": provider => \"runit\", path => \"/path/to/daemons\"; } This provider supports out of the box: * start/stop * enable/disable * restart * status " commands :sv => "/usr/bin/sv" class << self # this is necessary to autodetect a valid resource # default path, since there is no standard for such directory. - def defpath + def defpath(dummy_argument=:work_arround_for_ruby_GC_bug) unless defined?(@defpath) and @defpath ["/etc/sv", "/var/lib/service"].each do |path| if FileTest.exist?(path) @defpath = path break end end raise "Could not find the daemon directory (tested [/var/lib/service,/etc])" unless @defpath end @defpath end end # find the service dir on this node def servicedir unless defined?(@servicedir) and @servicedir ["/service", "/etc/service","/var/service"].each do |path| if FileTest.exist?(path) @servicedir = path break end end raise "Could not find service directory" unless @servicedir end @servicedir end def status begin output = sv "status", self.daemon return :running if output =~ /^run: / rescue Puppet::ExecutionFailure => detail unless detail.message =~ /(warning: |runsv not running$)/ raise Puppet::Error.new( "Could not get status for service %s: %s" % [ resource.ref, detail] ) end end return :stopped end def stop sv "stop", self.service end def start enable unless enabled? == :true sv "start", self.service end def restart sv "restart", self.service end # disable by removing the symlink so that runit # doesn't restart our service behind our back # note that runit doesn't need to perform a stop # before a disable def disable # unlink the daemon symlink to disable it File.unlink(self.service) if FileTest.symlink?(self.service) end end diff --git a/lib/puppet/provider/zone/solaris.rb b/lib/puppet/provider/zone/solaris.rb index 52007bb25..b047f6984 100644 --- a/lib/puppet/provider/zone/solaris.rb +++ b/lib/puppet/provider/zone/solaris.rb @@ -1,256 +1,256 @@ Puppet::Type.type(:zone).provide(:solaris) do desc "Provider for Solaris Zones." commands :adm => "/usr/sbin/zoneadm", :cfg => "/usr/sbin/zonecfg" defaultfor :operatingsystem => :solaris mk_resource_methods # Convert the output of a list into a hash def self.line2hash(line) fields = [:id, :name, :ensure, :path] properties = {} line.split(":").each_with_index { |value, index| next unless fields[index] properties[fields[index]] = value } # Configured but not installed zones do not have IDs if properties[:id] == "-" properties.delete(:id) end properties[:ensure] = symbolize(properties[:ensure]) return properties end def self.instances # LAK:NOTE See http://snurl.com/21zf8 [groups_google_com] x = adm(:list, "-cp").split("\n").collect do |line| new(line2hash(line)) end end # Perform all of our configuration steps. def configure # If the thing is entirely absent, then we need to create the config. # Is there someway to get this on one line? str = "create -b #{@resource[:create_args]}\nset zonepath=%s\n" % @resource[:path] # Then perform all of our configuration steps. It's annoying # that we need this much internal info on the resource. @resource.send(:properties).each do |property| if property.is_a? ZoneConfigProperty and ! property.insync?(properties[property.name]) str += property.configtext + "\n" end end str += "commit\n" setconfig(str) end def destroy zonecfg :delete, "-F" end def exists? properties[:ensure] != :absent end # Clear out the cached values. def flush @property_hash.clear end - def install + def install(dummy_argument=:work_arround_for_ruby_GC_bug) if @resource[:install_args] zoneadm :install, @resource[:install_args].split(" ") else zoneadm :install end end # Look up the current status. def properties if @property_hash.empty? @property_hash = status || {} if @property_hash.empty? @property_hash[:ensure] = :absent else @resource.class.validproperties.each do |name| @property_hash[name] ||= :absent end end end @property_hash.dup end # We need a way to test whether a zone is in process. Our 'ensure' # property models the static states, but we need to handle the temporary ones. def processing? if hash = status() case hash[:ensure] when "incomplete", "ready", "shutting_down" true else false end else false end end # Collect the configuration of the zone. def getconfig output = zonecfg :info name = nil current = nil hash = {} output.split("\n").each do |line| case line when /^(\S+):\s*$/ name = $1 current = nil # reset it when /^(\S+):\s*(.+)$/ hash[$1.intern] = $2 when /^\s+(\S+):\s*(.+)$/ if name unless hash.include? name hash[name] = [] end unless current current = {} hash[name] << current end current[$1.intern] = $2 else err "Ignoring '%s'" % line end else debug "Ignoring zone output '%s'" % line end end return hash end # Execute a configuration string. Can't be private because it's called # by the properties. def setconfig(str) command = "#{command(:cfg)} -z %s -f -" % @resource[:name] debug "Executing '%s' in zone %s with '%s'" % [command, @resource[:name], str] IO.popen(command, "w") do |pipe| pipe.puts str end unless $? == 0 raise ArgumentError, "Failed to apply configuration" end end def start # Check the sysidcfg stuff if cfg = @resource[:sysidcfg] zoneetc = File.join(@resource[:path], "root", "etc") sysidcfg = File.join(zoneetc, "sysidcfg") # if the zone root isn't present "ready" the zone # which makes zoneadmd mount the zone root zoneadm :ready unless File.directory?(zoneetc) unless File.exists?(sysidcfg) begin File.open(sysidcfg, "w", 0600) do |f| f.puts cfg end rescue => detail if Puppet[:debug] puts detail.stacktrace end raise Puppet::Error, "Could not create sysidcfg: %s" % detail end end end zoneadm :boot end # Return a hash of the current status of this zone. def status begin output = adm "-z", @resource[:name], :list, "-p" rescue Puppet::ExecutionFailure return nil end main = self.class.line2hash(output.chomp) # Now add in the configuration information config_status.each do |name, value| main[name] = value end main end def ready zoneadm :ready end def stop zoneadm :halt end def unconfigure zonecfg :delete, "-F" end def uninstall zoneadm :uninstall, "-F" end private # Turn the results of getconfig into status information. def config_status config = getconfig() result = {} result[:autoboot] = config[:autoboot] ? config[:autoboot].intern : :absent result[:pool] = config[:pool] result[:shares] = config[:shares] if dir = config["inherit-pkg-dir"] result[:inherit] = dir.collect { |dirs| dirs[:dir] } end if net = config["net"] result[:ip] = net.collect { |params| "%s:%s" % [params[:physical], params[:address]] } end result end def zoneadm(*cmd) begin adm("-z", @resource[:name], *cmd) rescue Puppet::ExecutionFailure => detail self.fail "Could not %s zone: %s" % [cmd[0], detail] end end def zonecfg(*cmd) # You apparently can't get the configuration of the global zone return "" if self.name == "global" begin cfg("-z", self.name, *cmd) rescue Puppet::ExecutionFailure => detail self.fail "Could not %s zone: %s" % [cmd[0], detail] end end end diff --git a/lib/puppet/rails/resource.rb b/lib/puppet/rails/resource.rb index 12d321143..984bdc05a 100644 --- a/lib/puppet/rails/resource.rb +++ b/lib/puppet/rails/resource.rb @@ -1,251 +1,251 @@ require 'puppet' require 'puppet/rails/param_name' require 'puppet/rails/param_value' require 'puppet/rails/puppet_tag' require 'puppet/rails/benchmark' require 'puppet/util/rails/collection_merger' class Puppet::Rails::Resource < ActiveRecord::Base include Puppet::Util::CollectionMerger include Puppet::Util::ReferenceSerializer include Puppet::Rails::Benchmark has_many :param_values, :dependent => :destroy, :class_name => "Puppet::Rails::ParamValue" has_many :param_names, :through => :param_values, :class_name => "Puppet::Rails::ParamName" has_many :resource_tags, :dependent => :destroy, :class_name => "Puppet::Rails::ResourceTag" has_many :puppet_tags, :through => :resource_tags, :class_name => "Puppet::Rails::PuppetTag" belongs_to :source_file belongs_to :host @tags = {} def self.tags @tags end # Determine the basic details on the resource. def self.rails_resource_initial_args(resource) result = [:type, :title, :line].inject({}) do |hash, param| # 'type' isn't a valid column name, so we have to use another name. to = (param == :type) ? :restype : param if value = resource.send(param) hash[to] = value end hash end # We always want a value here, regardless of what the resource has, # so we break it out separately. result[:exported] = resource.exported || false result end def add_resource_tag(tag) pt = Puppet::Rails::PuppetTag.accumulate_by_name(tag) resource_tags.build(:puppet_tag => pt) end def file if f = self.source_file return f.filename else return nil end end def file=(file) self.source_file = Puppet::Rails::SourceFile.find_or_create_by_filename(file) end def title unserialize_value(self[:title]) end def add_param_to_hash(param) @params_hash ||= [] @params_hash << param end def add_tag_to_hash(tag) @tags_hash ||= [] @tags_hash << tag end def params_hash=(hash) @params_hash = hash end def tags_hash=(hash) @tags_hash = hash end def [](param) return super || parameter(param) end # Make sure this resource is equivalent to the provided Parser resource. def merge_parser_resource(resource) accumulate_benchmark("Individual resource merger", :attributes) { merge_attributes(resource) } accumulate_benchmark("Individual resource merger", :parameters) { merge_parameters(resource) } accumulate_benchmark("Individual resource merger", :tags) { merge_tags(resource) } save() end def merge_attributes(resource) args = self.class.rails_resource_initial_args(resource) args.each do |param, value| unless resource[param] == value self[param] = value end end # Handle file specially if (resource.file and (!resource.file or self.file != resource.file)) self.file = resource.file end end def merge_parameters(resource) catalog_params = {} resource.each do |param, value| catalog_params[param.to_s] = value end db_params = {} deletions = [] @params_hash.each do |value| # First remove any parameters our catalog resource doesn't have at all. deletions << value['id'] and next unless catalog_params.include?(value['name']) # Now store them for later testing. db_params[value['name']] ||= [] db_params[value['name']] << value end # Now get rid of any parameters whose value list is different. # This might be extra work in cases where an array has added or lost # a single value, but in the most common case (a single value has changed) # this makes sense. db_params.each do |name, value_hashes| values = value_hashes.collect { |v| v['value'] } unless value_compare(catalog_params[name], values) value_hashes.each { |v| deletions << v['id'] } end end # Perform our deletions. Puppet::Rails::ParamValue.delete(deletions) unless deletions.empty? # Lastly, add any new parameters. catalog_params.each do |name, value| next if db_params.include?(name) values = value.is_a?(Array) ? value : [value] values.each do |v| param_values.build(:value => serialize_value(v), :line => resource.line, :param_name => Puppet::Rails::ParamName.accumulate_by_name(name)) end end end # Make sure the tag list is correct. def merge_tags(resource) in_db = [] deletions = [] resource_tags = resource.tags @tags_hash.each do |tag| deletions << tag['id'] and next unless resource_tags.include?(tag['name']) in_db << tag['name'] end Puppet::Rails::ResourceTag.delete(deletions) unless deletions.empty? (resource_tags - in_db).each do |tag| add_resource_tag(tag) end end def value_compare(v,db_value) v = [v] unless v.is_a?(Array) v == db_value end def name ref() end def parameter(param) if pn = param_names.find_by_name(param) if pv = param_values.find(:first, :conditions => [ 'param_name_id = ?', pn]) return pv.value else return nil end end end def parameters result = get_params_hash result.each do |param, value| if value.is_a?(Array) result[param] = value.collect { |v| v['value'] } else result[param] = value.value end end result end - def ref + def ref(dummy_argument=:work_arround_for_ruby_GC_bug) "%s[%s]" % [self[:restype].split("::").collect { |s| s.capitalize }.join("::"), self.title.to_s] end # Returns a hash of parameter names and values, no ActiveRecord instances. def to_hash Puppet::Rails::ParamValue.find_all_params_from_resource(self).inject({}) do |hash, value| hash[value['name']] ||= [] hash[value['name']] << value.value hash end end # Convert our object to a resource. Do not retain whether the object # is exported, though, since that would cause it to get stripped # from the configuration. def to_resource(scope) hash = self.attributes hash["type"] = hash["restype"] hash.delete("restype") # FIXME At some point, we're going to want to retain this information # for logging and auditing. hash.delete("host_id") hash.delete("updated_at") hash.delete("source_file_id") hash.delete("created_at") hash.delete("id") hash.each do |p, v| hash.delete(p) if v.nil? end hash[:scope] = scope hash[:source] = scope.source hash[:params] = [] names = [] self.param_names.each do |pname| # We can get the same name multiple times because of how the # db layout works. next if names.include?(pname.name) names << pname.name hash[:params] << pname.to_resourceparam(self, scope.source) end obj = Puppet::Parser::Resource.new(hash) # Store the ID, so we can check if we're re-collecting the same resource. obj.rails_id = self.id return obj end end diff --git a/lib/puppet/sslcertificates/ca.rb b/lib/puppet/sslcertificates/ca.rb index b5a246969..f6bcbc1f7 100644 --- a/lib/puppet/sslcertificates/ca.rb +++ b/lib/puppet/sslcertificates/ca.rb @@ -1,398 +1,398 @@ require 'sync' class Puppet::SSLCertificates::CA include Puppet::Util::Warnings Certificate = Puppet::SSLCertificates::Certificate attr_accessor :keyfile, :file, :config, :dir, :cert, :crl def certfile @config[:cacert] end # Remove all traces of a given host. This is kind of hackish, but, eh. def clean(host) host = host.downcase [:csrdir, :signeddir, :publickeydir, :privatekeydir, :certdir].each do |name| dir = Puppet[name] file = File.join(dir, host + ".pem") if FileTest.exists?(file) begin if Puppet[:name] == "puppetca" puts "Removing %s" % file else Puppet.info "Removing %s" % file end File.unlink(file) rescue => detail raise Puppet::Error, "Could not delete %s: %s" % [file, detail] end end end end def host2csrfile(hostname) File.join(Puppet[:csrdir], [hostname.downcase, "pem"].join(".")) end # this stores signed certs in a directory unrelated to # normal client certs def host2certfile(hostname) File.join(Puppet[:signeddir], [hostname.downcase, "pem"].join(".")) end # Turn our hostname into a Name object def thing2name(thing) thing.subject.to_a.find { |ary| ary[0] == "CN" }[1] end def initialize(hash = {}) Puppet.settings.use(:main, :ca, :ssl) self.setconfig(hash) if Puppet[:capass] if FileTest.exists?(Puppet[:capass]) #puts "Reading %s" % Puppet[:capass] #system "ls -al %s" % Puppet[:capass] #File.read Puppet[:capass] @config[:password] = self.getpass else # Don't create a password if the cert already exists unless FileTest.exists?(@config[:cacert]) @config[:password] = self.genpass end end end self.getcert init_crl unless FileTest.exists?(@config[:serial]) Puppet.settings.write(:serial) do |f| f << "%04X" % 1 end end end # Generate a new password for the CA. def genpass pass = "" 20.times { pass += (rand(74) + 48).chr } begin Puppet.settings.write(:capass) { |f| f.print pass } rescue Errno::EACCES => detail raise Puppet::Error, detail.to_s end return pass end # Get the CA password. def getpass if @config[:capass] and File.readable?(@config[:capass]) return File.read(@config[:capass]) else raise Puppet::Error, "Could not decrypt CA key with password: %s" % detail end end # Get the CA cert. def getcert if FileTest.exists?(@config[:cacert]) @cert = OpenSSL::X509::Certificate.new( File.read(@config[:cacert]) ) else self.mkrootcert end end # Retrieve a client's CSR. def getclientcsr(host) csrfile = host2csrfile(host) unless File.exists?(csrfile) return nil end return OpenSSL::X509::Request.new(File.read(csrfile)) end # Retrieve a client's certificate. def getclientcert(host) certfile = host2certfile(host) unless File.exists?(certfile) return [nil, nil] end return [OpenSSL::X509::Certificate.new(File.read(certfile)), @cert] end # List certificates waiting to be signed. This returns a list of hostnames, not actual # files -- the names can be converted to full paths with host2csrfile. - def list + def list(dummy_argument=:work_arround_for_ruby_GC_bug) return Dir.entries(Puppet[:csrdir]).find_all { |file| file =~ /\.pem$/ }.collect { |file| file.sub(/\.pem$/, '') } end # List signed certificates. This returns a list of hostnames, not actual # files -- the names can be converted to full paths with host2csrfile. - def list_signed + def list_signed(dummy_argument=:work_arround_for_ruby_GC_bug) return Dir.entries(Puppet[:signeddir]).find_all { |file| file =~ /\.pem$/ }.collect { |file| file.sub(/\.pem$/, '') } end # Create the root certificate. def mkrootcert # Make the root cert's name the FQDN of the host running the CA. name = Facter["hostname"].value if domain = Facter["domain"].value name += "." + domain end cert = Certificate.new( :name => name, :cert => @config[:cacert], :encrypt => @config[:capass], :key => @config[:cakey], :selfsign => true, :ttl => ttl, :type => :ca ) # This creates the cakey file Puppet::Util::SUIDManager.asuser(Puppet[:user], Puppet[:group]) do @cert = cert.mkselfsigned end Puppet.settings.write(:cacert) do |f| f.puts @cert.to_pem end Puppet.settings.write(:capub) do |f| f.puts @cert.public_key end return cert end def removeclientcsr(host) csrfile = host2csrfile(host) unless File.exists?(csrfile) raise Puppet::Error, "No certificate request for %s" % host end File.unlink(csrfile) end # Revoke the certificate with serial number SERIAL issued by this # CA. The REASON must be one of the OpenSSL::OCSP::REVOKED_* reasons def revoke(serial, reason = OpenSSL::OCSP::REVOKED_STATUS_KEYCOMPROMISE) time = Time.now revoked = OpenSSL::X509::Revoked.new revoked.serial = serial revoked.time = time enum = OpenSSL::ASN1::Enumerated(reason) ext = OpenSSL::X509::Extension.new("CRLReason", enum) revoked.add_extension(ext) @crl.add_revoked(revoked) store_crl end # Take the Puppet config and store it locally. def setconfig(hash) @config = {} Puppet.settings.params("ca").each { |param| param = param.intern if param.is_a? String if hash.include?(param) @config[param] = hash[param] Puppet[param] = hash[param] hash.delete(param) else @config[param] = Puppet[param] end } if hash.include?(:password) @config[:password] = hash[:password] hash.delete(:password) end if hash.length > 0 raise ArgumentError, "Unknown parameters %s" % hash.keys.join(",") end [:cadir, :csrdir, :signeddir].each { |dir| unless @config[dir] raise Puppet::DevError, "%s is undefined" % dir end } end # Sign a given certificate request. def sign(csr) unless csr.is_a?(OpenSSL::X509::Request) raise Puppet::Error, "CA#sign only accepts OpenSSL::X509::Request objects, not %s" % csr.class end unless csr.verify(csr.public_key) raise Puppet::Error, "CSR sign verification failed" end serial = nil Puppet.settings.readwritelock(:serial) { |f| serial = File.read(@config[:serial]).chomp.hex # increment the serial f << "%04X" % (serial + 1) } newcert = Puppet::SSLCertificates.mkcert( :type => :server, :name => csr.subject, :ttl => ttl, :issuer => @cert, :serial => serial, :publickey => csr.public_key ) sign_with_key(newcert) self.storeclientcert(newcert) return [newcert, @cert] end # Store the client's CSR for later signing. This is called from # server/ca.rb, and the CSRs are deleted once the certificate is actually # signed. def storeclientcsr(csr) host = thing2name(csr) csrfile = host2csrfile(host) if File.exists?(csrfile) raise Puppet::Error, "Certificate request for %s already exists" % host end Puppet.settings.writesub(:csrdir, csrfile) do |f| f.print csr.to_pem end end # Store the certificate that we generate. def storeclientcert(cert) host = thing2name(cert) certfile = host2certfile(host) if File.exists?(certfile) Puppet.notice "Overwriting signed certificate %s for %s" % [certfile, host] end Puppet::SSLCertificates::Inventory::add(cert) Puppet.settings.writesub(:signeddir, certfile) do |f| f.print cert.to_pem end end # TTL for new certificates in seconds. If config param :ca_ttl is set, # use that, otherwise use :ca_days for backwards compatibility def ttl days = @config[:ca_days] if days && days.size > 0 warnonce "Parameter ca_ttl is not set. Using depecated ca_days instead." return @config[:ca_days] * 24 * 60 * 60 else ttl = @config[:ca_ttl] if ttl.is_a?(String) unless ttl =~ /^(\d+)(y|d|h|s)$/ raise ArgumentError, "Invalid ca_ttl #{ttl}" end case $2 when 'y' unit = 365 * 24 * 60 * 60 when 'd' unit = 24 * 60 * 60 when 'h' unit = 60 * 60 when 's' unit = 1 else raise ArgumentError, "Invalid unit for ca_ttl #{ttl}" end return $1.to_i * unit else return ttl end end end private def init_crl if FileTest.exists?(@config[:cacrl]) @crl = OpenSSL::X509::CRL.new( File.read(@config[:cacrl]) ) else # Create new CRL @crl = OpenSSL::X509::CRL.new @crl.issuer = @cert.subject @crl.version = 1 store_crl @crl end end def store_crl # Increment the crlNumber e = @crl.extensions.find { |e| e.oid == 'crlNumber' } ext = @crl.extensions.reject { |e| e.oid == 'crlNumber' } crlNum = OpenSSL::ASN1::Integer(e ? e.value.to_i + 1 : 0) ext << OpenSSL::X509::Extension.new("crlNumber", crlNum) @crl.extensions = ext # Set last/next update now = Time.now @crl.last_update = now # Keep CRL valid for 5 years @crl.next_update = now + 5 * 365*24*60*60 sign_with_key(@crl) Puppet.settings.write(:cacrl) do |f| f.puts @crl.to_pem end end def sign_with_key(signable, digest = OpenSSL::Digest::SHA1.new) cakey = nil if @config[:password] begin cakey = OpenSSL::PKey::RSA.new( File.read(@config[:cakey]), @config[:password] ) rescue raise Puppet::Error, "Decrypt of CA private key with password stored in @config[:capass] not possible" end else cakey = OpenSSL::PKey::RSA.new( File.read(@config[:cakey]) ) end unless @cert.check_private_key(cakey) raise Puppet::Error, "CA Certificate is invalid" end signable.sign(cakey, digest) end end diff --git a/lib/puppet/type/k5login.rb b/lib/puppet/type/k5login.rb index 20c03241f..5526fda21 100644 --- a/lib/puppet/type/k5login.rb +++ b/lib/puppet/type/k5login.rb @@ -1,87 +1,87 @@ # $Id: k5login.rb 2468 2007-08-07 23:30:20Z digant $ # # Plug-in type for handling k5login files Puppet::Type.newtype(:k5login) do @doc = "Manage the .k5login file for a user. Specify the full path to the .k5login file as the name and an array of principals as the property principals." ensurable # Principals that should exist in the file newproperty(:principals, :array_matching => :all) do desc "The principals present in the .k5login file." end # The path/name of the k5login file newparam(:path) do isnamevar desc "The path to the file to manage. Must be fully qualified." validate do |value| unless value =~ /^#{File::SEPARATOR}/ raise Puppet::Error, "File paths must be fully qualified" end end end # To manage the mode of the file newproperty(:mode) do desc "Manage the k5login file's mode" defaultto { "644" } end provide(:k5login) do desc "The k5login provider is the only provider for the k5login type." # Does this file exist? def exists? File.exists?(@resource[:name]) end # create the file def create write(@resource.should(:principals)) should_mode = @resource.should(:mode) unless self.mode == should_mode self.mode = should_mode end end # remove the file def destroy File.unlink(@resource[:name]) end # Return the principals - def principals + def principals(dummy_argument=:work_arround_for_ruby_GC_bug) if File.exists?(@resource[:name]) File.readlines(@resource[:name]).collect { |line| line.chomp } else :absent end end # Write the principals out to the k5login file def principals=(value) write(value) end # Return the mode as an octal string, not as an integer def mode "%o" % (File.stat(@resource[:name]).mode & 007777) end # Set the file mode, converting from a string to an integer. def mode=(value) File.chmod(Integer("0" + value), @resource[:name]) end private def write(value) File.open(@resource[:name], "w") { |f| f.puts value.join("\n") } end end end diff --git a/lib/puppet/util/subclass_loader.rb b/lib/puppet/util/subclass_loader.rb index 8776e855c..b71ec7293 100644 --- a/lib/puppet/util/subclass_loader.rb +++ b/lib/puppet/util/subclass_loader.rb @@ -1,89 +1,89 @@ # A module for loading subclasses into an array and retrieving # them by name. Also sets up a method for each class so # that you can just do Klass.subclass, rather than Klass.subclass(:subclass). # # This module is currently used by network handlers and clients. module Puppet::Util::SubclassLoader attr_accessor :loader, :classloader # Iterate over each of the subclasses. def each @subclasses ||= [] @subclasses.each { |c| yield c } end # The hook method that sets up subclass loading. We need the name # of the method to create and the path in which to look for them. def handle_subclasses(name, path) unless self.is_a?(Class) raise ArgumentError, "Must be a class to use SubclassLoader" end @subclasses = [] @loader = Puppet::Util::Autoload.new(self, path, :wrap => false ) @subclassname = name @classloader = self # Now create a method for retrieving these subclasses by name. Note # that we're defining a class method here, not an instance. meta_def(name) do |subname| subname = subname.to_s.downcase unless c = @subclasses.find { |c| c.name.to_s.downcase == subname } loader.load(subname) c = @subclasses.find { |c| c.name.to_s.downcase == subname } # Now make the method that returns this subclass. This way we # normally avoid the method_missing method. if c and ! respond_to?(subname) define_method(subname) { c } end end return c end end # Add a new class to our list. Note that this has to handle subclasses of # subclasses, thus the reason we're keeping track of the @@classloader. def inherited(sub) @subclasses ||= [] sub.classloader = self.classloader if self.classloader == self @subclasses << sub else @classloader.inherited(sub) end end # See if we can load a class. def method_missing(method, *args) unless self == self.classloader super end return nil unless defined? @subclassname if c = self.send(@subclassname, method) return c else return nil end end # Retrieve or calculate a name. - def name + def name(dummy_argument=:work_arround_for_ruby_GC_bug) unless defined? @name @name = self.to_s.sub(/.+::/, '').intern end return @name end # Provide a list of all subclasses. def subclasses @loader.loadall @subclasses.collect { |klass| klass.name } end end