diff options
| author | luke <luke@980ebf18-57e1-0310-9a29-db15c13687c0> | 2006-09-05 01:51:23 +0000 |
|---|---|---|
| committer | luke <luke@980ebf18-57e1-0310-9a29-db15c13687c0> | 2006-09-05 01:51:23 +0000 |
| commit | 617fe58626aa8a13af10071ca87f66d6363cf058 (patch) | |
| tree | db9974c675c3b73cae56a253386f716e83bc8b6f /lib/puppet | |
| parent | 8f39318ce46148c3bd483d790c965f277a4eb1c9 (diff) | |
| download | puppet-617fe58626aa8a13af10071ca87f66d6363cf058.tar.gz puppet-617fe58626aa8a13af10071ca87f66d6363cf058.tar.xz puppet-617fe58626aa8a13af10071ca87f66d6363cf058.zip | |
Removing all of the changes I made towards refactoring in the last couple of days. They have all been moved into the sync-retrieve-refactor branch. This branch will soon become 0.19.0, and will not include that refactoring.
git-svn-id: https://reductivelabs.com/svn/puppet/trunk@1555 980ebf18-57e1-0310-9a29-db15c13687c0
Diffstat (limited to 'lib/puppet')
37 files changed, 1372 insertions, 1778 deletions
diff --git a/lib/puppet/provider.rb b/lib/puppet/provider.rb index 04169e6e1..f51ea4450 100644 --- a/lib/puppet/provider.rb +++ b/lib/puppet/provider.rb @@ -139,7 +139,9 @@ class Puppet::Provider return true end + # Remove the reference to the model, so GC can clean up. def clear + @model = nil end # Retrieve a named command. @@ -155,11 +157,6 @@ class Puppet::Provider @model.name end - # Remove the reference to the model, so GC can clean up. - def remove - @model = nil - end - def to_s "%s(provider=%s)" % [@model.to_s, self.class.name] end diff --git a/lib/puppet/provider/cron/parsed.rb b/lib/puppet/provider/cron/parsed.rb deleted file mode 100755 index 1626162bd..000000000 --- a/lib/puppet/provider/cron/parsed.rb +++ /dev/null @@ -1,161 +0,0 @@ -require 'puppet/provider/parsedfile' - -Puppet::Type.type(:cron).provide :parsed, :parent => Puppet::Provider::ParsedFile do - @fields = [:minute, :hour, :monthday, :month, :weekday, :command] - - # XXX This should be switched to use providers or something similar on - # the backend. - def self.defaulttype - case Facter["operatingsystem"].value - when "Solaris": - return Puppet::FileType.filetype(:suntab) - else - return Puppet::FileType.filetype(:crontab) - end - end - - self.filetype = self.defaulttype() - - # We have to maintain separate file objects for every user, unfortunately. - def self.filetype(user) - @tabs ||= {} - @tabs[user] ||= @filetype.new(user) - - @tabs[user] - end - - # Parse a user's cron job into individual cron objects. - # - # Autogenerates names for any jobs that don't already have one; these - # names will get written back to the file. - # - # This method also stores existing comments, and it stores all cron - # jobs in order, mostly so that comments are retained in the order - # they were written and in proximity to the same jobs. - def self.parse(user, text) - count = 0 - - envs = [] - instances = [] - text.chomp.split("\n").each { |line| - hash = {} - case line - when /^# Puppet Name: (.+)$/ - hash[:name] = $1 - next - when /^#/: - # add other comments to the list as they are - instances << line - next - when /^\s*(\w+)\s*=\s*(.+)\s*$/: - # Match env settings. - if hash[:name] - envs << line - else - instances << line - end - next - when /^@(\w+)\s+(.+)/ # FreeBSD special cron crap - fields().each do |field| - next if field == :command - hash[field] = :absent - end - hash[:special] = $1 - hash[:command] = $2 - else - if match = /^(\S+) (\S+) (\S+) (\S+) (\S+) (.+)$/.match(line) - fields().zip(match.captures).each { |param, value| - if value == "*" - hash[param] = [:absent] - else - if param == :command - hash[param] = [value] - else - # We always want the 'is' value to be an - # array - hash[param] = value.split(",") - end - end - } - else - # Don't fail on unmatched lines, just warn on them - # and skip them. - Puppet.warning "Could not match '%s'" % line - next - end - end - - unless envs.empty? - hash[:environment] = envs - end - - hash[:user] = user - - instances << hash - - envs.clear - count += 1 - } - - return instances - end - - def self.retrieve(user) - text = fileobj(user).read - if text.nil? or text == "" - return [] - else - self.parse(user, text) - end - end - - # Another override. This will pretty much only ever have one user's instances, - def self.store(instances) - instances.find_all { |i| i.is_a? Hash }.collect { |i| i[:user] }.each do |user| - fileobj(user).write(self.to_file(instances)) - end - end - - # Convert the current object a cron-style string. Adds the cron name - # as a comment above the cron job, in the form '# Puppet Name: <name>'. - def self.to_record(hash) - hash = {} - - str = "" - - str = "# Puppet Name: %s\n" % hash[:name] - - if @states.include?(:environment) and - @states[:environment].should != :absent - envs = @states[:environment].should - unless envs.is_a? Array - envs = [envs] - end - - envs.each do |line| str += (line + "\n") end - end - - line = nil - if special = hash[:special] - line = str + "@%s %s" % - [special, hash[:command]] - else - line = str + self.class.fields.collect { |f| - if hash[f] and hash[f] != :absent - hash[f] - else - "*" - end - }.join(" ") - end - - return line - end - - # Override the mechanism for retrieving instances, because we're user-specific. - def allinstances - self.class.retrieve(@model[:user]) - end -end - -# $Id$ diff --git a/lib/puppet/provider/host/parsed.rb b/lib/puppet/provider/host/parsed.rb deleted file mode 100644 index c606562a2..000000000 --- a/lib/puppet/provider/host/parsed.rb +++ /dev/null @@ -1,78 +0,0 @@ -require 'puppet/provider/parsedfile' - -Puppet::Type.type(:host).provide :parsed, :parent => Puppet::Provider::ParsedFile do - @path = "/etc/hosts" - @filetype = Puppet::FileType.filetype(:flat) - - confine :exists => @path - - # Parse a host file - # - # This method also stores existing comments, and it stores all host - # jobs in order, mostly so that comments are retained in the order - # they were written and in proximity to the same jobs. - def self.parse(text) - count = 0 - instances = [] - text.chomp.split("\n").each { |line| - hash = {} - case line - when /^#/, /^\s*$/: - # add comments and blank lines to the list as they are - instances << line - else - if line.sub!(/^(\S+)\s+(\S+)\s*/, '') - hash[:ip] = $1 - hash[:name] = $2 - - unless line == "" - line.sub!(/\s*/, '') - line.sub!(/^([^#]+)\s*/) do |value| - aliases = $1 - unless aliases =~ /^\s*$/ - hash[:alias] = aliases.split(/\s+/) - end - - "" - end - end - else - raise Puppet::Error, "Could not match '%s'" % line - end - - if hash[:alias] == "" - hash.delete(:alias) - end - - instances << hash - - count += 1 - end - } - - return instances - end - - # Convert the current object into a host-style string. - def self.to_record(hash) - [:ip, :name].each do |n| - unless hash.has_key? n - raise ArgumentError, "%s is a required attribute for hosts" % n - end - end - - str = "%s\t%s" % [hash[:ip], hash[:name]] - - if hash.include? :alias - if hash[:alias].is_a? Array - str += "\t%s" % hash[:alias].join("\t") - else - raise ArgumentError, "Aliases must be specified as an array" - end - end - - str - end -end - -# $Id$ diff --git a/lib/puppet/provider/mount/parsed.rb b/lib/puppet/provider/mount/parsed.rb deleted file mode 100755 index b768d6c5e..000000000 --- a/lib/puppet/provider/mount/parsed.rb +++ /dev/null @@ -1,171 +0,0 @@ -require 'puppet/provider/parsedfile' - -Puppet::Type.type(:mount).provide :parsed, :parent => Puppet::Provider::ParsedFile do - @filetype = Puppet::FileType.filetype(:flat) - - commands :mount => "mount", :umount => "umount", :df => "df" - - def self.init - @platform = Facter["operatingsystem"].value - case @platform - when "Solaris": - @path = "/etc/vfstab" - @fields = [:device, :blockdevice, :path, :fstype, :pass, :atboot, - :options] - @defaults = [ nil ] * @fields.size - when "Darwin": - @filetype = Puppet::FileType.filetype(:netinfo) - @filetype.format = "fstab" - @path = "mounts" - @fields = [:device, :path, :fstype, :options, :dump, :pass] - @defaults = [ nil ] * @fields.size - - # How to map the dumped table to what we want - @fieldnames = { - "name" => :device, - "dir" => :path, - "dump_freq" => :dump, - "passno" => :pass, - "vfstype" => :fstype, - "opts" => :options - } - else - @path = "/etc/fstab" - @fields = [:device, :path, :fstype, :options, :dump, :pass] - @defaults = [ nil ] * 4 + [ "0", "2" ] - end - - # Allow Darwin to override the default filetype - unless defined? @filetype - @filetype = Puppet::FileType.filetype(:flat) - end - end - - init - - confine :exists => @path - - def self.clear - init - super - end - - # Parse a mount tab. - # - # This method also stores existing comments, and it stores all - # mounts in order, mostly so that comments are retained in the - # order they were written and in proximity to the same fses. - def self.parse(text) - # provide a single exception for darwin & netinfo - if @filetype == Puppet::FileType.filetype(:netinfo) - return parseninfo(text) - end - count = 0 - - instances = [] - text.chomp.split("\n").each { |line| - hash = {} - case line - when /^#/, /^\s*$/: - # add comments and blank lines to the list as they are - instances << line - comment(line) - else - values = line.split(/\s+/) - if @fields.length < values.length - raise Puppet::Error, "Could not parse line %s" % line - end - - values = @defaults.zip(values).collect { |d, v| v || d } - unless @fields.length == values.length - raise Puppet::Error, "Could not parse line %s" % line - end - - @fields.zip(values).each do |field, value| - hash[field] = value - end - - instances << hash - count += 1 - end - } - - return instances - end - - # Parse a netinfo table. - def self.parseninfo(text) - array = @fileobj.to_array(text) - - instances = [] - array.each do |record| - hash = {} - @fieldnames.each do |name, field| - if value = record[name] - if field == :options - hash[field] = value.join(",") - else - hash[field] = value[0] - end - else - raise ArgumentError, "Field %s was not provided" % [name] - end - end - - instances << hash - end - - return instances - end - - # Convert the current object into an fstab-style string. - def self.to_record(hash) - self.fields.collect do |field| - if value = hash[field] - value - else - raise Puppet::Error, - "Could not retrieve value for %s" % field - end - end.join("\t") - end - - # This only works when the mount point is synced to the fstab. - def mount - output = %x{#{command(:mount)} #{@model[:path]} 2>&1} - - unless $? == 0 - raise Puppet::Error, "Could not mount %s: %s" % [@model[:path], output] - end - end - - # This only works when the mount point is synced to the fstab. - def unmount - output = %x{#{command(:umount)} #{@model[:path]}} - - unless $? == 0 - raise Puppet::Error, "Could not unmount %s" % @model[:path] - end - end - - # Is the mount currently mounted? - def mounted? - platform = Facter["operatingsystem"].value - df = command(:df) - case Facter["operatingsystem"].value - # Solaris's df prints in a very weird format - when "Solaris": df = "#{command(:df)} -k" - end - %x{#{df}}.split("\n").find do |line| - fs = line.split(/\s+/)[-1] - if platform == "Darwin" - fs == "/private/var/automount" + @model[:path] or - fs == @model[:path] - else - fs == @model[:path] - end - end - end -end - -# $Id$ diff --git a/lib/puppet/provider/package/portage.rb b/lib/puppet/provider/package/portage.rb deleted file mode 100644 index 873f63f68..000000000 --- a/lib/puppet/provider/package/portage.rb +++ /dev/null @@ -1,124 +0,0 @@ -Puppet::Type.type(:package).provide :portage do - desc "Provides packaging support for Gentoo's portage system." - - commands :emerge => "emerge", :eix => "eix" - - defaultfor :operatingsystem => :gentoo - - def self.list - search_format = /(\S+) (\S+) \[(.*)\] \[(\S*)\] ([\S]*) (.*)/ - result_fields = [:category, :name, :version, :version_available, - :vendor, :description] - command = "#{command(:eix)} --format \"{<installedversions>}<category> <name> [<installedversions>] [<best>] <homepage> <description>{}\"" - - begin - search_output = execute( command ) - - packages = [] - search_output.each do |search_result| - match = search_format.match( search_result ) - - if( match ) - package = {:ensure => :present} - result_fields.zip( match.captures ) { |field, value| package[field] = value } - if self.is_a? Puppet::Type and type = @model[:type] - package[:type] = type - elsif self.is_a? Module and self.respond_to? :name - package[:type] = self.name - else - raise Puppet::DevError, "Cannot determine package type" - end - if package[:version] - package[:version] = package[:version].split.last - end - - packages.push( Puppet.type(:package).installedpkg(package) ) - end - end - - return packages - rescue Puppet::ExecutionFailure => detail - raise Puppet::PackageError.new(detail) - end - end - - def install - if @model[:version] - # We must install a specific version - package_name = "=#{@model[:name]}-#{@model[:version]}" - else - package_name = @model[:name] - end - command = "EMERGE_DEFAULT_OPTS=\"\" #{command(:emerge)} #{package_name}" - begin - output = execute( command ) - rescue Puppet::ExecutionFailure => detail - raise Puppet::PackageError.new(detail) - end - end - - def uninstall - if @model[:version] - # We must uninstall a specific version - package_name = "=#{@model[:name]}-#{@model[:version]}" - else - package_name = @model[:name] - end - command ="EMERGE_DEFAULT_OPTS=\"\" #{command(:emerge)} --unmerge #{package_name}" - begin - output = execute( command ) - rescue Puppet::ExecutionFailure => detail - raise Puppet::PackageError.new(detail) - end - end - - def update - self.install - end - - def query - search_format = /(\S+) (\S+) \[(.*)\] \[(\S*)\] ([\S]*) (.*)/ - result_fields = [:category, :name, :version, :version_available, :vendor, :description] - - search_field = @model[:name].include?( '/' ) ? "--category-name" : "--name" - command = "#{command(:eix)} --format \"<category> <name> [<installedversions>] [<best>] <homepage> <description>\" --exact #{search_field} #{@model[:name]}" - - begin - search_output = execute( command ) - - packages = [] - search_output.each do |search_result| - match = search_format.match( search_result ) - - if( match ) - package = {} - result_fields.zip( match.captures ) { |field, value| package[field] = value unless value.empty? } - package[:ensure] = package[:version] ? :present : :absent - package[:version] = package[:version].split.last if package[:version] - packages << package - end - end - - case packages.size - when 0 - return nil - when 1 - return packages[0] - else - self.fail "More than one package with the specified name [#{@model[:name]}], please use category/name to disambiguate" - end - rescue Puppet::ExecutionFailure => detail - raise Puppet::PackageError.new(detail) - end - end - - def latest - return self.query[:version_available] - end - - def versionable? - true - end -end - -# $Id$ diff --git a/lib/puppet/provider/parsedfile.rb b/lib/puppet/provider/parsedfile.rb deleted file mode 100755 index f690c03a4..000000000 --- a/lib/puppet/provider/parsedfile.rb +++ /dev/null @@ -1,174 +0,0 @@ -require 'puppet' - -class Puppet::Provider::ParsedFile < Puppet::Provider - class << self - attr_accessor :filetype, :fields - attr_reader :path - attr_writer :fileobj - end - - # Override 'newstate' so that all states default to having the - # correct parent type - def self.newstate(name, options = {}, &block) - options[:parent] ||= Puppet::State::ParsedParam - super(name, options, &block) - end - - # Add another type var. - def self.initvars - @instances = [] - super - end - - # Add a non-object comment or whatever to our list of instances - def self.comment(line) - @instances << line - end - - # Override the default Puppet::Type method, because instances - # also need to be deleted from the @instances hash - def self.delete(child) - if @instances.include?(child) - @instances.delete(child) - end - super - end - - # Initialize the object if necessary. - def self.fileobj - @fileobj ||= @filetype.new(@path) - - @fileobj - end - - # Return the header placed at the top of each generated file, warning - # users that modifying this file manually is probably a bad idea. - def self.header -%{# HEADER: This file was autogenerated at #{Time.now} -# HEADER: by puppet. While it can still be managed manually, it -# HEADER: is definitely not recommended.\n} - end - - # Parse a file - # - # Subclasses must override this method. - def self.parse(text) - raise Puppet::DevError, "Parse was not overridden in %s" % - self.name - end - - # If they change the path, we need to get rid of our cache object - def self.path=(path) - @fileobj = nil - @path = path - end - - # Retrieve the text for the file. Returns nil in the unlikely - # event that it doesn't exist. - def self.retrieve - text = fileobj.read - if text.nil? or text == "" - # there is no file - return [] - else - self.parse(text) - end - end - - # Write out the file. - def self.store(instances) - if instances.empty? - Puppet.notice "No %s instances for %s" % [self.name, @path] - else - fileobj.write(self.to_file(instances)) - end - end - - # Collect all Host instances convert them into literal text. - def self.to_file(instances) - str = self.header() - unless instances.empty? - # Reject empty hashes and those with :ensure == :absent - str += instances.reject { |obj| - obj.is_a? Hash and (obj.empty? or obj[:ensure] == :absent) - }.collect { |obj| - # If it's a hash, convert it, otherwise just write it out - if obj.is_a?(Hash) - to_record(obj) - else - obj.to_s - end - }.join("\n") + "\n" - - return str - else - Puppet.notice "No %s instances" % self.name - return "" - end - end - - # A Simple wrapper method that subclasses can override, so there's more control - # over how instances are retrieved. - def allinstances - self.class.retrieve - end - - def clear - super - @instances = nil - end - - # Return a hash that maps to our info, if possible. - def hash - @instances = allinstances() - - if @instances and h = @instances.find do |o| - o.is_a? Hash and o[:name] == @model[:name] - end - @me = h - else - @me = {} - if @instances.empty? - @instances = [@me] - else - @instances << @me - end - end - - return @me - end - - def initialize(model) - super - - @instances = nil - end - - def store(hash = nil) - hash ||= self.model.to_hash - - unless @instances - self.hash - end - - if hash.empty? - @me.clear - else - hash.each do |name, value| - if @me[name] != hash[name] - @me[name] = hash[name] - end - end - - @me.each do |name, value| - unless hash.has_key? name - @me.delete(name) - end - end - end - - self.class.store(@instances) - end -end - -# $Id$ diff --git a/lib/puppet/provider/port/parsed.rb b/lib/puppet/provider/port/parsed.rb deleted file mode 100755 index d65a0777f..000000000 --- a/lib/puppet/provider/port/parsed.rb +++ /dev/null @@ -1,139 +0,0 @@ -require 'puppet/provider/parsedfile' - -Puppet::Type.type(:port).provide :parsed, :parent => Puppet::Provider::ParsedFile do - - @filetype = Puppet::FileType.filetype(:flat) - @path = "/etc/services" - - # Parse a services file - # - # This method also stores existing comments, and it stores all port - # info in order, mostly so that comments are retained in the order - # they were written and in proximity to the same ports. - def self.parse(text) - count = 0 - instances = [] - namehash = {} # For merging - text.chomp.split("\n").each { |line| - hash = {} - case line - when /^#/, /^\s*$/: - # add comments and blank lines to the list as they are - instances << line - else - if line.sub!(/^(\S+)\s+(\d+)\/(\w+)\s*/, '') - hash[:name] = $1 - hash[:number] = $2 - hash[:protocols] = [$3] - - unless line == "" - line.sub!(/^([^#]+)\s*/) do |value| - aliases = $1 - - # Remove any trailing whitespace - aliases.strip! - unless aliases =~ /^\s*$/ - hash[:alias] = aliases.split(/\s+/) - end - - "" - end - - line.sub!(/^\s*#\s*(.+)$/) do |value| - desc = $1 - unless desc =~ /^\s*$/ - hash[:description] = desc.sub(/\s*$/, '') - end - - "" - end - end - else - if line =~ /^\s+\d+/ and - Facter["operatingsystem"].value == "Darwin" - #Puppet.notice "Skipping wonky OS X port entry %s" % - # line.inspect - next - end - raise Puppet::Error, "Could not match '%s'" % line - end - - # If there's already a service with this name, then check - # to see if the only difference is the proto; if so, just - # add our proto and walk away - if obj = namehash[hash[:name]] - if portmerge(obj, hash) - next - end - end - - instances << hash - namehash[hash[:name]] = hash - - count += 1 - end - } - - return instances - end - - def self.portmerge(base, hash) - unless base.has_key?(:protocols) - return false - end - - # This method is only called from parsing, so we only worry - # about 'is' values. - proto = base[:protocols] - - if proto.nil? or proto == :absent - # We are an unitialized object; we've got 'should' - # values but no 'is' values - return false - end - - # If this is happening, our object exists - base[:ensure] = :present - - if hash[:protocols] - # The protocol can be a symbol, so... - if proto.is_a?(Symbol) - proto = [] - end - # Check to see if it already includes our proto - unless proto.include?(hash[:protocols]) - # We are missing their proto - proto += hash[:protocols] - base[:protocols] = proto - end - end - - if hash.include?(:description) and ! base.include?(:description) - base[:description] = hash[:description] - end - - return true - end - - # Convert the current object into one or more services entry. - def self.to_record(hash) - hash[:protocols].collect { |proto| - str = "%s\t%s/%s" % [hash[:name], hash[:number], proto] - - if value = hash[:alias] and value != :absent - str += "\t%s" % value.join(" ") - else - str += "\t" - end - - if value = hash[:description] and value != :absent - str += "\t# %s" % value - else - str += "\t" - end - str - }.join("\n") - end -end - -# $Id$ diff --git a/lib/puppet/provider/sshkey/parsed.rb b/lib/puppet/provider/sshkey/parsed.rb deleted file mode 100755 index e1dbeaad6..000000000 --- a/lib/puppet/provider/sshkey/parsed.rb +++ /dev/null @@ -1,56 +0,0 @@ -require 'puppet/provider/parsedfile' - -Puppet::Type.type(:sshkey).provide :parsed, :parent => Puppet::Provider::ParsedFile do - @filetype = Puppet::FileType.filetype(:flat) - @path = "/etc/ssh/ssh_known_hosts" - @fields = [:name, :type, :key] - - # Parse an sshknownhosts file - # - # This method also stores existing comments, and it stores all host - # jobs in order, mostly so that comments are retained in the order - # they were written and in proximity to the same jobs. - def self.parse(text) - count = 0 - instances = [] - text.chomp.split("\n").each { |line| - hash = {} - case line - when /^#/, /^\s*$/: - # add comments and blank lines to the list as they are - instances << line - else - hash = {} - fields().zip(line.split(" ")).each { |param, value| - hash[param] = value - } - - if hash[:name] =~ /,/ - names = hash[:name].split(",") - hash[:name] = names.shift - hash[:alias] = names - end - - if hash[:alias] == "" - hash.delete(:alias) - end - - instances << hash - count += 1 - end - } - - return instances - end - - # Convert the current object into an entry for a known-hosts file. - def self.to_record(hash) - name = hash[:name] - if hash.include?(:alias) - name += "," + hash[:alias].join(",") - end - [name, hash[:type], hash[:key]].join(" ") - end -end - -# $Id$ diff --git a/lib/puppet/server/fileserver.rb b/lib/puppet/server/fileserver.rb index 93f3310d1..49af26c54 100755 --- a/lib/puppet/server/fileserver.rb +++ b/lib/puppet/server/fileserver.rb @@ -63,7 +63,11 @@ class Server desc = [] CHECKPARAMS.each { |check| if state = obj.state(check) - desc << state.retrieve + unless state.is + mount.debug "Manually retrieving info for %s" % check + state.retrieve + end + desc << state.is else if check == "checksum" and obj.state(:type).is == "file" mount.notice "File %s does not have data for %s" % diff --git a/lib/puppet/statechange.rb b/lib/puppet/statechange.rb index d11a985fe..4884026fe 100644 --- a/lib/puppet/statechange.rb +++ b/lib/puppet/statechange.rb @@ -11,55 +11,19 @@ module Puppet # The log file generated when this object was changed. attr_reader :report - # Switch the goals of the state, thus running the change in reverse. - def backward - @state.should = @is - @should = @is - @is = @state.retrieve - @state.is = @is - - unless defined? @transaction - raise Puppet::Error, - "StateChange '%s' tried to be executed outside of transaction" % - self - end - unless @state.insync? - @state.info "Backing %s" % self - return self.go - else - @state.debug "rollback is already in sync: %s vs. %s" % - [@state.is.inspect, @state.should.inspect] - return nil - end - end - - def forward - #@state.debug "moving change forward" + def initialize(state) + @state = state + @path = [state.path,"change"].flatten + @is = state.is - unless defined? @transaction - raise Puppet::Error, - "StateChange '%s' tried to be executed outside of transaction" % - self + if state.insync? + raise Puppet::Error.new( + "Tried to create a change for in-sync state %s" % state.name + ) end + @should = state.should - return self.go - end - - # Generate an appropriate event from the is/should values. - def genevent - tail = if @state.name == :ensure - if @should == :present - "created" - elsif @should == :absent - "deleted" - else - @should.to_s - end - else - "changed" - end - - [state.parent.class.name.to_s, tail].join("_").intern + @changed = false end # Perform the actual change. This method can go either forward or @@ -81,39 +45,28 @@ module Puppet # [@state.is.inspect, @state.should.inspect] # The transaction catches any exceptions here. - if @state.method(:sync).arity == 0 - @state.warnstamp :syncwoutvalue, "sync() should accept a value" - events = @state.sync - else - events = @state.sync(@should) + events = @state.sync + if events.nil? + return nil end - unless events.is_a? Array + if events.is_a?(Array) + if events.empty? + return nil + end + else events = [events] end + + return events.collect { |event| + # default to a simple event type + if ! event.is_a?(Symbol) + @state.warning("State '%s' returned invalid event '%s'; resetting to default" % + [@state.class,event]) - events = events.collect do |e| - if e.nil? - genevent() - else - if ! e.is_a?(Symbol) - @state.warning( - "State '%s' returned invalid event '%s'; resetting" % - [@state.class,e] - ) - genevent() - else - e - end + event = @state.parent.class.name.id2name + "_changed" end - end.reject { |e| e == :nochange } - - if events.empty? - return nil - end - - return events.collect { |event| # i should maybe include object type, but the event type # should basically point to that, right? #:state => @state, @@ -129,25 +82,36 @@ module Puppet } end - def initialize(state, is = nil) - @state = state - @path = [state.path,"change"].flatten + def forward + #@state.debug "moving change forward" - if is - @is = is - else - state.warning "did not pass 'is' to statechange" - @is = state.is + unless defined? @transaction + raise Puppet::Error, + "StateChange '%s' tried to be executed outside of transaction" % + self end - if state.insync? - raise Puppet::Error.new( - "Tried to create a change for in-sync state %s" % state.name - ) - end - @should = state.should + return self.go + end - @changed = false + # Switch the goals of the state, thus running the change in reverse. + def backward + @state.should = @is + @state.retrieve + + unless defined? @transaction + raise Puppet::Error, + "StateChange '%s' tried to be executed outside of transaction" % + self + end + unless @state.insync? + @state.info "Backing %s" % self + return self.go + else + @state.debug "rollback is already in sync: %s vs. %s" % + [@state.is.inspect, @state.should.inspect] + return nil + end end def noop diff --git a/lib/puppet/transaction.rb b/lib/puppet/transaction.rb index d947afbf2..899aaa456 100644 --- a/lib/puppet/transaction.rb +++ b/lib/puppet/transaction.rb @@ -90,7 +90,7 @@ class Transaction # Mark that our change happened, so it can be reversed # if we ever get to that point - unless events.nil? + unless events.nil? or (events.is_a?(Array) and events.empty?) change.changed = true @objectmetrics[:applied] += 1 end @@ -98,15 +98,6 @@ class Transaction events }.flatten.reject { |e| e.nil? } - # If our child responds to a 'flush' method, call it. - if childevents.length > 0 and child.respond_to? :flush - begin - child.flush - rescue => detail - raise Puppet::Error, "Could not flush: %s" % detail, detail.backtrace - end - end - unless changes.empty? # Record when we last synced child.cache(:synced, Time.now) @@ -205,9 +196,6 @@ class Transaction [self.object_id, @count] allevents - ensure - # Unset 'is' everywhere. This is relatively hackish, but, eh. - @objects.each do |o| o.clear end end # Determine whether a given object has failed. @@ -329,9 +317,6 @@ class Transaction # And return the events for collection events }.flatten.reject { |e| e.nil? } - ensure - # Unset 'is' everywhere. This is relatively hackish, but, eh. - @objects.each do |o| o.clear end end # Trigger any subscriptions to a child. This does an upwardly recursive diff --git a/lib/puppet/type.rb b/lib/puppet/type.rb index 53712d365..d87cbcbd3 100644 --- a/lib/puppet/type.rb +++ b/lib/puppet/type.rb @@ -29,10 +29,13 @@ class Type < Puppet::Element attr_accessor :file, :line attr_reader :tags, :parent - attr_accessor :implicit - attr_writer :title + attr_writer :implicit, :title def implicit? - self.implicit + if defined? @implicit and @implicit + return true + else + return false + end end include Enumerable @@ -53,7 +56,10 @@ class Type < Puppet::Element # iterate across all of the subclasses of Type def self.eachtype @types.each do |name, type| - yield type + # Only consider types that have names + #if ! type.parameters.empty? or ! type.validstates.empty? + yield type + #end end end @@ -301,7 +307,6 @@ class Type < Puppet::Element def self.clear if defined? @objects @objects.each do |name, obj| - obj.clear obj.remove(true) end @objects.clear @@ -938,10 +943,8 @@ class Type < Puppet::Element end end - # Remove a state from the object; useful in testing or in cleanup - # when an error has been encountered. Removes all objects with a given name, - # not just states. Some classes have both an alias state and an alias - # metaparam. + # remove a state from the object; useful in testing or in cleanup + # when an error has been encountered def delete(attr) case attr when Puppet::Type @@ -949,20 +952,13 @@ class Type < Puppet::Element @children.delete(attr) end else - done = false if @states.has_key?(attr) @states.delete(attr) - done = true - end - if @parameters.has_key?(attr) + elsif @parameters.has_key?(attr) @parameters.delete(attr) - done = true - end - if @metaparams.has_key?(attr) + elsif @metaparams.has_key?(attr) @metaparams.delete(attr) - done = true - end - unless done + else raise Puppet::DevError.new("Undefined attribute '#{attr}' in #{self}") end end @@ -1269,7 +1265,7 @@ class Type < Puppet::Element # Remove the reference to the provider. if self.provider - @provider.remove + @provider.clear @provider = nil end end @@ -1711,19 +1707,6 @@ class Type < Puppet::Element #@cache[name] = value end - # Remove any vestages of the transaction we're in. This is currently the only - # way to know if you're within a transaction -- if '@is' is set, yes, else, - # no. - def clear - @states.each do |name, state| - state.is = nil - end - - if provider and provider.respond_to? :clear - provider.clear - end - end - # Look up the schedule and set it appropriately. This is done after # the instantiation phase, so that the schedule can be anywhere in the # file. @@ -1964,25 +1947,9 @@ class Type < Puppet::Element def retrieve # it's important to use the method here, as it follows the order # in which they're defined in the object - is = {} states().each { |state| - is[state.name] = state.retrieve + state.retrieve } - - is - end - - # Convert our object to a hash. This just includes states. - def to_hash - rethash = {} - - [@parameters, @metaparams, @states].each do |hash| - hash.each do |name, obj| - rethash[name] = obj.value - end - end - - rethash end # convert to a string @@ -2033,22 +2000,30 @@ class Type < Puppet::Element end # Retrieve the changes associated with all of the states. - def statechanges(is) + def statechanges # If we are changing the existence of the object, then none of # the other states matter. changes = [] - if @states.include?(:ensure) and ! @states[:ensure].insync? - changes = [Puppet::StateChange.new(@states[:ensure], is[:ensure])] + #self.info "ensuring %s from %s" % + # [@states[:ensure].should, @states[:ensure].is] + changes = [Puppet::StateChange.new(@states[:ensure])] # Else, if the 'ensure' state is correctly absent, then do # nothing - elsif @states.include?(:ensure) and is[:ensure] == :absent + elsif @states.include?(:ensure) and @states[:ensure].is == :absent + #self.info "Object is correctly absent" return [] else + #if @states.include?(:ensure) + # self.info "ensure: Is: %s, Should: %s" % + # [@states[:ensure].is, @states[:ensure].should] + #else + # self.info "no ensure state" + #end changes = states().find_all { |state| ! state.insync? }.collect { |state| - Puppet::StateChange.new(state, is[state.name]) + Puppet::StateChange.new(state) } end @@ -2084,35 +2059,11 @@ class Type < Puppet::Element # it's important that we call retrieve() on the type instance, # not directly on the state, because it allows the type to override # the method, like pfile does - is = self.retrieve - - unless is.is_a? Hash - warnstamp :nohashreturned, "'retrieve' did not return hash" - is = {} - end - - @@novalreturned ||= {} - states.each do |state| - unless is.include? state.name - warnstamp "no#{state.name}returned", - "did not get %s from retrieve" % state.name - is[state.name] = state.is - end - end - - # For now, set the 'is' values - is.each do |name, value| - # A special value to indicate we shouldn't do anything here; usually - # the result of an internal error. - next if value == :unmanaged - if @states.include? name - @states[name].is = value - end - end + self.retrieve # states() is a private method, returning an ordered list unless self.class.depthfirst? - changes += statechanges(is) + changes += statechanges() end changes << @children.collect { |child| @@ -2122,7 +2073,7 @@ class Type < Puppet::Element } if self.class.depthfirst? - changes += statechanges(is) + changes += statechanges() end changes.flatten! diff --git a/lib/puppet/type/cron.rb b/lib/puppet/type/cron.rb index a911dac04..03ac24d29 100755 --- a/lib/puppet/type/cron.rb +++ b/lib/puppet/type/cron.rb @@ -1,3 +1,7 @@ +require 'etc' +require 'facter' +require 'puppet/type/state' +require 'puppet/filetype' require 'puppet/type/parsedtype' module Puppet @@ -5,7 +9,7 @@ module Puppet # as parameters, with the 'command' as the single state. Also requires a # completely symbolic 'name' paremeter, which gets written to the file # and is used to manage the job. - newtype(:cron, Puppet::Type::ParsedType) do + newtype(:cron) do # A base class for all of the Cron parameters, since they all have # similar argument checking going on. We're stealing the base class @@ -333,6 +337,9 @@ module Puppet } " + @instances = {} + @tabs = {} + class << self attr_accessor :filetype diff --git a/lib/puppet/type/exec.rb b/lib/puppet/type/exec.rb index 8ccebd242..0dc3ec02a 100755 --- a/lib/puppet/type/exec.rb +++ b/lib/puppet/type/exec.rb @@ -111,29 +111,19 @@ module Puppet end end - def insync? - case self.is - when :failedchecks - return true - when :shouldrun - return false - else - raise ArgumentError, "Invalid 'is' value '%s'" % self.is.inspect - end - end - - # Figure out whether we should run. + # First verify that all of our checks pass. def retrieve - # First make sure all of our checks pass. + # Default to somethinng + if @parent.check - return :shouldrun + self.is = :notrun else - return :failedchecks + self.is = self.should end end # Actually execute the command. - def sync(value = nil) + def sync olddir = nil self.checkexe @@ -454,7 +444,6 @@ module Puppet val = [val] unless val.is_a? Array val.each do |value| unless @parameters[check].check(value) - info "Failed '%s' check" % check return false end end diff --git a/lib/puppet/type/group.rb b/lib/puppet/type/group.rb index c6e3324a7..b03f57907 100755 --- a/lib/puppet/type/group.rb +++ b/lib/puppet/type/group.rb @@ -26,12 +26,16 @@ module Puppet newstate(:ensure) do desc "The basic state that the object should be in." - newvalue(:present, :event => :group_created) do + newvalue(:present) do provider.create + + :group_created end - newvalue(:absent, :event => :group_deleted) do + newvalue(:absent) do provider.delete + + :group_removed end # If they're talking about the thing at all, they generally want to @@ -46,7 +50,7 @@ module Puppet def change_to_s begin - if self.is == :absent + if @is == :absent return "created" elsif self.should == :absent return "removed" @@ -65,11 +69,30 @@ module Puppet def retrieve if provider.exists? - return :present + @is = :present else - return :absent + @is = :absent end end + + # The default 'sync' method only selects among a list of registered + # values. + def sync + if self.insync? + self.info "already in sync" + return nil + #else + #self.info "%s vs %s" % [self.is.inspect, self.should.inspect] + end + unless self.class.values + self.devfail "No values defined for %s" % + self.class.name + end + + # Set ourselves to whatever our should value is. + self.set + end + end newstate(:gid) do @@ -100,17 +123,16 @@ module Puppet end def retrieve - return provider.gid + @is = provider.gid end - def sync(value) + def sync if self.should == :absent raise Puppet::DevError, "GID cannot be deleted" else - provider.gid = value + provider.gid = self.should + :group_modified end - - return nil end munge do |gid| @@ -162,19 +184,18 @@ module Puppet def retrieve if @provider.exists? - return super + super else # the group does not exist #unless @states.include?(:gid) # self[:gid] = :auto #end - current = {} @states.each { |name, state| - current[state.name] = :absent + state.is = :absent } - return current + return end end end diff --git a/lib/puppet/type/mount.rb b/lib/puppet/type/mount.rb deleted file mode 100755 index 641ff8592..000000000 --- a/lib/puppet/type/mount.rb +++ /dev/null @@ -1,117 +0,0 @@ -require 'puppet/type/parsedtype' - -module Puppet - newtype(:mount, Puppet::Type::ParsedType) do - newstate(:ensure) do - desc "Control what to do with this mount. If the value is - ``present``, the mount is entered into the mount table, - but not mounted, if it is ``absent``, the entry is removed - from the mount table and the filesystem is unmounted if - currently mounted, if it is ``mounted``, the filesystem - is entered into the mount table and mounted." - - newvalue(:present, :event => :mount_created) do - end - - newvalue(:absent, :event => :mount_deleted) do - if provider.mounted? - provider.unmount - end - end - - newvalue(:mounted, :event => :mount_mounted) do - # We have to flush any changes to disk. - @parent.flush - - provider.mount - end - - defaultto do - if @parent.managed? - :mounted - else - nil - end - end - - def retrieve - if provider.mounted? - return :mounted - else - val = super() - return val - end - end - - # The default method doesn't, um, do anything, since it's all - # done in the flush at the end. - #def sync(value) - # set(value) - #end - end - - newstate(:device) do - desc "The device providing the mount. This can be whatever - device is supporting by the mount, including network - devices or devices specified by UUID rather than device - path, depending on the operating system." - end - - # Solaris specifies two devices, not just one. - newstate(:blockdevice) do - desc "The the device to fsck. This is state is only valid - on Solaris, and in most cases will default to the correct - value." - - # Default to the device but with "dsk" replaced with "rdsk". - defaultto do - if Facter["operatingsystem"].value == "Solaris" - device = @parent.value(:device) - if device =~ %r{/dsk/} - device.sub(%r{/dsk/}, "/rdsk/") - else - nil - end - else - nil - end - end - end - - newstate(:fstype) do - desc "The mount type. Valid values depend on the - operating system." - end - - newstate(:options) do - desc "Mount options for the mounts, as they would - appear in the fstab." - end - - newstate(:pass) do - desc "The pass in which the mount is checked." - end - - newstate(:atboot) do - desc "Whether to mount the mount at boot. Not all platforms - support this." - end - - newstate(:dump) do - desc "Whether to dump the mount. Not all platforms - support this." - end - - newparam(:path) do - desc "The mount path for the mount." - - isnamevar - end - - @doc = "Manages mounted mounts, including putting mount - information into the mount table. The actual behavior depends - on the value of the 'ensure' parameter." - end -end - -# $Id$ diff --git a/lib/puppet/type/package.rb b/lib/puppet/type/package.rb index 3c4d20cc9..f7ae0f0d4 100644 --- a/lib/puppet/type/package.rb +++ b/lib/puppet/type/package.rb @@ -136,7 +136,8 @@ module Puppet @is = @parent.retrieve end - def sync(value) + def sync + value = self.should unless value.is_a?(Symbol) value = value.intern end diff --git a/lib/puppet/type/parsedtype.rb b/lib/puppet/type/parsedtype.rb index 9d37adf17..b0e5f618d 100755 --- a/lib/puppet/type/parsedtype.rb +++ b/lib/puppet/type/parsedtype.rb @@ -4,205 +4,352 @@ require 'puppet/filetype' require 'puppet/type/state' module Puppet - # The base parameter for all of these types. Its only job is to copy - # the 'should' value to the 'is' value and to do support the right logging - # and such. - class State::ParsedParam < Puppet::State - # This is the info retrieved from disk. - attr_accessor :found - - def self.isoptional - @isoptional = true - end + class State + # The base parameter for all of these types. Its only job is to copy + # the 'should' value to the 'is' value and to do support the right logging + # and such. + class ParsedParam < Puppet::State + def self.isoptional + @isoptional = true + end - def self.isoptional? - if defined? @isoptional - return @isoptional - else - return false + def self.isoptional? + if defined? @isoptional + return @isoptional + else + return false + end end - end - # By default, support ':absent' as a value for optional - # parameters. Any parameters that define their own validation - # need to do this manuallly. - validate do |value| - if self.class.isoptional? and ( - value == "absent" or value == :absent - ) - return :absent - else - return value + # By default, support ':absent' as a value for optional + # parameters. Any parameters that define their own validation + # need to do this manuallly. + validate do |value| + if self.class.isoptional? and ( + value == "absent" or value == :absent + ) + return :absent + else + return value + end end - end - def clear - super - @found = nil - end + # Fix things so that the fields have to match exactly, instead + # of only kinda + def insync? + self.is == self.should + end - # Fix things so that the fields have to match exactly, instead - # of only kinda - def insync? - self.is == self.should - end + # Normally this would retrieve the current value, but our state is not + # actually capable of doing so. + def retrieve + # If we've synced, then just copy the values over and return. + # This allows this state to behave like any other state. + if defined? @synced and @synced + # by default, we only copy over the first value. + @is = @synced + @synced = false + return + end - # Normally this would retrieve the current value, but our state is not - # actually capable of doing so. So, we retrieve the whole object and - # just collect our current state. Note that this method is not called - # during a transaction, since transactions call the parent object method. - def retrieve - @parent.retrieve[self.name] - end + unless defined? @is and ! @is.nil? + @is = :absent + end + end - # If the ensure state is out of sync, it will always be called - # first, so I don't need to worry about that. - def sync(value) - # We only pass through to the parent method if there are values - # defined. Otherwise, there's no work to do, since all of the - # work is done in the flush. - if self.class.values and ! self.class.values.empty? - super(value) - end - # The value gets flushed later. - return nil - end - end + # If the ensure state is out of sync, it will always be called + # first, so I don't need to worry about that. + def sync(nostore = false) + ebase = @parent.class.name.to_s - # The collection of classes that are just simple records aggregated - # into a file. See 'host.rb' for an example. - class Type::ParsedType < Puppet::Type - @name = :parsedtype - - # Convert the hash to an object. - def self.hash2obj(hash) - obj = nil - - namevar = self.namevar - unless hash.include?(namevar) and hash[namevar] - raise Puppet::DevError, "Hash was not passed with namevar" - end - - # if the obj already exists with that name... - if obj = self[hash[namevar]] - # We're assuming here that objects with the same name - # are the same object, which *should* be the case, assuming - # we've set up our naming stuff correctly everywhere. - - # Mark found objects as present - obj.is = [:ensure, :present] - obj.state(:ensure).found = :present - hash.each { |param, value| - if state = obj.state(param) - state.is = value - elsif val = obj[param] - obj[param] = val - else - # There is a value on disk, but it should go away - obj.is = [param, value] - obj[param] = :absent + tail = nil + if self.class.name == :ensure + # We're either creating or destroying the object + if @is == :absent + #@is = self.should + tail = "created" + + # If we're creating it, then sync all of the other states + # but tell them not to store (we'll store just once, + # at the end). + unless nostore + @parent.eachstate { |state| + next if state == self or state.name == :ensure + state.sync(true) + } + end + elsif self.should == :absent + @parent.remove(true) + tail = "deleted" end - } - else - # create a new obj, since no existing one seems to - # match - obj = self.create(namevar => hash[namevar]) - - # We can't just pass the hash in at object creation time, - # because it sets the should value, not the is value. - hash.delete(namevar) - hash.each { |param, value| - obj.is = [param, value] - } + else + # We don't do the work here, it gets done in 'store' + tail = "changed" + end + @synced = self.should + + # This should really only be done once per run, rather than + # every time. I guess we need some kind of 'flush' mechanism. + if nostore + self.retrieve + else + @parent.store + end + + return (ebase + "_" + tail).intern end end + end - # Override 'newstate' so that all states default to having the - # correct parent type - def self.newstate(name, options = {}, &block) - options[:parent] ||= Puppet::State::ParsedParam - super(name, options, &block) - end + class Type + # The collection of classes that are just simple records aggregated + # into a file. See 'host.rb' for an example. + class ParsedType < Puppet::Type + @name = :parsedtype + class << self + attr_accessor :filetype, :hostfile, :fields + attr_reader :path + attr_writer :fileobj + end - def self.list - retrieve.collect { |i| i.is_a? Hash }.collect { |i| hash2obj(i) } - end + # Override 'newstate' so that all states default to having the + # correct parent type + def self.newstate(name, options = {}, &block) + options[:parent] ||= Puppet::State::ParsedParam + super(name, options, &block) + end - def self.listbyname - retrieve.collect { |i| i.is_a? Hash }.collect { |i| i[:name] } - end + # Add another type var. + def self.initvars + @instances = [] + super + end - # Make sure they've got an explicit :ensure class. - def self.postinit - unless validstate? :ensure - newstate(:ensure) do - newvalue(:present) do - # The value will get flushed appropriately - return nil - end + # In addition to removing the instances in @objects, we have to remove + # per-user host tab information. + def self.clear + @instances = [] + @fileobj = nil + super + end - newvalue(:absent) do - # The value will get flushed appropriately - return nil - end + # Add a non-object comment or whatever to our list of instances + def self.comment(line) + @instances << line + end + + # Override the default Puppet::Type method, because instances + # also need to be deleted from the @instances hash + def self.delete(child) + if @instances.include?(child) + @instances.delete(child) + end + super + end + + # Initialize the object if necessary. + def self.fileobj + @fileobj ||= @filetype.new(@path) + + @fileobj + end + + # Return the header placed at the top of each generated file, warning + # users that modifying this file manually is probably a bad idea. + def self.header +%{# HEADER: This file was autogenerated at #{Time.now} +# HEADER: by puppet. While it can still be managed manually, it +# HEADER: is definitely not recommended.\n} + end + + # Convert the hash to an object. + def self.hash2obj(hash) + obj = nil - defaultto do - if @parent.managed? - :present + namevar = self.namevar + unless hash.include?(namevar) and hash[namevar] + raise Puppet::DevError, "Hash was not passed with namevar" + end + + # if the obj already exists with that name... + if obj = self[hash[namevar]] + # We're assuming here that objects with the same name + # are the same object, which *should* be the case, assuming + # we've set up our naming stuff correctly everywhere. + + # Mark found objects as present + obj.is = [:ensure, :present] + hash.each { |param, value| + if state = obj.state(param) + state.is = value + elsif val = obj[param] + obj[param] = val else - nil + # There is a value on disk, but it should go away + obj.is = [param, value] + obj[param] = :absent end - end + } + else + # create a new obj, since no existing one seems to + # match + obj = self.create(namevar => hash[namevar]) + + # We can't just pass the hash in at object creation time, + # because it sets the should value, not the is value. + hash.delete(namevar) + hash.each { |param, value| + obj.is = [param, value] + } end + + # And then add it to our list of instances. This maintains the order + # in the file. + @instances << obj end - end - def exists? - h = self.retrieve + def self.list + retrieve - if h[:ensure] == :absent - return false - else - return true + self.collect do |obj| + obj + end end - end - # Flush our content to disk. - def flush - provider.store(self.to_hash) - end + # Return the last time the file was loaded. Could + # be used for reducing writes, but currently is not. + def self.loaded?(user) + fileobj().loaded + end - # Retrieve our current state from our provider - def retrieve - h = nil - if h = provider.hash and ! h.empty? - h[:ensure] ||= :present - - # If they passed back info we don't have, then mark it to - # be deleted. - h.each do |name, value| - next unless self.class.validstate?(name) - unless @states.has_key? name - self.newstate(name, :should => :absent) - end + # Parse a file + # + # Subclasses must override this method. + def self.parse(text) + raise Puppet::DevError, "Parse was not overridden in %s" % + self.name + end + + # If they change the path, we need to get rid of our cache object + def self.path=(path) + @fileobj = nil + @path = path + end + + # Retrieve the text for the file. Returns nil in the unlikely + # event that it doesn't exist. + def self.retrieve + text = fileobj.read + if text.nil? or text == "" + # there is no file + return nil + else + # First we mark all of our objects absent; any objects + # subsequently found will be marked present + self.each { |obj| + obj.is = [:ensure, :absent] + } + + # We clear this, so that non-objects don't get duplicated + @instances.clear + self.parse(text) end + end - @states.each do |name, state| - unless h.has_key? name - h[name] = :absent - end + # Write out the file. + def self.store + # Make sure all of our instances are in the to-be-written array + self.each do |inst| + @instances << inst unless @instances.include? inst + end + + if @instances.empty? + Puppet.notice "No %s instances for %s" % [self.name, @path] + else + fileobj.write(self.to_file()) end - return h - else - h = {} - @states.each do |name, state| - h[name] = :absent + end + + # Collect all Host instances convert them into literal text. + def self.to_file + str = self.header() + unless @instances.empty? + str += @instances.reject { |obj| + # Don't write out objects that should be absent + if obj.is_a?(self) + if obj.should(:ensure) == :absent + true + end + end + }.collect { |obj| + if obj.is_a?(self) + obj.to_record + else + obj.to_s + end + }.join("\n") + "\n" + + return str + else + Puppet.notice "No %s instances" % self.name + return "" end end - return h + # The 'store' method knows how to handle absence vs. presence + def create + self.store + end + + # The 'store' method knows how to handle absence vs. presence + def destroy + self.store + end + + # hash2obj marks the 'ensure' state as present + def exists? + @states.include?(:ensure) and @states[:ensure].is == :present + end + + # Override the default Puppet::Type method because we need to call + # the +@filetype+ retrieve method. + def retrieve + self.class.retrieve() + + self.eachstate { |st| + st.retrieve + } + end + + # Write the entire file out. + def store + self.class.store() + end + + def value(name) + unless name.is_a? Symbol + name = name.intern + end + if @states.include? name + val = @states[name].value + if val == :absent + return nil + else + return val + end + elsif @parameters.include? name + return @parameters[name].value + else + return nil + end + end end end end +require 'puppet/type/parsedtype/host' +require 'puppet/type/parsedtype/port' +require 'puppet/type/parsedtype/mount' +require 'puppet/type/parsedtype/sshkey' + # $Id$ diff --git a/lib/puppet/type/host.rb b/lib/puppet/type/parsedtype/host.rb index d68fe25a2..00cfe7857 100755 --- a/lib/puppet/type/host.rb +++ b/lib/puppet/type/parsedtype/host.rb @@ -1,7 +1,11 @@ +require 'etc' +require 'facter' require 'puppet/type/parsedtype' +require 'puppet/type/state' module Puppet newtype(:host, Puppet::Type::ParsedType) do + newstate(:ip) do desc "The host's IP address." end @@ -13,19 +17,6 @@ module Puppet make those aliases available in your Puppet scripts and also on disk." - # Make sure our "is" value is always an array. - def is - current = super - unless current.is_a? Array - current = [current] - end - current - end - - def is_to_s - self.is.join(" ") - end - # We have to override the feeding mechanism; it might be nil or # white-space separated def is=(value) @@ -84,6 +75,69 @@ module Puppet @doc = "Installs and manages host entries. For most systems, these entries will just be in /etc/hosts, but some systems (notably OS X) will have different solutions." + + @instances = [] + + @path = "/etc/hosts" + @fields = [:ip, :name, :alias] + + @filetype = Puppet::FileType.filetype(:flat) + + # Parse a host file + # + # This method also stores existing comments, and it stores all host + # jobs in order, mostly so that comments are retained in the order + # they were written and in proximity to the same jobs. + def self.parse(text) + count = 0 + hash = {} + text.chomp.split("\n").each { |line| + case line + when /^#/, /^\s*$/: + # add comments and blank lines to the list as they are + @instances << line + else + if line.sub!(/^(\S+)\s+(\S+)\s*/, '') + hash[:ip] = $1 + hash[:name] = $2 + + unless line == "" + line.sub!(/\s*/, '') + line.sub!(/^([^#]+)\s*/) do |value| + aliases = $1 + unless aliases =~ /^\s*$/ + hash[:alias] = aliases + end + + "" + end + end + else + raise Puppet::Error, "Could not match '%s'" % line + end + + if hash[:alias] == "" + hash.delete(:alias) + end + + hash2obj(hash) + + hash.clear + count += 1 + end + } + end + + # Convert the current object into a host-style string. + def to_record + str = "%s\t%s" % [self.state(:ip).value, self[:name]] + + if value = self.value(:alias) + str += "\t%s" % value.join("\t") + end + + str + end end end diff --git a/lib/puppet/type/parsedtype/mount.rb b/lib/puppet/type/parsedtype/mount.rb new file mode 100755 index 000000000..3f186ddca --- /dev/null +++ b/lib/puppet/type/parsedtype/mount.rb @@ -0,0 +1,271 @@ +require 'etc' +require 'facter' +require 'puppet/type/parsedtype' +require 'puppet/type/state' + +module Puppet + newtype(:mount, Puppet::Type::ParsedType) do + ensurable do + desc "Control what to do with this mount. If the value is + ``present``, the mount is entered into the mount table, + but not mounted, if it is ``absent``, the entry is removed + from the mount table and the filesystem is unmounted if + currently mounted, if it is ``mounted``, the filesystem + is entered into the mount table and mounted." + + newvalue(:present) do + @parent.create() + end + + newvalue(:absent) do + @parent.destroy() + + if @parent.mounted? + @parent.unmount + end + + :mount_removed + end + + newvalue(:mounted) do + if @is == :absent + set_present + end + + @parent.mount + + :mount_mounted + end + + def retrieve + if @parent.mounted? + @is = :mounted + else + val = super() + @is = val + end + end + end + + newstate(:device) do + desc "The device providing the mount. This can be whatever + device is supporting by the mount, including network + devices or devices specified by UUID rather than device + path, depending on the operating system." + end + + # Solaris specifies two devices, not just one. + newstate(:blockdevice) do + desc "The the device to fsck. This is state is only valid + on Solaris, and in most cases will default to the correct + value." + + # Default to the device but with "dsk" replaced with "rdsk". + defaultto do + if Facter["operatingsystem"].value == "Solaris" + device = @parent.value(:device) + if device =~ %r{/dsk/} + device.sub(%r{/dsk/}, "/rdsk/") + else + nil + end + else + nil + end + end + end + + newstate(:fstype) do + desc "The mount type. Valid values depend on the + operating system." + end + + newstate(:options) do + desc "Mount options for the mounts, as they would + appear in the fstab." + end + + newstate(:pass) do + desc "The pass in which the mount is checked." + end + + newstate(:atboot) do + desc "Whether to mount the mount at boot. Not all platforms + support this." + end + + newstate(:dump) do + desc "Whether to dump the mount. Not all platforms + support this." + end + + newparam(:path) do + desc "The mount path for the mount." + + isnamevar + end + + @doc = "Manages mounted mounts, including putting mount + information into the mount table. The actual behavior depends + on the value of the 'ensure' parameter." + + def self.init + @platform = Facter["operatingsystem"].value + case @platform + when "Solaris": + @path = "/etc/vfstab" + @fields = [:device, :blockdevice, :path, :fstype, :pass, :atboot, + :options] + @defaults = [ nil ] * @fields.size + when "Darwin": + @filetype = Puppet::FileType.filetype(:netinfo) + @filetype.format = "fstab" + @path = "mounts" + @fields = [:device, :path, :fstype, :options, :dump, :pass] + @defaults = [ nil ] * @fields.size + + # How to map the dumped table to what we want + @fieldnames = { + "name" => :device, + "dir" => :path, + "dump_freq" => :dump, + "passno" => :pass, + "vfstype" => :fstype, + "opts" => :options + } + else + @path = "/etc/fstab" + @fields = [:device, :path, :fstype, :options, :dump, :pass] + @defaults = [ nil ] * 4 + [ "0", "2" ] + end + + # Allow Darwin to override the default filetype + unless defined? @filetype + @filetype = Puppet::FileType.filetype(:flat) + end + end + + init + + def self.clear + init + super + end + + # Parse a mount tab. + # + # This method also stores existing comments, and it stores all + # mounts in order, mostly so that comments are retained in the + # order they were written and in proximity to the same fses. + def self.parse(text) + # provide a single exception for darwin & netinfo + if @filetype == Puppet::FileType.filetype(:netinfo) + parseninfo(text) + return + end + count = 0 + hash = {} + text.chomp.split("\n").each { |line| + case line + when /^#/, /^\s*$/: + # add comments and blank lines to the list as they are + comment(line) + else + values = line.split(/\s+/) + if @fields.length < values.length + raise Puppet::Error, "Could not parse line %s" % line + end + + values = @defaults.zip(values).collect { |d, v| v || d } + unless @fields.length == values.length + raise Puppet::Error, "Could not parse line %s" % line + end + + @fields.zip(values).each do |field, value| + hash[field] = value + end + + hash2obj(hash) + + hash.clear + count += 1 + end + } + end + + # Parse a netinfo table. + def self.parseninfo(text) + array = @fileobj.to_array(text) + + hash = {} + array.each do |record| + @fieldnames.each do |name, field| + if value = record[name] + if field == :options + hash[field] = value.join(",") + else + hash[field] = value[0] + end + else + raise ArgumentError, "Field %s was not provided" % [name] + end + end + + + hash2obj(hash) + hash.clear + end + end + + # This only works when the mount point is synced to the fstab. + def mount + output = %x{mount #{self[:path]} 2>&1} + + unless $? == 0 + raise Puppet::Error, "Could not mount %s: %s" % [self[:path], output] + end + end + + # This only works when the mount point is synced to the fstab. + def unmount + output = %x{umount #{self[:path]}} + + unless $? == 0 + raise Puppet::Error, "Could not mount %s" % self[:path] + end + end + + # Is the mount currently mounted? + def mounted? + platform = Facter["operatingsystem"].value + df = "df" + case Facter["operatingsystem"].value + # Solaris's df prints in a very weird format + when "Solaris": df = "df -k" + end + %x{#{df}}.split("\n").find do |line| + fs = line.split(/\s+/)[-1] + if platform == "Darwin" + fs == "/private/var/automount" + self[:path] or + fs == self[:path] + else + fs == self[:path] + end + end + end + + # Convert the current object into an fstab-style string. + def to_record + self.class.fields.collect do |field| + if value = self.value(field) + value + else + raise Puppet::Error, + "Could not retrieve value for %s" % field + end + end.join("\t") + end + end +end + +# $Id$ diff --git a/lib/puppet/type/parsedtype/port.rb b/lib/puppet/type/parsedtype/port.rb new file mode 100755 index 000000000..f8a08913f --- /dev/null +++ b/lib/puppet/type/parsedtype/port.rb @@ -0,0 +1,261 @@ +require 'etc' +require 'facter' +require 'puppet/type/parsedtype' +require 'puppet/type/state' + +module Puppet + newtype(:port, Puppet::Type::ParsedType) do + newstate(:protocols) do + desc "The protocols the port uses. Valid values are *udp* and *tcp*. + Most services have both protocols, but not all. If you want + both protocols, you must specify that; Puppet replaces the + current values, it does not merge with them. If you specify + multiple protocols they must be as an array." + + def is=(value) + case value + when String + @is = value.split(/\s+/) + else + @is = value + end + end + + def is + @is + end + + # We actually want to return the whole array here, not just the first + # value. + def should + if defined? @should + if @should[0] == :absent + return :absent + else + return @should + end + else + return nil + end + end + + validate do |value| + valids = ["udp", "tcp", "ddp", :absent] + unless valids.include? value + raise Puppet::Error, + "Protocols can be either 'udp' or 'tcp', not %s" % value + end + end + end + + newstate(:number) do + desc "The port number." + end + + newstate(:description) do + desc "The port description." + isoptional + end + + newstate(:alias) do + desc "Any aliases the port might have. Multiple values must be + specified as an array. Note that this state has the same name as + one of the metaparams; using this state to set aliases will make + those aliases available in your Puppet scripts and also on disk." + + isoptional + + # We have to override the feeding mechanism; it might be nil or + # white-space separated + def is=(value) + # If it's just whitespace, ignore it + case value + when /^\s+$/ + @is = nil + when String + @is = value.split(/\s+/) + when Symbol + @is = value + else + raise Puppet::DevError, "Invalid value %s" % value.inspect + end + end + + # We actually want to return the whole array here, not just the first + # value. + def should + if defined? @should + if @should[0] == :absent + return :absent + else + return @should + end + else + return nil + end + end + + validate do |value| + if value.is_a? String and value =~ /\s/ + raise Puppet::Error, + "Aliases cannot have whitespace in them: %s" % + value.inspect + end + end + + munge do |value| + unless value == "absent" or value == :absent + # Add the :alias metaparam in addition to the state + @parent.newmetaparam( + @parent.class.metaparamclass(:alias), value + ) + end + value + end + end + + newparam(:name) do + desc "The port name." + + isnamevar + end + + @doc = "Installs and manages port entries. For most systems, these + entries will just be in /etc/services, but some systems (notably OS X) + will have different solutions." + + @path = "/etc/services" + @fields = [:ip, :name, :alias] + + @filetype = Puppet::FileType.filetype(:flat) + + # Parse a services file + # + # This method also stores existing comments, and it stores all port + # info in order, mostly so that comments are retained in the order + # they were written and in proximity to the same ports. + def self.parse(text) + count = 0 + hash = {} + text.chomp.split("\n").each { |line| + hash.clear + case line + when /^#/, /^\s*$/: + # add comments and blank lines to the list as they are + @instances << line + else + if line.sub!(/^(\S+)\s+(\d+)\/(\w+)\s*/, '') + hash[:name] = $1 + hash[:number] = $2 + hash[:protocols] = $3 + + unless line == "" + line.sub!(/^([^#]+)\s*/) do |value| + aliases = $1 + + # Remove any trailing whitespace + aliases.strip! + unless aliases =~ /^\s*$/ + hash[:alias] = aliases + end + + "" + end + + line.sub!(/^\s*#\s*(.+)$/) do |value| + desc = $1 + unless desc =~ /^\s*$/ + hash[:description] = desc.sub(/\s*$/, '') + end + + "" + end + end + else + if line =~ /^\s+\d+/ and + Facter["operatingsystem"].value == "Darwin" + #Puppet.notice "Skipping wonky OS X port entry %s" % + # line.inspect + next + end + raise Puppet::Error, "Could not match '%s'" % line + end + + # If there's already a service with this name, then check + # to see if the only difference is the proto; if so, just + # add our proto and walk away + if obj = self[hash[:name]] + if obj.portmerge(hash) + next + end + end + + hash2obj(hash) + + count += 1 + end + } + end + + def portmerge(hash) + unless @states.include?(:protocols) + return false + end + + # This method is only called from parsing, so we only worry + # about 'is' values. + proto = self.state(:protocols).is + + if proto.nil? or proto == :absent + # We are an unitialized object; we've got 'should' + # values but no 'is' values + return false + end + + # If this is happening, our object exists + self.is = [:ensure, :present] + + if hash[:protocols] + # The protocol can be a symbol, so... + if proto.is_a?(Symbol) + proto = [] + end + # Check to see if it already includes our proto + unless proto.include?(hash[:protocols]) + # We are missing their proto + proto << hash[:protocols] + @states[:protocols].is = proto + end + end + + if hash.include?(:description) and ! @states.include?(:description) + self.is = [:description, hash[:description]] + end + + return true + end + + # Convert the current object into one or more services entry. + def to_record + self.state(:protocols).value.collect { |proto| + str = "%s\t%s/%s" % [self[:name], self.value(:number), + proto] + + if value = self.value(:alias) and value != :absent + str += "\t%s" % value.join(" ") + else + str += "\t" + end + + if value = self.value(:description) and value != :absent + str += "\t# %s" % value + else + str += "\t" + end + str + }.join("\n") + end + end +end + +# $Id$ diff --git a/lib/puppet/type/parsedtype/sshkey.rb b/lib/puppet/type/parsedtype/sshkey.rb new file mode 100755 index 000000000..d12d4c697 --- /dev/null +++ b/lib/puppet/type/parsedtype/sshkey.rb @@ -0,0 +1,123 @@ +require 'etc' +require 'facter' +require 'puppet/type/parsedtype' +require 'puppet/type/state' + +module Puppet + newtype(:sshkey, Puppet::Type::ParsedType) do + newstate(:type) do + desc "The encryption type used. Probably ssh-dss or ssh-rsa." + end + + newstate(:key) do + desc "The key itself; generally a long string of hex digits." + end + + # FIXME This should automagically check for aliases to the hosts, just + # to see if we can automatically glean any aliases. + newstate(:alias) do + desc "Any alias the host might have. Multiple values must be + specified as an array. Note that this state has the same name + as one of the metaparams; using this state to set aliases will + make those aliases available in your Puppet scripts." + + # We actually want to return the whole array here, not just the first + # value. + def should + if defined? @should + return @should + else + return nil + end + end + + validate do |value| + if value =~ /\s/ + raise Puppet::Error, "Aliases cannot include whitespace" + end + if value =~ /,/ + raise Puppet::Error, "Aliases cannot include whitespace" + end + end + + # Make a puppet alias in addition. + munge do |value| + # Add the :alias metaparam in addition to the state + @parent.newmetaparam(@parent.class.metaparamclass(:alias), value) + value + end + end + + newparam(:name) do + desc "The host name." + + isnamevar + end + + @doc = "Installs and manages ssh host keys. At this point, this type + only knows how to install keys into /etc/ssh/ssh_known_hosts, and + it cannot manage user authorized keys yet." + + @instances = [] + + # FIXME This should be configurable. + @path = "/etc/ssh/ssh_known_hosts" + @fields = [:name, :type, :key] + + @filetype = Puppet::FileType.filetype(:flat) +# case Facter["operatingsystem"].value +# when "Solaris": +# @filetype = Puppet::FileType::SunOS +# else +# @filetype = Puppet::CronType::Default +# end + + # Parse a host file + # + # This method also stores existing comments, and it stores all host + # jobs in order, mostly so that comments are retained in the order + # they were written and in proximity to the same jobs. + def self.parse(text) + count = 0 + hash = {} + text.chomp.split("\n").each { |line| + case line + when /^#/, /^\s*$/: + # add comments and blank lines to the list as they are + @instances << line + else + hash = {} + fields().zip(line.split(" ")).each { |param, value| + hash[param] = value + } + + if hash[:name] =~ /,/ + names = hash[:name].split(",") + hash[:name] = names.shift + hash[:alias] = names + end + + if hash[:alias] == "" + hash.delete(:alias) + end + + hash2obj(hash) + + hash.clear + count += 1 + end + } + end + + # Convert the current object into a host-style string. + def to_record + name = self[:name] + if @states.include?(:alias) + name += "," + @states[:alias].value.join(",") + end + [name, @states[:type].value, @states[:key].value].join(" ") + end + end +end + +# $Id$ diff --git a/lib/puppet/type/pfile.rb b/lib/puppet/type/pfile.rb index ac892c069..ec666497d 100644 --- a/lib/puppet/type/pfile.rb +++ b/lib/puppet/type/pfile.rb @@ -691,7 +691,7 @@ module Puppet if child = self.newchild(file, true, options) # Mark any unmanaged files for removal if purge is set. - if self[:purge] == :true and ! child.managed? + if self[:purge] == :true and child.implicit? child[:ensure] = :absent end @@ -761,8 +761,6 @@ module Puppet # a wrapper method to make sure the file exists before doing anything def retrieve - is = {} - if @states.include?(:source) # This probably isn't the best place for it, but we need # to make sure that we have a corresponding checksum state. @@ -772,35 +770,35 @@ module Puppet # We have to retrieve the source info before the recursion happens, # although I'm not exactly clear on why. - is[:source] = @states[:source].retrieve + @states[:source].retrieve end if @parameters.include?(:recurse) self.recurse end - if stat = self.stat(true) - states().each { |state| - # We don't want to call 'describe()' twice, so only do a local - # retrieve on the source. - if state.name == :source - is[state.name] = state.retrieve(false) - else - is[state.name] = state.retrieve - end - } - else + unless stat = self.stat(true) self.debug "File does not exist" @states.each { |name,state| # We've already retreived the source, and we don't # want to overwrite whatever it did. This is a bit # of a hack, but oh well, source is definitely special. next if name == :source - is[name] = :absent + state.is = :absent } + + return end - return is + states().each { |state| + # We don't want to call 'describe()' twice, so only do a local + # retrieve on the source. + if state.name == :source + state.retrieve(false) + else + state.retrieve + end + } end # Set the checksum, from another state. There are multiple states that diff --git a/lib/puppet/type/pfile/checksum.rb b/lib/puppet/type/pfile/checksum.rb index 514c47690..cd82a4439 100755 --- a/lib/puppet/type/pfile/checksum.rb +++ b/lib/puppet/type/pfile/checksum.rb @@ -191,7 +191,7 @@ module Puppet if self.insync? self.debug "Checksum is already in sync" - return :nochange + return nil end #@parent.debug "%s(%s): after refresh, is '%s'" % # [self.class.name,@parent.name,@is] @@ -207,7 +207,7 @@ module Puppet @parent[:path] ) end - return :nochange + return nil end end @@ -215,7 +215,7 @@ module Puppet if self.updatesum return :file_changed else - return :nochange + return nil end end @@ -270,8 +270,6 @@ module Puppet end #@parent.debug "checksum state is %s" % self.is - - return @is end # Store the new sum to the state db. diff --git a/lib/puppet/type/pfile/content.rb b/lib/puppet/type/pfile/content.rb index 3eb875dc2..a8422431f 100755 --- a/lib/puppet/type/pfile/content.rb +++ b/lib/puppet/type/pfile/content.rb @@ -29,36 +29,36 @@ module Puppet def retrieve stat = nil unless stat = @parent.stat - return :absent + @is = :absent + return end if stat.ftype == "link" and @parent[:links] == :ignore - return self.should + self.is = self.should + return end # Don't even try to manage the content on directories if stat.ftype == "directory" and @parent[:links] == :ignore @parent.delete(:content) - return :notmanaged + return end begin - retval = File.read(@parent[:path]) + @is = File.read(@parent[:path]) rescue => detail - retval = :unknown + @is = nil raise Puppet::Error, "Could not read %s: %s" % [@parent.title, detail] end - - return retval end # Just write our content out to disk. - def sync(value) - @parent.write { |f| f.print value } + def sync + @parent.write { |f| f.print self.should } - if self.is == :absent + if @is == :absent return :file_created else return :file_changed diff --git a/lib/puppet/type/pfile/ensure.rb b/lib/puppet/type/pfile/ensure.rb index 0debc245a..ac045dfd6 100755 --- a/lib/puppet/type/pfile/ensure.rb +++ b/lib/puppet/type/pfile/ensure.rb @@ -35,25 +35,19 @@ module Puppet # Most 'ensure' states have a default, but with files we, um, don't. nodefault - newvalue(:absent, :event => :file_deleted) do + newvalue(:absent) do File.unlink(@parent[:path]) end aliasvalue(:false, :absent) - newvalue(:file, :event => :file_created) do + newvalue(:file) do # Make sure we're not managing the content some other way - if state = (@parent.state(:content) || @parent.state(:source)) - # Manually sync the state, and reset its is value to it knows it's - # in sync. - should = state.should - state.commit - - # The 'sync' method here syncs any states that might still be - # out of sync like 'mode', so we need to mark this in sync. - state.is = should + if state = @parent.state(:content) or state = @parent.state(:source) + state.sync else @parent.write(false) { |f| f.flush } + mode = @parent.should(:mode) end return :file_created end @@ -65,7 +59,7 @@ module Puppet set_file end - newvalue(:directory, :event => :directory_created) do + newvalue(:directory) do mode = @parent.should(:mode) parent = File.dirname(@parent[:path]) unless FileTest.exists? parent @@ -87,7 +81,7 @@ module Puppet end - newvalue(:link, :event => :link_created) do + newvalue(:link) do if state = @parent.state(:target) state.retrieve @@ -103,7 +97,7 @@ module Puppet end # Symlinks. - newvalue(/./, :event => :link_created) do + newvalue(/./) do # This code never gets executed. We need the regex to support # specifying it, but the work is done in the 'symlink' code block. end @@ -136,9 +130,8 @@ module Puppet # We have to treat :present specially, because it works with any # type of file. def insync? - is = self.is if self.should == :present - if is.nil? or is == :absent + if @is.nil? or @is == :absent return false else return true @@ -149,22 +142,19 @@ module Puppet end def retrieve - retval = nil if stat = @parent.stat(false) - retval = stat.ftype.intern + @is = stat.ftype.intern else if self.should == :false - retval = :false + @is = :false else - retval = :absent + @is = :absent end end - - return retval end - def sync(value) - event = super(value) + def sync + event = super # There are some cases where all of the work does not get done on # file creation, so we have to do some extra checking. @@ -174,7 +164,7 @@ module Puppet thing.retrieve unless thing.insync? - thing.commit + thing.sync end end diff --git a/lib/puppet/type/pfile/group.rb b/lib/puppet/type/pfile/group.rb index ce6d43422..9431b0e65 100755 --- a/lib/puppet/type/pfile/group.rb +++ b/lib/puppet/type/pfile/group.rb @@ -41,7 +41,8 @@ module Puppet stat = @parent.stat(false) unless stat - return :absent + self.is = :absent + return end # Set our method appropriately, depending on links. @@ -50,7 +51,7 @@ module Puppet else @method = :chown end - return stat.gid + self.is = stat.gid end # Determine if the group is valid, and if so, return the UID @@ -81,7 +82,7 @@ module Puppet # Normal users will only be able to manage certain groups. Right now, # we'll just let it fail, but we should probably set things up so # that users get warned if they try to change to an unacceptable group. - def sync(value) + def sync if @is == :absent @parent.stat(true) self.retrieve @@ -89,17 +90,17 @@ module Puppet if @is == :absent self.debug "File '%s' does not exist; cannot chgrp" % @parent[:path] - return :nochange + return nil end if self.insync? - return :nochange + return nil end end gid = nil - unless gid = Puppet::Util.gid(value) - raise Puppet::Error, "Could not find group %s" % value + unless gid = Puppet::Util.gid(self.should) + raise Puppet::Error, "Could not find group %s" % self.should end begin @@ -107,7 +108,7 @@ module Puppet File.send(@method,nil,gid,@parent[:path]) rescue => detail error = Puppet::Error.new( "failed to chgrp %s to %s: %s" % - [@parent[:path], value, detail.message]) + [@parent[:path], self.should, detail.message]) raise error end return :file_changed diff --git a/lib/puppet/type/pfile/mode.rb b/lib/puppet/type/pfile/mode.rb index 9dd993682..ee6fb37d8 100755 --- a/lib/puppet/type/pfile/mode.rb +++ b/lib/puppet/type/pfile/mode.rb @@ -89,35 +89,35 @@ module Puppet # off mode management entirely. if stat = @parent.stat(false) + self.is = stat.mode & 007777 unless defined? @fixed if defined? @should and @should @should = @should.collect { |s| self.dirmask(s) } end end - return stat.mode & 007777 else - return :absent + self.is = :absent end #self.debug "chmod state is %o" % self.is end - def sync(value) + def sync if @is == :absent @parent.stat(true) self.retrieve if @is == :absent self.debug "File does not exist; cannot set mode" - return :nochange + return nil end if self.insync? # we're already in sync - return :nochange + return nil end end - mode = value + mode = self.should if mode == :absent # This is really only valid for create states... diff --git a/lib/puppet/type/pfile/source.rb b/lib/puppet/type/pfile/source.rb index 057673457..744d66f34 100755 --- a/lib/puppet/type/pfile/source.rb +++ b/lib/puppet/type/pfile/source.rb @@ -116,7 +116,6 @@ module Puppet return nil end - retval = nil # If we're a normal file, then set things up to copy the file down. case @stats[:type] when "file": @@ -125,20 +124,20 @@ module Puppet if sum.is == :absent sum.retrieve(true) end - retval = sum.is + @is = sum.is else - retval = :absent + @is = :absent end else self.info "File does not have checksum" - retval = :absent + @is = :absent end # if replace => false then fake the checksum so that the file # is not overwritten. - unless retval == :absent + unless @is == :absent if @parent[:replace] == :false info "Not replacing existing file" - retval = @stats[:checksum] + @is = @stats[:checksum] end end @should = [@stats[:checksum]] @@ -155,11 +154,11 @@ module Puppet end # we'll let the :ensure state do our work @should.clear - retval = true + @is = true when "link": case @parent[:links] when :ignore - retval = :nocopy + @is = :nocopy @should = [:nocopy] self.info "Ignoring link %s" % @source return @@ -176,7 +175,7 @@ module Puppet self.err "Cannot use files of type %s as sources" % @stats[:type] @should = [:nocopy] - retval = :nocopy + @is = :nocopy end # Take each of the stats and set them as states on the local file @@ -197,8 +196,6 @@ module Puppet # @parent.info "Already specified %s" % stat end } - - return retval end # The special thing here is that we need to make sure that 'should' @@ -229,19 +226,22 @@ module Puppet end end - def sync(value) + def sync if @is == :notdescribed self.retrieve # try again if @is == :notdescribed @parent.log "Could not retreive information on %s" % @parent.title - return :nochange + return nil end if @is == @should - return :nochange + return nil end end + case @stats[:type] + when "link": + end unless @stats[:type] == "file" #if @stats[:type] == "directory" #[@parent.name, @is.inspect, @should.inspect] diff --git a/lib/puppet/type/pfile/target.rb b/lib/puppet/type/pfile/target.rb index 6bdc408f2..4a725d652 100644 --- a/lib/puppet/type/pfile/target.rb +++ b/lib/puppet/type/pfile/target.rb @@ -56,8 +56,8 @@ module Puppet def retrieve if @parent.state(:ensure).should == :directory + @is = self.should @linkmaker = true - return self.should else if stat = @parent.stat # If we're just checking the value @@ -73,18 +73,18 @@ module Puppet warning "Changing ensure to directory; recurse is %s but %s" % [@parent[:recurse].inspect, @parent.recurse?] @parent[:ensure] = :directory + @is = should @linkmaker = true - return should else if stat.ftype == "link" + @is = File.readlink(@parent[:path]) @linkmaker = false - return File.readlink(@parent[:path]) else - return :notlink + @is = :notlink end end else - return :absent + @is = :absent end end end diff --git a/lib/puppet/type/pfile/type.rb b/lib/puppet/type/pfile/type.rb index 4a7b9641d..e5db7d694 100755 --- a/lib/puppet/type/pfile/type.rb +++ b/lib/puppet/type/pfile/type.rb @@ -8,21 +8,18 @@ module Puppet #end def retrieve - retval = nil if stat = @parent.stat(false) - retval = stat.ftype + @is = stat.ftype else - retval = :absent + @is = :absent end # so this state is never marked out of sync - @should = [retval] - - return retval + @should = [@is] end - def sync(value) + def sync raise Puppet::Error, ":type is read-only" end end diff --git a/lib/puppet/type/pfile/uid.rb b/lib/puppet/type/pfile/uid.rb index e05ba4f4c..a492b31f4 100755 --- a/lib/puppet/type/pfile/uid.rb +++ b/lib/puppet/type/pfile/uid.rb @@ -81,7 +81,8 @@ module Puppet def retrieve unless stat = @parent.stat(false) - return :absent + @is = :absent + return end # Set our method appropriately, depending on links. @@ -91,17 +92,15 @@ module Puppet @method = :chown end - retval = stat.uid + self.is = stat.uid # On OS X, files that are owned by -2 get returned as really # large UIDs instead of negative ones. This isn't a Ruby bug, # it's an OS X bug, since it shows up in perl, too. - if retval > 120000 - self.warning "current state is silly: %s" % retval - retval = :absent + if @is > 120000 + self.warning "current state is silly: %s" % @is + @is = :absent end - - return retval end # If we're not root, we can check the values but we cannot change @@ -117,19 +116,19 @@ module Puppet end end - def sync(value) + def sync unless Process.uid == 0 unless defined? @@notifieduid self.notice "Cannot manage ownership unless running as root" #@parent.delete(self.name) @@notifieduid = true end - return :nochange + return nil end user = nil - unless user = self.validuser?(value) - tmp = value + unless user = self.validuser?(self.should) + tmp = self.should unless defined? @@usermissing @@usermissing = {} end @@ -140,7 +139,7 @@ module Puppet self.notice "user %s does not exist" % tmp @@usermissing[tmp] = 1 end - return :nochange + return nil end if @is == :absent @@ -148,10 +147,10 @@ module Puppet self.retrieve if @is == :absent self.debug "File does not exist; cannot set owner" - return :nochange + return nil end if self.insync? - return :nochange + return nil end #self.debug "%s: after refresh, is '%s'" % [self.class.name,@is] end diff --git a/lib/puppet/type/port.rb b/lib/puppet/type/port.rb deleted file mode 100755 index 3e73e625f..000000000 --- a/lib/puppet/type/port.rb +++ /dev/null @@ -1,110 +0,0 @@ -require 'puppet/type/parsedtype' - -module Puppet - newtype(:port, Puppet::Type::ParsedType) do - @doc = "Installs and manages port entries. For most systems, these - entries will just be in /etc/services, but some systems (notably OS X) - will have different solutions." - - newstate(:protocols) do - desc "The protocols the port uses. Valid values are *udp* and *tcp*. - Most services have both protocols, but not all. If you want - both protocols, you must specify that; Puppet replaces the - current values, it does not merge with them. If you specify - multiple protocols they must be as an array." - - def is=(value) - case value - when String - @is = value.split(/\s+/) - else - @is = value - end - end - - def is - @is - end - - # We actually want to return the whole array here, not just the first - # value. - def should - if defined? @should - if @should[0] == :absent - return :absent - else - return @should - end - else - return nil - end - end - - validate do |value| - valids = ["udp", "tcp", "ddp", :absent] - unless valids.include? value - raise Puppet::Error, - "Protocols can be either 'udp' or 'tcp', not %s" % value - end - end - end - - newstate(:number) do - desc "The port number." - end - - newstate(:description) do - desc "The port description." - isoptional - end - - newstate(:alias) do - desc "Any aliases the port might have. Multiple values must be - specified as an array. Note that this state has the same name as - one of the metaparams; using this state to set aliases will make - those aliases available in your Puppet scripts and also on disk." - - isoptional - - # We actually want to return the whole array here, not just the first - # value. - def should - if defined? @should - if @should[0] == :absent - return :absent - else - return @should - end - else - return nil - end - end - - validate do |value| - if value.is_a? String and value =~ /\s/ - raise Puppet::Error, - "Aliases cannot have whitespace in them: %s" % - value.inspect - end - end - - munge do |value| - unless value == "absent" or value == :absent - # Add the :alias metaparam in addition to the state - @parent.newmetaparam( - @parent.class.metaparamclass(:alias), value - ) - end - value - end - end - - newparam(:name) do - desc "The port name." - - isnamevar - end - end -end - -# $Id$ diff --git a/lib/puppet/type/sshkey.rb b/lib/puppet/type/sshkey.rb deleted file mode 100755 index e769199bf..000000000 --- a/lib/puppet/type/sshkey.rb +++ /dev/null @@ -1,58 +0,0 @@ -module Puppet - newtype(:sshkey, Puppet::Type::ParsedType) do - @doc = "Installs and manages ssh host keys. At this point, this type - only knows how to install keys into /etc/ssh/ssh_known_hosts, and - it cannot manage user authorized keys yet." - - newstate(:type) do - desc "The encryption type used. Probably ssh-dss or ssh-rsa." - end - - newstate(:key) do - desc "The key itself; generally a long string of hex digits." - end - - # FIXME This should automagically check for aliases to the hosts, just - # to see if we can automatically glean any aliases. - newstate(:alias) do - desc "Any alias the host might have. Multiple values must be - specified as an array. Note that this state has the same name - as one of the metaparams; using this state to set aliases will - make those aliases available in your Puppet scripts." - - # We actually want to return the whole array here, not just the first - # value. - def should - if defined? @should - return @should - else - return nil - end - end - - validate do |value| - if value =~ /\s/ - raise Puppet::Error, "Aliases cannot include whitespace" - end - if value =~ /,/ - raise Puppet::Error, "Aliases cannot include whitespace" - end - end - - # Make a puppet alias in addition. - munge do |value| - # Add the :alias metaparam in addition to the state - @parent.newmetaparam(@parent.class.metaparamclass(:alias), value) - value - end - end - - newparam(:name) do - desc "The host name." - - isnamevar - end - end -end - -# $Id$ diff --git a/lib/puppet/type/state.rb b/lib/puppet/type/state.rb index e186c401d..de0cfeb00 100644 --- a/lib/puppet/type/state.rb +++ b/lib/puppet/type/state.rb @@ -8,11 +8,12 @@ require 'puppet/parameter' module Puppet class State < Puppet::Parameter + attr_accessor :is + # Because 'should' uses an array, we have a special method for handling # it. We also want to keep copies of the original values, so that # they can be retrieved and compared later when merging. attr_reader :shouldorig - attr_writer :is class << self attr_accessor :unmanaged @@ -92,19 +93,11 @@ class State < Puppet::Parameter def change_to_s begin if @is == :absent - if self.name == :ensure - return "created as '%s'" % [self.should_to_s] - else - return "defined '%s' as '%s'" % - [self.name, self.should_to_s] - end + return "defined '%s' as '%s'" % + [self.name, self.should_to_s] elsif self.should == :absent or self.should == [:absent] - if self.name == :ensure - return "deleted from '%s'" % self.is_to_s - else - return "undefined %s from '%s'" % - [self.name, self.is_to_s] - end + return "undefined %s from '%s'" % + [self.name, self.is_to_s] else return "%s changed '%s' to '%s'" % [self.name, self.is_to_s, self.should_to_s] @@ -116,11 +109,6 @@ class State < Puppet::Parameter [self.name, detail] end end - - # Just a simple wrapper method to sync our current 'should' value. - def commit - self.sync(self.should) - end # initialize our state def initialize(hash) @@ -179,9 +167,8 @@ class State < Puppet::Parameter end # Look for a matching value - is = self.is @should.each { |val| - if is == val + if @is == val return true end } @@ -190,17 +177,12 @@ class State < Puppet::Parameter return false end - # If the '@is' value is set, then return it, else retrieve it. - def is - @is || self.retrieve - end - # because the @should and @is vars might be in weird formats, # we need to set up a mechanism for pretty printing of the values # default to just the values, but this way individual states can # override these methods def is_to_s - self.is + @is end # Send a log message. @@ -254,11 +236,17 @@ class State < Puppet::Parameter # provider. In other words, if the state name is 'gid', we'll call # 'provider.gid' to retrieve the current value. def retrieve - provider.send(self.class.name) + @is = provider.send(self.class.name) end # Call the method associated with a given value. - def set(value) + def set + if self.insync? + self.log "already in sync" + return nil + end + + value = self.should method = "set_" + value.to_s event = nil if self.respond_to?(method) @@ -288,18 +276,28 @@ class State < Puppet::Parameter end end - if event == :nochange - return :nochange - end - if setevent = self.class.event(value) return setevent else if event and event.is_a?(Symbol) - return event + if event == :nochange + return nil + else + return event + end else - # StateChange will autogenerate an event. - return nil + # Return the appropriate event. + event = case self.should + when :present: (@parent.class.name.to_s + "_created").intern + when :absent: (@parent.class.name.to_s + "_removed").intern + else + (@parent.class.name.to_s + "_changed").intern + end + + #self.log "made event %s because 'should' is %s, 'is' is %s" % + # [event, self.should.inspect, self.is.inspect] + + return event end end end @@ -349,20 +347,20 @@ class State < Puppet::Parameter # The default 'sync' method only selects among a list of registered # values. - def sync(value = nil) - - unless value - warnstamp :novalsynced, "No value passed to sync" - value = self.should + def sync + if self.insync? + self.info "already in sync" + return nil + #else + #self.info "%s vs %s" % [self.is.inspect, self.should.inspect] end - unless self.class.values self.devfail "No values defined for %s" % self.class.name end # Set ourselves to whatever our should value is. - self.set(value || self.should) + self.set end def to_s @@ -387,9 +385,15 @@ class State < Puppet::Parameter @doc ||= "The basic state that the object should be in." end + def self.inherited(sub) + # Add in the two states that everyone will have. + sub.class_eval do + end + end + def change_to_s begin - if self.is == :absent + if @is == :absent return "created" elsif self.should == :absent return "removed" @@ -412,9 +416,9 @@ class State < Puppet::Parameter # @is values set. This seems to be the source of quite a few bugs, # although they're mostly logging bugs, not functional ones. if @parent.exists? - return :present + @is = :present else - return :absent + @is = :absent end end diff --git a/lib/puppet/type/user.rb b/lib/puppet/type/user.rb index b0c3081e4..3a78d9669 100755 --- a/lib/puppet/type/user.rb +++ b/lib/puppet/type/user.rb @@ -50,7 +50,7 @@ module Puppet def change_to_s begin - if self.is == :absent + if @is == :absent return "created" elsif self.should == :absent return "removed" @@ -69,11 +69,30 @@ module Puppet def retrieve if provider.exists? - return :present + @is = :present else - return :absent + @is = :absent end end + + # The default 'sync' method only selects among a list of registered + # values. + def sync + if self.insync? + self.info "already in sync" + return nil + #else + #self.info "%s vs %s" % [self.is.inspect, self.should.inspect] + end + unless self.class.values + self.devfail "No values defined for %s" % + self.class.name + end + + # Set ourselves to whatever our should value is. + self.set + end + end newstate(:uid) do @@ -174,21 +193,24 @@ module Puppet end def is_to_s - self.is.join(",") + @is.join(",") end # We need to override this because the groups need to # be joined with commas def should + unless defined? @is + retrieve + end + @should ||= [] if @parent[:membership] == :inclusive @should.sort else - current = self.is members = @should - if current.is_a?(Array) - members += current + if @is.is_a?(Array) + members += @is end members.uniq.sort end @@ -196,13 +218,9 @@ module Puppet def retrieve if tmp = provider.groups - if tmp == :absent - return tmp - else - return tmp.split(",") - end + @is = tmp.split(",") else - return :absent + @is = :absent end end @@ -210,13 +228,13 @@ module Puppet unless defined? @should and @should return false end - unless defined? self.is and self.is + unless defined? @is and @is return false end - unless self.is.class == @should.class + unless @is.class == @should.class return false end - return self.is.sort == @should.sort + return @is.sort == @should.sort end validate do |value| @@ -225,8 +243,8 @@ module Puppet end end - def sync(value) - provider.groups = value.join(",") + def sync + provider.groups = self.should.join(",") :user_changed end end @@ -356,16 +374,27 @@ module Puppet def retrieve absent = false - current = {} states().each { |state| - if current[:ensure] == :absent - current[state.name] = :absent + if absent + state.is = :absent else - current[state.name] = state.retrieve + state.retrieve end - } - return current + if state.name == :ensure and state.is == :absent + absent = true + next + end + } + #if provider.exists? + # super + #else + # # the user does not exist + # @states.each { |name, state| + # state.is = :absent + # } + # return + #end end end end diff --git a/lib/puppet/util.rb b/lib/puppet/util.rb index 515a91c36..a818a77bf 100644 --- a/lib/puppet/util.rb +++ b/lib/puppet/util.rb @@ -512,15 +512,6 @@ module Util return seconds end - def warnstamp(var, msg) - $stampwarnings ||= {} - $stampwarnings[self.class] ||= {} - unless $stampwarnings[self.class][var] - warning msg - $stampwarnings[self.class][var] = true - end - end - module_function :memory end end |
