#!/usr/bin/perl

eval 'exec /usr/bin/perl  -S $0 ${1+"$@"}'
    if 0; # not running under some shell
# $Id: youri-check.in 896 2006-04-20 21:57:48Z guillomovitch $

=head1 NAME

youri-check - package check agent

=head1 VERSION

Version 1.0

=head1 SYNOPSIS

youri-check [options] <mode>

Options:

    --config <file>        use file <file> as config file
    --skip-media <media>   skip media <media>
    --skip-plugin <plugin> skip plugin <plugin>
    --verbose              verbose run
    --test                 test run
    --help                 print this help message

=head1 DESCRIPTION

B<youri-check> allows to check packages in a repository.

In input mode, all medias defined in configuration are passed to a list of
input plugins, each of them storing their result in a persistent resultset. In
output mode, this resultset is passed to a list of output plugins, each of them
producing arbitrary effects.

=head1 OPTIONS

=over

=item B<--config> <file>

Use given file as configuration, instead of normal one.

=item B<--skip-media> <media>

Skip media with given identity.

=item B<--skip-plugin> <plugin>

Skip plugin with given identity.

=item B<--verbose>

Produce more verbose output (can be used more than once)

=item B<--test>

Don't perform any modification.

=item B<--help>

Print a brief help message and exits.

=back

=head1 CONFIGURATION

Configuration is read from the first file found among:

=over

=item * the one specified by B<--config> option on command-line

=item * $HOME/.youri/check.conf

=item * /etc/youri/check.conf

=back

All additional configuration files specified by B<includes> directive are then
processed. Then command line options. Any directive overrides prior definition.

=over

=item B<includes> I<files>

Uses space-separated list I<files> as a list of additional configuration files.

=item B<resolver> I<id>

Declare a maintainer resolver object with identity I<id>.

=item B<preferences> I<id>

Declare a maintainer preferences object with identity I<id>.

=item B<resultset> I<id>

Declare a resultset object with identity I<id>.

=item B<medias> I<ids>

Declares a list of media objects with identity taken in space-separated list
I<ids>.

=item B<inputs> I<ids>

Declares a list of input plugin objects with identity taken in space-separated
list I<ids>.

=item B<outputs> I<ids>

Declares a list of output plugin objects with identity taken in space-separated
list I<ids>.

=back

Each object declared in configuration must be fully defined later, using a
configuration section, starting with bracketed object identity, followed by at
least a class directive, then any number of additional object-specific
directives.

Example:

        objects = foo
        
        [foo]
        class = Foo::Bar
        key1  = value1
        key2  = value2

=head1 SEE ALSO

Youri::Config, for configuration file format.

Each used plugin man page, for available options.

=head1 COPYRIGHT AND LICENSE

Copyright (C) 2002-2006, YOURI project

This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.

=cut

use strict;
use warnings;

use Getopt::Long;
use File::Spec;
use Youri::Config qw/:argcount :expand/;
use Youri::Utils;
use Net::Config qw/%NetConfig/;
use Pod::Usage;
use DateTime;

my %packages;
my %maintainers;

my %options = (
    verbose => 0
);
GetOptions(
    'config=s'       => \$options{config},
    'skip-plugin=s@' => \$options{skip}->{plugins},
    'skip-media=s@'  => \$options{skip}->{medias},
    'h|help'         => \$options{help},
    't|test'         => \$options{test},
    'v|verbose+'     => \$options{verbose}
);

pod2usage(-verbose => 0)  if $options{help};
pod2usage(-verbose => 0, -message => "No mode specified, aborting\n") unless @ARGV;

my $mode = $ARGV[0];

my $config = Youri::Config->new(
    {
        CREATE => 1,
        GLOBAL => {
           DEFAULT  => '',
           EXPAND   => EXPAND_VAR | EXPAND_ENV,
           ARGCOUNT => ARGCOUNT_ONE,
       }
    },
    'includes'    => { ARGCOUNT => ARGCOUNT_ONE },
    'resolver'    => { ARGCOUNT => ARGCOUNT_ONE },
    'preferences' => { ARGCOUNT => ARGCOUNT_ONE },
    'resultset'   => { ARGCOUNT => ARGCOUNT_ONE },
    'medias'      => { ARGCOUNT => ARGCOUNT_ONE },
    'inputs'      => { ARGCOUNT => ARGCOUNT_ONE },
    'outputs'     => { ARGCOUNT => ARGCOUNT_ONE },
);

# find configuration file to use
my $config_file;
if ($options{config}) {
    if (! -f $options{config}) {
        warn "Non-existing file $options{config}, skipping";
    } elsif (! -r $options{config}) {
        warn "Non-readable file $options{config}, skipping";
    } else {
        $config_file = $options{config};
    }
};

unless ($config_file) {
    foreach my $file (
        "$ENV{HOME}/.youri/check.conf",
        "/etc/youri/check.conf"
    ) {
        next unless -f $file && -r $file;
        $config_file = $file;
        last;
    }
}

die 'No config file found, aborting' unless $config_file;
$config->file($config_file);

