[backend] add --parse-hdrmd5 to bs_admin
[opensuse:build-service.git] / src / backend / bs_admin
1 #!/usr/bin/perl -w
2 #
3 # Copyright (c) 2008 Adrian Schroeter, Novell Inc.
4 #
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License version 2 as
7 # published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU General Public License
15 # along with this program (see the file COPYING); if not, write to the
16 # Free Software Foundation, Inc.,
17 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
18 #
19 ################################################################
20 #
21 # The Admin Tool
22 #
23
24 BEGIN {
25   my ($wd) = $0 =~ m-(.*)/- ;
26   $wd ||= '.';
27   unshift @INC,  "$wd/build";
28   unshift @INC,  "$wd";
29 }
30
31 use POSIX;
32 use Data::Dumper;
33 use Getopt::Long;
34 use Storable ();
35 use XML::Structured ':bytes';
36
37 use Build;
38
39 our $nosharedtrees;
40 use BSConfig;
41 use BSFileDB;
42 use BSWatcher;
43 use BSUtil;
44 use BSXML;
45 use BSKiwiXML;
46 use BSProductXML;
47 use BSDB;
48 use BSDBIndex;
49 use BSSolv;
50
51 $nosharedtrees = $BSConfig::nosharedtrees if defined($BSConfig::nosharedtrees);
52
53 my $reporoot  = "$BSConfig::bsdir/build";
54 my $eventroot = "$BSConfig::bsdir/events";
55 my $projectsdir = "$BSConfig::bsdir/projects";
56 my $srcrepdir = "$BSConfig::bsdir/sources";
57 my $treesdir = $nosharedtrees ? "$BSConfig::bsdir/trees" : $srcrepdir;
58 my $sourcedb = "$BSConfig::bsdir/db/source";
59
60
61 sub echo_help {
62     print "\n
63 The Open Build Service Admin Tool
64 =====================================
65
66 *** This tool is only intended to be used by experienced admins on
67 *** the backend server ! 
68
69 General options
70 ===============
71
72  --help
73    Gives this help output.
74
75 Job Controlling
76 ===============
77
78  --shutdown-scheduler <architecture>
79    Stops the scheduler nicely with dumping out its current state 
80    for fast startup.
81
82  --check-project <project> <architecture>
83  --check-project <project> <repository> <architecture>
84    Check status of a project and its repositories again
85
86  --deep-check-project <project> <architecture>
87  --deep-check-project <project> <repository> <architecture>
88    Check status of a project and its repositories again
89    This deep check includes also the sources, in case of lost events.
90
91  --check-package <project> <package> <architecture>
92    Check status of a package in all repositories
93
94  --publish-repository <project> <repository>
95    Creates an event for the publisher. The scheduler is NOT scanning for new packages.
96    The publisher may skip the event, if nothing has changed. So you might need to remove
97    the directory in your /repos/ directory first.
98
99  --clone-repository <source project> <source repository> <destination repository>
100  --clone-repository <source project> <source repository> <destination project> <destination repository>
101    Clone an existing repo into another existing repository.
102    Usefull for creating snapshots.
103
104  --rescan-repository <project> <repository> <architecture>
105    Asks the scheduler to scan a repository for new packages and add
106    them to the cache file.
107
108  --force-check-project <project> <repository> <architecture>
109    Enforces the check of an repository, even when it is currently blocked due to amount of
110    calculating time.
111
112  --create-patchinfo-from-updateinfo
113    creates a patchinfo submission based on an updateinfo information.
114
115 Maintenance Tasks
116 =================
117
118 Note: the --update-*-db calls are usually only needed when corrupt data has been created, for
119       example after a file system corruption.
120
121  --update-source-db
122    Update the index for all source files.
123
124  --update-request-db
125    Updates the index for all requests.
126
127 Debug Options
128 =============
129
130  --dump-cache <project> <repository> <architecture>
131    Dumps out the content of a binary cache file.
132    This shows all the content of a repository, including all provides
133    and requires.
134
135  --dump-state <architecture>
136
137  --dump-relsync <file>
138    To dump content of :relsync files.
139
140  --set-relsync <file> <key> <value>
141    Modify key content in a a :relsync file.
142
143  --check-meta-xml <project>
144  --check-meta-xml <project> <package>
145    Is parsing a project or package xml file and puts out error messages, in case of errors.
146
147  --check-product-xml <file>
148    Is parsing a product xml file and puts out error messages, in case of errors.
149    It does expand all xi:include references and validates the result.
150
151  --check-product-group-xml <file>
152    Is parsing a group xml file from a product definition and puts out error messages, in case of errors.
153    
154  --check-kiwi-xml <file>
155  --check-kiwi-xml <project> <package>
156    Is parsing a kiwi xml file and puts out error messages, in case of errors.
157
158  --check-pattern-xml <file>
159    Is parsing a pattern xml file and puts out error messages, in case of errors.
160
161  --check-request-xml <file>
162    Is parsing a request xml file and puts out error messages, in case of errors.
163
164  --parse-build-desc <file> [<arch>]
165    Parse a spec, dsc or kiwi file with the Build script parser.
166 ";
167 }
168
169 #### FIXME: these functions are copied from src server. We should move it to some util class maybe.
170 my $srcrevlay = [qw{rev vrev srcmd5 version time user comment requestid}];
171 sub getrev {
172   my ($projid, $packid, $rev) = @_;
173   die("bad projid\n") if $projid =~ /\// || $projid =~ /^\./;
174   return {'srcmd5' => 'pattern', 'rev' => 'pattern'} if $packid eq '_pattern';
175   die("bad packid\n") if $packid =~ /\// || $packid =~ /^\./;
176   undef $rev if $rev && ($rev eq 'latest' || $rev eq 'build');
177   undef $rev if $rev && $rev eq 'upload' && ! -e "$projectsdir/$projid.pkg/$packid.upload-MD5SUMS";
178   if (!defined($rev)) {
179     $rev = BSFileDB::fdb_getlast("$projectsdir/$projid.pkg/$packid.rev", $srcrevlay);
180     $rev = {'srcmd5' => 'empty'} unless $rev;
181   } elsif ($rev =~ /^[0-9a-f]{32}$/) {
182     return undef unless -e "$projectsdir/$projid.pkg/$packid.rev";
183     $rev = {'srcmd5' => $rev, 'rev' => $rev};
184   } elsif ($rev eq 'upload') {
185     $rev = {'srcmd5' => 'upload', 'rev' => 'upload'}
186   } elsif ($rev eq 'repository') {
187     $rev = {'srcmd5' => 'empty', 'rev' => 'repository'}
188   } else {
189     $rev = BSFileDB::fdb_getmatch("$projectsdir/$projid.pkg/$packid.rev", $srcrevlay, 'rev', $rev);
190   }
191   $rev->{'srcmd5'} =~ s/\/.*// if $rev;         # XXX still needed?
192   return $rev;
193 }
194 sub lsrep {
195   my ($projid, $packid, $srcmd5) = @_;
196   die("no such revision\n") unless defined $srcmd5;
197   local *F;
198   die("bad packid\n") if $packid =~ /\// || $packid =~ /^\./;
199   if ($srcmd5 eq 'upload') {
200     open(F, '<', "$projectsdir/$projid.pkg/$packid.upload-MD5SUMS") || die("$packid/$srcmd5-$packid: not in repository\n");
201   } elsif ($srcmd5 eq 'pattern') {
202     open(F, '<', "$projectsdir/$projid.pkg/pattern-MD5SUMS") || return {};
203   } elsif ($srcmd5 eq 'empty') {
204     return {};
205   } else {
206     die("bad srcmd5 '$srcmd5'\n") if $srcmd5 !~ /^[0-9a-f]{32}$/;
207     if (!open(F, '<', "$srcrepdir/$packid/$srcmd5-MD5SUMS")) {
208       return {'_linkerror' => $srcmd5} if -e "$srcrepdir/$packid/$srcmd5-_linkerror";
209       die("$packid/$srcmd5-$packid: not in repository\n");
210     }
211   }
212   my @files = <F>;
213   close F;
214   chomp @files;
215   return {map {substr($_, 34) => substr($_, 0, 32)} @files};
216 }
217 sub repreadxml {
218   my ($rev, $packid, $filename, $md5, $dtd, $nonfatal) = @_;
219   return readxml("$srcrepdir/$packid/$md5-$filename", $dtd, $nonfatal);
220 }
221 sub findprojects {
222   local *D;
223   opendir(D, $projectsdir) || die("$projectsdir: $!\n");
224   my @projids = grep {s/\.xml$//} readdir(D);
225   closedir(D);
226   return sort @projids;
227 }
228 sub findpackages {
229   my ($projid) = @_;
230   my @packids;
231   if (opendir(D, "$projectsdir/$projid.pkg")) {
232     @packids = grep {s/\.xml$//} readdir(D);
233     closedir(D);
234   }
235   return sort @packids;
236 }
237
238 sub updatelinkinfodb {
239   my ($projid, $packid, $rev, $files) = @_;
240
241   mkdir_p($sourcedb) unless -d $sourcedb;
242   my $linkdb = BSDB::opendb($sourcedb, 'linkinfo');
243   my $linkinfo;
244   if ($files && $files->{'_link'}) {
245     my $l = repreadxml($rev, $packid, '_link', $files->{'_link'}, $BSXML::link, 1);
246     if ($l) {
247       $linkinfo = {};
248       $linkinfo->{'project'} = defined $l->{'project'} ? $l->{'project'} : $projid;
249       $linkinfo->{'package'} = defined $l->{'package'} ? $l->{'package'} : $packid;
250       $linkinfo->{'rev'} = $l->{'rev'} if defined $l->{'rev'};
251     }    
252   }
253   $linkdb->store("$projid/$packid", $linkinfo);
254 }
255 sub findfile {
256   my ($projid, $packid, $repoid, $ext, $files) = @_;
257   $files = lsrep($projid, $packid, $files) unless ref $files;
258   return ($files->{"$packid-$repoid.$ext"}, "$packid-$repoid.$ext") if defined($repoid) && $files->{"$packid-$repoid.$ext"};
259   return ($files->{"$packid.$ext"}, "$packid.$ext") if $files->{"$packid.$ext"} && defined($repoid);
260   my @files = grep {/\.$ext$/} keys %$files;
261   @files = grep {/^\Q$packid\E/i} @files if @files > 1;
262   return ($files->{$files[0]}, $files[0]) if @files == 1;
263   if (@files > 1) {
264     if (!defined($repoid)) {
265       # return (undef, undef);
266       @files = sort @files;
267       return ($files->{$files[0]}, $files[0]);
268     }
269     @files = grep {/^\Q$packid-$repoid\E/i} @files if @files > 1;
270     return ($files->{$files[0]}, $files[0]) if @files == 1;
271   }
272   return (undef, undef);
273 }
274 #### end of copy from src server
275
276 sub find_latest_file {
277   my ($project, $package, $type) = @_;
278
279   my $rev = getrev($project, $package);
280   if (!$rev || $rev->{'srcmd5'} eq 'empty') {
281     return ( "Refered to non existing $type in $project $package" );
282   }
283   my $files = lsrep($project, $package, $rev->{'srcmd5'});
284 # FIXME: handle source links
285 #   $files = handlelinks($projid, $pinfo, $files, $rev) if ref($files) && $files->{'_link'};
286   if (!ref $files) {
287     return( "could not get file list for $project $package" );
288   }
289   my ($md5, $file) = findfile($project, $package, undef, $type, $files);
290   return ($md5, $file);
291 }
292
293 sub dump_nStore {
294   my ($file) = @_;
295   my $cache = Storable::retrieve($file) || {};
296   print Dumper($cache);
297   return $cache
298 }
299
300 sub dump_cache {
301   my ($project, $repo, $arch) = @_;
302   my $full = "$reporoot/$project/$repo/$arch/:full";
303   return dump_solv("$full.solv") if -e "$full.solv";
304   return dump_nStore("$full.cache") if -e "$full.cache";
305   die("neither $full.cache nor $full.solv exists\n");
306 }
307
308 sub dump_solv {
309   my ($fn) = @_;
310   my $pool = BSSolv::pool->new();
311   my $repo = $pool->repofromfile(0, $fn);
312   my %names = $repo->pkgnames();
313   my $r = {};
314   for my $p (values %names) {
315     $r->{$pool->pkg2name($p)} = $pool->pkg2data($p);
316   }
317   print Dumper($r);
318 }
319
320 sub clone_repository {
321   my ($srcproject, $srcrepo, $destproject, $destrepo) = @_;
322   my $srcdir  = "$reporoot/$srcproject/$srcrepo";
323   my $destdir = "$reporoot/$destproject/$destrepo";
324   my $tmpdir  = "$BSConfig::bsdir/tmp";
325
326   die("Destination repo must get created by scheduler first!\n") unless -d $destdir;
327
328   mkdir_p($tmpdir) || die("mkdir_p $tmpdir: $!\n");
329   $tmpdir .= "/bs_admin.$$";
330   if (-d $tmpdir) {
331     system('rm', '-rf', $tmpdir) && die("removing of $tmpdir failed!\n");
332   }
333   if (-d "$tmpdir.old") {
334     system('rm', '-rf', "$tmpdir.old") && die("removing of $tmpdir.old failed!\n");
335   }
336
337   print "cloning $srcproject / $srcrepo\n";
338   system('cp', '-al', $srcdir, $tmpdir) && die("cloning failed !");
339
340   # remove jobhistory files
341   for my $a (ls($tmpdir)) {
342     unlink("$tmpdir/$a/:jobhistory");
343   }
344
345   print "exchanging with $destproject / $destrepo\n";
346   rename($destdir, "$tmpdir.old") || die("rename $destdir $tmpdir.old: $!\n");
347   rename($tmpdir, $destdir) || die("rename $tmpdir $destdir: $!\n");
348
349   print "tell schedulers about the change ";
350   my @archs = grep {-d "$destdir/$_"} ls($destdir);
351   for my $a (@archs) {
352     print "$a, ";
353     write_event($destproject, $destrepo, $a, 'scanrepo');
354   }
355
356   print "\nremoving old tree in $tmpdir.old\n";
357   system('rm', '-rf', "$tmpdir.old") && die("removing of $tmpdir.old failed!\n");
358
359   print "finished. Have a nice day.\n";
360 }
361
362 sub update_request_db {
363   my $requestdb  = "$BSConfig::bsdir/db/request";
364   my $requestdir = "$BSConfig::bsdir/requests";
365   mkdir_p($requestdb) unless -d $requestdb;
366
367   my $db = BSDB::opendb($requestdb, '');
368   $db->{'noindex'} = {'id' => 1};
369
370   my @allrequests = ls($requestdir);
371   my $i = 0;
372   my $count = @allrequests;
373   for my $rid (@allrequests) {
374     next if $rid eq ".nextid";
375     $i++;
376     print "$i / $count        \r";
377     my $req = readxml("$requestdir/$rid", $BSXML::request, 1);
378     print "WARNING: unable to parse request: $rid!\n" unless $req;
379     $db->updateindex($rid, {}, $req || {});
380   }
381 }
382
383 sub check_xml_file {
384   my ($file, $type) = @_;
385
386   print "parsing $file\n";
387   my $xmldesc = readxml("$file", $type, 0);
388   if ( defined($xmldesc) ) {
389     print "Succesfull parsed file !\n";
390   } else {
391     die("ERROR: Unable to parse xml file !\n");
392   }
393 }
394
395 sub check_product_xml_file {
396   my ($file) = @_;
397
398   print "parsing $file\n";
399   my $xmldesc = BSProductXML::readproductxml("$file", 0, 1 );
400   if ( defined($xmldesc) ) {
401     print "Succesfull parsed file !\n";
402   } else {
403     die("ERROR: Unable to parse xml file !\n");
404   }
405 }
406
407
408 sub check_kiwi_xml {
409   my ($project, $package) = @_;
410
411   my ($md5, $file) = find_latest_file($project, $package, 'kiwi');
412   if (defined($md5) && defined($file)) {
413     my $f = "$srcrepdir/$package/$md5-$file";
414     check_xml_file($f, $BSKiwiXML::kiwidesc);
415   } else {
416     die("ERROR: No kiwi config file found in $project / $package !\n");
417   }
418 }
419
420 sub check_meta_xml {
421   my ($project, $package) = @_;
422   my $file;
423
424   if (defined($package)){
425     $file = "$projectsdir/${project}.pkg/${package}.xml";
426     $metadesc = readxml("$file", $BSXML::pack, 0);
427   } else {
428     $file = "$projectsdir/$project.xml";
429     $metadesc = readxml("$file", $BSXML::proj, 0);
430   }
431
432   if (defined($metadesc)) {
433     print "Succesfull parsed $file !\n";
434   } else {
435     die("ERROR: Unable to parse Meta XML in $file !\n");
436   }
437 }
438
439 sub write_event {
440   my ($project, $repo, $arch, $event, $package) = @_;
441   my $evname = "${event}::$project";
442   $evname .= "::$package" if defined $package;
443   $evname .= "::$repo" if defined $repo;
444   my $ev = { 'type' => $event };
445   $ev->{'project'} = $project if defined $project;
446   $ev->{'package'} = $package if defined $package;
447   $ev->{'repository'} = $repo if defined $repo;
448   writexml("$eventroot/$arch/.$evname$$", "$eventroot/$arch/$evname", $ev, $BSXML::event);
449   local *F;
450   if (sysopen(F, "$eventroot/$arch/.ping", POSIX::O_WRONLY|POSIX::O_NONBLOCK)) {
451     syswrite(F, 'x');
452     close(F);
453   }
454 }
455
456 sub scan_repo {
457   my ($project, $repo, $arch) = @_;
458   write_event( $project, $repo, $arch, 'scanrepo' );
459 }
460
461 sub wipe_notyet {
462   my ($project, $repo, $arch) = @_;
463   write_event( $project, $repo, $arch, 'wipenotyet' );
464 }
465
466 sub dump_state {
467   my ($arch) = @_;
468   write_event( undef, undef, $arch, 'dumpstate' );
469 }
470
471 sub shutdown_scheduler {
472   my ($arch) = @_;
473   write_event( '', undef, $arch, 'exitcomplete' );
474 }
475
476 sub check_project {
477   my ($project, $repo, $arch, $deep) = @_;
478   if (defined $deep) {
479     write_event($project, $repo, $arch, 'package');
480   } else {
481     write_event($project, $repo, $arch, 'recheck');
482   }
483 }
484
485 sub check_package {
486   my ($project, $package, $arch) = @_;
487   write_event($project, undef, $arch, 'package', $package);
488 }
489
490 # make stdout non-buffered
491 $| = 1;
492
493 #
494 # Argument parsing
495 #
496 if ( @ARGV < 1 ){
497   echo_help();
498   exit(1);
499 }
500
501 while (@ARGV) {
502   my $arg = shift @ARGV;
503   if ($arg eq "--help") {
504     echo_help();
505     exit(0);
506   }
507   if ($arg eq "--check-meta-xml") {
508     die("ERROR: need at least a project name as argument!\n") if @ARGV < 1;
509     my $project = shift @ARGV;
510     if (@ARGV == 1) {
511       my $package = shift @ARGV;
512       check_meta_xml($project, $package);
513     } else {
514       check_meta_xml($project);
515     }
516   } elsif ($arg eq "--check-product-group-xml") {
517     die("ERROR: need a file name as argument!\n") if @ARGV != 1;
518     my $file = shift @ARGV;
519     check_xml_file($file, $BSProductXML::group);
520   } elsif ($arg eq "--check-product-xml") {
521     die("ERROR: need a file name as argument!\n") if @ARGV != 1;
522     my $file = shift @ARGV;
523     check_product_xml_file($file);
524   } elsif ($arg eq "--check-pattern-xml") {
525     die("ERROR: need a file name as argument!\n") if @ARGV != 1;
526     my $file = shift @ARGV;
527     check_xml_file($file, $BSXML::pattern);
528   } elsif ($arg eq "--check-request-xml") {
529     die("ERROR: need a file name !\n") if @ARGV != 1;
530     my $file = shift @ARGV;
531     check_xml_file($file, $BSXML::request);
532   } elsif ($arg eq "--update-request-db") {
533     BSUtil::drop_privs_to($BSConfig::bsuser, $BSConfig::bsgroup);
534     update_request_db();
535   } elsif ($arg eq "--update-source-db") {
536     BSUtil::drop_privs_to($BSConfig::bsuser, $BSConfig::bsgroup);
537     for my $projid (findprojects()) {
538       for my $packid (findpackages($projid)) {
539         print "$projid/$packid\n";
540         my $rev = getrev($projid, $packid);
541         my $files = lsrep($projid, $packid, $rev->{'srcmd5'});
542         updatelinkinfodb($projid, $packid, $rev, $files);
543       }
544     }
545   } elsif ($arg eq "--check-kiwi-xml") {
546     die("ERROR: need either file name or project and package as argument!\n") if @ARGV < 1;
547     if (@ARGV == 1){
548       my $file = shift @ARGV;
549       check_xml_file($file, $BSKiwiXML::kiwidesc);
550     } else {
551       my $project = shift @ARGV;
552       my $package = shift @ARGV;
553       check_kiwi_xml($project, $package);
554     }
555   } elsif ($arg eq "--parse-build-desc") {
556     die("ERROR: need a file name as argument (spec, dsc or kiwi)!\n") if @ARGV < 1;
557     my $file = shift @ARGV;
558     my $cf = $cfile = $arch = undef;
559     $arch = shift @ARGV if @ARGV > 0;
560     if (@ARGV > 0) {
561       $cfile = shift @ARGV if @ARGV == 1;
562       $cf = Build::read_config( $arch, $cfile );
563     };
564     $cf->{'arch'} = $arch if $arch;
565     my $ret = Build::parse($cf, $file);
566     print Dumper($ret);
567   } elsif ($arg eq "--parse-hdrmd5") {
568     die("ERROR: need a file name as argument (rpm or deb)!\n") if @ARGV != 1;
569     my $file = shift @ARGV;
570     my $ret = Build::queryhdrmd5($file);
571     print Dumper($ret);
572   } elsif ($arg eq "--dump-cache") {
573     if (@ARGV == 1) {
574       $fullfile = shift @ARGV;
575       die("ERROR: invalid filename (must end with .cache or .solv)\n") if $fullfile !~ /\.(?:solv|cache)$/;
576       dump_solv($fullfile) if $fullfile =~ /\.solv$/;
577       dump_nStore($fullfile) if $fullfile =~ /\.cache$/;
578     } else {
579       die("ERROR: need project, repository and architecture as argument!\n") if @ARGV < 3;
580       my $project = shift @ARGV;
581       my $repo = shift @ARGV;
582       my $arch = shift @ARGV;
583       dump_cache($project, $repo, $arch);
584     }
585   } elsif ($arg eq "--dump-relsync" || $arg eq "--set-relsync") {
586     die("ERROR: need file as argument!\n") if @ARGV < 1;
587     my $file = shift @ARGV;
588     my $s = dump_nStore($file);
589     my $key = shift @ARGV;
590     my $value = shift @ARGV;
591     if ( defined($key) && defined($value) ){
592       $s->{$key} = $value;
593       print "\nChanged to:\n";
594       print Dumper($s);
595       Storable::nstore($s, $file);
596     };
597   } elsif ($arg eq "--dump-state") {
598     die("ERROR: need architecture as argument!\n") if @ARGV < 1;
599     my $arch = shift @ARGV;
600     dump_state( $arch );
601   } elsif ($arg eq "--shutdown-scheduler") {
602     die("ERROR: need architecture as argument!\n") if @ARGV < 1;
603     my $arch = shift @ARGV;
604     shutdown_scheduler( $arch );
605   } elsif ( $arg eq "--check-project" ) {
606     die("ERROR: need at least project and architecture as argument!\n") if @ARGV < 2;
607     my $project = shift @ARGV;
608     my $repo;
609     $repo = shift @ARGV if @ARGV == 2;
610     my $arch = shift @ARGV;
611     check_project($project, $repo, $arch);
612   } elsif ( $arg eq "--check-package" ) {
613     die("ERROR: need project, package and architecture as argument!\n") if @ARGV < 3;
614     my $project = shift @ARGV;
615     my $package = shift @ARGV;
616     my $arch = shift @ARGV;
617     check_package($project, $package, $arch);
618   } elsif ( $arg eq "--deep-check-project" ) {
619     die("ERROR: need at least project and architecture as argument!\n") if @ARGV < 2;
620     my $project = shift @ARGV;
621     my $repo;
622     $repo = shift @ARGV if @ARGV == 2;
623     my $arch = shift @ARGV;
624     check_project($project, $repo, $arch, 1);
625   } elsif ( $arg eq "--publish-repository" ) {
626     die("ERROR: need project, repository and architecture as argument!\n") if @ARGV != 2;
627     my $project = shift @ARGV;
628     my $repo = shift @ARGV;
629     my $evname = "${project}::${repo}";
630     my $ev = { 'type' => "publish" };
631     $ev->{'project'} = $project;
632     $ev->{'repository'} = $repo;
633     writexml("$eventroot/publish/.$evname$$", "$eventroot/publish/$evname", $ev, $BSXML::event);
634     local *F;
635     if (sysopen(F, "$eventroot/publish/.ping", POSIX::O_WRONLY|POSIX::O_NONBLOCK)) {
636       syswrite(F, 'x');
637       close(F);
638     }
639   } elsif ( $arg eq "--clone-repository" ) {
640     die("ERROR: need source project & repository and destination project & repository as argument!\n") if @ARGV < 3;
641     my $srcproject = shift @ARGV;
642     my $srcrepo = shift @ARGV;
643     my $destproject;
644     my $destrepo;
645     if (@ARGV == 1) {
646        $destrepo = shift @ARGV;
647        $destproject = $srcproject;
648     } else {
649        $destproject = shift @ARGV;
650        $destrepo = shift @ARGV;
651     }
652     clone_repository($srcproject, $srcrepo, $destproject, $destrepo);
653   } elsif ($arg eq "--rescan-repository") {
654     die("ERROR: need project, repository and architecture as argument!\n") if @ARGV < 3;
655     my $project = shift @ARGV;
656     my $repo = shift @ARGV;
657     my $arch = shift @ARGV;
658     scan_repo( $project, $repo, $arch );
659   } elsif ($arg eq "--force-check-project") {
660     die("ERROR: need project, repository and architecture as argument!\n") if @ARGV < 3;
661     my $project = shift @ARGV;
662     my $repo = shift @ARGV;
663     my $arch = shift @ARGV;
664     wipe_notyet($project, $repo, $arch);
665     check_project($project, $repo, $arch);
666   } elsif ($arg eq "--create-patchinfo-from-updateinfo") {
667     my $uf = shift @ARGV;
668     my $pooldirecotory = shift @ARGV;
669     my $updateinfo = readxml($uf, $BSXML::updateinfoitem);
670     my $patchinfo= {};
671     $patchinfo->{'name'} = $updateinfo->{'id'};
672     $patchinfo->{'summary'} = $updateinfo->{'title'};
673     $patchinfo->{'description'} = $updateinfo->{'description'};
674     $patchinfo->{'version'} = $updateinfo->{'version'};
675     $patchinfo->{'category'} = $updateinfo->{'type'};
676     $patchinfo->{'category'} = "normal" if $updateinfo->{'type'} eq "recommended";
677     $patchinfo->{'packager'} = $updateinfo->{'from'};
678     $patchinfo->{'bugzilla'} = [];
679     $patchinfo->{'CVE'} = [];
680     for my $ref (@{$updateinfo->{'references'}->{'reference'} || []}) {
681       if ($ref->{'type'} eq 'bugzilla') {
682         my $b = { '_content' => $ref->{'id'} };
683         push @{$patchinfo->{'bugzilla'}}, $b;
684       } elsif ($ref->{'type'} eq 'cve') {
685         push @{$patchinfo->{'CVE'}}, $ref->{'id'};
686       } else {
687        die("Unhadled type $ref->{'type'}");
688       };
689     };
690     delete $patchinfo->{'CVE'} unless @{$patchinfo->{'CVE'}} > 0;
691     delete $patchinfo->{'bugzilla'} unless @{$patchinfo->{'bugzilla'}} > 0;
692     my $id = "$patchinfo->{'name'}-$patchinfo->{'version'}";
693     mkdir($id);
694     writexml("._patchinfo", "$id/_patchinfo", $patchinfo, $BSXML::patchinfo);
695
696     for my $file (@{$updateinfo->{'pkglist'}->{'collection'}[0]->{'package'} || []}) {
697       system( "find $pooldirecotory -name $file->{'filename'} | xargs -I {} cp {} $id/" ) && die( "$file->{'filename'} not found in $pooldirecotory" );
698     }
699     my $ufc;
700     $ufc->{'update'} = [];
701     push @{$ufc->{"update"}}, $updateinfo;
702     writexml("$id/.updateinfo.xml", "$id/updateinfo.xml", $ufc, $BSXML::updateinfo);
703   } else {
704     echo_help();
705     exit(1)
706   }
707 }
708