#!/usr/bin/env ruby

#
# = Synopsis
#
# Run a stand-alone +puppet+ script.
#
# = Usage
#
#   puppet [-h|--help] [-V|--version] [-d|--debug] [-v|--verbose]
#               [-l|--logdest <file>] <file>
#
# = Description
#
# This is the standalone puppet execution script; use it to execute
# individual scripts that you write.  If you need to execute site-wide
# scripts, use +puppetd+ and +puppetmasterd+.
#
# = Options
#
# Note that any configuration parameter that's valid in the configuration file
# is also a valid long argument.  For example, 'ssldir' is a valid configuration
# parameter, so you can specify '--ssldir <directory>' as an argument.
#
# See the configuration file documentation at
# http://reductivelabs.com/projects/puppet/reference/configref.html for
# the full list of acceptable parameters. A commented list of all
# configuration options can also be generated by running puppet with
# '--genconfig'.
#
# debug::
#   Enable full debugging.
#
# help::
#   Print this help message
#
# loadclasses::
#   Load any stored classes.  +puppetd+ caches configured classes (usually at
#   /etc/puppet/classes.txt), and setting this option causes all of those classes
#   to be set in your +puppet+ manifest.
#
# logdest::
#   Where to send messages.  Choose between syslog, the console, and a log file.
#   Defaults to sending messages to the console.
#
# verbose::
#   Print extra information.
#
# = Example
#
#   puppet -l /tmp/script.log script.pp
#
# = Author
#
# Luke Kanies
#
# = Copyright
#
# Copyright (c) 2005 Reductive Labs, LLC
# Licensed under the GNU Public License

require 'puppet'
require 'puppet/network/handler'
require 'puppet/network/client'
require 'getoptlong'

options = [
    [ "--debug",	"-d",			GetoptLong::NO_ARGUMENT ],
    [ "--help",		"-h",			GetoptLong::NO_ARGUMENT ],
    [ "--logdest",	"-l",			GetoptLong::REQUIRED_ARGUMENT ],
    [ "--execute",	"-e",			GetoptLong::REQUIRED_ARGUMENT ],
    [ "--loadclasses", "-L",		GetoptLong::NO_ARGUMENT ],
    [ "--verbose",  "-v",			GetoptLong::NO_ARGUMENT ],
    [ "--use-nodes",    			GetoptLong::NO_ARGUMENT ],
    [ "--version",  "-V",           GetoptLong::NO_ARGUMENT ]
]

# Add all of the config parameters as valid options.
Puppet.settings.addargs(options)

result = GetoptLong.new(*options)

options = {
    :debug => false,
    :verbose => false,
    :noop => false,
    :logfile => false,
    :loadclasses => false,
    :logset => false,
    :code => nil
}


master = {
    :Local => true
}

begin
    result.each { |opt,arg|
        case opt
            when "--version"
                puts "%s" % Puppet.version
                exit
            when "--help"
                if Puppet.features.usage?
                    RDoc::usage && exit
                else
                    puts "No help available unless you have RDoc::usage installed"
                    exit
                end
            when "--use-nodes"
                options[:UseNodes] = true
            when "--verbose"
                options[:verbose] = true
            when "--debug"
                options[:debug] = true
            when "--execute"
                options[:code] = arg
            when "--loadclasses"
                options[:loadclasses] = true
            when "--logdest"
                begin
                    Puppet::Util::Log.newdestination(arg)
                    options[:logset] = true
                rescue => detail
                    $stderr.puts detail.to_s
                end
            else
                Puppet.settings.handlearg(opt, arg)
        end
    }
rescue GetoptLong::InvalidOption => detail
    $stderr.puts "Try '#{$0} --help'"
    exit(1)
end

Puppet.parse_config

# Now parse the config
if Puppet[:config] and File.exists? Puppet[:config]
    Puppet.settings.parse(Puppet[:config])
end

if Puppet.settings.print_configs?
    exit(Puppet.settings.print_configs ? 0 : 1)
end

# If noop is set, then also enable diffs
if Puppet[:noop]
    Puppet[:show_diff] = true
end

unless options[:logset]
    Puppet::Util::Log.newdestination(:console)
end

client = nil
server = nil

Puppet.settraps

if options[:debug]
    Puppet::Util::Log.level = :debug
elsif options[:verbose]
    Puppet::Util::Log.level = :info
end

# Set our code or file to use.
if options[:code] or ARGV.length == 0
    Puppet[:code] = options[:code] || STDIN.read
else
    Puppet[:manifest] = ARGV.shift
end

if Puppet[:parseonly]
    begin
        Puppet::Parser::Interpreter.new.parser(Puppet[:environment])
    rescue => detail
        Puppet.err detail
        exit 1
    end
    exit 0
end

# Collect our facts.
facts = Puppet::Node::Facts.find("me")
facts.name = facts.values["hostname"]

# Find our Node
node = Puppet::Node.find(facts.name)

# Merge in the facts.
node.merge(facts.values)

# Allow users to load the classes that puppetd creates.
if options[:loadclasses]
    file = Puppet[:classfile]
    if FileTest.exists?(file)
        unless FileTest.readable?(file)
            $stderr.puts "%s is not readable" % file
            exit(63)
        end

        node.classes = File.read(file).split(/[\s\n]+/)
    end
end

begin
    # Compile our catalog
    catalog = Puppet::Node::Catalog.find(node.name, :use_node => node)

    # Translate it to a RAL catalog
    catalog = catalog.to_ral

    catalog.host_config = true if Puppet[:graph] or Puppet[:report]

    catalog.finalize

    # And apply it
    catalog.apply
rescue => detail
    if Puppet[:trace]
        puts detail.backtrace
    end
    if detail.is_a?(XMLRPC::FaultException)
        $stderr.puts detail.message
    else
        $stderr.puts detail
    end
    exit(1)
end