# process inclusions
my $need_rescan;
foreach my $include (split(/\s+/, $config->includes())) {
    # convert relative path to absolute ones
    $include = File::Spec->rel2abs(
        $include, (File::Spec->splitpath($config_file))[1]
    );

    if (! -f $include) {
        warn "Non-existing file $include, skipping";
    } elsif (! -r $include) {
        warn "Non-readable file $include, skipping";
    } else {
        $config->file($include);
        $need_rescan = 1;
    }
}

$config->file($config_file) if $need_rescan;

# libnet configuration
my %netconfig = $config->varlist("^netconfig_", 1);
$NetConfig{$_} = $netconfig{$_} foreach keys %netconfig;

if ($mode eq 'input') {

    my $resolver;
    my $resolver_id = $config->resolver();
    if ($resolver_id) {
        report("Creating maintainer resolver $resolver_id");
        eval {
            $resolver = create_instance(
                'Youri::Check::Maintainer::Resolver',
                test    => $options{test},
                verbose => $options{verbose} > 1 ? $options{verbose} - 2 : 0,
                $config->get_section($resolver_id)
            );
        };
        print STDERR "Failed to create maintainer resolver $resolver_id: $@\n" if $@;
    }

    my $preferences;
    my $preferences_id = $config->preferences();
    if ($preferences_id) {
        report("Creating maintainer preferences $preferences_id");
        eval {
            $preferences = create_instance(
                'Youri::Check::Maintainer::Preferences',
                test      => $options{test},
                verbose   => $options{verbose} > 1 ? $options{verbose} - 2 : 0,
                $config->get_section($preferences_id)
            );
        };
        print STDERR "Failed to create maintainer preferences $preferences_id: $@\n" if $@;
    }

    my $resultset;
    my $resultset_id = $config->resultset();
    if ($resultset_id) {
        report("Creating resultset $resultset_id");
        eval {
            $resultset = create_instance(
                'Youri::Check::Resultset',
                test     => $options{test},
                verbose  => $options{verbose} > 0 ? $options{verbose} - 1 : 0,
                mode     => $mode,
                resolver => $resolver,
                $config->get_section($resultset_id)
            );
        };
        print STDERR "Failed to create resultset $resultset_id: $@\n" if $@;
    }

    my @medias;
    my %skip_medias = map { $_ => 1 } @{$options{skip}->{medias}};
    foreach my $id (split(/\s+/, $config->medias())) {
        next if $skip_medias{$id};
        report("Creating media $id");
        eval {
            push(
                @medias,
                 create_instance(
                    'Youri::Media',
                    id      => $id,
                    test    => $options{test},
                    verbose => $options{verbose} > 0 ? $options{verbose} - 1 : 0,
                    $config->get_section($id)
                )
            );
        };
        print STDERR "Failed to create media $id: $@\n" if $@;
    }

    my %skip_plugins = map { $_ => 1 } @{$options{skip}->{plugins}};
    foreach my $id (split(/\s+/, $config->inputs())) {
        next if $skip_plugins{$id};
        report("Creating input $id");
        my $input;
        eval {
            $input = create_instance(
                'Youri::Check::Input',
                id         => $id,
                test       => $options{test},
                verbose    => $options{verbose} > 0 ? $options{verbose} - 1 : 0,
                resolver   => $resolver,
                preferences => $preferences,
                $config->get_section($id)
            );
        };
        if ($@) {
            print STDERR "Failed to create input $id: $@\n";
        } else {
            eval {
                $input->prepare(@medias);
            };
            if ($@) {
                print STDERR "Failed to prepare input $id: $@\n";
            } else {
                foreach my $media (@medias) {
                    next if $media->skip_input($id);
                    my $media_id = $media->get_id();
                    report("running input $id on media $media_id");
                    eval {
                        $input->run($media, $resultset);
                    };
                    if ($@) {
                        print STDERR "Failed to run input $id on media $media_id: $@\n";
                    }
                }
            }
        }
    }
} elsif ($mode eq 'output') {

    my $resultset;
    my $resultset_id = $config->resultset();
    if ($resultset_id) {
        report("Creating resultset $resultset_id");
        eval {
            $resultset = create_instance(
                'Youri::Check::Resultset',
                test    => $options{test},
                verbose => $options{verbose} > 0 ? $options{verbose} - 1 : 0,
                mode    => $mode,
                $config->get_section($resultset_id)
            );
        };
        print STDERR "Failed to create resultset $resultset_id: $@\n" if $@;
    }

    my %skip_plugins = map { $_ => 1 } @{$options{skip}->{plugins}};
    foreach my $id (split(/\s+/, $config->outputs())) {
        next if $skip_plugins{$id};
        report("Creating output $id");
        my $output;
        eval {
            $output = create_instance(
                'Youri::Check::Output',
                id      => $id,
                test    => $options{test},
                verbose => $options{verbose} > 0 ? $options{verbose} - 1 : 0,
                config  => $config,
                $config->get_section($id)
            );
        };
        if ($@) {
            print STDERR "Failed to create output $id: $@\n";
        } else {
            report("running output $id");
            eval {
                $output->run($resultset);
            };
            if ($@) {
                print STDERR "Failed to run output $id: $@\n";
            }
        }
    }
} else {
    die "Invalid mode $mode";
}

sub report {
    my ($message) = @_;
    print DateTime->now()->strftime('[%H:%M:%S] ')
        if $options{verbose} > 1;
    print "$message\n"
        if $options{verbose} > 0;
}
