# Description of yum repositories

require 'puppet/propertychange'
require 'puppet/util/inifile'

module Puppet
    # A property for one entry in a .ini-style file
    class IniProperty < Puppet::Property
        def insync?(is)
            # A should property of :absent is the same as nil
            if is.nil? && (should.nil? || should == :absent)
                return true
            end
            return super(is)
        end

        def sync
            if insync?(retrieve)
                result = nil
            else
                result = set(self.should)
                if should == :absent
                    resource.section[inikey] = nil
                else
                    resource.section[inikey] = should
                end
            end
            return result
        end

        def retrieve
            return resource.section[inikey]
        end
        
        def inikey
            name.to_s
        end

        # Set the key associated with this property to KEY, instead
        # of using the property's NAME
        def self.inikey(key)
            # Override the inikey instance method
            # Is there a way to do this without resorting to strings ?
            # Using a block fails because the block can't access
            # the variable 'key' in the outer scope
            self.class_eval("def inikey ; \"#{key.to_s}\" ; end")
        end

    end

    # Doc string for properties that can be made 'absent'
    ABSENT_DOC="Set this to 'absent' to remove it from the file completely"

    newtype(:yumrepo) do
        @doc = "The client-side description of a yum repository. Repository
                configurations are found by parsing /etc/yum.conf and
                the files indicated by reposdir in that file (see yum.conf(5)
                for details)

                Most parameters are identical to the ones documented 
                in yum.conf(5)

                Continuation lines that yum supports for example for the
                baseurl are not supported. No attempt is made to access
                files included with the **include** directive"

        class << self
            attr_accessor :filetype
            # The writer is only used for testing, there should be no need
            # to change yumconf in any other context
            attr_accessor :yumconf
        end

        self.filetype = Puppet::Util::FileType.filetype(:flat)

        @inifile = nil
        
        @yumconf = "/etc/yum.conf"

        # Where to put files for brand new sections
        @defaultrepodir = nil

        def self.instances
            l = []
            check = validproperties
            clear
            inifile.each_section do |s|
                next if s.name == "main"
                obj = create(:name => s.name, :check => check)
                current_values = obj.retrieve
                obj.eachproperty do |property|
                    if current_values[property].nil?
                        obj.delete(property.name)
                    else
                        property.should = current_values[property]
                    end
                end
                obj.delete(:check)
                l << obj
            end
            l
        end

        # Return the Puppet::Util::IniConfig::File for the whole yum config
        def self.inifile
            if @inifile.nil?
                @inifile = read()
                main = @inifile['main']
                if main.nil?
                    raise Puppet::Error, "File #{yumconf} does not contain a main section" 
                end
                reposdir = main['reposdir'] 
                reposdir ||= "/etc/yum.repos.d, /etc/yum/repos.d"
                reposdir.gsub!(/[\n,]/, " ")
                reposdir.split.each do |dir|
                    Dir::glob("#{dir}/*.repo").each do |file|
                        if File.file?(file)
                            @inifile.read(file)
                        end
                    end
                end
                reposdir.split.each do |dir|
                    if File::directory?(dir) && File::writable?(dir)
                        @defaultrepodir = dir
                        break
                    end
                end
            end
            return @inifile
        end

        # Parse the yum config files. Only exposed for the tests
        # Non-test code should use self.inifile to get at the
        # underlying file
        def self.read
            result = Puppet::Util::IniConfig::File.new()
            result.read(yumconf)
            main = result['main']
            if main.nil?
                raise Puppet::Error, "File #{yumconf} does not contain a main section" 
            end
            reposdir = main['reposdir']
            reposdir ||= "/etc/yum.repos.d, /etc/yum/repos.d"
            reposdir.gsub!(/[\n,]/, " ")
            reposdir.split.each do |dir|
                Dir::glob("#{dir}/*.repo").each do |file|
                    if File.file?(file)
                        result.read(file)
                    end
                end
            end
            if @defaultrepodir.nil?
                reposdir.split.each do |dir|
                    if File::directory?(dir) && File::writable?(dir)
                        @defaultrepodir = dir
                        break
                    end
                end
            end
            return result
        end

        # Return the Puppet::Util::IniConfig::Section with name NAME
        # from the yum config
        def self.section(name)
            result = inifile[name]
            if result.nil?
                # Brand new section
                path = yumconf
                unless @defaultrepodir.nil?
                    path = File::join(@defaultrepodir, "#{name}.repo")
                end
                Puppet::info "create new repo #{name} in file #{path}"
                result = inifile.add_section(name, path)
            end
            return result
        end

        # Store all modifications back to disk
        def self.store
            inifile.store
        end

        def self.clear
            @inifile = nil
            @yumconf = "/etc/yum.conf"
            @defaultrepodir = nil
            super
        end

        # Return the Puppet::Util::IniConfig::Section for this yumrepo resource
        def section
            self.class.section(self[:name])
        end

        def evaluate
            changes = super
            # FIXME: Dirty, dirty hack
            # We amend the go method of the last change to trigger
            # writing the whole file
            # A cleaner solution would be to either use the composite
            # pattern and encapsulate all changes into a change that does
            # not depend on a property and triggers storing, or insert another
            # change at the end of changes to trigger storing Both
            # solutions require that the PropertyChange interface be
            # abstracted so that it can work with a change that is not
            # directly backed by a Property
            unless changes.empty?
                class << changes[-1]
                    def go
                        result = super
                        self.property.resource.store
                        return result
                    end
                end
            end
            return changes
        end

        # Store modifications to this yumrepo resource back to disk
        def store
            self.class.store
        end

        newparam(:name) do
            desc "The name of the repository."
            isnamevar
        end

        newproperty(:descr, :parent => Puppet::IniProperty) do
            desc "A human readable description of the repository. 
                  #{ABSENT_DOC}"
            newvalue(:absent) { self.should = :absent }
            newvalue(/.*/) { }
            inikey "name"
        end
        
        newproperty(:mirrorlist, :parent => Puppet::IniProperty) do
            desc "The URL that holds the list of mirrors for this repository.
                  #{ABSENT_DOC}"
            newvalue(:absent) { self.should = :absent }
            # Should really check that it's a valid URL
            newvalue(/.*/) { }
        end

        newproperty(:baseurl, :parent => Puppet::IniProperty) do
            desc "The URL for this repository.\n#{ABSENT_DOC}"
            newvalue(:absent) { self.should = :absent }
            # Should really check that it's a valid URL
            newvalue(/.*/) { }
        end
        
        newproperty(:enabled, :parent => Puppet::IniProperty) do
            desc "Whether this repository is enabled or disabled. Possible 
                  values are '0', and '1'.\n#{ABSENT_DOC}"
            newvalue(:absent) { self.should = :absent }
            newvalue(%r{(0|1)}) { }
        end

        newproperty(:gpgcheck, :parent => Puppet::IniProperty) do
            desc "Whether to check the GPG signature on packages installed
                  from this repository. Possible values are '0', and '1'.
                  \n#{ABSENT_DOC}"
            newvalue(:absent) { self.should = :absent }
            newvalue(%r{(0|1)}) { }
        end

        newproperty(:gpgkey, :parent => Puppet::IniProperty) do
            desc "The URL for the GPG key with which packages from this
                  repository are signed.\n#{ABSENT_DOC}"
            newvalue(:absent) { self.should = :absent }
            # Should really check that it's a valid URL
            newvalue(/.*/) { }
        end

        newproperty(:include, :parent => Puppet::IniProperty) do
            desc "A URL from which to include the config.\n#{ABSENT_DOC}"
            newvalue(:absent) { self.should = :absent }
            # Should really check that it's a valid URL
            newvalue(/.*/) { }
        end

        newproperty(:exclude, :parent => Puppet::IniProperty) do
            desc "List of shell globs. Matching packages will never be
                  considered in updates or installs for this repo.
                  #{ABSENT_DOC}"
            newvalue(:absent) { self.should = :absent }
            newvalue(/.*/) { }
        end

        newproperty(:includepkgs, :parent => Puppet::IniProperty) do
            desc "List of shell globs. If this is set, only packages
                  matching one of the globs will be considered for
                  update or install.\n#{ABSENT_DOC}"
            newvalue(:absent) { self.should = :absent }
            newvalue(/.*/) { }
        end

        newproperty(:enablegroups, :parent => Puppet::IniProperty) do
            desc "Determines whether yum will allow the use of
              package groups for this  repository. Possible 
              values are '0', and '1'.\n#{ABSENT_DOC}"
            newvalue(:absent) { self.should = :absent }
            newvalue(%r{(0|1)}) { }
        end

        newproperty(:failovermethod, :parent => Puppet::IniProperty) do
            desc "Either 'roundrobin' or 'priority'.\n#{ABSENT_DOC}"
            newvalue(:absent) { self.should = :absent }
            newvalue(%r(roundrobin|priority)) { }
        end

        newproperty(:keepalive, :parent => Puppet::IniProperty) do
            desc "Either '1' or '0'. This tells yum whether or not HTTP/1.1 
              keepalive  should  be  used with this repository.\n#{ABSENT_DOC}"
            newvalue(:absent) { self.should = :absent }
            newvalue(%r{(0|1)}) { }
        end

        newproperty(:timeout, :parent => Puppet::IniProperty) do
            desc "Number of seconds to wait for a connection before timing 
                  out.\n#{ABSENT_DOC}"
            newvalue(:absent) { self.should = :absent }
            newvalue(%r{[0-9]+}) { }
        end

        newproperty(:metadata_expire, :parent => Puppet::IniProperty) do
            desc "Number of seconds after which the metadata will expire.
                  #{ABSENT_DOC}"
            newvalue(:absent) { self.should = :absent }
            newvalue(%r{[0-9]+}) { }
        end

        newproperty(:protect, :parent => Puppet::IniProperty) do
            desc "Enable or disable protection for this repository. Requires 
                  that the protectbase plugin is installed and enabled.
                  #{ABSENT_DOC}"
            newvalue(:absent) { self.should = :absent }
            newvalue(%r{(0|1)}) { }
        end

        newproperty(:priority, :parent => Puppet::IniProperty) do
            desc "Priority of this repository from 1-99. Requires that
                  the priorities plugin is installed and enabled.
                  #{ABSENT_DOC}"
            newvalue(:absent) { self.should = :absent }
            newvalue(%r{[1-9][0-9]?}) { }
        end

        
        
    end
end
