source: ProjectBuilder/devel/rpmbootstrap/bin/rpmbootstrap@ 1177

Last change on this file since 1177 was 1177, checked in by Bruno Cornec, 13 years ago

r4161@eelzbach2: bruno | 2011-02-06 21:07:30 +0100

  • Introduction of a new hash $pbos to manage all os related info through a single data structure. All functions reviewed accordingly. Externally transparent, hopefully, but much cleaner code as a consequence. VM/VE/RM remains to be tested.
File size: 15.7 KB
RevLine 
[976]1#!/usr/bin/perl -w
2#
3# rpmbootstrap application, a debootstrap like for RPM distros
4#
5# $Id$
6#
7# Copyright B. Cornec 2010
8# Provided under the GPL v2
9
10# Syntax: see at end
11
12use strict 'vars';
13use Getopt::Long qw(:config auto_abbrev no_ignore_case);
14use Data::Dumper;
15use English;
16use LWP::UserAgent;
[984]17use File::Basename;
18use File::Copy;
19use File::Find;
[976]20use ProjectBuilder::Version;
21use ProjectBuilder::Base;
22use ProjectBuilder::Env;
23use ProjectBuilder::Conf;
24use ProjectBuilder::Distribution;
25
26# Global variables
27my %opts; # CLI Options
28
29=pod
30
31=head1 NAME
32
33rpmbootstrap - creates a chrooted RPM based distribution a la debootstrap, aka Virtual Environment (VE)
34
35=head1 DESCRIPTION
36
[1082]37rpmbootstrap creates a chroot environment (Virtual Environment or VE)
38with a minimal distribution in it, suited for building packages for example.
39It's very much like debootstrap but for RPM based distribution.
40It aims at supporting all distributions supported by project-builder.org
41(RHEL, RH, Fedora, OpeSUSE, SLES, Mandriva, ...)
[976]42
[1082]43It is inspired by work done by Steve Kemp for rinse (http://www.steve.org.uk/),
44and similar to mock, but fully integrated with project-builder.org
45(which also supports rinse and mock).
[976]46
47=head1 SYNOPSIS
48
[991]49rpmbootstrap [-vhmqpdk][-s script][-i iso][-a pkg1[,pkg2,...]] distribution-version-arch [target-dir] [mirror [script]]
[976]50
[1082]51rpmbootstrap [--verbose][--help][--man][--quiet][--print-rpms][--download-only]
52[--keep][--script script][--iso iso][--add pkg1,[pkg2,...]] distribution-version-arch [target-dir] [mirror [script]]
[976]53
54=head1 OPTIONS
55
56=over 4
57
58=item B<-v|--verbose>
59
60Print a brief help message and exits.
61
62=item B<-h|--help>
63
64Print a brief help message and exits.
65
66=item B<--man>
67
68Prints the manual page and exits.
69
70=item B<-q|--quiet>
71
72Do not print any output.
73
74=item B<-p|--print-rpms>
75
[1082]76Print the packages to be installed, and exit.
77Note that a target directory must be specified so rpmbootstrap can determine
78which packages should be installed, and to resolve dependencies.
79The target directory will be deleted.
[976]80
81=item B<-d|--download-only>
82
83Download packages, but don't perform installation.
84
85=item B<-k|--keep>
86
87Keep packages in the cache dir for later reuse. By default remove them.
88
89=item B<-s|--script script>
90
91Name of the script you want to execute on the related VEs after the installation.
[1082]92It is executed in host environment.
93You can use the chroot command to execute actions in the VE.
[976]94
95=item B<-i|--iso iso_image>
96
97Name of the ISO image of the distribution you want to install on the related VE.
98
[991]99=item B<-a|--add pkg1[,pkg2,...]>
100
[1082]101Additional packages to add from the distribution you want to install on the related VE
102at the end of the chroot build.
[991]103
[976]104=back
105
106=head1 ARGUMENTS
107
[1044]108=over 4
109
[976]110=item B<distribution-version-arch>
111
112Full name of the distribution that needs to be installed in the VE. E.g. fedora-11-x86_64.
113
114=item B<target-dir>
115
[1082]116This is the target directory under which the VE will be created.
117Created on the fly if needed.
118If none is given use the default directory hosting VE for project-builder.org
119(Cf: vepath parameter in $HOME/.pbrc)
[976]120
[1044]121=back
122
[976]123=head1 EXAMPLE
124
125To setup a Fedora 12 distribution with an i386 architecture issue:
126
127rpmbootstrap fedora-12-i386 /tmp/fedora/12/i386
128
129=head1 WEB SITES
130
[1082]131The main Web site of the project is available at L<http://www.project-builder.org/>.
132Bug reports should be filled using the trac instance of the project at L<http://trac.project-builder.org/>.
[976]133
134=head1 USER MAILING LIST
135
[1082]136Cf: L<http://www.mondorescue.org/sympa/info/pb-announce> for announces and
137L<http://www.mondorescue.org/sympa/info/pb-devel> for the development of the pb project.
[976]138
139=head1 CONFIGURATION FILE
140
141Uses Project-Builder.org configuration file (/etc/pb/pb.conf or /usr/local/etc/pb/pb.conf)
142
143=head1 AUTHORS
144
145The Project-Builder.org team L<http://trac.project-builder.org/> lead by Bruno Cornec L<mailto:bruno@project-builder.org>.
146
147=head1 COPYRIGHT
148
149Project-Builder.org is distributed under the GPL v2.0 license
150described in the file C<COPYING> included with the distribution.
151
152=cut
153
154# ---------------------------------------------------------------------------
155
156my ($projectbuilderver,$projectbuilderrev) = pb_version_init();
157my $appname = "rpmbootstrap";
[983]158$ENV{'PBPROJ'} = $appname;
[976]159
160# Initialize the syntax string
161
162pb_syntax_init("$appname Version $projectbuilderver-$projectbuilderrev\n");
[984]163pb_temp_init();
[976]164
165GetOptions("help|?|h" => \$opts{'h'},
[1177]166 "man|m" => \$opts{'man'},
167 "verbose|v+" => \$opts{'v'},
168 "quiet|q" => \$opts{'q'},
169 "log-files|l=s" => \$opts{'l'},
170 "script|s=s" => \$opts{'s'},
171 "print-rpms|p" => \$opts{'p'},
172 "download-only|d" => \$opts{'d'},
173 "keep|k" => \$opts{'k'},
174 "iso|i=s" => \$opts{'i'},
175 "add|a=s" => \$opts{'a'},
176 "version|V=s" => \$opts{'V'},
[976]177) || pb_syntax(-1,0);
178
179if (defined $opts{'h'}) {
180 pb_syntax(0,1);
181}
182if (defined $opts{'man'}) {
183 pb_syntax(0,2);
184}
185if (defined $opts{'v'}) {
186 $pbdebug = $opts{'v'};
187}
188if (defined $opts{'q'}) {
189 $pbdebug=-1;
190}
191if (defined $opts{'l'}) {
192 open(pbLOG,"> $opts{'l'}") || die "Unable to log to $opts{'l'}: $!";
193 $pbLOG = \*pbLOG;
194 $pbdebug = 0 if ($pbdebug == -1);
[1177]195}
[976]196pb_log_init($pbdebug, $pbLOG);
[981]197#pb_display_init("text","");
[976]198
[981]199#if (defined $opts{'s'}) {
200#$pbscript = $opts{'s'};
201#}
202#if (defined $opts{'i'}) {
203#$iso = $opts{'i'};
204#}
[976]205
206# Get VE name
207$ENV{'PBV'} = shift @ARGV;
208die pb_syntax(-1,1) if (not defined $ENV{'PBV'});
209
210die "Needs to be run as root" if ($EFFECTIVE_USER_ID != 0);
211
212#
213# Initialize distribution info from pb conf file
214#
[984]215pb_log(0,"Starting VE build for $ENV{'PBV'}\n");
[1177]216my $pbos = pb_distro_get_context($ENV{'PBV'});
[976]217
218#
219# Check target dir
220# Create if not existent and use default if none given
221#
222pb_env_init_pbrc(); # to get content of HOME/.pbrc
223my $vepath = shift @ARGV;
224
[982]225#
226# Check for command requirements
227#
228my ($req,$opt) = pb_conf_get_if("oscmd","oscmdopt");
[1128]229pb_check_requirements($req,$opt,$appname);
[982]230
[976]231if (not defined $vepath) {
232 my ($vestdpath) = pb_conf_get_if("vepath");
[1177]233 $vepath = "$vestdpath->{'default'}/$pbos->{'name'}/$pbos->{'version'}/$pbos->{'arch'}";
[976]234}
235
[981]236die pb_log(0,"No target-dir specified and no default vepath found in $ENV{'PBETC'}\n") if (not defined $vepath);
[976]237
238pb_mkdir_p($vepath) if (! -d $vepath);
239
240#
241# Get the package list to download, store them in a cache directory
242#
243my ($rbscachedir) = pb_conf_get_if("rbscachedir");
[1177]244my ($pkgs,$mirror) = pb_distro_get_param($pbos->{'name'},$pbos->{'version'},$pbos->{'arch'},pb_conf_get("rbsmindep","rbsmirrorsrv"));
[976]245
246my $cachedir = "/var/cache/rpmbootstrap";
247$cachedir = $rbscachedir->{'default'} if (defined $rbscachedir->{'default'});
248
249# Point to the right subdir and create it if needed
[1177]250$cachedir .= "/$pbos->{'name'}-$pbos->{'version'}-$pbos->{'arch'}";
[976]251pb_mkdir_p($cachedir) if (! -d $cachedir);
252
253# Get the complete package name from the mirror
254#
255my $ua = LWP::UserAgent->new;
256$ua->timeout(10);
257$ua->env_proxy;
258
[984]259pb_log(0,"Downloading package list from $mirror ...\n");
[976]260my $response = $ua->get($mirror);
261if (! $response->is_success) {
[992]262 if ($mirror =~ /i386/) {
263 # Some distro have an i586 or i686 mirror dir instead for i386
[1177]264 warn "Unable to download packages from $mirror for $pbos->{'name'}-$pbos->{'version'}-$pbos->{'arch'}.";
[992]265 $mirror =~ s|/i386/|/i586/|;
266 $response = $ua->get($mirror);
267 if (! $response->is_success) {
[1177]268 die "Unable to download packages from $mirror for $pbos->{'name'}-$pbos->{'version'}-$pbos->{'arch'}";
[992]269 }
270 }
[976]271}
[983]272pb_log(3,"Mirror $mirror gave answer: ".Dumper($response->dump(maxlength => 0))."\n");
[976]273
[1031]274# Try to find where the repodata structure is for later usage
275my $repo = $mirror;
276my $found = 0;
[1177]277if ($pbos->{'install'} =~ /yum/) {
[1031]278 my $response1;
279 while ($found == 0) {
280 $response1 = $ua->get("$repo/repodata");
281 if (! $response1->is_success) {
282 $repo = dirname($repo);
283
284 pb_log(2,"REPO analyzed: $repo\n");
285 # There is a limit to the loop, when / is reached and nothing found
286 my ($scheme, $account, $host, $port, $path) = pb_get_uri($repo);
287 die "Unable to find the repodata structure of the mirror $mirror\nPlease check the URL or warn the dev team.\n" if ($path =~ /^[\/]+$/);
[1177]288
[1031]289 # / not reached, so looping
290 next;
291 } else {
292 # repodata found $repo is correct
293 $found = 1;
294 last;
295 }
296 }
297}
298
[983]299# Manages architectures specificities
[1177]300my $parch = $pbos->{'arch'};
301$parch = "i[3456]86" if ($pbos->{'arch'} eq "i386");
[983]302
303# Get the list of packages and their URL in this hash
304my %url;
305foreach my $l (split(/\n/,$response->as_string())) {
306 # Find a href ref
307 if ($l =~ /<a href="(.*)">(.*)<\/a>/i) {
308 my $url = $1;
309 my $pkg = $1;
310 my $desc = $2;
311 pb_log(3,"Found desc URL $desc: ");
312 # find an rpm package ref name-ver-tag.arch.rpm
313 if ($pkg =~ /(.+)-([^-]+)-([^-]+)\.(noarch|$parch)\.rpm$/) {
314 pb_log(3,"package ($1 + $2 + $3 + $4)\n");
315 $url{$1} = "$mirror/$url";
316 } else {
317 pb_log(3,"not a package\n");
318 }
319 }
[976]320}
321
[984]322#
323# Prepare early the yum cache env for the VE in order to copy in it packages on the fly
324#
[990]325my $oscachedir = "/tmp";
326my $osupdcachedir;
327my $osupdname = "";
328
[1177]329if ($pbos->{'install'} =~ /yum/) {
[990]330 $oscachedir = "$vepath/var/cache/yum/core/packages/";
331 $osupdcachedir = "$vepath/var/cache/yum/updates-released/packages/";
332 $osupdname = "YUM";
333 # Recent Fedora release use a new yum cache dir
[1177]334 if (($pbos->{'name'} eq "fedora") && ($pbos->{'version'} > 8)) {
335 $oscachedir = "$vepath/var/cache/yum/$pbos->{'arch'}/$pbos->{'version'}/fedora/packages";
336 $osupdcachedir = "$vepath/var/cache/yum/$pbos->{'arch'}/$pbos->{'version'}/updates/packages";
[990]337 $osupdcachedir = "$vepath/var/cache/yum/updates-released/packages/";
338 }
[1177]339} elsif ($pbos->{'install'} =~ /zypper/) {
340 $oscachedir = "$vepath/var/cache/zypp/packages/opensuse/suse/$pbos->{'arch'}";
[990]341 $osupdname = "Zypper";
[1177]342} elsif ($pbos->{'install'} =~ /urpmi/) {
[992]343 $oscachedir = "$vepath/var/cache/urpmi/rpms";
344 $osupdname = "URPMI";
[984]345}
[991]346pb_log(1,"Setting up $osupdname cache in VE\n");
[990]347pb_mkdir_p($oscachedir);
348pb_mkdir_p($osupdcachedir) if (defined $osupdcachedir);
[983]349
[976]350# For each package to process, get it, put it in the cache dir
351# and extract it in the target dir. If not asked to keep, remove it
352# Just download if asked so.
353
[983]354my $warning = 0;
355my $lwpkg ="";
[981]356foreach my $p (split(/,/,$pkgs)) {
[983]357 pb_log(1,"Processing package $p ...\n");
358 # Just print packages names if asked so.
[984]359 if (defined $url{$p}) {
360 if ($opts{'p'}) {
[983]361 pb_log(0,"$url{$p}\n");
[984]362 next;
[983]363 } else {
[984]364 # Now download if not already in cache
365 my $p1 = basename($url{$p});
366 if (! -f "$cachedir/$p1") {
367 pb_system("wget --quiet -O $cachedir/$p1 $url{$p}","Downloading package $p1 ...");
368 } else {
369 pb_log(1,"Package $p1 already in cache\n");
370 }
[1177]371
[984]372 # End if download only
373 if ($opts{'d'}) {
374 next;
375 }
[1177]376
[984]377 #
[990]378 # Copy the cached .RPM files into the oscachedir directory, so that os doesn't need to download them again.
[984]379 #
[990]380 pb_log(1,"Link package into $oscachedir\n");
[1177]381 copy("$cachedir/$p1",$oscachedir) if (defined $oscachedir);
382 symlink("$oscachedir/$p1","$osupdcachedir/p1") if (defined $osupdcachedir);
[984]383
384 # And extract it to the finale dir
385 pb_system("cd $vepath ; rpm2cpio $cachedir/$p1 | cpio -ivdum","Extracting package $p1 into $vepath");
[1177]386
[984]387 # Remove cached package if not asked to keep
388 if (! $opts{'k'}) {
389 unlink("$cachedir/$p1");
390 }
[1177]391
[983]392 }
[984]393 } else {
394 pb_log(0,"WARNING: unable to find URL for $p\n");
395 $warning++;
396 $lwpkg .= " $p";
[976]397 }
[983]398}
399
400if ($warning ge 1) {
[1177]401pb_log(0,"$warning WARNINGS found.\nMaybe you should review your package list for $pbos->{'name'}-$pbos->{'version'}-$pbos->{'arch'}\nand remove$lwpkg\n");
[983]402}
403
[984]404# Stop here if we just print
[983]405if ($opts{'p'}) {
406 exit(0);
407}
[984]408
409# Now executes the VE finalization steps required for it to work correctly
410pb_log(0,"VE post configuration\n");
411
412# yum needs that distro-release package be installed, so force it
[1177]413if ($pbos->{'install'} =~ /yum/) {
414 my $ddir = $pbos->{'name'};
[990]415 foreach my $p1 (<$cachedir/($ddir|redhat)-release-*.rpm>) {
416 copy("$cachedir/$p1","$vepath/tmp");
417 pb_system("chroot $vepath rpm -ivh --force --nodeps /tmp/$p1","Forcing RPM installation of $p1");
418 unlink("$vepath/tmp/$p1");
419 }
[984]420}
421#
422# Make sure there is a resolv.conf file present, such that DNS lookups succeed.
423#
424pb_log(1,"Creating resolv.conf\n");
425pb_mkdir_p("$vepath/etc");
426copy("/etc/resolv.conf","$vepath/etc/");
427
428#
429# BUGFIX:
430#
[1177]431if ((($pbos->{'name'} eq "centos") || ($pbos->{'name'} eq "rhel")) && ($pbos->{'version'} eq "5")) {
[984]432 pb_log(1,"BUGFIX for centos-5\n");
433 pb_mkdir_p("$vepath/usr/lib/python2.4/site-packages/urlgrabber.skx");
434 foreach my $i (<$vepath/usr/lib/python2.4/site-packages/urlgrabber/keepalive.*>) {
435 move($i,"$vepath/usr/lib/python2.4/site-packages/urlgrabber.skx/");
436 }
437}
438
439#
440# /proc needed
441#
442pb_mkdir_p("$vepath/proc");
443pb_system("mount -o bind /proc $vepath/proc","Mounting /proc");
444
445#
446# Some devices may be needed
447#
[992]448pb_mkdir_p("$vepath/dev");
[1029]449chmod 0755,"$vepath/dev";
[984]450pb_system("mknod -m 644 $vepath/dev/random c 1 8","Creating $vepath/dev/random") if (! -c "$vepath/dev/random");
451pb_system("mknod -m 644 $vepath/dev/urandom c 1 9","Creating $vepath/dev/urandom") if (! -c "$vepath/dev/urandom");
452pb_system("mknod -m 666 $vepath/dev/zero c 1 5","Creating $vepath/dev/zero") if (! -c "$vepath/dev/zero");
453
[990]454my $minipkglist;
455
[1027]456pb_log(1,"Adapting $osupdname repository entries\n");
[1177]457if ($pbos->{'install'} =~ /yum/) {
[984]458 #
459 # Force the architecture for yum
460 # The goal is to allow i386 chroot on x86_64
461 #
462 # FIX: Not sufficient to have yum working
463 # mirrorlist is not usable
464 # $releasever also needs to be filtered
465 # yum.conf as well
466 foreach my $i (<$vepath/etc/yum.repos.d/*>,"$vepath/etc/yum.conf") {
[1177]467 pb_system("sed -i -e 's/\$basearch/$pbos->{'arch'}/g' $i","","quiet");
468 pb_system("sed -i -e 's/\$releasever/$pbos->{'version'}/g' $i","","quiet");
[984]469 pb_system("sed -i -e 's/^mirrorlist/#mirrorlist/' $i","","quiet");
[1015]470 # rather use neutral separators here
[1031]471 pb_system("sed -i -e 's|^#baseurl.*\$|baseurl=$repo|' $i","","quiet");
[984]472 }
[990]473 $minipkglist = "ldconfig yum passwd vim-minimal dhclient authconfig";
[1177]474} elsif ($pbos->{'install'} =~ /zypper/) {
[990]475 pb_mkdir_p("$vepath/etc/zypp/repos.d");
[1177]476 open(REPO,"> $vepath/etc/zypp/repos.d/$pbos->{'name'}-$pbos->{'version'}") || die "Unable to create repo file";
[990]477 my $baseurl = dirname(dirname($mirror));
[992]478 # Setup the repo
[990]479 print REPO << 'EOF';
480[opensuse]
[1177]481name=$pbos->{'name'}-$pbos->{'version'}
[990]482baseurl=$baseurl
483enabled=1
484gpgcheck=1
[984]485
[990]486EOF
[992]487 close(REPO);
[990]488 $minipkglist = "zypper vim-minimal dhclient";
489 # Bootstraping zypper
[1177]490 if ($pbos->{'version'} eq "10.2") {
491 pb_system("chroot $vepath /bin/bash -c \"yes | /usr/bin/zypper sa $baseurl $pbos->{'name'}-$pbos->{'version'}\"","Bootstrapping Zypper");
[990]492 }
[1177]493} elsif ($pbos->{'install'} =~ /urpmi/) {
[992]494 # Setup the repo
[993]495 my $baseurl = dirname(dirname(dirname($mirror)));
496 pb_system("chroot $vepath /bin/bash -c \"urpmi.addmedia --distrib $baseurl\"","Bootstrapping URPMI");
497 $minipkglist = "ldconfig urpmi passwd vim-minimal dhcp-client";
[984]498}
499
500#
[990]501# Run "install the necessary modules".
502# No need for sudo here
503#
[1177]504$pbos->{'install'} =~ s/sudo//g;
505pb_system("chroot $vepath /bin/bash -c \"$pbos->{'install'} $minipkglist \"","Bootstrapping OS by running $pbos->{'install'} $minipkglist");
[990]506
507#
[984]508# make 'passwd' work.
509#
510pb_log(1,"Authfix\n");
511pb_system("chroot $vepath /bin/bash -c \"if [ -x /usr/bin/authconfig ]; then /usr/bin/authconfig --enableshadow --update; fi\"","Calling authconfig");
512
[991]513# Installed additional packages we were asked to
514if (defined $opts{'a'}) {
515 $opts{'a'} =~ s/,/ /g;
[1177]516 pb_system("chroot $vepath /bin/bash -c \"$pbos->{'install'} $opts{'a'} \"","Adding packages to OS by running $pbos->{'install'} $opts{'a'}");
[991]517}
518
[984]519#
520# Clean up
521#
522pb_log(1,"Cleaning up\n");
[1177]523if ($pbos->{'install'} =~ /yum/) {
[984]524 pb_system("chroot $vepath /usr/bin/yum clean all","Cleaning yum");
525}
526pb_system("umount $vepath/proc","Unmounting /proc");
527find(\&unlink_old_conf, $vepath);
528
529# Add additional packages if asked for
530
531# Executes post-install step if asked for
532if ($opts{'s'}) {
533 pb_system("$opts{'s'} $vepath","Executing the post-install script: $opts{'s'} $vepath");
534}
535
536# Function for File::Find
537sub unlink_old_conf {
538
539 unlink($_) if ($_ =~ /\.rpmorig$/);
540 unlink($_) if ($_ =~ /\.rpmnew$/);
541}
Note: See TracBrowser for help on using the repository browser.