Puppet::Type.type(:zone).provide(:solaris) do
    desc "Provider for Solaris Zones."

    commands :adm => "/usr/sbin/zoneadm", :cfg => "/usr/sbin/zonecfg"
    defaultfor :operatingsystem => :solaris

    mk_resource_methods

    # Convert the output of a list into a hash
    def self.line2hash(line)
        fields = [:id, :name, :ensure, :path]

        properties = {}
        line.split(":").each_with_index { |value, index|
            properties[fields[index]] = value
        }

        # Configured but not installed zones do not have IDs
        if properties[:id] == "-"
            properties.delete(:id)
        end

        properties[:ensure] = symbolize(properties[:ensure])

        return properties
    end

    def self.instances
        adm(:list, "-cp").split("\n").collect do |line|
            new(line2hash(line))
        end
    end

    # Perform all of our configuration steps.
    def configure
        # If the thing is entirely absent, then we need to create the config.
        str = %{create -b
set zonepath=%s
} % @resource[:path]

        # Then perform all of our configuration steps.  It's annoying
        # that we need this much internal info on the resource.
        @resource.send(:properties).each do |property|
            if property.is_a? ZoneConfigProperty and ! property.insync?(properties[property.name])
                str += property.configtext + "\n"
            end
        end

        str += "commit\n"
        setconfig(str)
    end

    def destroy
        zonecfg :delete, "-F"
    end

    def exists?
        properties[:ensure] != :absent
    end

    # Clear out the cached values.
    def flush
        @property_hash.clear
    end

    def install
        zoneadm :install
    end

    # Look up the current status.
    def properties
        if @property_hash.empty?
            @property_hash = status || {}
            if @property_hash.empty?
                @property_hash[:ensure] = :absent
            else
                @resource.class.validproperties.each do |name|
                    @property_hash[name] ||= :absent
                end
            end

        end
        @property_hash.dup
    end

    # We need a way to test whether a zone is in process.  Our 'ensure'
    # property models the static states, but we need to handle the temporary ones.
    def processing?
        if hash = status()
            case hash[:ensure]
            when "incomplete", "ready", "shutting_down"
                true
            else
                false
            end
        else
            false
        end
    end

    # Collect the configuration of the zone.
    def getconfig
        output = zonecfg :info

        name = nil
        current = nil
        hash = {}
        output.split("\n").each do |line|
            case line
            when /^(\S+):\s*$/:
                name = $1
                current = nil # reset it
            when /^(\S+):\s*(.+)$/:
                hash[$1.intern] = $2
            when /^\s+(\S+):\s*(.+)$/:
                if name
                    unless hash.include? name
                        hash[name] = []
                    end

                    unless current
                        current = {}
                        hash[name] << current
                    end
                    current[$1.intern] = $2
                else
                    err "Ignoring '%s'" % line
                end
            else
                debug "Ignoring zone output '%s'" % line
            end
        end

        return hash
    end

    # Execute a configuration string.  Can't be private because it's called
    # by the properties.
    def setconfig(str)
        command = "#{command(:cfg)} -z %s -f -" % @resource[:name]
        debug "Executing '%s' in zone %s with '%s'" % [command, @resource[:name], str]
        IO.popen(command, "w") do |pipe|
            pipe.puts str
        end

        unless $? == 0
            raise ArgumentError, "Failed to apply configuration"
        end
    end

    def start
        # Check the sysidcfg stuff
        if cfg = @resource[:sysidcfg]
            path = File.join(@resource[:path], "root", "etc", "sysidcfg")

            unless File.exists?(path)
                begin
                    File.open(path, "w", 0600) do |f|
                        f.puts cfg
                    end
                rescue => detail
                    if Puppet[:debug]
                        puts detail.stacktrace
                    end
                    raise Puppet::Error, "Could not create sysidcfg: %s" % detail
                end
            end
        end

        zoneadm :boot
    end

    # Return a hash of the current status of this zone.
    def status
        begin
            output = adm "-z", @resource[:name], :list, "-p"
        rescue Puppet::ExecutionFailure
            return nil
        end

        main = self.class.line2hash(output.chomp)

        # Now add in the configuration information
        config_status.each do |name, value|
            main[name] = value
        end

        main
    end

    def stop
        zoneadm :halt
    end

    def unconfigure
        zonecfg :delete, "-F"
    end

    def uninstall
        zoneadm :uninstall, "-F"
    end

    private

    # Turn the results of getconfig into status information.
    def config_status
        config = getconfig()
        result = {}

        result[:autoboot] = config[:autoboot] ? config[:autoboot].intern : :absent
        result[:pool] = config[:pool]
        result[:shares] = config[:shares]
        if dir = config["inherit-pkg-dir"]
            result[:inherit] = dir.collect { |dirs| dirs[:dir] }
        end
        if net = config["net"]
            result[:ip] = net.collect { |params| "%s:%s" % [params[:physical], params[:address]] }
        end

        result
    end

    def zoneadm(*cmd)
        begin
            adm("-z", @resource[:name], *cmd)
        rescue Puppet::ExecutionFailure => detail
            self.fail "Could not %s zone: %s" % [cmd[0], detail]
        end
    end

    def zonecfg(*cmd)
        # You apparently can't get the configuration of the global zone
        return "" if self.name == "global"

        begin
            cfg("-z", self.name, *cmd)
        rescue Puppet::ExecutionFailure => detail
            self.fail "Could not %s zone: %s" % [cmd[0], detail]
        end
    end
end

# $Id: solaris.rb 2577 2007-06-14 03:39:23Z luke $


syntax highlighted by Code2HTML, v. 0.9.1