#!/usr/bin/perl # $Id$ =head1 NAME youri-upload - package upload agent =head1 VERSION Version 2.0 =head1 SYNOPSIS youri-upload [options] Options: --config use file as config file --skip-pre
       skip pre 
    --skip-action  skip action 
    --skip-check    skip check 
    --skip-post      skip post 
    --skip-reject  skip reject 
    --define = pass additional values
    --list-targets         list available targets
    --list-checks  list configured checks for for 
    --list-actions  list configured actions for for 
    --verbose              verbose run
    --test                 test run
    --help                 print this help message

=head1 DESCRIPTION

B 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

Use given file as configuration, instead of normal one.

=item B<--skip-pre> I

Skip pre transaction plugin with given identity

=item B<--skip-check> I

Skip check plugin with given identity.

=item B<--skip-action> I

Skip action plugin with given identity.

=item B<--skip-post> I

Skip post transaction plugin with given identity.

=item B<--skip-reject> I

Skip reject action plugin with given identity.

=item B<--define> =

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<--list-targets>

List available targets

=item B<--list-checks> I

List configured checks for given target 

=item B<--list-actions> I

List configured actions for given target 

=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 * @sysconfdir@/youri/upload.conf

=back

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

=over

=item B I

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

=item B I

Declares a repository object with identity I.

=item B I

Declares a list of upload target objects with identity taken in space-separated list I.

=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 Youri::Config;
use Youri::Utils;
use Pod::Usage;

my $config = Youri::Config->new(
    command_spec => [
        'config=s',
        'skip-pre=s@',
        'skip-check=s@',
        'skip-action=s@',
        'skip-post=s@',
        'skip-reject=s@',
        'list-targets!',
        'list-checks=s',
        'list-actions=s',
        'define=s%',
        'help|h!',
        'test|t!',
	'server|s',
        'verbose|v!'
    ],
    file_spec => [
        'includes=s',
        'targets=s',
        'repository=s',
    ],
    directories => [ '@sysconfdir@', "$ENV{HOME}/.youri" ],
    file_name   => 'upload.conf',
    caller => $0,
);

# compute available targets first
my %targets = map { $_ => 1 } split(/\s+/, $config->get('targets'));

if ($config->get('list-targets')) {
    print join(' ', keys %targets) . "\n";
    exit 0;
} elsif ($config->get('list-checks')) {
    my %target = get_target_config($config->get('list-checks'));
    print join(' ', @{$target{checks}}) . "\n";
    exit 0;
} elsif ($config->get('list-actions')) {
    my %target = get_target_config($config->get('list-actions'));
    print join(' ', @{$target{actions}}) . "\n";
    exit 0;
}

pod2usage(-verbose => 0, -message => "No target specified, aborting\n")
    unless @ARGV > 0;

# convenient global flags
my $test    = $config->get('test');
my $verbose = $config->get('verbose');

# check target
my $target = shift @ARGV;
my %target = get_target_config($target);

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

# perfrom pre action
my %skip_pres = map { $_ => 1 } @{$config->get('skip-pre')};
my $pre_packages = [];
my $ok = 1;
foreach my $id (@{$target{pre}}) {
    next if $skip_pres{$id};
    print "Creating pre $id\n" if $verbose;
    my $pre;
    eval {
        $pre = create_instance(
            'Youri::Upload::Pre',
            id       => $id,
            test     => $test,
            verbose  => $verbose > 0 ? $verbose - 1 : 0,
            $config->get_section($id)
        );
    };
    if ($@) {
	print STDERR "Failed to create pre $id: $@\n";
    } else {
	print "running pre $id\n" if $verbose;
	unless ($pre->run($pre_packages, $repository, $target, $config->get('define'))) {
	    print STDERR "Error: " . $pre->get_error() . "\n";
	    $ok = 0;
	}
    }
}
exit(1) unless $ok;

# create packages group
my @packages_group;
foreach my $group ((map { [ { section => "", file => $_ } ]  } @ARGV), @$pre_packages) {
    my @packages;
    foreach my $opt (@$group) {
	print "Preparing upload for $opt->{file}\n" if $verbose;
	push(
	    @packages,
	    create_instance(
		'Youri::Package',
		class => $repository->get_package_class(),
		file => $opt->{file},
		%$opt
	    )
	);
    }
    @packages or next;

# check all packages pass all tests
    my %skip_checks = map { $_ => 1 } @{$config->get('skip-check')};
    my $ok = 1;
    my @error;
    foreach my $id (@{$target{checks}}) {
	next if $skip_checks{$id};
	print "Creating check $id\n" if $verbose;
	my $check;
	eval {
	    $check = create_instance(
		'Youri::Upload::Check',
		id       => $id,
		test     => $test,
		verbose  => $verbose > 0 ? $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 $verbose;
		unless ($check->run($package, $repository, $target, $config->get('define'))) {
		    my $err = $check->get_error();
		    print STDERR "Error: $err\n";
		    push @error, $err;
		    $ok = 0
		}
	    }

	}
    }
    if (!$ok) {
	# reject the packages
	my %skip_rejects = map { $_ => 1 } @{$config->get('skip-reject')};
	foreach my $id (@{$target{rejects}}) {
	    next if $skip_rejects{$id};
	    print "Creating reject $id\n" if $verbose;
	    my $reject;
	    eval {
		$reject = create_instance(
		    'Youri::Upload::Reject',
		    id       => $id,
		    test     => $test,
		    verbose  => $verbose > 0 ? $verbose - 1 : 0,
		    $config->get_section($id)
		);
	    };
	    if ($@) {
		print STDERR "Failed to create reject $id: $@\n";
	    } else {
		foreach my $package (@packages) {
		    print "running reject $id on package $package\n" if $verbose;
		    eval {
			$reject->run($package, \@error, $repository, $target, $config->get('define'));
		    };
		    if ($@) {
			print STDERR "Failed to run action $id on package $package: $@\n";
		    }
		}
	    }
	}
	next
    } 

# proceed further
    my %skip_actions = map { $_ => 1 } @{$config->get('skip-action')};
    foreach my $id (@{$target{actions}}) {
	next if $skip_actions{$id};
	print "Creating action $id\n" if $verbose;
	my $action;
	eval {
	    $action = create_instance(
		'Youri::Upload::Action',
		id       => $id,
		test     => $test,
		verbose  => $verbose > 0 ? $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 $verbose;
		eval {
		    $action->run($package, $repository, $target, $config->get('define'));
		};
		if ($@) {
		    print STDERR "Failed to run action $id on package $package: $@\n";
		}
	    }
	}
    }
}
# perfrom post action
my %skip_posts = map { $_ => 1 } @{$config->get('skip-post')};
foreach my $id (@{$target{post}}) {
    next if $skip_posts{$id};
    print "Creating post $id\n" if $verbose;
    my $post;
    eval {
	$post = create_instance(
	    'Youri::Upload::Post',
	    id       => $id,
	    test     => $test,
	    verbose  => $verbose > 0 ? $verbose - 1 : 0,
	    $config->get_section($id)
	);
    };
    if ($@) {
	print STDERR "Failed to create post $id: $@\n";
    } else {
	print "running post $id\n" if $verbose;
	unless ($post->run($repository, $target, $config->get('define'))) {
	    print STDERR "Error: " . $post->get_error() . "\n";
	}
    }
}

sub get_target_config {
    my ($id) = @_;
    die "Unavailable target $target" unless $targets{$id};

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