#!/usr/bin/perl use strict; use Inline::Files; use Data::Dumper; use Switch; use XML::Simple; use Getopt::Long; use utf8; 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", # Каталоги "path_labmaker" => "/var/labmaker/", "path_classes" => "/var/labmaker/classes/", "path_lablogs" => "/var/labmaker/lablogs/", "courses_path" => "/var/labmaker/courses/", "outpath" => "/var/labmaker/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" => "r", ); 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); } 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 { local $/; $_=; %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}", "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; for my $m (sort keys %Machines) { if (!@SelectedMachines || grep $_ eq $i, @SelectedMachines) { print "$m:\n" if $Config{"show_host"} =~ /y/i; my %myenv = ( %Config, host => $m, dirs => "/root /home/".$Machines{$m}->{"user"}, lablogs => $Config{"path_lablogs"}."/". $XMLClass->{"course"}."/". $XMLClass->{"date"}."/". "$m", lab => $arg, email => $Machines{$m}->{"student"}->{"email"}, company => $Machines{$m}->{"student"}->{"company"}, center => $XMLClass->{"center"}, course => $XMLClass->{"course"}, date => $XMLClass->{"date"}, name => $Machines{$m}->{"name"}, coursepath => $XMLCourse->{"path"}, ); 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++; } } =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->{"encoding"}; 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". " --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". " --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

$head

Курс: $course_name ($course)
Начало: $date
Учебный центр: $center
Инструктор: $instructor

HEAD for my $student (@{$XMLClass->{"student"}}) { my $user = $student->{"user"}; my $hostname = $student->{"host"}; print HTML "\n"; print HTML "\n"; print HTML "\n"; print HTML "\n"; print HTML "\n"; print HTML "\n"; } print HTML < TAIL close (HTML); } sub load_run { my $runfile = $Config{"path_labmaker"}."/".$Config{"path_runfile"}; open (RUN, $runfile) or return; while () { 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"; while () { 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 __SCRIPTS__ ###install cat $sshkey | $lmssh $ssh_user@$host /bin/sh -c '"mkdir -p ~/.ssh; cat >>~/.ssh/authorized_keys; chmod 600 ~/.ssh/authorized_keys"' ###install-lm cat $lminstall | ssh $ssh_user@$host /bin/sh -s $dirs ###copy-lablogs for i in $dirs do mkdir -p $lablogs/${i##*/} scp -q $ssh_user@$host:${i}/.labmaker/* $lablogs/${i##*/} done ###setlab for i in $dirs do echo $lab | ssh $ssh_user@$host "cat > "${i}"/.labmaker/lab" done ###makeout common=$course-$date personal=$course-$date-$email mkdir -p $outpath/${common}/{Lablogs,Docs} mkdir -p $outpath/${personal}/{Course,Files} cd $outpath/${personal} ln -s ../${common}/Lablogs . ln -s ../${common}/Docs . cd ~- export UG_PERSONAL=${PWD}/$outpath/${personal}/Course export UG_CENTER="$center" export UG_COURSE="$course" export UG_DATE="$date" export UG_STUDENT="$name" export UG_COMPANY="$company" cd $coursepath; make personal; cd ~- ###watch cat taillast.pl | ssh $ssh_user@$host perl - /root/.labmaker
",$student->{"firstname"}," ",$student->{"surname"},"",$hostname,"",$user,"","root","