#!/usr/bin/perl

eval 'exec /usr/bin/perl  -S $0 ${1+"$@"}'
    if 0; # not running under some shell
# $Id: youri-upload.in 864 2006-04-10 19:04:05Z guillomovitch $

=head1 NAME

youri-upload - package upload agent

=head1 VERSION

Version 2.0

=head1 SYNOPSIS

youri-upload [options] <target> <files>

Options:

    --config <file>        use file <file> as config file
    --skip-check <check>   skip check <check>
    --skip-action <action> skip action <action>
    --define <key>=<value> pass additional values
    --verbose              verbose run
    --test                 test run
    --help                 print this help message

=head1 DESCRIPTION

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

All packages given on command lines are passed to a list of check plugins,
depending on given upload target. If none of them fails, all packages are
passed to a list of action plugins, depending also on given upload target.

=head1 OPTIONS

=over

=item B<--config> I<file>

Use given file as configuration, instead of normal one.

=item B<--skip-check> I<id>

Skip check plugin with given identity.

=item B<--skip-action> I<id>

Skip action plugin with given identity.

=item B<--define> <key>=<value>

Define additional parameters, to be used by plugins.

=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/upload.conf

=item * /etc/youri/upload.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<repository> I<id>

Declares a repository object with identity I<id>.

=item B<targets> I<ids>

Declares a list of upload target 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 Pod::Usage;

my %options = (
    verbose => 0
);
GetOptions(
    'config=s'       => \$options{config},
    'skip-check=s@'  => \$options{skip}->{checks},
    'skip-action=s@' => \$options{skip}->{actions},
    'define=s%'      => \$options{define},
    'h|help'         => \$options{help},
    't|test'         => \$options{test},
    'v|verbose+'     => \$options{verbose}
);

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

my $config = Youri::Config->new(
    {
        CREATE => 1,
        GLOBAL => {
           DEFAULT  => '',
           EXPAND   => EXPAND_VAR | EXPAND_ENV,
           ARGCOUNT => ARGCOUNT_ONE,
       }
    },
    'includes'   => { ARGCOUNT => ARGCOUNT_ONE },
    'targets'    => { ARGCOUNT => ARGCOUNT_ONE },
    'repository' => { 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/upload.conf",
        "/etc/youri/upload.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;

# check target
my $target = shift @ARGV;
my %targets = map { $_ => 1 } split(/\s+/, $config->targets());
die "Unavailable target $target" unless $targets{$target};

my %target = $config->get_section($target);
die "Undefined target $target" unless %target;

# create repository
my $repository;
my $repository_id = $config->repository();
die "No repository declared" unless $repository_id;
print "Creating repository $repository_id\n" if $options{verbose};
eval {
    $repository = create_instance(
        'Youri::Repository',
        test    => $options{test},
        verbose => $options{verbose} > 0 ? $options{verbose} - 1 : 0,
        $config->get_section($repository_id)
    );
};
die "Failed to create repository $repository_id: $@\n" if $@;

# create packages
my @packages;
foreach my $file (@ARGV) {
    push(
        @packages,
        create_instance(
            'Youri::Package',
            class => $repository->get_package_class(),
            file => $file
        )
    );
}

# check all packages pass all tests
my %skip_checks = map { $_ => 1 } @{$options{skip}->{checks}};
foreach my $id (@{$target{checks}}) {
    next if $skip_checks{$id};
    print "Creating check $id\n" if $options{verbose};
    my $check;
    eval {
        $check = create_instance(
            'Youri::Upload::Check',
            id       => $id,
            test     => $options{test},
            verbose  => $options{verbose} > 0 ? $options{verbose} - 1 : 0,
            $config->get_section($id)
        );
    };
    if ($@) {
        print STDERR "Failed to create check $id: $@\n";
    } else {
        foreach my $package (@packages) {
            print "running check $id on package $package\n" if $options{verbose};
            unless ($check->run($package, $repository, $target, $options{define})) {
                print STDERR "Error: " . $check->get_error() . ", aborting\n";
                exit(1);
            }
        }
    }
}

# proceed further
my %skip_actions = map { $_ => 1 } @{$options{skip}->{actions}};
foreach my $id (@{$target{actions}}) {
    next if $skip_actions{$id};
    print "Creating action $id\n" if $options{verbose};
    my $action;
    eval {
        $action = create_instance(
            'Youri::Upload::Action',
            id       => $id,
            test     => $options{test},
            verbose  => $options{verbose} > 0 ? $options{verbose} - 1 : 0,
            $config->get_section($id)
        );
    };
    if ($@) {
        print STDERR "Failed to create action $id: $@\n";
    } else {
        foreach my $package (@packages) {
            print "running action $id on package $package\n" if $options{verbose};
            eval {
                $action->run($package, $repository, $target, $options{define});
                };
            if ($@) {
                print STDERR "Failed to run action $id on package $package: $@\n";
            }
        }
    }
}
