From abc9a802404902718dc808fdce36f226533f02de Mon Sep 17 00:00:00 2001 From: Pascal Terjan Date: Fri, 7 Jan 2011 08:07:11 +0000 Subject: get_file_name returns a full path, which Install does not like --- bin/youri-check.in | 395 ++++++++++++++++++++++++++++++ bin/youri-submit | 534 +++++++++++++++++++++++++++++++++++++++++ bin/youri-submit-proxy.in | 77 ++++++ bin/youri-submit-restricted.in | 64 +++++ bin/youri-submit.in | 534 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 1604 insertions(+) create mode 100755 bin/youri-check.in create mode 100755 bin/youri-submit create mode 100755 bin/youri-submit-proxy.in create mode 100755 bin/youri-submit-restricted.in create mode 100755 bin/youri-submit.in (limited to 'bin') diff --git a/bin/youri-check.in b/bin/youri-check.in new file mode 100755 index 0000000..b32f4fd --- /dev/null +++ b/bin/youri-check.in @@ -0,0 +1,395 @@ +#!/usr/bin/perl +# $Id: youri-check.in 1699 2006-10-16 11:33:58Z warly $ + +=head1 NAME + +youri-check - package check agent + +=head1 VERSION + +Version 1.0 + +=head1 SYNOPSIS + +youri-check [options] + +Options: + + --config use file as config file + --skip-media skip media + --skip-plugin skip plugin + --parallel parallel run + --verbose verbose run + --test test run + --help print this help message + +=head1 DESCRIPTION + +B 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> + +Use given file as configuration, instead of normal one. + +=item B<--skip-media> + +Skip media with given identity. + +=item B<--skip-plugin> + +Skip plugin with given identity. + +=item B<--parallel> + +Run all plugins parallelously + +=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 * @sysconfdir@/youri/check.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 + +Declare a maintainer resolver object with identity I. + +=item B I + +Declare a maintainer preferences object with identity I. + +=item B I + +Declare a resultset object with identity I. + +=item B I + +Declares a list of media objects with identity taken in space-separated list +I. + +=item B I + +Declares a list of input plugin objects with identity taken in space-separated +list I. + +=item B I + +Declares a list of output plugin 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; +use Net::Config qw/%NetConfig/; +use DateTime; + +my $config = Youri::Config->new( + command_spec => [ + 'config=s', + 'skip-plugin=s@', + 'skip-media=s@', + 'parallel!', + 'help|h!', + 'test|t!', + 'verbose|v!' + ], + file_spec => [ + 'includes=s', + 'resolver=s', + 'preferences=s', + 'resultset=s', + 'medias=s', + 'inputs=s', + 'outputs=s' + ], + directories => [ '@sysconfdir@', "$ENV{HOME}/.youri" ], + file_name => 'check.conf', + caller => $0, +); + +pod2usage( + -verbose => 0, + -message => "No mode specified, aborting\n" +) unless @ARGV; + +my $mode = $ARGV[0]; + +# convenient global flags +my $test = $config->get('test'); +my $verbose = $config->get('verbose'); + +# libnet configuration +my %netconfig = $config->get_section('netconfig'); +$NetConfig{$_} = $netconfig{$_} foreach keys %netconfig; + +# resultset creation +my $resultset_id = $config->get('resultset'); +die "No resultset defined" unless $resultset_id; + +report("Creating resultset $resultset_id"); +my $resultset = create_instance( + 'Youri::Check::Resultset', + test => $test, + verbose => $verbose > 0 ? $verbose - 1 : 0, + $config->get_section($resultset_id) +); + +my $children; + +my %skip_plugins = map { $_ => 1 } @{$config->get('skip-plugin')}; + +if ($mode eq 'input') { + + # additional objects + + my $resolver; + my $resolver_id = $config->get('resolver'); + if ($resolver_id) { + report("Creating maintainer resolver $resolver_id"); + eval { + $resolver = create_instance( + 'Youri::Check::Maintainer::Resolver', + test => $test, + verbose => $verbose > 1 ? $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->get('preferences'); + if ($preferences_id) { + report("Creating maintainer preferences $preferences_id"); + eval { + $preferences = create_instance( + 'Youri::Check::Maintainer::Preferences', + test => $test, + verbose => $verbose > 1 ? $verbose - 2 : 0, + $config->get_section($preferences_id) + ); + }; + print STDERR "Failed to create maintainer preferences $preferences_id: $@\n" if $@; + } + + my @medias; + my %skip_medias = map { $_ => 1 } @{$config->get('skip-media')}; + foreach my $id (split(/\s+/, $config->get('medias'))) { + next if $skip_medias{$id}; + report("Creating media $id"); + eval { + push( + @medias, + create_instance( + 'Youri::Media', + id => $id, + test => $test, + verbose => $verbose > 0 ? $verbose - 1 : 0, + $config->get_section($id) + ) + ); + }; + print STDERR "Failed to create media $id: $@\n" if $@; + } + + # prepare resultset + $resultset->reset(); + $resultset->set_resolver($resolver); + + + foreach my $id (split(/\s+/, $config->get('inputs'))) { + next if $skip_plugins{$id}; + report("Creating input $id"); + my $input; + eval { + $input = create_instance( + 'Youri::Check::Input', + id => $id, + test => $test, + verbose => $verbose > 0 ? $verbose - 1 : 0, + resolver => $resolver, + preferences => $preferences, + $config->get_section($id) + ); + }; + if ($@) { + print STDERR "Failed to create input $id: $@\n"; + } else { + if ($config->get('parallel')) { + # fork + my $pid = fork; + die "Can't fork: $!" unless defined $pid; + if ($pid) { + # parent process + $children++; + next; + } + } + eval { + $input->prepare(@medias); + }; + if ($@) { + print STDERR "Failed to prepare input $id: $@\n"; + } else { + # clone resultset in child process + $resultset = $config->get('parallel') ? + $resultset->clone() : + $resultset; + + 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"; + } + } + } + if ($config->get('parallel')) { + # child process + exit; + } + } + } + +} elsif ($mode eq 'output') { + + foreach my $id (split(/\s+/, $config->get('outputs'))) { + next if $skip_plugins{$id}; + report("Creating output $id"); + my $output; + eval { + $output = create_instance( + 'Youri::Check::Output', + id => $id, + test => $test, + verbose => $verbose > 0 ? $verbose - 1 : 0, + config => $config, + $config->get_section($id) + ); + }; + if ($@) { + print STDERR "Failed to create output $id: $@\n"; + } else { + if ($config->get('parallel')) { + # fork + my $pid = fork; + die "Can't fork: $!" unless defined $pid; + if ($pid) { + # parent process + $children++; + next; + } + } + + # clone resultset in child process + $resultset = $config->get('parallel') ? + $resultset->clone() : + $resultset; + + report("running output $id"); + eval { + $output->run($resultset); + }; + if ($@) { + print STDERR "Failed to run output $id: $@\n"; + } + + if ($config->get('parallel')) { + # child process + exit; + } + } + } +} else { + die "Invalid mode $mode"; +} + +# wait for all forked processus termination +while ($children) { + wait; + $children--; +} + +sub report { + my ($message) = @_; + print DateTime->now()->strftime('[%H:%M:%S] ') + if $verbose > 1; + print "$message\n" + if $verbose > 0; +} diff --git a/bin/youri-submit b/bin/youri-submit new file mode 100755 index 0000000..2cec209 --- /dev/null +++ b/bin/youri-submit @@ -0,0 +1,534 @@ +#!/usr/bin/perl +# $Id: youri-submit 232579 2007-12-17 19:45:47Z blino $ + +=head1 NAME + +youri-submit - package submission tool + +=head1 VERSION + +Version 2.0 + +=head1 SYNOPSIS + +youri-submit [options] + +youri-submit --list [target] + +youri-submit --help [category] [item] + +Options: + + --config use file as config file + --skip-pre
       skip pre 
