#!/usr/bin/env perl use strict; use warnings; use v5.10; use Digest::SHA qw(sha1_base64); use File::Basename; use utf8; use ACU::API::Projects; use ACU::Defense; use ACU::LDAP; use ACU::Log; $ACU::Log::log_file = "/var/log/hooks/" . basename($0) . ".log"; use ACU::Process; # First, check if the repository is in the subjects/ directory exit 0 if ($ENV{GL_REPO} !~ /^subjects\//); my ($ref, $oldsha, $newsha) = @ARGV; log DONE, "This is a subject repository!"; my %known_tags = ( "defense" => \&tag_defense, "grades" => \&tag_grades, "project" => \&tag_project, "subject" => \&tag_document, "ref" => \&tag_ref, "tests" => \&tag_tests, ); if ($ref =~ m<^refs/tags(/.+)$>) { my $tag = $1; my @args; while ($tag =~ m<[,/]([^,]*)>g) { push @args, $1; } my $create = ($newsha ne '0' x 40); if (exists $known_tags{$args[0]}) { exit $known_tags{$args[0]}($create, @args); } } exit 0; sub check_xml { my $content = shift; my $dtd = shift; my $fh; if ($dtd) { open $fh, "|xmllint --noout --dtdvalid $dtd -"; } else { open $fh, "|xmllint --noout -"; } print $fh ${ $content }; close $fh; return $?; } sub repository_name { my $repo = $ENV{GL_REPO}; $repo =~ s#subject.*/([^/]+)$#$1#; return $repo; } sub tag_defense { my $creation = shift; # From here, we have: # 0: "defense" # 1: $version # 2: $id # 3: $path # 4: $year my $version = $_[1] // 1; my $project_id = repository_name(); if ($_[2]) { $project_id .= "-" . $_[2]; } $project_id = lc $project_id; $project_id =~ s/[^a-z0-9-_]/_/g; my $path; if ($_[3]) { if ($_[3] =~ /^(?:defenses\/)?([a-zA-Z0-9_.\/-]+?)(?:.xml)?$/) { $path = "defenses/".$1.".xml"; } else { $path = $_[3]; } } else { # Looking for an uniq defense file in defenses/ $path = qx(git ls-tree -r --name-only $ARGV[2] defenses/ | egrep '\.xml\$'); my $nb_defenses = $path =~ tr/\n//; if ($nb_defenses > 1) { log ERROR, "Veuillez préciser le chemin de la soutenance à utiliser avec un tag : defense,", $_[1] // "", ",", $_[2] // "", ",file_to_use"; exit 1; } elsif ($nb_defenses == 0) { log ERROR, "Aucune soutenance n'a été trouvée dans le dossier defenses/"; exit 1; } chomp($path); } log WARN, "Placez votre soutenance dans le dossier defenses/." if ($path !~ /^defenses/); my $defense_id = basename($path); $defense_id =~ s/\.xml$//; $defense_id =~ s/[^a-zA-Z0-9_.-]/_/g; my $year; if ($_[4]) { # Check on year if ($_[4] !~ /^\d+$/) { log ERROR, "project:*:* second argument is the year. Tag format: project:id:year"; } $year = $_[4]; } else { $year = LDAP::get_year; } # Determine full tag my $long_tag; { my $proj_id = $_[2] // ""; $long_tag = "defense,$version,$proj_id,$path,$year"; } if ($creation) { my $newref = $ARGV[2]; log INFO, "Looking for $path..."; # Check file exists my $content = qx(git show $newref:$path); if ($?) { log ERROR, "Impossible de trouver la soutenance."; } # Check DTD validity if (check_xml(\$content, "http://acu.epita.fr/dtd/defense.dtd")) { log ERROR, "Corrigez les erreurs du fichier $path avant de publier la soutenance."; } # TODO: check user permissions # TODO: check presence in project.xml # Generate questions and answer id my $defense = Defense->new(\$content); $defense->genIds($defense_id); # Send data to intradata log INFO, "Attente d'un processus de publication..."; if (my $err = Process::Client::launch("intradata_get", { action => "update", type => "defense", id => $project_id, "year" => $year, "defense_id" => $defense_id, "version" => $version }, { "$defense_id.xml" => $defense->toString() })) { if (${ $err } ne "Ok") { log ERROR, "Erreur durant le processus de publication : " . ${ $err }; } } if ($long_tag) { qx(git tag -f $long_tag $newref); if (! $?) { log INFO, "Tag long créé : $long_tag."; } } } else { # Is the long tag existing qx(git tag | egrep "^$long_tag\$"); if ($?) { log ERROR, "Tag long correspondant introuvable : $long_tag."; } if ($long_tag) { qx(git tag -d $long_tag); if (! $?) { log INFO, "Tag long supprimé : $long_tag."; } } } } sub tag_document { } sub tag_grades { my $creation = shift; # From here, we have: # 0: "defense" # 1: $version # 2: $id # 3: $year my $version = $_[1] // 1; my $project_id = repository_name(); if ($_[2]) { $project_id .= "-" . $_[2]; } $project_id = lc $project_id; $project_id =~ s/[^a-z0-9-_]/_/g; my $year; if ($_[3]) { # Check on year if ($_[3] !~ /^\d+$/) { log ERROR, "grades,*,*,* second argument is the year. Tag format: grades,version,id,year"; } $year = $_[3]; } else { $year = LDAP::get_year; } # Determine full tag my $long_tag; { my $proj_id = $_[2] // ""; $long_tag = "grades,$version,$proj_id,$year"; } if ($creation) { my $newref = $ARGV[2]; # Check file exists my $content = qx(git show $newref:grades/grades.xml); if ($?) { log ERROR, "Impossible de trouver le fichier de notation."; } # Check DTD validity if (check_xml(\$content, "http://acu.epita.fr/dtd/grading.dtd")) { log ERROR, "Corrigez les erreurs du fichier grades.xml avant de lancer la génération des notes."; } # TODO: check user permissions # Send data to intradata log INFO, "Attente d'un processus de publication..."; Process::Client::launch("intradata_get", { action => "generate", type => "grades", id => $project_id, "year" => $year, "version" => $version }, { "grading.xml" => $content }, 1); if ($long_tag) { qx(git tag -f $long_tag $newref); if (! $?) { log INFO, "Tag long créé : $long_tag."; } } } else { # Is the long tag existing qx(git tag | egrep "^$long_tag\$"); if ($?) { log ERROR, "Tag long correspondant introuvable : $long_tag."; } if ($long_tag) { qx(git tag -d $long_tag); if (! $?) { log INFO, "Tag long supprimé : $long_tag."; } } } } sub tag_project { my $creation = shift; # From here, we have: # 0: "project" # 1: $id # 2: $year my $project_id = repository_name(); my $flavour = ""; if ($_[1]) { # Check on ID/flavour_id if ($_[1] =~ /^\d+$/) { log ERROR, "project:* tag can't take version. Tag format: project:id:year"; } $project_id .= "-" . $_[1]; $flavour = $_[1]; } $project_id = lc $project_id; $project_id =~ s/[^a-z0-9-_]/_/g; my $year; if ($_[2]) { # Check on year if ($_[2] !~ /^\d+$/) { log ERROR, "project:*:* second argument is the year. Tag format: project:id:year"; } $year = $_[2]; } else { $year = LDAP::get_year; } # Determine full tag my $long_tag; if (!$_[2]) { my $proj_id = $_[1] // ""; $long_tag = "project,$proj_id,$year"; } if ($creation) { my $newref = $ARGV[2]; my $content = qx(git show $newref:project.xml); # Check file exists if ($?) { log ERROR, "Créez un fichier project.xml à la racine du dépôt."; } # Check DTD validity if (check_xml(\$content, "http://acu.epita.fr/dtd/project.dtd")) { log ERROR, "Corrigez les erreurs du fichier project.xml avant de lancer la création du projet."; } # TODO: check user permissions # Project already online? my $project; eval { $project = API::Project::get($project_id, $year); }; if ($project) { log INFO, "Mise à jour du projet $project_id"; } else { log INFO, "Création du projet $project_id"; } # Generate token for VCS submission my $dom = XML::LibXML->load_xml(string => (\$content)); my $mod = 0; for my $vcs ($dom->documentElement()->getElementsByTagName("vcs")) { if (! $vcs->hasAttribute("tag") || $vcs->getAttribute("tag") =~ /^(ACU|YAKA)-/) { log ERROR, "Un tag de rendu ne peut pas commencer par ACU- ou YAKA-."; # C'est réservé pour les moulettes } if (! $vcs->hasAttribute("token")) { if ($project) { # Looking for an old token my @rendus = grep { exists $_->{vcs} and $_->{vcs}{tag} eq $vcs->getAttribute("tag"); } @{ $project->{submissions} }; if (@rendus == 1) { log DEBUG, "Use existing token: ".$rendus[0]->{vcs}{token}; $vcs->setAttribute("token", substr($rendus[0]->{vcs}{token}, 2, 23)); $mod = 1; next; } } my $token; do { $token = sha1_base64(rand); $token =~ s/[^a-zA-Z0-9]//g; } while (length $token < 12); $vcs->setAttribute("token", substr($token, 2, 23)); $mod = 1; } } if ($mod) { $content = $dom->toString(); } # Send data to intradata log INFO, "Attente d'un processus de publication..."; if (my $err = Process::Client::launch("intradata_get", { action => "update", type => "project", id => $project_id, "year" => $year }, { "butler.xml" => $content })) { if (${ $err } ne "Ok") { log ERROR, "Erreur durant le processus de publication : " . ${ $err }; } } log INFO, "Information de l'intranet..."; # Call API eval { API::Projects::add($project_id, $flavour, $year); }; if ($@) { my $err = $@; if ($err =~ /[pP]roject [aA]ll?ready [eE]xists/) { log WARN, $err; } else { log ERROR, $err; } } log DONE, "Projet créé/mis à jour avec succès."; if ($long_tag) { qx(git tag -f $long_tag $newref); if (! $?) { log INFO, "Tag long créé : $long_tag."; } } } else { # Is the long tag existing qx(git tag | egrep "^$long_tag\$"); if ($?) { log ERROR, "Tag long correspondant introuvable : $long_tag."; } log USAGE, "Suppression du projet !"; if ($long_tag) { qx(git tag -d $long_tag); if (! $?) { log INFO, "Tag long supprimé : $long_tag."; } } } } sub tag_ref { my $creation = shift; # From here, we have: # 0: "ref" # 1: $id # 2: rendu-X # 3: $year my $project_id = repository_name(); if ($_[1]) { # Check on ID/flavour_id if ($_[1] =~ /^\d+$/) { log ERROR, "ref,* tag can't take version. Tag format: ref,id,rendu,year"; } $project_id .= "-" . $_[1]; } $project_id = lc $project_id; $project_id =~ s/[^a-z0-9-_]/_/g; my $rendu; if ($_[2]) { $rendu = $_[2]; } else { $rendu = ""; } my $year; if ($_[3]) { # Check on year if ($_[3] !~ /^\d+$/) { log ERROR, "ref,*,*,* third argument is the year. Tag format: ref,id,rendu,year"; } $year = $_[3]; } else { $year = LDAP::get_year; } # Determine full tag my $long_tag; { my $proj_id = $_[1] // ""; $long_tag = "ref,$proj_id,$rendu,$year"; } if ($creation) { my $newref = $ARGV[2]; log INFO, "Création/mise à jour de la ref..."; my $content = qx(git show $newref:ref/Makefile); # Check file exists if ($?) { log ERROR, "Un fichier Makefile est requis pour pouvoir compiler et exécuter la ref."; } log INFO, "Création de la tarball..."; my $archive = qx(git archive --format=tgz $newref ref/); # Send data to moulette log INFO, "Attente d'un processus de compilation..."; if (my $err = Process::Client::launch("moulette_get", { type => "ref", id => $project_id, "year" => $year, "rendu" => $rendu, "file" => "ref_$rendu.tgz" }, { "ref_$rendu.tgz" => $archive })) { if (${ $err } ne "Ok") { log ERROR, "Erreur durant le processus de compilation : " . ${ $err }; } } if ($long_tag) { qx(git tag -f $long_tag $newref); if (! $?) { log INFO, "Tag long créé : $long_tag."; } } } else { # Is the long tag existing qx(git tag | egrep "^$long_tag\$"); if ($?) { log ERROR, "Tag long correspondant introuvable : $long_tag."; } log USAGE, "Suppression du tag de ref !"; if ($long_tag) { qx(git tag -d $long_tag); if (! $?) { log INFO, "Tag long supprimé : $long_tag."; } } } } sub tag_tests { my $creation = shift; # From here, we have: # 0: "tests" # 1: $id # 2: rendu-X # 3: $year my $project_id = repository_name(); if ($_[1]) { # Check on ID/flavour_id if ($_[1] =~ /^\d+$/) { log ERROR, "tests,* tag can't take version. Tag format: tests,id,rendu,year"; } $project_id .= "-" . $_[1]; } $project_id = lc $project_id; $project_id =~ s/[^a-z0-9-_]/_/g; my $rendu = $_[2] // ""; my $year; if ($_[3]) { # Check on year if ($_[3] !~ /^\d+$/) { log ERROR, "tests,*,*,* third argument is the year. Tag format: tests,id,rendu,year"; } $year = $_[3]; } else { $year = LDAP::get_year; } # Determine full tag my $long_tag; { my $proj_id = $_[1] // ""; $long_tag = "tests,$proj_id,$rendu,$year"; } if ($creation) { my $newref = $ARGV[2]; log INFO, "Création/mise à jour de la testsuite..."; my $content = qx(git show $newref:tests/Makefile); # Check file exists if ($?) { log ERROR, "Un fichier Makefile est requis pour pouvoir compiler la testsuite."; } log INFO, "Création de la tarball..."; my $archive = qx(git archive --format=tgz $newref tests/); # Send data to moulette log INFO, "Attente d'un processus de compilation..."; if (my $err = Process::Client::launch("moulette_get", { type => "tests", id => $project_id, "year" => $year, "rendu" => $rendu, "file" => "tests_$rendu.tgz" }, { "tests_$rendu.tgz" => $archive })) { if (${ $err } ne "Ok") { log ERROR, "Erreur durant le processus de compilation : " . ${ $err }; } } if ($long_tag) { qx(git tag -f $long_tag $newref); if (! $?) { log INFO, "Tag long créé : $long_tag."; } } } else { # Is the long tag existing qx(git tag | egrep "^$long_tag\$"); if ($?) { log ERROR, "Tag long correspondant introuvable : $long_tag."; } log USAGE, "Suppression du tag de la testsuite !"; if ($long_tag) { qx(git tag -d $long_tag); if (! $?) { log INFO, "Tag long supprimé : $long_tag."; } } } }