aboutsummaryrefslogtreecommitdiffstats
path: root/bin
diff options
context:
space:
mode:
Diffstat (limited to 'bin')
-rwxr-xr-xbin/youri-submit-proxy.in77
-rwxr-xr-xbin/youri-submit-restricted.in64
-rwxr-xr-xbin/youri-submit.in404
3 files changed, 390 insertions, 155 deletions
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] <target> <files>
+
+=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] <target> <files>
+
+=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
index c232cfb..07118f1 100755
--- a/bin/youri-submit.in
+++ b/bin/youri-submit.in
@@ -3,7 +3,7 @@
=head1 NAME
-youri-upload - package upload agent
+youri-submit - package submission tool
=head1 VERSION
@@ -11,27 +11,30 @@ Version 2.0
=head1 SYNOPSIS
-youri-upload [options] <target> <files>
+youri-submit [options] <target> <files>
+youri-submit --list <category> [target]
+
+youri-submit --help [category] [item]
+
Options:
--config <file> use file <file> as config file
--skip-pre <pre> skip pre <pre>
- --skip-action <action> skip action <action>
--skip-check <check> skip check <check>
+ --skip-action <action> skip action <action>
--skip-post <post> skip post <post>
--skip-reject <reject> skip reject <reject>
--define <key>=<value> pass additional values
- --list-targets list available targets
- --list-checks <target> list configured checks for for <target>
- --list-actions <target> list configured actions for for <target>
+ --clean delete package after success
--verbose verbose run
--test test run
- --help print this help message
+ --list <category> list items from given category
+ --help [category] display contextual help
=head1 DESCRIPTION
-B<youri-upload> allows to upload packages in a repository.
+B<youri-submit> 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
@@ -69,6 +72,10 @@ Skip reject action plugin with given identity.
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)
@@ -77,21 +84,16 @@ Produce more verbose output (can be used more than once)
Don't perform any modification.
-=item B<--list-targets>
-
-List available targets
-
-=item B<--list-checks> I<target>
-
-List configured checks for given target
-
-=item B<--list-actions> I<target>
-
-List configured actions for given target
-
-=item B<--help>
-
-Print a brief help message and exits.
+=item B<--list> I<category>
+
+List available items from given category and exits. Category must be either
+B<targets>, B<actions> or B<checks>. A target is needed for the two last ones.
+
+=item B<--help> I<category>
+
+Display help for given category and exits. Category must be either
+B<repository>, B<action> or B<check>. An item is needed for the two last ones.
+If no category given, display standard help.
=back
@@ -103,48 +105,51 @@ Configuration is read from the first file found among:
=item * the one specified by B<--config> option on command-line
-=item * $HOME/.youri/upload.conf
-
-=item * @sysconfdir@/youri/upload.conf
+=item * $HOME/.youri/submit.conf
+
+=item * @sysconfdir@/youri/submit.conf
=back
-All additional configuration files specified by B<includes> directive are then
-processed. Then command line options. Any directive overrides prior definition.
+The configuration file should be a YAML-format files, with the following
+mandatory top-level directives:
=over
-=item B<includes> I<files>
+=item B<repository>
-Uses space-separated list I<files> as a list of additional configuration files.
+The definition of repository plugin to be used.
-=item B<repository> I<id>
+=item B<targets>
-Declares a repository object with identity I<id>.
+The list of available submission targets, each one being composed from the
+following keys:
-=item B<targets> I<ids>
+=over
-Declares a list of upload target objects with identity taken in space-separated list I<ids>.
+=item B<checks>
+
+The list of check plugins to use for this target.
+
+=item B<actions>
+
+The list of action plugins to use for this target.
=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.
+=item B<checks>
+
+The list of check plugin definitions, indexed by their identity.
-Example:
+=item B<actions>
+
+The list of action plugin definitions, indexed by their identity.
- objects = foo
-
- [foo]
- class = Foo::Bar
- key1 = value1
- key2 = value2
+=back
=head1 SEE ALSO
-Youri::Config, for configuration file format.
+Youri::Config, for additional details about configuration file format.
Each used plugin man page, for available options.
@@ -164,90 +169,136 @@ 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,
+ 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',
);
-# 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";
+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;
# convenient global flags
-my $test = $config->get('test');
-my $verbose = $config->get('verbose');
+my $test = $config->get_arg('test');
+my $verbose = $config->get_arg('verbose');
# check target
my $target = shift @ARGV;
-my %target = get_target_config($target);
+my $target_conf = $config->get_param('targets')->{$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;
+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',
- test => $test,
- verbose => $verbose > 0 ? $verbose - 1 : 0,
- targets => [ keys %targets ],
- $config->get_section($repository_id)
+ $repository_conf,
+ {
+ test => $test,
+ verbose => $verbose > 0 ? $verbose - 1 : 0,
+ targets => [ keys %{$config->get_param('targets')} ],
+ }
);
};
-die "Failed to create repository $repository_id: $@\n" if $@;
+die "Failed to create repository: $@\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}}) {
+foreach my $id (@{$target_conf->{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)
+ {
+ id => $id,
+ test => $test,
+ verbose => $verbose > 0 ? $verbose - 1 : 0,
+ }
);
};
if ($@) {
@@ -272,29 +323,42 @@ foreach my $group ((map { [ { section => "", file => $_ } ] } @ARGV), @$pre_pac
@packages,
create_instance(
'Youri::Package',
- class => $repository->get_package_class(),
- file => $opt->{file},
- %$opt
+ {
+ 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 %errors;
+ my $skip_check = $config->get_arg('skip-check');
+ my %skip_check = $skip_check ? map { $_ => 1 } @{$skip_check} : ();
my @error;
- foreach my $id (@{$target{checks}}) {
+ foreach my $id (@{$target_conf->{checks}}) {
next if $skip_checks{$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::Upload::Check',
- id => $id,
- test => $test,
- verbose => $verbose > 0 ? $verbose - 1 : 0,
- $config->get_section($id)
+ $check_conf,
+ {
+ id => $id,
+ test => $test,
+ verbose => $verbose > 0 ? $verbose - 1 : 0,
+ }
);
};
if ($@) {
@@ -302,30 +366,40 @@ foreach my $group ((map { [ { section => "", file => $_ } ] } @ARGV), @$pre_pac
} 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
- }
+ my @errors = $check->run(
+ $package,
+ $repository,
+ $target,
+ $config->get_arg('define')
+ );
+ push(@{$errors{$package}}, @errors) if @errors;
+ }
+ }
+ if (%errors) {
+ print "Submission errors, aborting:\n";
+ foreach my $package (keys %errors) {
+ print "- $package:\n";
+ foreach my $error (@{$errors{$package}}) {
+ print " - $error\n";
}
-
}
- }
- if (!$ok) {
# reject the packages
- my %skip_rejects = map { $_ => 1 } @{$config->get('skip-reject')};
- foreach my $id (@{$target{rejects}}) {
+ 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};
eval {
$reject = create_instance(
'Youri::Upload::Reject',
- id => $id,
- test => $test,
- verbose => $verbose > 0 ? $verbose - 1 : 0,
- $config->get_section($id)
+ $reject_conf,
+ {
+ id => $id,
+ test => $test,
+ verbose => $verbose > 0 ? $verbose - 1 : 0,
+ }
);
};
if ($@) {
@@ -334,7 +408,7 @@ foreach my $group ((map { [ { section => "", file => $_ } ] } @ARGV), @$pre_pac
foreach my $package (@packages) {
print "running reject $id on package $package\n" if $verbose;
eval {
- $reject->run($package, \@error, $repository, $target, $config->get('define'));
+ $reject->run($package, \%errors, $repository, $target, $config->get('define'));
};
if ($@) {
print STDERR "Failed to run action $id on package $package: $@\n";
@@ -346,48 +420,70 @@ foreach my $group ((map { [ { section => "", file => $_ } ] } @ARGV), @$pre_pac
}
# proceed further
- my %skip_actions = map { $_ => 1 } @{$config->get('skip-action')};
- foreach my $id (@{$target{actions}}) {
+ 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_actions{$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::Upload::Action',
- id => $id,
- test => $test,
- verbose => $verbose > 0 ? $verbose - 1 : 0,
- $config->get_section($id)
+ $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('define'));
- };
- if ($@) {
- print STDERR "Failed to run action $id on package $package: $@\n";
- }
- }
- }
+ 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";
+ }
+ }
}
}
# perfrom post action
-my %skip_posts = map { $_ => 1 } @{$config->get('skip-post')};
-foreach my $id (@{$target{post}}) {
- next if $skip_posts{$id};
+my $skip_post = $config->get_arg('skip-post');
+my %skip_post = $skip_post ? map { $_ => 1 } @{$skip_post} : ();
+foreach my $id (@{$target_conf->{post}}) {
+ 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 action $id, skipping\n";
+ next;
+ }
eval {
$post = create_instance(
'Youri::Upload::Post',
- id => $id,
- test => $test,
- verbose => $verbose > 0 ? $verbose - 1 : 0,
- $config->get_section($id)
+ $post_conf,
+ {
+ id => $id,
+ test => $test,
+ verbose => $verbose > 0 ? $verbose - 1 : 0,
+ }
);
};
if ($@) {
@@ -400,11 +496,9 @@ foreach my $id (@{$target{post}}) {
}
}
-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;
+if ($config->get('clean')) {
+ foreach my $package (@packages) {
+ print "cleaning file $package\n" if $verbose;
+ unlink $package->as_file();
+ }
}