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