+    --skip-check    skip check 
+    --skip-action  skip action 
+    --skip-post      skip post 
+    --skip-reject  skip reject 
+    --define = pass additional values
+    --clean                delete package after success
+    --verbose              verbose run
+    --test                 test run
+    --list       list items from given category
+    --help [category]      display contextual help
+
+=head1 DESCRIPTION
+
+B allows to submit packages to 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<--clean>
+
+Delete submited packages upon successfull submission.
+
+=item B<--verbose>
+
+Produce more verbose output (can be used more than once)
+
+=item B<--test>
+
+Don't perform any modification.
+
+=item B<--list> I
+ 
+List available items from given category and exits. Category must be either
+B, B or B. A target is needed for the two last ones.
+ 
+=item B<--help> I
+ 
+Display help for given category and exits. Category must be either
+B, B or B. An item is needed for the two last ones.
+If no category given, display standard help.
+
+=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/submit.conf
+ 
+=item * /usr/local/etc/youri/submit.conf
+
+=back
+
+The configuration file should be a YAML-format files, with the following
+mandatory top-level directives:
+
+=over
+
+=item B
+
+The definition of repository plugin to be used.
+
+=item B
+
+The list of available submission targets, each one being composed from the
+following keys:
+
+=over
+
+=item B
+
+The list of check plugins to use for this target.
+
+=item B
+
+The list of action plugins to use for this target.
+
+=back
+
+=item B
+
+The list of check plugin definitions, indexed by their identity.
+
+=item B
+ 
+The list of action plugin definitions, indexed by their identity.
+
+=back
+
+=head1 SEE ALSO
+
+Youri::Config, for additional details about 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(
+    args => {
+        'skip-check'  => '=s@',
+        'skip-action' => '=s@',
+        'define'      => '=s%',
+        'verbose'     => '|v!',
+        'clean'       => '!',
+        'test'        => '|t!',
+        'list'        => '|l!',
+        'config'      => '=s',
+        'skip-prei'   => '=s@',
+        'skip-post'   => '=s@',
+        'skip-reject' => '=s@',
+    },
+    directories => [ "$ENV{HOME}/.youri", '/usr/local/etc/youri' ],
+    file => 'submit.conf',
+);
+
+if ($config->get_arg('list')) {
+    my $category = $ARGV[0];
+    pod2usage(-verbose => 0, -message => "No category specified, aborting\n")
+        unless $category;
+    if ($category eq 'targets') { 
+        print join(' ', keys %{$config->get_param('targets')});
+    } elsif ($category eq 'checks' || $category eq 'actions') {
+        my $target = $ARGV[1];
+        pod2usage(-verbose => 0, -message => "No target specified, aborting\n")
+            unless $target;
+        if ($category eq 'checks') {
+            my $checks = $config->get_param('targets')->{$target}->{checks};
+            print join(' ', @{$checks}) if $checks;
+        } else {
+            my $actions = $config->get_param('targets')->{$target}->{actions};
+            print join(' ', @{$actions}) if $actions;
+        }
+    } else {
+        pod2usage(-verbose => 0, -message => "Invalid category $category, aborting\n")
+    }
+    print "\n";
+    exit 0;
+}
+
+if ($config->get_arg('help')) {
+    my $category = $ARGV[0];
+    my ($item, $section);
+    if ($category eq 'repository') { 
+        $section = $config->get_param('repository');
+        pod2usage(
+            -verbose => 0,
+            -message => "No repository defined, aborting\n"
+        ) unless $section;
+    } elsif ($category eq 'check' || $category eq 'action') {
+        $item = $ARGV[1];
+        pod2usage(
+            -verbose => 0,
+            -message => "No item specified, aborting\n"
+        ) unless $item;
+        if ($category eq 'check') {
+            $section = $config->get_param('checks')->{$item};
+            pod2usage(
+                -verbose => 0,
+                -message => "No such check $item defined, aborting\n"
+            ) unless $section;
+        } else  {
+            $section = $config->get_param('actions')->{$item};
+            pod2usage(
+                -verbose => 0,
+                -message => "No such action $item defined, aborting\n"
+            ) unless $section;
+        }
+    } else {
+        pod2usage(-verbose => 0, -message => "Invalid category $category, aborting\n")
+    }
+    my $file = $section->{class} . '.pm';
+    $file =~ s/::/\//g;
+    pod2usage(
+        -verbose  => 99,
+        -sections => 'NAME|DESCRIPTION',
+        -input    => $file,
+        -pathlist => \@INC
+    );
+}
+
+
+pod2usage(-verbose => 0, -message => "No target specified, aborting\n")
+    unless @ARGV > 0;
+pod2usage(-verbose => 0, -message => "No packages specified, aborting\n")
+    unless @ARGV > 1 || $config->get_param('allow_omitting_packages');
+
+# convenient global flags
+my $test    = $config->get_arg('test');
+my $verbose = $config->get_arg('verbose');
+
+# check target
+my $target = shift @ARGV;
+my $target_conf = $config->get_param('targets')->{$target};
+
+# create repository
+my $repository;
+my $repository_conf = $config->get_param('repository');
+die "No repository declared" unless $repository_conf;
+print "Creating repository\n" if $verbose;
+eval {
+    $repository = create_instance(
+        'Youri::Repository',
+        $repository_conf,
+        {
+            test    => $test,
+            verbose => $verbose > 0 ? $verbose - 1 : 0,
+            targets => [ keys %{$config->get_param('targets')} ],
+        }
+    );
+};
+die "Failed to create repository: $@\n" if $@;
+
+# perfrom pre action
+my @errors;
+my $pre_packages = [];
+my $skip_pres = $config->get_arg('skip-pre');
+my %skip_pres = $skip_pres ?  map { $_ => 1 } @{$skip_pres} : ();
+foreach my $id (@{$target_conf->{pres}}) { 
+    next if $skip_pres{$id};
+    print "Creating pre $id\n" if $verbose;
+    my $pre;
+    my $pre_conf = $config->get_param('pres')->{$id};
+
+    if (!$pre_conf) {
+	print STDERR "No such pre $id, skipping\n";
+	next;
+    }
+    eval {
+	$pre = create_instance(
+	    'Youri::Submit::Pre',
+	    $pre_conf,
+	    {
+		id       => $id,
+		test     => $test,
+		verbose  => $verbose > 0 ? $verbose - 1 : 0,
+	    }
+	);
+    };
+    if ($@) {
+	print STDERR "Failed to create pre $id: $@\n";
+    } else {
+	print "running pre $id\n" if $verbose;
+	my @err = $pre->run(
+	    $pre_packages,
+	    $repository,
+	    $target,
+	    $config->get_arg('define')
+	);
+	push(@errors, @err) if $err[0];
+    }
+}
+
+if (@errors) {
+    print "Pre-submission errors, aborting:\n";
+    foreach my $error (@errors) {
+	print " - $error\n";
+    }
+    exit(1)
+}
+
+# create packages group
+my $group_error;
+my @packages_group;
+foreach my $group ([ map { { section => "", file => $_ } } @ARGV ], @$pre_packages) {
+    my @packages;
+    foreach my $opt (@$group) {
+	print "Preparing upload for $opt->{file} in $opt->{section}\n" if $verbose;
+	$repository->{packages}{$opt->{file}}{section} = $opt->{section};
+	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 %errors;
+    my $skip_check = $config->get_arg('skip-check');
+    my %skip_check = $skip_check ?  map { $_ => 1 } @{$skip_check} : ();
+    my @error;
+    foreach my $id (@{$target_conf->{checks}}) {
+	next if $skip_check{$id};
+	print "Creating check $id\n" if $verbose;
+	my $check;
+	my $check_conf = $config->get_param('checks')->{$id};
+
+	if (!$check_conf) {
+	    print STDERR "No such check $id, skipping\n";
+	    next;
+	}
+	eval {
+	    $check = create_instance(
+		'Youri::Submit::Check',
+		$check_conf,
+		{ 
+		    id       => $id,
+		    test     => $test,
+		    verbose  => $verbose > 0 ? $verbose - 1 : 0,
+		}
+	    );
+	};
+	if ($@) {
+	    print STDERR "Failed to create check $id: $@\n";
+	} else {
+	    foreach my $package (@packages) {
+		print "running check $id on package $package\n" if $verbose;
+		my @errors = $check->run(
+		    $package,
+		    $repository,
+		    $target,
+		    $config->get_arg('define')
+		);
+		push(@{$errors{$package}}, @errors) if $errors[0];
+	    }
+	}
+    }
+    if (%errors) {
+	print "Submission errors, aborting:\n";
+	foreach my $package (keys %errors) {
+	    print "- $package:\n";
+	    foreach my $error (@{$errors{$package}}) {
+		print " - $error\n";
+	    }
+	}
+	# reject the packages
+	my $skip_rejects = $config->get_arg('skip-reject');
+	my %skip_rejects = $skip_rejects ?  map { $_ => 1 } @{$skip_rejects} : ();
+	foreach my $id (@{$target_conf->{rejects}}) {
+	    next if $skip_rejects{$id};
+	    print "Creating reject $id\n" if $verbose;
+	    my $reject;
+	    my $reject_conf = $config->get_param('rejects')->{$id};
+	
+	    if (!$reject_conf) {
+		print STDERR "No such reject $id, skipping\n";
+		next;
+	    }
+	    eval {
+		$reject = create_instance(
+		    'Youri::Submit::Reject',
+		    $reject_conf,
+		    {
+			id       => $id,
+			test     => $test,
+			verbose  => $verbose > 0 ? $verbose - 1 : 0,
+		    }
+		);
+	    };
+	    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, \%errors, $repository, $target, $config->get_arg('define'));
+		    };
+		    if ($@) {
+			print STDERR "Failed to run action $id on package $package: $@\n";
+		    }
+		}
+	    }
+	}
+	$group_error = 1;
+	next
+    } 
+
+# proceed further
+    my $skip_action = $config->get_arg('skip-action');
+    my %skip_action = $skip_action ?  map { $_ => 1 } @{$skip_action} : ();
+    foreach my $id (@{$target_conf->{actions}}) {
+	next if $skip_action{$id};
+	print "Creating action $id\n" if $verbose;
+	my $action;
+	my $action_conf = $config->get_param('actions')->{$id};
+
+	if (!$action_conf) {
+	    print STDERR "No such action $id, skipping\n";
+	    next;
+	}
+	eval {
+	    $action = create_instance(
+		'Youri::Submit::Action',
+		$action_conf,
+		{
+		    id       => $id,
+		    test     => $test,
+		    verbose  => $verbose > 0 ? $verbose - 1 : 0,
+		}
+	    );
+	};
+	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_arg('define')
+		    );
+		};
+		if ($@) {
+		    print STDERR "Failed to run action $id on package $package: $@\n";
+		}
+	    }
+	}
+    }
+
+    if ($config->get_arg('clean')) {
+	foreach my $package (@packages) {
+	    print "cleaning file $package\n" if $verbose;
+	    unlink $package->as_file();
+	}
+    }
+}
+
+# perfrom post action
+my $skip_post = $config->get_arg('skip-post');
+my %skip_post = $skip_post ?  map { $_ => 1 } @{$skip_post} : ();
+foreach my $id (@{$target_conf->{posts}}) {
+    next if $skip_post{$id};
+    print "Creating post $id\n" if $verbose;
+    my $post;
+    my $post_conf = $config->get_param('posts')->{$id};
+
+    if (!$post_conf) {
+	print STDERR "No such post $id, skipping\n";
+	next;
+    }
+    eval {
+	$post = create_instance(
+	    'Youri::Submit::Post',
+	    $post_conf,
+	    { 
+		id       => $id,
+		test     => $test,
+		verbose  => $verbose > 0 ? $verbose - 1 : 0,
+	    }
+	);
+    };
+    if ($@) {
+	print STDERR "Failed to create post $id: $@\n";
+    } else {
+	print "running post $id\n" if $verbose;
+	my @err = $post->run($repository, $target, $config->get_arg('define'));
+	print STDERR "Error $id: @err\n" if @err
+    }
+}
+
+exit(1) if $group_error;
diff --git a/bin/youri-submit-proxy.in b/bin/youri-submit-proxy.in
new file mode 100755
index 0000000..67fed6e
--- /dev/null
+++ b/bin/youri-submit-proxy.in
@@ -0,0 +1,77 @@
+#!/usr/bin/perl
+
+=head1 NAME
+
+youri-submit-proxy - proxy wrapper over youri-submit-restricted
+
+=head1 VERSION
+
+Version 1.0
+
+=head1 SYNOPSIS
+
+youri-submit-proxy [options]  
+
+=head1 DESCRIPTION
+
+youri-submit-proxy is a proxy wrapper over youri-submit-restricted, intended to
+be used in collaborative work to change uid before calling it through sudo.
+
+=head1 SEE ALSO
+
+youri-submit-restricted(1), youri-submit(1)
+
+=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 Fcntl ':mode';
+use File::Basename;
+
+my ($uid, $gid);
+if (-l $0) {
+    # this is a symlink, get uid and gid from it
+    ($uid, $gid) = (lstat($0))[4, 5];
+} else {
+    ($uid, $gid) = (stat($0))[4, 5];
+}
+my $user = getpwuid($uid) or die "unknown uid $uid";
+my $prog = '@bindir@/youri-submit-restricted';
+
+my %dirs;
+my @options;
+foreach my $arg (@ARGV) {
+    if (-f $arg) {
+        # push parent dir in list
+        my $parent = dirname($arg);
+        $dirs{$parent}++;
+    }
+    push(@options, $arg);
+}
+
+foreach my $dir (keys %dirs) {
+    # save original perms and gid
+    my ($orig_mode, $orig_gid) = (stat($dir))[2,5];
+    $dirs{$dir} = {
+        mode => $orig_mode,
+        gid => $orig_gid
+    };
+    # ensure correct perms and gid
+    chown -1, $gid, $dir;
+    chmod $orig_mode|S_IRGRP|S_IWGRP, $dir;
+}
+
+# call wrapped program
+system('sudo', '-H', '-u', $user, $prog, @options);
+
+foreach my $dir (keys %dirs) {
+    # restore original perms and gid
+    chown -1, $dirs{$dir}->{gid}, $dir;
+    chmod $dirs{$dir}->{mode}, $dir;
+}
diff --git a/bin/youri-submit-restricted.in b/bin/youri-submit-restricted.in
new file mode 100755
index 0000000..d28ba84
--- /dev/null
+++ b/bin/youri-submit-restricted.in
@@ -0,0 +1,64 @@
+#!/usr/bin/perl -T
+
+=head1 NAME
+
+youri-submit-restricted - filtering wrapper over youri-submit
+
+=head1 VERSION
+
+Version 1.0
+
+=head1 SYNOPSIS
+
+youri-submit-restricted [options]  
+
+=head1 DESCRIPTION
+
+youri-submit-restricted is just a filtering wrapper over youri-submit, intended
+to be used in collaborative work to sanitize environment and options before
+calling it.
+
+=head1 SEE ALSO
+
+youri-submit(1)
+
+=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;
+
+my $prog = '@bindir@/youri-submit';
+my @prohibited_options = qw/--config --skip-check --skip-action/;
+my %prohibited_options = map { $_ => 1 } @prohibited_options;
+my @prohibited_envvars = qw/
+    ENV BASH_ENV IFS CDPATH
+    PERLLIB PERL5LIB PERL5OPT PERLIO
+    PERLIO_DEBUG PERL5DB PERL_ENCODING
+    PERL_HASH_SEED PERL_SIGNALS PERL_UNICODE
+/;
+
+my @options;
+while (my $arg = shift @ARGV) {
+    if ($prohibited_options{$arg}) {
+        # drop prohibited options
+        print STDERR "prohibited option $arg, skipping\n";
+        shift @ARGV;
+    } else {
+        # untaint everything else
+        $arg =~ /(.*)/;
+        push(@options, $1);
+    }
+}
+
+# secure ENV
+$ENV{PATH} = "/sbin:/usr/sbin:/bin:/usr/bin:/usr/X11R6/bin";
+delete $ENV{$_} foreach @prohibited_envvars;
+
+# call wrapped program
+system($prog, @options);
diff --git a/bin/youri-submit.in b/bin/youri-submit.in
new file mode 100755
index 0000000..9892d0e
--- /dev/null
+++ b/bin/youri-submit.in
@@ -0,0 +1,534 @@
+#!/usr/bin/perl
+# $Id: youri-submit.in 232668 2007-12-21 14:37:04Z blino $
+
+=head1 NAME
+
+youri-submit - package submission tool
+
+=head1 VERSION
+
+Version 2.0
+
+=head1 SYNOPSIS
+
+youri-submit [options]  
+
+youri-submit --list  [target]
+
+youri-submit --help [category] [item]
+ 
+Options:
+
+    --config         use file  as config file
+    --skip-pre 
       skip pre 
