summaryrefslogtreecommitdiffstats
path: root/lib/puppet
diff options
context:
space:
mode:
authorluke <luke@980ebf18-57e1-0310-9a29-db15c13687c0>2006-10-17 19:41:37 +0000
committerluke <luke@980ebf18-57e1-0310-9a29-db15c13687c0>2006-10-17 19:41:37 +0000
commit7e488b2dfb9e4c04a9be0c5f824947b55fd0226c (patch)
tree1fbbf4cb602ba777a88039fc2eea810603c4c721 /lib/puppet
parentce4c49417b7f8192c7a0da757745dafc32fb6de5 (diff)
downloadpuppet-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.rb20
-rw-r--r--lib/puppet/provider/host/parsed.rb78
-rwxr-xr-xlib/puppet/provider/parsedfile.rb174
-rwxr-xr-xlib/puppet/type/host.rb (renamed from lib/puppet/type/parsedtype/host.rb)82
-rwxr-xr-xlib/puppet/type/parsedtype.rb470
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$