Code

Swapped php4/php5 in dependencies
[gosa.git] / contrib / fai / goto-fai / ldap2fai
1 #!/usr/bin/perl
2 # $Id$
3 #*********************************************************************
4 #
5 # ldap2fai -- read FAI config from LDAP and create config space
6 #
7 # This script is part of FAI (Fully Automatic Installation)
8 # (c) 2005, Thomas Lange <lange@informatik.uni-koeln.de>
9 # (c) 2005, Jens Nitschke <jens.nitschke@2int.de>
10 # (c) 2005, Jan-Marek Glogowski <glogow@fbihome.de>
11 # (c) 2005, Cajus Pollmeier <pollmeier@gonicus.de>
12 #
13 #*********************************************************************
15 use strict;
16 use Net::LDAP;
17 use MIME::Base64;
18 use Getopt::Std;
19 use File::Path;
20 use File::Copy;
21 use vars qw/ %opt /;
23 my $base;
24 my $ldapuri;
25 my $ldapdir = "/etc/ldap/ldap.conf";
26 my $outdir = "/fai";
27 my $verbose = 0;
28 my $opt_string = 'c:d:hv';
29 my $hostname;
31 getopts( "$opt_string", \%opt ) or usage("Hello");
32 usage("Help") if $opt{h};
34 $verbose = $opt{v} ? 1 : 0;
35 $outdir  = $opt{d} ? $opt{d} : $outdir;
36 $ldapdir = $opt{c} ? $opt{c} : $ldapdir;
38 # Get MAC from cmdline
39 my $mac = shift @ARGV;
40 $mac eq '' && usage("MAC address not specified.");
42 # Is outdir a directory
43 -d "$outdir" || usage("'$outdir' is not a directory.\n");
45 my @classes=(); # the classes a host belongs to
47 # initialize ldap
48 setup();
49 my $ldap = Net::LDAP->new("$ldapuri") or die "$@";
50 my $mesg = $ldap->bind;
52 # create class hooks debconf disk_config package_config scripts files
53 my @dirs= qw/class hooks debconf disk_config package_config scripts files/;
54 foreach (@dirs) {
55   -d "$outdir/$_" || mkpath "$outdir/$_" 
56     || warn "WARNING: Can't create subdir $outdir/$_ $!\n";
57 }
59 @classes= get_classes($mac);
60 prt_scripts();
61 prt_package_list();
62 prt_debconf();
63 prt_templates();
64 prt_var();
65 prt_hooks();
66 prt_disk_config();
68 # create sources list
69 if (!$hostname) {
70   -d "${outdir}/files/etc/apt/sources.list" 
71     || mkpath "${outdir}/files/etc/apt/sources.list";
72   copy ("${outdir}/tmp/apt-sources.list",
73     "${outdir}/files/etc/apt/sources.list/$hostname") ;
74 }
76 $mesg = $ldap->unbind;   # take down session
77 exit 0;
79 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
80 sub setup
81 {
82   # Read LDAP
83   open (LDAPCONF,"${ldapdir}") 
84     || usage("Can't open LDAP configuration$!\n");
85   my @content=<LDAPCONF>;
86   close(LDAPCONF);
88   # Scan LDAP config
89   foreach my $line (@content) {
90     $line =~ /^\s*(#|$)/ && next;
91     chomp($line);
93     if ($line =~ /^BASE\s+(.*)$/) {
94       $base= $1;
95       next;
96     }
97     if ($line =~ m#^URI\s+ldaps?://([^/:]+).*$#) {
98       $ldapuri= $1;
99       next;
100     }
101   }
104 sub usage
106   (@_) && print STDERR "\n@_\n\n";
108   print STDERR << "EOF";
109 usage: $0 [-hv] [-c config] [-d outdir] <MAC>
111 -h        : this (help) message
112 -c        : LDAP config file (default: ${ldapdir})
113 -d        : output dir (default: ${outdir})
114 -v        : be verbose
115 EOF
116         exit -1;
118 #-----------------------------------------------------------------------------------
120 sub write_file {
122         my @opts = @_;
123         my $len = scalar @_;
124         ($len < 2) && return;
126         my $filename = shift;
127         my $data = shift;
129         open (SCRIPT,">${filename}") || warn "Can't create ${filename}. $!\n";
130         print SCRIPT $data;
131         close(SCRIPT);
133   ($opts[2] ne "") && chmod oct($opts[2]),${filename};
134         ($opts[3] ne "") && chown_files(${filename}, $opts[3]);
137 #-----------------------------------------------------------------------------------
139 sub chown_files
141   my @owner = split('.',@_[1]);
142   my $filename = @_[0];
143   my ($uid,$gid);
144   $uid = getpwnam(@owner[0]);
145   $gid = getgrnam(@owner[1]);
146   
147   chown $uid, $gid, $filename;
150 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
151 sub get_classes {
153         # return list of FAI classes defined for host
154         my $mac = shift;
155         my (@classes,$mesg,$entry);
157   $mesg = $ldap->search(
158     base => "ou=systems,$base",
159     filter => "(&(macAddress=$mac)(objectClass=gotoWorkstation))",
160     attrs => [ 'FAIclass', 'cn']);
161   $mesg->code && die $mesg->error;
162   # normally, only one value should be returned
163   if ($mesg->count != 1) {
164       die "LDAP search for client failed. ".$mesg->count." entries have been returned\n";
165     }
166   
167    # this assigns the last value to @classes     
168    $entry= ($mesg->entries)[0];
169    @classes= split /\s+/,$entry->get_value('FAIclass');
171   # get hostname
172         my $hname= $entry->get_value('cn');
173   my $dn= $entry->dn;
174   $hostname= $hname;
176   # Search for object groups containing this client
177   $mesg = $ldap->search(
178     base => "ou=groups,$base",
179     filter => "(&(objectClass=gosaGroupOfNames)(objectClass=FAIobject)(member=$dn))",
180     attrs => [ 'FAIclass' ]);
181   $mesg->code && die $mesg->error;
182         foreach my $m ($mesg->entries) {
183     push @classes, split /\s+/,$m->get_value('FAIclass');
184   }
186         # print all classes to the file with hostname
187         open (FAICLASS,">$outdir/class/$hname") || warn "Can't create $outdir/class/$hname. $!\n";
188   my @newclasses;
189         foreach my $class (@classes) {
191     # We need to walk through the list of classes and watch out for
192     # a profile which is named like the class. Replace the profile
193     # name by the names of the included classes.
194     $mesg = $ldap->search(
195       base => "ou=systems,$base",
196       filter => "(&(objectClass=FAIprofile)(cn=$class))",
197       attrs => [ 'FAIclass' ]);
198     $mesg->code && die $mesg->error;
200     if ($mesg->count > 0){
201       foreach my $m ($mesg->entries) {
202         foreach my $tc (split /\s+/,$m->get_value('FAIclass')){
203           print FAICLASS "$tc\n";
204           push @newclasses, $tc;
205         }
206       }
207     } else {
208       print FAICLASS "$class\n";
209       push @newclasses, $class;
210     }
211   }
212         close(FAICLASS);
213         print "Host $hname belongs to FAI classes: ",join ' ',@newclasses,"\n" if $verbose;
214         return @newclasses;
217 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
218 sub get_variables {
219         # gets all variables defined for a class
220         # returns a list of lines in bourne shell syntax
222    my $class = shift; 
223         my ($mesg,$var_base,$entry,$line,@vars);
225         $mesg = $ldap->search(
226                       base => "$base",
227                       filter => "(&(cn=$class)(objectClass=FAIvariable))",
228                       attrs => [ 'cn']);
229         return if ($mesg->count() == 0); # skip if no such object exists              
230         $mesg->code && die $mesg->error;
232         $entry=($mesg->entries)[0];
233         $var_base=$entry->dn;
235         $mesg = $ldap->search(
236                         base => "$var_base",
237                         filter => "(objectClass=FAIvariableEntry)",
238                         attrs => ['cn', 'FAIvariableContent']);
239         return if ($mesg->count() == 0); # skip if no such object exists
240         $mesg->code && die $mesg->error;
241                         
243         foreach $entry ($mesg->entries) {
244                 $line= sprintf "%s=\'%s\'\n", $entry->get_value('cn'), 
245                         $entry->get_value('FAIvariableContent');
246                 push @vars,$line;
247         }
248         return @vars;
251 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
252 sub prt_var {
254         my (@lines, $hname);
256         foreach my $class (@classes) {
257                 @lines = get_variables($class);
258                 next until @lines; # do not create .var file if no variables are defined
259                 open (FAIVAR,">$outdir/class/${class}.var") 
260       || warn "Can't create $outdir/class/$hname.var.$!\n";
261                 print FAIVAR @lines;
262                 close(FAIVAR);
263         }
266 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
267 sub get_disk_config {
269         my $class = shift;
270   my ($mesg,$entry,$line,@diskconfig,$partition_base,$dn,%diskline,$xxmesg);
272         # Search for partition schema for the specified class
273         $mesg = $ldap->search(
274                       base => "$base",
275                       filter => "(&(cn=$class)(objectClass=FAIpartitionTable))" );
277         return if ($mesg->count() == 0); # skip if no such object exists
278         $mesg->code && die $mesg->error;
280         $entry=($mesg->entries)[0];
281         $partition_base= $entry->dn;
282         
283         # Search for disks
284         $mesg = $ldap->search(
285                       base => "$partition_base",
286                       filter => "(objectClass=FAIpartitionDisk)" );
288         return if ($mesg->code == 32); # skip if no such object exists
289         $mesg->code && die $mesg->error;
291         foreach $entry ($mesg->entries) {
292     my $logic_count= 4;
293     my $primary_count= 0;
294                 my $dn=$entry->dn;
295                 my $disk=$entry->get_value('cn');
296     my $part;
297                 undef %diskline;
298                 $diskline{0} = "disk_config $disk\n";
299                 $xxmesg = $ldap->search(
300                         base => "$dn",
301                         filter => "objectClass=FAIpartitionEntry" );
302                 $xxmesg->code && die $xxmesg->error;
303                 foreach my $dl ($xxmesg->entries) {
304       if ($dl->get_value('FAIpartitionType') eq 'primary'){
305         $primary_count++;
306       } else {
307         $logic_count++;
308       }
309                         if ($dl->get_value('FAIpartitionFlags') eq 'preserve'){
310         if ($dl->get_value('FAIpartitionType') eq 'primary'){
311           $part= 'preserve'.$primary_count;
312         } else {
313           $part= 'preserve'.$logic_count;
314         }
315                                 $line= sprintf "%-7s %-12s %-12s %-10s ; %s\n",
316                                         $dl->get_value('FAIpartitionType'),
317                                         $dl->get_value('FAImountPoint'),
318                                         $part,
319                                         $dl->get_value('FAImountOptions') eq '' 
320                                                 ? 'rw' : $dl->get_value('FAImountOptions'),
321                                         $dl->get_value('FAIfsOptions');
322                         }         
323                         elsif ($dl->get_value('FAIfsType') eq 'swap'){
324                                 $line= sprintf "%-7s %-12s %-12s %-10s\n",
325                                 $dl->get_value('FAIpartitionType'),
326                                 $dl->get_value('FAImountPoint'),
327                                 $dl->get_value('FAIpartitionSize'),
328                                 $dl->get_value('FAImountOptions') eq '' 
329                                         ? 'rw' : $dl->get_value('FAImountOptions');
330                         } 
331                         else {
332                                 $line= sprintf "%-7s %-12s %-12s %-10s ; %s %s\n",
333                                 $dl->get_value('FAIpartitionType'),
334                                 $dl->get_value('FAImountPoint'),
335                                 $dl->get_value('FAIpartitionSize'),
336                                 $dl->get_value('FAImountOptions') eq '' 
337                                         ? 'rw' : $dl->get_value('FAImountOptions'),
338                                 $dl->get_value('FAIfsOptions'),
339                                 $dl->get_value('FAIfsType');
340                         }
342                         $diskline{$dl->get_value('FAIpartitionNr')}=$line;
343                 }
344                 foreach my $l (sort {$a <=> $b} keys %diskline) {
345                         push @diskconfig, $diskline{$l};
346                 }
347         }
348         return @diskconfig;
351 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
352 sub prt_disk_config {
354         # create one disk_config file
356         my ($class,@lines);
358         foreach $class (reverse @classes) {
359                 @lines=get_disk_config($class);
360                 next until @lines; # skip if nothing is defined for this class
362     print "Generating partition layout for class '${class}'\n." if $verbose;
363                 open (FAIVAR,">${outdir}/disk_config/${class}") 
364       || warn "Can't create $outdir/disk_config/$class. $!\n";
365                 print FAIVAR join '',@lines;
366                 close(FAIVAR);
367                 last; # finish when one config file is created
368         }
371 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
372 sub get_packages {
374         # gets list of packages defined for a class
376         my $class = shift;
377         my ($mesg,$entry,$line,$method,%packlist);
379   -d "${outdir}/tmp" || mkpath "${outdir}/tmp"
380     || warn "Can't create ${outdir}/tmp. $!\n";
381   print "Generate sources.list for install\n" if $verbose;
382         open (SOURCES,">>${outdir}/tmp/apt-sources.list") 
383     || warn "Can't create ${outdir}/tmp/apt-sources.list. $!\n";
385         $mesg = $ldap->search(
386                         base => "$base",
387                         filter => "(&(cn=$class)(objectClass=FAIpackageList))" ,
388                         attrs => [ 'FAIpackage', 'FAIinstallMethod', 
389                  'FAIdebianMirror', 'FAIdebianRelease', 'FAIdebianSection']);
391         $mesg->code && die $mesg->error;
392         # should also return only one value
394         undef %packlist;
395         foreach $entry ($mesg->entries) {
396                 $method=$entry->get_value('FAIinstallMethod');
397                 push @{$packlist{$method}}, $entry->get_value('FAIpackage');
399                 print SOURCES "deb ".$entry->get_value('FAIdebianMirror')." ".$entry->get_value('FAIdebianRelease')." ";
400     my $section;
401     foreach $section ($entry->get_value('FAIdebianSection')){
402       print SOURCES "$section ";
403     }
404     print SOURCES "\n";
405         }
407   close (SOURCES);
409         # return a ref to the hash of arrays (key of the hash is the method),
410         # the value is the array of package names for this method
411         return \%packlist;
414 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
415 sub prt_package_list {
417         my (@lines,$plist,$method,$value);
419         foreach my $class (@classes) {
420                 $plist=get_packages($class);
421                 # test if hash contains any keys or values
422                 unless (keys %{$plist}) {
423                         next;
424                 }
426     print "Generate package list for class '$class'.\n" if $verbose;
427                 open (PACKAGES,">$outdir/package_config/$class") 
428       || warn "Can't create $outdir/package_config/$class. $!\n";
429                 while (($method, $value) = each %{$plist}) {
430                         print PACKAGES "PACKAGES $method\n";
431                         print PACKAGES join "\n",@{$value};
432                         print PACKAGES "\n";
433                 }
434                 close(PACKAGES);
435         }
438 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
439 sub get_templates {
441         # get list of template-files defined for a class
442         my $class = shift;
443         my ($mesg,$entry,$str,$pfad,$name,$owner,$mode,$template_base,@template);
445         $mesg = $ldap->search(
446                         base => "$base",
447                         filter => "(&(cn=$class)(objectClass=FAItemplate))",
448                         attrs => ['cn']);
449         return if ($mesg->count() == 0); # skip if no such object exists
450         $mesg->code && die $mesg->error;
452         $entry=($mesg->entries)[0];
453         $template_base=$entry->dn;
455         $mesg = $ldap->search(
456                         base => "$template_base",
457                         filter => "(objectClass=FAItemplateEntry)",
458                         attrs => ['FAItemplateFile', 'FAItemplatePath', 'FAIowner', 'FAImode' ,'cn']);
459         return if ($mesg->count() == 0); # skip if no such object exists
460         $mesg->code && die $mesg->error;
462         foreach $entry ($mesg->entries) {
463                 $name = $entry->get_value('cn');
464                 $owner = $entry->get_value('FAIowner');
465                 $owner = $entry->get_value('FAImode');
466                 $pfad = $entry->get_value('FAItemplatePath');
467                 chomp($pfad);
468                 -d "${outdir}/files/${pfad}" || mkpath "${outdir}/files/${pfad}"
469       || warn "WARNING: Can't create subdir ${outdir}/files/${pfad} !$\n";
470     print "Generate template '$pfad' ($name) for class '$class'.\n" if $verbose;
471                 write_file( "${outdir}/files/${pfad}/${class}", 
472                         $entry->get_value('FAItemplateFile'),$entry->get_value('FAImode'),$entry->get_value('FAIowner'));
473         }
476 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
477 sub prt_templates {
478         my ($class);
480         foreach $class (reverse @classes) {
481                 get_templates($class);
482         }
485 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
486 sub get_debconf {
488         # gets list of packages defined for a class
490         my $class = shift; 
491         my ($mesg,$entry,$str,$debconf_base,@debconf);
493         $mesg = $ldap->search(
494                         base => "$base",
495                         filter => "(&(cn=$class)(objectClass=FAIpackageList))",
496                         attrs => ['cn']);
497         return if ($mesg->count() == 0); # skip if no such object exists
498         $mesg->code && die $mesg->error;
500         $entry=($mesg->entries)[0];
501         $debconf_base=$entry->dn;
503         $mesg = $ldap->search(
504                         base => "$debconf_base",
505                         filter => "(objectClass=FAIdebconfInfo)" ,
506                         attrs => [ 'FAIpackage', 'FAIvariable', 
507                                 'FAIvariableType','FAIvariableContent']);
508         $mesg->code && die $mesg->error;
510         # undef @debconf;
511         foreach $entry ($mesg->entries) {
512                 $str = sprintf "%s %s %s %s\n",
513                 $entry->get_value('FAIpackage'),
514                 $entry->get_value('FAIvariable'),
515                 $entry->get_value('FAIvariableType'),
516                 $entry->get_value('FAIvariableContent');
517                 push @debconf, $str;
518         }
519         return @debconf;
522 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
523 sub prt_debconf {
525         my @lines;
526         my $class;
528         foreach $class (@classes) {
529                 @lines = get_debconf($class);
530                 next until @lines;
531     print "Generate DebConf for class '$class'.\n" if $verbose;
532                 open (DEBCONF,">${outdir}/debconf/${class}") || warn "Can't create $outdir/debconf/$class. $!\n";
533                 print DEBCONF @lines;
534                 close(DEBCONF);
535         }
538 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
539 sub prt_scripts {
540         my ($class,@lines);
542         foreach $class (@classes) {
543                 get_scripts($class);
544         }
547 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
548 sub get_scripts {
550         # gets list of packages defined for a class
552         my $class = shift;
553         my ($mesg,$entry,$str,$script_base,$prio,$name,$script);
555         $mesg = $ldap->search(
556                         base => "$base",
557                         filter => "(&(cn=$class)(objectClass=FAIscript))",
558                         attrs => ['cn']);
559         return if ($mesg->count() == 0); # skip if no such object exists
560         $mesg->code && die $mesg->error;
562         $entry=($mesg->entries)[0];
563         $script_base= $entry->dn;
565         $mesg = $ldap->search(
566                         base => "$script_base",
567                         filter => "(objectClass=FAIscriptEntry)",
568                         attrs => ['FAIpriority', 'FAIscript', 'cn']);
569         return if ($mesg->count() == 0); # skip if no such object exists
570         $mesg->code && die $mesg->error;
571         
572         foreach $entry ($mesg->entries) {
573                 $name  = $entry->get_value('cn');
574                 $prio  = $entry->get_value('FAIpriority');
575                 $script= sprintf('%02d-%s', $prio, $name);
577     -d "$outdir/scripts/$class" || mkpath "$outdir/scripts/$class" ||
578        warn "WARNING: Can't create subdir $outdir/scripts/$class !$\n";
580                 write_file("${outdir}/scripts/${class}/${script}",
581                         $entry->get_value('FAIscript'), "0700");
582         }
585 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
586 sub prt_hooks {
587         my ($class,@lines);
589         foreach $class (reverse @classes) {
590                 get_hooks($class);
591         }
594 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
595 sub get_hooks {
597         # gets list of packages defined for a class
599         my $class = shift;
600         my ($mesg,$entry,$str,$hook_base,$prio,$task,$hook,$name);
602         $mesg = $ldap->search(
603                         base => "$base",
604                         filter => "(&(cn=$class)(objectClass=FAIhook))",
605                         attrs => ['cn']);
606         return if ($mesg->count() == 0); # skip if no such object exists
607         $mesg->code && die $mesg->error;
609         $entry=($mesg->entries)[0];
610         $hook_base= $entry->dn;
612         $mesg = $ldap->search(
613                         base => "$hook_base",
614                         filter => "(objectClass=FAIhookEntry)",
615                         attrs => ['FAItask', 'FAIscript', 'cn']);
616         return if ($mesg->count() == 0); # skip if no such object exists
617         $mesg->code && die $mesg->error;
618         
619         foreach $entry ($mesg->entries) {
620                 $name = $entry->get_value('cn');
621                 $task = $entry->get_value('FAItask');
622                 $prio = $entry->get_value('FAIpriority');
623                 $hook = sprintf('%s.%s', ${task}, ${class});
625                 write_file("${outdir}/hooks/${hook}", 
626                         $entry->get_value('FAIscript'), "0700");
627         }
630 # vim:ts=2:sw=2:expandtab:shiftwidth=2:syntax:paste