#!/usr/bin/env ruby

# Simple IRB shell for mcollective
#
#    mc-irb nrpe
#    Determining the amount of hosts matching filter for 2 seconds .... 47
#    >> rpc :runcommand, :command => "check_disks"
#
#     * [ ============================================================> ] 47 / 47
#
#
#     dev1.your.net                      Request Aborted
#        CRITICAL
#                     Output: DISK CRITICAL - free space: / 176 MB (4% inode=86%);
#                  Exit Code: 2
#           Performance Data:  /=3959MB;3706;3924;0;4361 /boot=26MB;83;88;0;98 /dev/shm=0MB;217;230;0;256
#
#    => true
#    >> mchelp
#    <shows the DDL based help for the chosen agent>
#    => true
#    >> rpc(:runcommand, :command => "check_disks") do |resp|
#    ?> puts resp[:sender] + ":   " + resp[:data][:output]
#    >> end
#
#     * [ ============================================================> ] 47 / 47
#
#     dev1.your.net:   DISK OK
#     <snip>
#    => true
#    >>
#
# You can access the agent variable via @agent from where you can do the usual manipulation of filters etc,
# if you wish to switch to a different agent mid run just do newagent("some_other_agent")
#
# If you install the Bond gem you'll get some DDL assisted completion in the rpc method
require 'rubygems'
require 'irb'

def consolize &block
    yield

    IRB.setup(nil)
    irb = IRB::Irb.new
    IRB.conf[:MAIN_CONTEXT] = irb.context
    irb.context.evaluate("require 'irb/completion'", 0)

    begin
        require 'bond'
        Bond.start

        Bond.complete(:method => "rpc") do |e|
            begin
                if e.argument == 1
                    if e.arguments.last == "?"
                        puts "\n\nActions for #{@agent_name}:\n"

                        @agent.ddl.actions.each do |action|
                           puts "%20s - %s" % [ ":#{action}", @agent.ddl.action_interface(action)[:description] ]
                        end

                        print "\n" + e.line
                    end

                    @agent.ddl.actions

                elsif e.argument > 1
                    action = eval(e.arguments[0]).to_s
                    ddl = @agent.ddl.action_interface(action)

                    if e.arguments.last == "?"
                        puts "\n\nArguments for #{action}:\n"
                        ddl[:input].keys.each do |input|
                            puts "%20s - %s" % [ ":#{input}", ddl[:input][input][:description] ]
                        end

                        print "\n" + e.line
                    end

                    [ddl[:input].keys, :verbose].flatten
                end
            rescue Exception
                []
            end
        end
    rescue Exception
    end

    trap("SIGINT") do
        irb.signal_handle
    end
    catch(:IRB_EXIT) do
        irb.eval_input
    end
end

def mchelp
    system("mc-rpc --agent-help #{@agent_name}|less")
    true
end

def rpc(method_name, *args, &block)
    unless block_given?
        if args.size > 0
            args = args.first
        else
            args = {}
        end

        if args[:verbose]
            args.delete(:verbose)

            printrpc(@agent.send(method_name, args), :verbose => true)
            printrpcstats
        else
            printrpc @agent.send(method_name, args)
            printrpcstats
        end

    else
        @agent.send(method_name, args.first).each do |resp|
            yield resp
        end

        printrpcstats
    end

    true
rescue MCollective::DDLValidationError => e
    puts "Request did not pass DDL validation: #{e}"
end

def print_filter
    puts "Active Filter matched #{discover.size} hosts:"
    puts "\tIdentity: #{@agent.filter['identity'].pretty_inspect}"
    puts "\t Classes: #{@agent.filter['cf_class'].pretty_inspect}"
    puts "\t   Facts: #{@agent.filter['fact'].pretty_inspect}"
    puts "\t  Agents: #{@agent.filter['agent'].pretty_inspect}"

    discover.size > 0 ? true : false
end

def newagent(agent)
    @agent_name = agent

    @options[:filter]["agent"] = []
    @agent = rpcclient(@agent_name, :options => @options)

    discover

    @agent.progress = true

    print_filter
end

def identity_filter(*args)
    @agent.identity_filter(*args)

    print_filter
end

def fact_filter(*args)
    @agent.fact_filter(*args)

    print_filter
end

def agent_filter(*args)
    @agent.agent_filter(*args)

    print_filter
end

def class_filter(*args)
    @agent.class_filter(*args)

    print_filter
end

def reset_filter
    @agent.reset_filter

    print_filter
end

def reset
    @agent.reset

    print_filter
end

def discover
    @agent.discover
end

def mc?
    puts <<EOF
    Available Commands:

        rpc                                 - Performs an RPC request
        reset                               - Resets the discovered knowledge
        discover                            - Performs a new discovery showing
                                              hosts that was found
        newagent                            - Switches to a new agent
        mchelp                              - The DDL created help for the agent

    Filter Commands:
        Filter arguments should be enclosed in "your.host.com" if they are strings
        else use /your.host/ to match Regular expressions

        identity_filter [identity]          - Sets an identity filter
        fact_filter [factname], [factvalue] - Sets a fact filter
        class_filter [classname]            - Sets a class filter
        agent_filter [agentname]            - Sets an agent filter
        reset_filter                        - Resets to the filter to blank
        print_filter                        - Displays current filter

    Available Variables:

        @agent_name                         - The name of the active agent
        @agent                              - The active RPC client

    Completions:

        While doing an RPC request, press ?<tab> for a list of actions or
        arguments, do simple :<tab> to get completion on action names and
        arguments without description of each

EOF
    true
end

consolize do
    require 'mcollective'


    include MCollective::RPC

    @options = rpcoptions

    unless ARGV.size == 1
        puts "Please specify an agent name on the command line"
        exit 1
    end

    puts "The Marionette Collective Interactive Ruby Shell version #{MCollective.version}"

    puts
    newagent(ARGV[0])
    puts
    puts "Use mc? to get help on using this shell"
end
