diff options
| author | luke <luke@980ebf18-57e1-0310-9a29-db15c13687c0> | 2006-10-17 19:41:37 +0000 |
|---|---|---|
| committer | luke <luke@980ebf18-57e1-0310-9a29-db15c13687c0> | 2006-10-17 19:41:37 +0000 |
| commit | 7e488b2dfb9e4c04a9be0c5f824947b55fd0226c (patch) | |
| tree | 1fbbf4cb602ba777a88039fc2eea810603c4c721 /lib/puppet | |
| parent | ce4c49417b7f8192c7a0da757745dafc32fb6de5 (diff) | |
| download | puppet-7e488b2dfb9e4c04a9be0c5f824947b55fd0226c.tar.gz puppet-7e488b2dfb9e4c04a9be0c5f824947b55fd0226c.tar.xz puppet-7e488b2dfb9e4c04a9be0c5f824947b55fd0226c.zip | |
Working on migrating the parsedtypes to using providers for the parsing aspects. This just converts the hosts; I will convert the others next. This is all work from the sync-retrieve-refactor branch, modified to work with trunk.
git-svn-id: https://reductivelabs.com/svn/puppet/trunk@1799 980ebf18-57e1-0310-9a29-db15c13687c0
Diffstat (limited to 'lib/puppet')
| -rw-r--r-- | lib/puppet/metatype/attributes.rb | 20 | ||||
| -rw-r--r-- | lib/puppet/provider/host/parsed.rb | 78 | ||||
| -rwxr-xr-x | lib/puppet/provider/parsedfile.rb | 174 | ||||
| -rwxr-xr-x | lib/puppet/type/host.rb (renamed from lib/puppet/type/parsedtype/host.rb) | 82 | ||||
| -rwxr-xr-x | lib/puppet/type/parsedtype.rb | 470 |
5 files changed, 447 insertions, 377 deletions
diff --git a/lib/puppet/metatype/attributes.rb b/lib/puppet/metatype/attributes.rb index 8619399ee..d647e174f 100644 --- a/lib/puppet/metatype/attributes.rb +++ b/lib/puppet/metatype/attributes.rb @@ -1,10 +1,7 @@ require 'puppet' require 'puppet/type' -# see the bottom of the file for the rest of the inclusions - class Puppet::Type - class << self include Puppet::Util::ClassGen attr_reader :states @@ -270,6 +267,10 @@ class Puppet::Type @states << s end + if options[:event] + s.event = options[:event] + end + # define_method(name) do # @states[name].should # end @@ -678,6 +679,19 @@ class Puppet::Type 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 + # Meta-parameter methods: These methods deal with the results # of specifying metaparameters diff --git a/lib/puppet/provider/host/parsed.rb b/lib/puppet/provider/host/parsed.rb new file mode 100644 index 000000000..c606562a2 --- /dev/null +++ b/lib/puppet/provider/host/parsed.rb @@ -0,0 +1,78 @@ +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/parsedfile.rb b/lib/puppet/provider/parsedfile.rb new file mode 100755 index 000000000..f690c03a4 --- /dev/null +++ b/lib/puppet/provider/parsedfile.rb @@ -0,0 +1,174 @@ +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/type/parsedtype/host.rb b/lib/puppet/type/host.rb index c2036599a..d68fe25a2 100755 --- a/lib/puppet/type/parsedtype/host.rb +++ b/lib/puppet/type/host.rb @@ -1,13 +1,9 @@ -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. (ipv4 or ipv6)" + desc "The host's IP address." end newstate(:alias) do @@ -17,6 +13,19 @@ 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) @@ -75,69 +84,6 @@ 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.rb b/lib/puppet/type/parsedtype.rb index b0e5f618d..ae7918819 100755 --- a/lib/puppet/type/parsedtype.rb +++ b/lib/puppet/type/parsedtype.rb @@ -4,352 +4,210 @@ require 'puppet/filetype' require 'puppet/type/state' module Puppet - 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 + # 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 - def self.isoptional? - if defined? @isoptional - return @isoptional - else - return false - 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 - end + def self.isoptional? + if defined? @isoptional + return @isoptional + else + return false end + end - # Fix things so that the fields have to match exactly, instead - # of only kinda - def insync? - self.is == self.should + # 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 - # 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 + def clear + super + @found = nil + end - unless defined? @is and ! @is.nil? - @is = :absent - end - end + # Fix things so that the fields have to match exactly, instead + # of only kinda + def insync? + self.is == self.should + 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 + # 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 + end - tail = nil + # All this does is return an event; all of the work gets done + # in the flush method on the model. + def sync + if e = self.class.event(self.should) + return e + else 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" + if self.should == :absent + return (@parent.class.name.to_s + "_removed").intern + else + return (@parent.class.name.to_s + "_created").intern end else - # We don't do the work here, it gets done in 'store' - tail = "changed" + return (@parent.class.name.to_s + "_changed").intern 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 - 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 - - # 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 - - # 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 - - # 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 - - 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 - # There is a value on disk, but it should go away - obj.is = [param, value] - obj[param] = :absent - 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| + # 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] - } - end - - # And then add it to our list of instances. This maintains the order - # in the file. - @instances << obj - end - - def self.list - retrieve - - self.collect do |obj| - obj - end - 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 + obj[param] = :absent + 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 + 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 + # 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 - # If they change the path, we need to get rid of our cache object - def self.path=(path) - @fileobj = nil - @path = path - end + def self.list + retrieve.collect { |i| i.is_a? Hash }.collect { |i| hash2obj(i) } + 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 + def self.listbyname + retrieve.collect { |i| i.is_a? Hash }.collect { |i| i[:name] } + 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 + # 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 - if @instances.empty? - Puppet.notice "No %s instances for %s" % [self.name, @path] - else - fileobj.write(self.to_file()) - end - end + newvalue(:absent) do + # The value will get flushed appropriately + return nil + 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 + defaultto do + if @parent.managed? + :present else - obj.to_s + nil end - }.join("\n") + "\n" - - return str - else - Puppet.notice "No %s instances" % self.name - return "" + end end end + end - # 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() + def exists? + h = self.retrieve - self.eachstate { |st| - st.retrieve - } + if h[:ensure] == :absent + return false + else + return true end + end - # Write the entire file out. - def store - self.class.store() - end + # Flush our content to disk. + def flush + provider.store(self.to_hash) + end - def value(name) - unless name.is_a? Symbol - name = name.intern + # Retrieve our current state from our provider + def retrieve + 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 end - if @states.include? name - val = @states[name].value - if val == :absent - return nil + + @states.each do |name, state| + if h.has_key? name + state.is = h[name] else - return val + state.is = :absent end - elsif @parameters.include? name - return @parameters[name].value - else - return nil end + + return h + else + @states.each do |name, state| + state.is = :absent + end + return nil 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$ |
