devi@0: #!/usr/bin/perl
devi@0: 
devi@0: 
devi@0: use strict;
devi@0: use Data::Dumper;
devi@0: use Switch;
devi@0: use XML::Simple;
devi@0: use Getopt::Long;
devi@4: use utf8;
devi@0: 
devi@40: use lib "/usr/local/bin";
devi@40: use l3config;
devi@40: 
devi@0: our $XMLClass;
devi@0: our $XMLCourse;
devi@0: our @Labs;
devi@0: 
devi@92: our %Machines;          # Machines list from class.xml
devi@92: our @SelectedMachines;      # Machines list given as the command line argument
devi@0: 
devi@0: our $Config_File = "labmaker.conf";
devi@40: our %Config_ = (
devi@92:     "show_host"     => "no",
devi@0: 
devi@92:     # Вспомогательные программы
devi@92:     #"l3-report"    => "./lm-report",
devi@92:     "l3-report" => "./l3-report",
devi@3: 
devi@92:     # Каталоги
devi@92:     "path_lilalo" => "/var/lilalo/",
devi@92:     "path_classes"  => "/var/lilalo/classes/",
devi@92:     "path_lablogs"  => "/var/lilalo/lablogs/",
devi@92:     "courses_path"  => "/var/lilalo/courses/",
devi@92:     "outpath"   => "/var/lilalo/out/",
devi@92:     "path_web"  => "/var/www/l3",       # Путь к web-отчётам
devi@92:     "path_share"    => "./share/",      # Путь к web-отчётам
devi@0: 
devi@92:     # Файлы
devi@92:     "runfile"   => "lm.run", 
devi@92:     "logfile"   => "lm.log", 
devi@0: 
devi@92:     "class"     => "class",                 # Имя файла класса
devi@92:     "class_suffix"  => ".xml",              # Cуффикс файла класса
devi@92:     "classfile" => "",
devi@0: 
devi@92:     "sshkey"    => "$ENV{HOME}/.ssh/id_dsa.pub",
devi@92:     "lmssh"     => "./lm-ssh",
devi@92:     "lminstall" => "./lm-install",
devi@92:     "ssh_user"  => "root",
devi@0: );
devi@0: 
devi@0: our %Run = (
devi@92:     "lab" => ""
devi@0: );
devi@0: 
devi@0: our %Scripts;
devi@0: 
devi@0: sub load_class;
devi@0: sub load_config;
devi@0: sub load_course;
devi@0: sub load_scripts;
devi@0: 
devi@92: sub lm_get;
devi@0: sub lm_next;
devi@0: sub lm_prev;
devi@0: sub lm_start;
devi@0: sub lm_stop;
devi@0: sub lm_set;
devi@0: sub lm_do;
devi@0: sub lm_report;
devi@0: sub lm_show_hosts;
devi@69: sub lm_show_email;
devi@0: sub lm_show_labs;
devi@0: 
devi@0: sub load_run;
devi@0: sub save_run;
devi@0: sub print_log;
devi@0: sub print_usage_info;
devi@0: sub main();
devi@0: 
devi@0: main();
devi@0: 
devi@0: sub main()
devi@0: {
devi@92:     binmode STDOUT, ":utf8";
devi@0: 
devi@92:     if (! @ARGV) {
devi@92:         print_usage_info();
devi@92:         exit(0);
devi@92:     }
devi@0: 
devi@92:     if ($ARGV[0] eq "get") {
devi@92:          lm_get;
devi@92:          exit(0);
devi@92:     }
devi@0: 
devi@92:     init_config();
devi@92:     #load_config;
devi@92:     load_run;
devi@92:     load_scripts;
devi@92:     load_class;
devi@92:     load_course;
devi@92:     
devi@92:     my $arg = join " ", @ARGV;
devi@0: 
devi@92:     # Getting @SelectedMachines if any
devi@92:     if ($arg =~ s/@(.*?)\s//) {
devi@92:         my $machines = $1;
devi@92:         my @list = split /,/, $machines;
devi@92:         for my $interval (@list) {
devi@92:             my ($first, $last) = split /-/, $interval;
devi@0: 
devi@92:             push @SelectedMachines, $first;
devi@92:             while ($first < $last) {
devi@92:                 push @SelectedMachines, ++$first;
devi@92:             }   
devi@92:         }
devi@92:     }
devi@92: 
devi@92:     # Choose command to do
devi@92:     switch ($arg) {
devi@92:         case "next" { lm_next }
devi@92:         case "prev" { lm_prev }
devi@92:         case /set / { $arg =~ /set (.*)/; lm_set $1 }
devi@92:         case "report"   { lm_report }
devi@92:         case "start"    { lm_start }
devi@92:         case "stop" { lm_stop }
devi@92:         case "show hosts" { lm_show_hosts }
devi@92:         case "show email" { lm_show_email }
devi@92:         case "show labs" { lm_show_labs }
devi@92:         case /do /  { $arg =~ /do (.*)/;  lm_do "$1" }
devi@92:         else        { print_usage_info() }
devi@92:     }
devi@92:     save_run;
devi@92:     exit(0);
devi@0: }
devi@0: 
devi@0: sub load_scripts
devi@0: {
devi@92:     open (SCRIPTS, "$Config{l3scripts}")
devi@92:         or die "Cant open l3scripts file: ".$Config{l3scripts}.": $!\n";
devi@92:     binmode SCRIPTS, ":utf8";
devi@92:     local $/;
devi@92:     $_=<SCRIPTS>;
devi@92:     close(SCRIPTS);
devi@40: 
devi@92:     %Scripts = ("empty-element", split (/###(.*)\n/));
devi@92:     delete($Scripts{"empty-element"});
devi@40: 
devi@0: }
devi@0: 
devi@0: sub load_config
devi@0: {
devi@92:     my %file_config;
devi@92:     my %argv_config;
devi@92:     #read_config_file(\%file_config, $Config_File);
devi@92:     GetOptions(\%argv_config, map "$_=s", keys %Config);
devi@92:     %Config = (%Config, %file_config, %argv_config);
devi@0: }
devi@0: 
devi@0: sub load_course
devi@0: {
devi@92:     $XMLCourse = XMLin($Config{"courses_path"}.$XMLClass->{"course"}.".xml", ForceArray => 1 )  
devi@92:         or die "Can't open file of the course ",$XMLClass->{"course"}," [with .xml extension]\n";
devi@92: #   print Dumper($XMLCourse);
devi@92:     for my $lab (@{$XMLCourse->{"module"}}) {
devi@92:         push @Labs, $lab->{"code"};
devi@92:     }
devi@0: }
devi@0: 
devi@0: sub load_class
devi@0: {
devi@92:     my $classfile =
devi@92:     $Config{"classfile"} || 
devi@92:     $Config{"path_classes"}."/".$Config{"class"}.$Config{"class_suffix"};
devi@92:     $XMLClass = XMLin($classfile , ForceArray => [ 'student' ] )  
devi@92:         or die "Can't open file of the class ",$classfile,"\n";
devi@0: 
devi@92:     for my $student (@{$XMLClass->{"student"}}) {
devi@92:         $Machines{$student->{"host"}} = {
devi@92:             "name"  => "$student->{firstname} $student->{surname}",
devi@92:             "firstname" => "$student->{firstname}",
devi@92:             "user"  => "$student->{user}",
devi@92:             "email" => "$student->{email}",
devi@92:             "student" => $student,
devi@92:         }   
devi@92:     }
devi@92: #   print Dumper($XMLClass);
devi@92: #   print Dumper(\%Machines);
devi@92: }
devi@92: 
devi@92: sub lm_get
devi@92: {
devi@92:     print "Getting class description file...";
devi@95:     if (system("cd $Config{path_classes}; rm -f class.xml ; wget xgu.ru/l3/classes/class.xml") ==0 )
devi@92:     {
devi@92:         print "Ok\n";
devi@92:     } 
devi@92:     else {
devi@92:         die "Can't load class file\n"
devi@92:     }
devi@0: }
devi@0: 
devi@0: 
devi@0: sub lm_next
devi@0: {
devi@92:     for(my $i=0; $i<=$#Labs; $i++){
devi@92:         if ( $Labs[$i] eq $Run{"lab"} ) {
devi@92:             if ($i < $#Labs) {
devi@92:                 lm_set($Labs[$i+1]);
devi@92:                 return ;
devi@92:             } else {
devi@92:                 die "Lab ", $Run{"lab"}, " is the last. Switch to next lab is impossible"
devi@92:             }
devi@92:         }
devi@92:         
devi@92:     }
devi@92:     die "Lab ", $Run{"lab"}, " not found. Don't know which is next"
devi@0: }
devi@0: 
devi@0: sub lm_prev
devi@0: # Switch to previous lab
devi@0: {
devi@92:     for(my $i=0; $i<=$#Labs; $i++){
devi@92:         if ( $Labs[$i] eq $Run{"lab"} ) {
devi@92:             if ($i > 0) {
devi@92:                 lm_set($Labs[$i-1]);
devi@92:                 return ;
devi@92:             } else {
devi@92:                 die "Lab ", $Run{"lab"}, " is the first. Switch to previous lab is impossible"
devi@92:             }
devi@92:         }
devi@92:         
devi@92:     }
devi@92:     die "Lab ", $Run{"lab"}, " not found. Don't know which is previous"
devi@0: }
devi@0: 
devi@0: sub lm_set
devi@0: # Switch to $_[0] lab
devi@0: # FIXME
devi@0: {
devi@92:     my $lab = shift;
devi@92:     print "Current lab is $lab\n";
devi@92:     $Run{"lab"} = "$lab";
devi@92:     lm_do "setlab", $lab;
devi@0: }
devi@0: 
devi@0: 
devi@0: sub lm_start
devi@0: # Start new training day
devi@0: {
devi@92:     print_log(`date`." STARTED\n");
devi@92:     if ($Run{"lab"}) {
devi@92:         lm_next;
devi@92:     }
devi@92:     else
devi@92:     {
devi@92:         # First lab in the course
devi@92:         lm_set($Labs[0]);
devi@92:     }
devi@0: }
devi@0: 
devi@0: sub lm_stop
devi@0: # Stop this training day
devi@0: {
devi@92:     print_log(`date`." STOPPED\n");
devi@0: }
devi@0: 
devi@0: 
devi@0: sub lm_show_hosts
devi@0: # Show hosts used to run a commands
devi@0: {
devi@92:     my $i=1;
devi@92:     for my $m (sort keys %Machines) {
devi@92:         if (!@SelectedMachines || grep /^$i$/, @SelectedMachines) {
devi@92:             print "($i)","\t",$m,"\t",$Machines{$m}->{"name"},"\n";
devi@92:         }   
devi@92:         $i++;
devi@92:     }
devi@0: }
devi@0: 
devi@69: sub lm_show_email
devi@69: # Show hosts used to run a commands
devi@69: {
devi@92:     my $i=1;
devi@92:     for my $m (sort keys %Machines) {
devi@92:         if (!@SelectedMachines || grep /^$i$/, @SelectedMachines) {
devi@92:             print $Machines{$m}->{"email"},"\t",$Machines{$m}->{"name"},"\n";
devi@92:         }   
devi@92:         $i++;
devi@92:     }
devi@69: }
devi@69: 
devi@0: sub lm_show_labs
devi@0: # Show hosts used to run a commands
devi@0: {
devi@92:     my $i=1;
devi@92:     for my $lab (@Labs) {
devi@92:         print $lab;
devi@92:         print "*" if $lab eq $Run{"lab"};
devi@92:         print "\n";
devi@92:     }
devi@0: }
devi@0: 
devi@0: sub lm_do
devi@0: # Do the $_[0] command on all of the hosts 
devi@0: {
devi@92:     my $command = shift;
devi@92:     my $arg = join " ", @_;
devi@92:     my $i=1;
devi@40: 
devi@92:     my %myenv = ( %Config, 
devi@92:                 lab =>  $arg,           
devi@92:                 center  =>  $XMLClass->{"center"},
devi@92:                 course  =>  $XMLClass->{"course"},
devi@92:                 date    =>  $XMLClass->{"date"},
devi@92:                 stopdate    =>  $XMLClass->{"stop-date"},
devi@92:                 instructor  =>  $XMLClass->{"instructor"}->{"firstname"}." ".$XMLClass->{"instructor"}->{"surname"},
devi@92:                 manager     =>  $XMLClass->{"manager"}->{"firstname"}." ".$XMLClass->{"manager"}->{"surname"},
devi@92:                 coursepath =>   $XMLCourse->{"path"},
devi@92:             );
devi@40: 
devi@92:     if (grep { $_ eq "PRE-$command"} keys %Scripts) {
devi@92:         $_=$Scripts{"PRE-$command"};
devi@92:         s/\$(\w+)/$myenv{$1}/ge;
devi@92:         open(SHELL, "|/bin/sh -s");
devi@92:         binmode SHELL, ":utf8";
devi@92:         print SHELL $_;
devi@92:         close (SHELL);
devi@92:     }
devi@40: 
devi@40: 
devi@92:     for my $m (sort keys %Machines) {
devi@92:         if (!@SelectedMachines || grep $_ eq $i, @SelectedMachines) {
devi@92:             print "$m:\n" if $Config{"show_host"} =~ /y/i;
devi@0: 
devi@92:             %myenv = ( %myenv,
devi@92:                 host    =>  $m,
devi@92:                 ipaddress   =>  $Machines{$m}->{"ipaddress"},
devi@92:                 dirs    =>  "/root /home/".$Machines{$m}->{"user"},
devi@92:                 lablogs =>  $Config{"path_lablogs"}."/".
devi@92:                         $XMLClass->{"course"}."/".
devi@92:                         $XMLClass->{"date"}."/".
devi@92:                         "$m",
devi@92:                 email   =>  $Machines{$m}->{"student"}->{"email"},
devi@92:                 company =>  $Machines{$m}->{"student"}->{"company"},
devi@92:                 name    =>  $Machines{$m}->{"name"},
devi@92:                 firstname   =>  $Machines{$m}->{"firstname"},
devi@92:             );
devi@92:             if (grep { $_ eq $command} keys %Scripts) {
devi@92:                 $_=$Scripts{"$command"};
devi@92:                 s/\$(\w+)/$myenv{$1}/ge;
devi@92:                 open(SHELL, "|/bin/sh -s");
devi@92:                 binmode SHELL, ":utf8";
devi@92:                 print SHELL $_;
devi@92:                 close (SHELL);
devi@92:             }
devi@92:             else {
devi@92:                 my $res = `ssh $Config{"ssh_user"}\@$m $command`;
devi@92:                 if ($res) {
devi@92:                     my $count = ($res =~ s/(^)/$m: /mg);
devi@92:                     print $res;
devi@92:                     print "\n" if ($count > 1);
devi@92:                 }
devi@92:             }   
devi@92:         }   
devi@92:         $i++;
devi@92:     }
devi@40: 
devi@92:     if (grep { $_ eq "POST-$command"} keys %Scripts) {
devi@92:         $_=$Scripts{"POST-$command"};
devi@92:         s/\$(\w+)/$myenv{$1}/ge;
devi@92:         open(SHELL, "|/bin/sh -s");
devi@92:         binmode SHELL, ":utf8";
devi@92:         print SHELL $_;
devi@92:         close (SHELL);
devi@92:     }
devi@0: }
devi@0: 
devi@0: 
devi@3: 
devi@3: =cut comment
devi@3: 
devi@3: lm report
devi@3: 
devi@3: Построить html представление для журналов текущего класса.
devi@3: Для построения используется скрипт l3-report.
devi@3: 
devi@3: =cut
devi@3: 
devi@0: sub lm_report
devi@0: {
devi@0: 
devi@92:     my $webdir = $Config{"path_web"};
devi@92:     my $course=$XMLClass->{"course"};
devi@92:     my $date=$XMLClass->{"date"};
devi@92:     my $encoding=$XMLClass->{"charset"};
devi@0: 
devi@92:     my $center = $XMLClass->{"center"};
devi@92:     my $instructor = $XMLClass->{"instructor"}->{"firstname"}." ".$XMLClass->{"instructor"}->{"surname"};
devi@92:     my $course_name = $XMLCourse->{"fullname"}[0];
devi@4: 
devi@5: 
devi@92:     # Собственно журналы
devi@5: 
devi@92:     for my $student (@{$XMLClass->{"student"}}) {
devi@92:         my $user = $student->{"user"};
devi@92:         my $hostname = $student->{"host"};
devi@92:         my $encoding = $student->{"charset"};
devi@92:         my $student_name = $student->{"firstname"}." ".$student->{"surname"};
devi@5: 
devi@92:         system("mkdir -p $webdir/$date/$hostname");
devi@92:         system("cp ".$Config{"path_share"}."/*.{ico,css} $webdir/$date/$hostname");
devi@92:         system($Config{"l3-report"}.
devi@92:             " --input ".$Config{"path_lablogs"}."/$course/$date/$hostname/$user".
devi@92:             " --diffs ".$Config{"path_lablogs"}."/$course/$date/$hostname/$user ".
devi@92:                    $Config{"path_lablogs"}."/$course/$date/$hostname/root".
devi@92:             " --output $webdir/$date/$hostname/$user.html".
devi@92:             " --course-name '$course_name'".
devi@92:             " --course-code '$course'".
devi@92:             " --course-date '$date'".
devi@92:             " --course-center '$center'".
devi@92:             " --course-student '$student_name'".
devi@92:             " --course-trainer '$instructor'".
devi@92:             " --encoding $encoding"
devi@92:         );
devi@92:         system($Config{"l3-report"}.
devi@92:             " --input ".$Config{"path_lablogs"}."/$course/$date/$hostname/root".
devi@92:             " --diffs ".$Config{"path_lablogs"}."/$course/$date/$hostname/root ".
devi@92:             " --output $webdir/$date/$hostname/root.html".
devi@92:             " --course-name '$course_name'".
devi@92:             " --course-code '$course'".
devi@92:             " --course-date '$date'".
devi@92:             " --course-center '$center'".
devi@92:             " --course-student '$student_name'".
devi@92:             " --course-trainer '$instructor'".
devi@92:             " --encoding $encoding"
devi@92:         );
devi@92:     }
devi@5: 
devi@92:     # Индекс для данного класса
devi@4: 
devi@92:     my $head;
devi@4: 
devi@92:     $head="Журналы лабораторных работ";
devi@92:     open(HTML, ">$webdir/$date/index.html")
devi@92:         or die "Can't open $webdir/$date/index.html for writing";
devi@92:     binmode HTML, ":utf8";
devi@92:     print HTML <<HEAD;
devi@92:     <html>
devi@92:     <head>
devi@92:     <meta content='text/html; charset=utf-8' http-equiv='Content-Type' />
devi@92:     <title>$head</title>
devi@92:     </head>
devi@92:     <body>
devi@92:     <h1>$head</h1>
devi@92:     <p>
devi@92:     Курс: $course_name ($course)<br/>
devi@92:     Начало: $date<br/>
devi@92:     Учебный центр: $center <br/>
devi@92:     Инструктор: $instructor <br/>
devi@92:     </p>
devi@92:     <table>
devi@4: HEAD
devi@92:     for my $student (@{$XMLClass->{"student"}}) {
devi@92:         my $user = $student->{"user"};
devi@92:         my $hostname = $student->{"host"};
devi@92:         print HTML "<tr>\n";
devi@92:         print HTML "<td>",$student->{"firstname"}," ",$student->{"surname"},"</td>\n";
devi@92:         print HTML "<td>",$hostname,"</td>\n";
devi@92:         print HTML "<td><a href=\"$hostname/$user.html\">",$user,"</td>\n";
devi@92:         print HTML "<td><a href=\"$hostname/root.html\">","root","</td>\n";
devi@92:         print HTML "</tr>\n";
devi@92:     }
devi@92:     print HTML <<TAIL;
devi@92:     </table>
devi@92:     </html>
devi@4: TAIL
devi@92:     close (HTML);
devi@4: 
devi@92:     
devi@92:     
devi@0: }
devi@0: 
devi@0: sub load_run
devi@0: {
devi@92:     my $runfile = $Config{"path_labmaker"}."/".$Config{"path_runfile"};
devi@92:     open (RUN, $runfile)
devi@92:         or return;
devi@92:     while (<RUN>) {
devi@92:         chomp;
devi@92:         my ($var, $val) = split /\s+/,$_,2;
devi@92:         $Run{$var}=$val;
devi@92:     }
devi@92:     close (RUN);    
devi@0: }
devi@0: 
devi@0: sub save_run
devi@0: {
devi@92:     my $runfile = $Config{"path_labmaker"}."/".$Config{"path_runfile"};
devi@92:     open (RN, "$runfile")
devi@92:         or die "Can't save running state to $runfile";
devi@92:     for my $var (keys %Run) {
devi@92:         print RN $var,"\t",$Run{$var},"\n";
devi@92:     }
devi@92:     close (RN); 
devi@0: }
devi@0: 
devi@0: sub print_log
devi@0: {
devi@92:     my $logfile = $Config{"path_labmaker"}."/".$Config{"path_logfile"};
devi@92:     open (LOG, ">>$logfile")
devi@92:         or die "Can't open logfile $logfile for writing";
devi@92:     print LOG  @_;
devi@92:     close (LOG);    
devi@0: }
devi@0: 
devi@0: 
devi@0: sub print_usage_info
devi@0: {
devi@92:     print "Usage:\n\n\t$0 [host-list] command\n";
devi@92:     print <<'USAGE';
devi@0: 
devi@0: Commands:
devi@0: 
devi@92:     next        -- next lab
devi@92:     prev        -- prev lab
devi@92:     set LAB     -- set current lab to LAB
devi@92:     start       -- start this day training
devi@92:     stop        -- stop this day training
devi@92:     show hosts  -- show available hosts in the class
devi@92:     show labs   -- show available labs in the course
devi@92:     do COMMAND  -- do specified command on the hosts of hostlist
devi@92:     report      -- generate XML/HTML reports
devi@0: 
devi@92:     
devi@0: do commands:
devi@92:     
devi@92:     install [PROFILE] -- install profile 
devi@92:     
devi@92: Host list:  
devi@0: 
devi@92:     @N      -- machine N
devi@92:     @N1-N2      -- all of the machines from N1 to N2
devi@92:     @N1,N2,N3   -- machine N1, N2 and N3
devi@0: 
devi@92:     N* is numbers or domain names of the machines.
devi@92:     
devi@92:     If host list is not specified, 
devi@92:     command is executed on all of the machines
devi@0: 
devi@40: USAGE
devi@40: }
devi@0: 
devi@0: