require 'puppet/provider/parsedfile'
require 'erb'

Puppet::Type.type(:interface).provide(:redhat) do
	INTERFACE_DIR = "/etc/sysconfig/network-scripts"
    confine :exists => INTERFACE_DIR
    defaultfor :operatingsystem => [:fedora, :centos, :redhat]

    # Create the setter/gettor methods to match the model.
    mk_resource_methods

    ALIAS_TEMPLATE = ERB.new <<-ALIAS
DEVICE=<%= self.device %>
ONBOOT=<%= self.on_boot %>
BOOTPROTO=<%= self.bootproto %>
IPADDR=<%= self.name %>
NETMASK=<%= self.netmask %>
BROADCAST=
ALIAS


    LOOPBACK_TEMPLATE = ERB.new <<-LOOPBACKDUMMY
DEVICE=<%= self.device %>
ONBOOT=<%= self.on_boot %>
BOOTPROTO=static
IPADDR=<%= self.name %>
NETMASK=255.255.255.255
BROADCAST=
LOOPBACKDUMMY

	# maximum number of dummy interfaces
	MAX_DUMMIES = 10

	# maximum number of aliases per interface
	MAX_ALIASES_PER_IFACE = 10


	@@dummies = []
	@@aliases = Hash.new { |hash, key| hash[key] = [] }

	# calculate which dummy interfaces are currently already in
	# use prior to needing to call self.next_dummy later on.
	def self.instances
		# parse all of the config files at once
		Dir.glob("%s/ifcfg-*" % INTERFACE_DIR).collect do |file|

			record = parse(file)

			# store the existing dummy interfaces
			if record[:interface_type] == :dummy
				@@dummies << record[:ifnum] unless @@dummies.include?(record[:ifnum])
			end

			if record[:interface_type] == :alias
				@@aliases[record[:interface]] << record[:ifnum]
			end
            new(record)
		end
	end

	# return the next avaliable dummy interface number, in the case where
	# ifnum is not manually specified
	def self.next_dummy
		MAX_DUMMIES.times do |i|
			unless @@dummies.include?(i.to_s)
				@@dummies << i.to_s
				return i.to_s
			end
		end
	end

	# return the next available alias on a given interface, in the case
	# where ifnum if not manually specified
	def self.next_alias(interface)
		MAX_ALIASES_PER_IFACE.times do |i|
			unless @@aliases[interface].include?(i.to_s)
				@@aliases[interface] << i.to_s
				return i.to_s
			end
		end
	end

    # base the ifnum, for dummy / loopback interface in linux
    # on the last octect of the IP address

    # Parse the existing file.
    def self.parse(file)

        opts = {}
        return opts unless FileTest.exists?(file)

        File.open(file) do |f|
            f.readlines.each do |line|
                if line =~ /^(\w+)=(.+)$/
                    opts[$1.downcase.intern] = $2
                end
            end
        end

        # figure out the "real" device information
        case opts[:device]
        when /:/:
            if opts[:device].include?(":")
                opts[:interface], opts[:ifnum] = opts[:device].split(":")
            end

            opts[:interface_type] = :alias
        when /^dummy/:
            opts[:interface_type] = :loopback
			opts[:interface] = "dummy"

			# take the number of the dummy interface, as this is used
			# when working out whether to call next_dummy when dynamically
			# creating these
			opts[:ifnum] = opts[:device].sub("dummy",'')

			@@dummies << opts[:ifnum].to_s unless @@dummies.include?(opts[:ifnum].to_s)
        else
			opts[:interface_type] = :normal
			opts[:interface] = opts[:device]
        end

		# translate whether we come up on boot to true/false
		case opts[:onboot].downcase
		when "yes":
			opts[:onboot] = :true
		when "no":
			opts[:onboot] = :false
		else
			# this case should never happen, but just in case
			opts[:onboot] = false
		end


        # Remove any attributes we don't want.  These would be
        # pretty easy to support.
        [:bootproto, :broadcast, :netmask, :device].each do |opt|
            if opts.include?(opt)
                opts.delete(opt)
            end
        end

        if opts.include?(:ipaddr)
            opts[:name] = opts[:ipaddr]
            opts.delete(:ipaddr)
        end

        return opts

    end

    # Prefetch our interface list, yo.
    def self.prefetch(resources)
        instances.each do |prov|
            if resource = resources[prov.name]
                resource.provider = prov
            end
        end
    end

    def create
        @resource.class.validproperties.each do |property|
            if value = @resource.should(property)
                @property_hash[property] = value
            end
        end
        @property_hash[:name] = @resource.name

        return (@resource.class.name.to_s + "_created").intern
    end

    def destroy
        File.unlink(@resource[:target])
    end

    def exists?
        FileTest.exists?(@resource[:target])
    end

    # generate the content for the interface file, so this is dependent
    # on whether we are adding an alias to a real interface, or a loopback
    # address (also dummy) on linux. For linux it's quite involved, and we
    # will use an ERB template
	def generate
		# choose which template to use for the interface file, based on
		# the interface type
        case @resource.should(:interface_type)
        when :loopback
			return LOOPBACK_TEMPLATE.result(binding)
        when :alias
			return ALIAS_TEMPLATE.result(binding)
		end
	end

    # Where should the file be written out?
	# This defaults to INTERFACE_DIR/ifcfg-<namevar>, but can have a
	# more symbolic name by setting interface_desc in the type. 
    def file_path
		@resource[:interface_desc] ||= @resource[:name]
       	return File.join(INTERFACE_DIR, "ifcfg-" + @resource[:interface_desc])

    end

	# create the device name, so this based on the IP, and interface + type
	def device
		case @resource.should(:interface_type)
		when :loopback
			@property_hash[:ifnum] ||= self.class.next_dummy
        	return "dummy" + @property_hash[:ifnum]
		when :alias
			@property_hash[:ifnum] ||= self.class.next_alias(@resource[:interface])
        	return @resource[:interface] + ":" + @property_hash[:ifnum]
		end
    end

	# whether the device is to be brought up on boot or not. converts
	# the true / false of the type, into yes / no values respectively
	# writing out the ifcfg-* files
	def on_boot
		case @property_hash[:onboot].to_s
		when "true"
			return "yes"
		when "false"
			return "no"
		else
			return "neither"
		end
	end

    # Write the new file out.
    def flush
        # Don't flush to disk if we're removing the config.
        return if @resource.should(:ensure) == :absent

        @property_hash.each do |name, val|
            if val == :absent
                raise ArgumentError, "Propety %s must be provided" % val
            end
        end

        File.open(@resource[:target], "w") do |f|
            f.puts generate()
        end
    end

    def prefetch
        @property_hash = self.class.parse(@resource[:target])
    end
end

# $Id: redhat.rb 2747 2007-08-05 19:01:39Z luke $


syntax highlighted by Code2HTML, v. 0.9.1