diff options
Diffstat (limited to 'bin')
-rwxr-xr-x | bin/youri-check.in | 395 | ||||
-rwxr-xr-x | bin/youri-submit | 534 | ||||
-rwxr-xr-x | bin/youri-submit-proxy.in | 77 | ||||
-rwxr-xr-x | bin/youri-submit-restricted.in | 64 | ||||
-rwxr-xr-x | bin/youri-submit.in | 534 |
5 files changed, 1604 insertions, 0 deletions
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] <mode> + +Options: + + --config <file> use file <file> as config file + --skip-media <media> skip media <media> + --skip-plugin <plugin> skip plugin <plugin> + --parallel parallel run + --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<--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<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 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] <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-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 + --clean delete package after success + --verbose verbose run + --test test run + --list <category> list items from given category + --help [category] display contextual help + +=head1 DESCRIPTION + +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 +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-pre> I<id> + +Skip pre transaction plugin with given identity + +=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<--skip-post> I<id> + +Skip post transaction plugin with given identity. + +=item B<--skip-reject> I<id> + +Skip reject action plugin with given identity. + +=item B<--define> <key>=<value> + +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<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 + +=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<repository> + +The definition of repository plugin to be used. + +=item B<targets> + +The list of available submission targets, each one being composed from the +following keys: + +=over + +=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 + +=item B<checks> + +The list of check plugin definitions, indexed by their identity. + +=item B<actions> + +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] <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 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] <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-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 + --clean delete package after success + --verbose verbose run + --test test run + --list <category> list items from given category + --help [category] display contextual help + +=head1 DESCRIPTION + +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 +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-pre> I<id> + +Skip pre transaction plugin with given identity + +=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<--skip-post> I<id> + +Skip post transaction plugin with given identity. + +=item B<--skip-reject> I<id> + +Skip reject action plugin with given identity. + +=item B<--define> <key>=<value> + +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<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 + +=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<repository> + +The definition of repository plugin to be used. + +=item B<targets> + +The list of available submission targets, each one being composed from the +following keys: + +=over + +=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 + +=item B<checks> + +The list of check plugin definitions, indexed by their identity. + +=item B<actions> + +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; |