+    --skip-check    skip check 
+    --skip-action  skip action 
+    --skip-post      skip post 
+    --skip-reject  skip reject 
+    --define = pass additional values
+    --clean                delete package after success
+    --verbose              verbose run
+    --test                 test run
+    --list       list items from given category
+    --help [category]      display contextual help
+
+=head1 DESCRIPTION
+
+B allows to submit packages to 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<--clean>
+
+Delete submited packages upon successfull submission.
+
+=item B<--verbose>
+
+Produce more verbose output (can be used more than once)
+
+=item B<--test>
+
+Don't perform any modification.
+
+=item B<--list> I
+ 
+List available items from given category and exits. Category must be either
+B, B or B. A target is needed for the two last ones.
+ 
+=item B<--help> I
+ 
+Display help for given category and exits. Category must be either
+B, B or B. An item is needed for the two last ones.
+If no category given, display standard help.
+
+=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/submit.conf
+ 
+=item * /usr/local/etc/youri/submit.conf
+
+=back
+
+The configuration file should be a YAML-format files, with the following
+mandatory top-level directives:
+
+=over
+
+=item B
+
+The definition of repository plugin to be used.
+
+=item B
+
+The list of available submission targets, each one being composed from the
+following keys:
+
+=over
+
+=item B
+
+The list of check plugins to use for this target.
+
+=item B
+
+The list of action plugins to use for this target.
+
+=back
+
+=item B
+
+The list of check plugin definitions, indexed by their identity.
+
+=item B
+ 
+The list of action plugin definitions, indexed by their identity.
+
+=back
+
+=head1 SEE ALSO
+
+Youri::Config, for additional details about 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(
+    args => {
+        'skip-check'  => '=s@',
+        'skip-action' => '=s@',
+        'define'      => '=s%',
+        'verbose'     => '|v!',
+        'clean'       => '!',
+        'test'        => '|t!',
+        'list'        => '|l!',
+        'config'      => '=s',
+        'skip-prei'   => '=s@',
+        'skip-post'   => '=s@',
+        'skip-reject' => '=s@',
+    },
+    directories => [ "$ENV{HOME}/.youri", '@sysconfdir@/youri' ],
+    file => 'submit.conf',
+);
+
+if ($config->get_arg('list')) {
+    my $category = $ARGV[0];
+    pod2usage(-verbose => 0, -message => "No category specified, aborting\n")
+        unless $category;
+    if ($category eq 'targets') { 
+        print join(' ', keys %{$config->get_param('targets')});
+    } elsif ($category eq 'checks' || $category eq 'actions') {
+        my $target = $ARGV[1];
+        pod2usage(-verbose => 0, -message => "No target specified, aborting\n")
+            unless $target;
+        if ($category eq 'checks') {
+            my $checks = $config->get_param('targets')->{$target}->{checks};
+            print join(' ', @{$checks}) if $checks;
+        } else {
+            my $actions = $config->get_param('targets')->{$target}->{actions};
+            print join(' ', @{$actions}) if $actions;
+        }
+    } else {
+        pod2usage(-verbose => 0, -message => "Invalid category $category, aborting\n")
+    }
+    print "\n";
+    exit 0;
+}
+
+if ($config->get_arg('help')) {
+    my $category = $ARGV[0];
+    my ($item, $section);
+    if ($category eq 'repository') { 
+        $section = $config->get_param('repository');
+        pod2usage(
+            -verbose => 0,
+            -message => "No repository defined, aborting\n"
+        ) unless $section;
+    } elsif ($category eq 'check' || $category eq 'action') {
+        $item = $ARGV[1];
+        pod2usage(
+            -verbose => 0,
+            -message => "No item specified, aborting\n"
+        ) unless $item;
+        if ($category eq 'check') {
+            $section = $config->get_param('checks')->{$item};
+            pod2usage(
+                -verbose => 0,
+                -message => "No such check $item defined, aborting\n"
+            ) unless $section;
+        } else  {
+            $section = $config->get_param('actions')->{$item};
+            pod2usage(
+                -verbose => 0,
+                -message => "No such action $item defined, aborting\n"
+            ) unless $section;
+        }
+    } else {
+        pod2usage(-verbose => 0, -message => "Invalid category $category, aborting\n")
+    }
+    my $file = $section->{class} . '.pm';
+    $file =~ s/::/\//g;
+    pod2usage(
+        -verbose  => 99,
+        -sections => 'NAME|DESCRIPTION',
+        -input    => $file,
+        -pathlist => \@INC
+    );
+}
+
+
+pod2usage(-verbose => 0, -message => "No target specified, aborting\n")
+    unless @ARGV > 0;
+pod2usage(-verbose => 0, -message => "No packages specified, aborting\n")
+    unless @ARGV > 1 || $config->get_param('allow_omitting_packages');
+
+# convenient global flags
+my $test    = $config->get_arg('test');
+my $verbose = $config->get_arg('verbose');
+
+# check target
+my $target = shift @ARGV;
+my $target_conf = $config->get_param('targets')->{$target};
+
+# create repository
+my $repository;
+my $repository_conf = $config->get_param('repository');
+die "No repository declared" unless $repository_conf;
+print "Creating repository\n" if $verbose;
+eval {
+    $repository = create_instance(
+        'Youri::Repository',
+        $repository_conf,
+        {
+            test    => $test,
+            verbose => $verbose > 0 ? $verbose - 1 : 0,
+            targets => [ keys %{$config->get_param('targets')} ],
+        }
+    );
+};
+die "Failed to create repository: $@\n" if $@;
+
+# perfrom pre action
+my @errors;
+my $pre_packages = [];
+my $skip_pres = $config->get_arg('skip-pre');
+my %skip_pres = $skip_pres ?  map { $_ => 1 } @{$skip_pres} : ();
+foreach my $id (@{$target_conf->{pres}}) { 
+    next if $skip_pres{$id};
+    print "Creating pre $id\n" if $verbose;
+    my $pre;
+    my $pre_conf = $config->get_param('pres')->{$id};
+
+    if (!$pre_conf) {
+	print STDERR "No such pre $id, skipping\n";
+	next;
+    }
+    eval {
+	$pre = create_instance(
+	    'Youri::Submit::Pre',
+	    $pre_conf,
+	    {
+		id       => $id,
+		test     => $test,
+		verbose  => $verbose > 0 ? $verbose - 1 : 0,
+	    }
+	);
+    };
+    if ($@) {
+	print STDERR "Failed to create pre $id: $@\n";
+    } else {
+	print "running pre $id\n" if $verbose;
+	my @err = $pre->run(
+	    $pre_packages,
+	    $repository,
+	    $target,
+	    $config->get_arg('define')
+	);
+	push(@errors, @err) if $err[0];
+    }
+}
+
+if (@errors) {
+    print "Pre-submission errors, aborting:\n";
+    foreach my $error (@errors) {
+	print " - $error\n";
+    }
+    exit(1)
+}
+
+# create packages group
+my $group_error;
+my @packages_group;
+foreach my $group ([ map { { section => "", file => $_ } } @ARGV ], @$pre_packages) {
+    my @packages;
+    foreach my $opt (@$group) {
+	print "Preparing upload for $opt->{file} in $opt->{section}\n" if $verbose;
+	$repository->{packages}{$opt->{file}}{section} = $opt->{section};
+	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 %errors;
+    my $skip_check = $config->get_arg('skip-check');
+    my %skip_check = $skip_check ?  map { $_ => 1 } @{$skip_check} : ();
+    my @error;
+    foreach my $id (@{$target_conf->{checks}}) {
+	next if $skip_check{$id};
+	print "Creating check $id\n" if $verbose;
+	my $check;
+	my $check_conf = $config->get_param('checks')->{$id};
+
+	if (!$check_conf) {
+	    print STDERR "No such check $id, skipping\n";
+	    next;
+	}
+	eval {
+	    $check = create_instance(
+		'Youri::Submit::Check',
+		$check_conf,
+		{ 
+		    id       => $id,
+		    test     => $test,
+		    verbose  => $verbose > 0 ? $verbose - 1 : 0,
+		}
+	    );
+	};
+	if ($@) {
+	    print STDERR "Failed to create check $id: $@\n";
+	} else {
+	    foreach my $package (@packages) {
+		print "running check $id on package $package\n" if $verbose;
+		my @errors = $check->run(
+		    $package,
+		    $repository,
+		    $target,
+		    $config->get_arg('define')
+		);
+		push(@{$errors{$package}}, @errors) if $errors[0];
+	    }
+	}
+    }
+    if (%errors) {
+	print "Submission errors, aborting:\n";
+	foreach my $package (keys %errors) {
+	    print "- $package:\n";
+	    foreach my $error (@{$errors{$package}}) {
+		print " - $error\n";
+	    }
+	}
+	# reject the packages
+	my $skip_rejects = $config->get_arg('skip-reject');
+	my %skip_rejects = $skip_rejects ?  map { $_ => 1 } @{$skip_rejects} : ();
+	foreach my $id (@{$target_conf->{rejects}}) {
+	    next if $skip_rejects{$id};
+	    print "Creating reject $id\n" if $verbose;
+	    my $reject;
+	    my $reject_conf = $config->get_param('rejects')->{$id};
+	
+	    if (!$reject_conf) {
+		print STDERR "No such reject $id, skipping\n";
+		next;
+	    }
+	    eval {
+		$reject = create_instance(
+		    'Youri::Submit::Reject',
+		    $reject_conf,
+		    {
+			id       => $id,
+			test     => $test,
+			verbose  => $verbose > 0 ? $verbose - 1 : 0,
+		    }
+		);
+	    };
+	    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, \%errors, $repository, $target, $config->get_arg('define'));
+		    };
+		    if ($@) {
+			print STDERR "Failed to run action $id on package $package: $@\n";
+		    }
+		}
+	    }
+	}
+	$group_error = 1;
+	next
+    } 
+
+# proceed further
+    my $skip_action = $config->get_arg('skip-action');
+    my %skip_action = $skip_action ?  map { $_ => 1 } @{$skip_action} : ();
+    foreach my $id (@{$target_conf->{actions}}) {
+	next if $skip_action{$id};
+	print "Creating action $id\n" if $verbose;
+	my $action;
+	my $action_conf = $config->get_param('actions')->{$id};
+
+	if (!$action_conf) {
+	    print STDERR "No such action $id, skipping\n";
+	    next;
+	}
+	eval {
+	    $action = create_instance(
+		'Youri::Submit::Action',
+		$action_conf,
+		{
+		    id       => $id,
+		    test     => $test,
+		    verbose  => $verbose > 0 ? $verbose - 1 : 0,
+		}
+	    );
+	};
+	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_arg('define')
+		    );
+		};
+		if ($@) {
+		    print STDERR "Failed to run action $id on package $package: $@\n";
+		}
+	    }
+	}
+    }
+
+    if ($config->get_arg('clean')) {
+	foreach my $package (@packages) {
+	    print "cleaning file $package\n" if $verbose;
+	    unlink $package->as_file();
+	}
+    }
+}
+
+# perfrom post action
+my $skip_post = $config->get_arg('skip-post');
+my %skip_post = $skip_post ?  map { $_ => 1 } @{$skip_post} : ();
+foreach my $id (@{$target_conf->{posts}}) {
+    next if $skip_post{$id};
+    print "Creating post $id\n" if $verbose;
+    my $post;
+    my $post_conf = $config->get_param('posts')->{$id};
+
+    if (!$post_conf) {
+	print STDERR "No such post $id, skipping\n";
+	next;
+    }
+    eval {
+	$post = create_instance(
+	    'Youri::Submit::Post',
+	    $post_conf,
+	    { 
+		id       => $id,
+		test     => $test,
+		verbose  => $verbose > 0 ? $verbose - 1 : 0,
+	    }
+	);
+    };
+    if ($@) {
+	print STDERR "Failed to create post $id: $@\n";
+    } else {
+	print "running post $id\n" if $verbose;
+	my @err = $post->run($repository, $target, $config->get_arg('define'));
+	print STDERR "Error $id: @err\n" if @err
+    }
+}
+
+exit(1) if $group_error;
-- 
cgit v1.2.1