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