feat: add full Zonemaster stack with Docker and Spanish UI

- Clone all 5 Zonemaster component repos (LDNS, Engine, CLI, Backend, GUI)
- Dockerfile.backend: 8-stage multi-stage build LDNS→Engine→CLI→Backend
- Dockerfile.gui: Astro static build served via nginx
- docker-compose.yml: backend (internal) + frontend (port 5353)
- nginx.conf: root redirects to /es/, /api/ proxied to backend
- zonemaster-gui/config.ts: defaultLanguage set to 'es' (Spanish)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-21 08:19:24 +02:00
commit 8d4eaa1489
1567 changed files with 204155 additions and 0 deletions

View File

@@ -0,0 +1,73 @@
# Document generation
## Background
The utility scripts described below are used to update or generate some documents
in the public document tree.
* The scripts should be run at each Zonemaster release and the resulting update
should be submitted to correct git branch of this git repository.
## Pre-requisite
The scripts should be run on a computer where the updated git branch of
`zonemaster/zonemaster` has been cloned, and the equivalent version of
`zonemaster/engine` has been **installed**. Usually the git branches will
be the `develop` branches of each repository.
## Scripts
* [generateTestCaseList.pl]
* This tool extracts all Test Case specifications and creates a Markdown table
to be inserted in [Test Case README].
* [updateTestPlanReadme.pl]
* This tool extracts all Test Case specifications per Test Plan and creates
Markdown tables. The tables are automatically added to the Test Plan
README.md file which is located in the directory named after the Test Plan.
* [generateTestMessages.pl]
* This tools creates a map between the Zonemaster message tags from the
[Zonemaster-Engine] implementation of the Test Cases and the Test Case
specifications. This is used to create the complete [TestMessages.md] file.
* [generateImplementedTestCases.pl]
* This tool creates a list of implemented test cases from the
[Zonemaster-Engine] implementation. This is used to create the complete
[ImplementedTestCases.md] file.
## Steps to be run
1. You are assumed to start from the root of the zonemaster/zonemaster tree.
Go to directory to execute the commands from.
```sh
cd docs/public/specifications/tests
```
2. All commands below must be run from the directory you are in now.
3. Remove Test Case table from [README.md][Test Case README] in that directory
and save the file.
4. Run:
```sh
../../../../utils/generateTestCaseList.pl >> README.md
```
5. Run:
```sh
../../../../utils/updateTestPlanReadme.pl
```
6. Run:
```sh
../../../../utils/generateTestMessages.pl > TestMessages.md
```
7. Run:
```sh
../../../../utils/generateImplementedTestCases.pl > ImplementedTestCases.md
```
8. Verify any updated documents.
9. Submit to git.
[generateImplementedTestCases.pl]: generateImplementedTestCases.pl
[generateTestCaseList.pl]: generateTestCaseList.pl
[generateTestMessages.pl]: generateTestMessages.pl
[ImplementedTestCases.md]: ../docs/public/specifications/tests/ImplementedTestCases.md
[Test Case README]: ../docs/public/specifications/tests/README.md
[TestMessages.md]: ../docs/public/specifications/tests/TestMessages.md
[updateTestPlanReadme.pl]: generateTestMessages.pl
[Zonemaster-Engine]: https://github.com/zonemaster/zonemaster-engine

View File

@@ -0,0 +1,37 @@
#!/usr/bin/env perl
use 5.16.0;
use warnings;
use Zonemaster::Engine;
use File::Basename;
# page header
print '<!-- File generated by ', basename ($0), ", script in zonemaster/zonemaster util directory.\n";
print "Use that script to generate a new file for each release of Zonemaster when \n";
print "Zonemaster-Engine also has been updated.-->\n\n";
print "# Implemented Test Cases\n\n";
print "Index of Text Case specifications is also found in [README](README.md).\n";
print "[Zonemaster-Engine] is the repository of the implementation of the Test Cases (the methods).\n";
print "\n\n";
# table header
print '|Test level (linked to Perl code at CPAN)|Method name in Perl code|',
"Test Case Identifier (linked to specification)|\n";
print '|:---------------------------------------|:-----------------------|',
"----------------------------------------------|\n";
# table content
foreach my $module ( sort { fc $a cmp fc $b } Zonemaster::Engine::Test->modules ) {
my $full = "Zonemaster::Engine::Test::$module";
my $ref = $full->metadata;
my $modulelink = "https://metacpan.org/pod/Zonemaster::Engine::Test::$module";
for my $method (sort keys %$ref) {
my $testcase = uc ($method);
my $testcaselink = "${module}-TP/${method}.md";
printf "| [%s](%s) | %s | [%s](%s) |\n", $module, $modulelink, $method, $testcase, $testcaselink;
}
}
print "\n[Zonemaster-Engine]: https://github.com/zonemaster/zonemaster-engine\n";

View File

@@ -0,0 +1,144 @@
#!/usr/bin/env perl
# Copyright (c) 2014, IIS (The Internet Infrastructure Foundation)
# Copyright (c) 2014, AFNIC
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
#
# Redistributions in binary form must reproduce the above copyright notice, this
# list of conditions and the following disclaimer in the documentation and/or
# other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
use strict;
use warnings;
use Pod::Usage;
use Getopt::Long;
use File::Basename;
# command line options
my $dirloc = '.'; # directory for searching test cases
my $help;
my $DEBUG = 0;
# global variables
my $tcCounter = 0;
sub main {
GetOptions(
'help|?' => \$help,
'dir|d=s' => \$dirloc,
'debug' => \$DEBUG,
) or pod2usage(2);
if ($help) {
pod2usage(1);
exit;
}
opendir(my $dir, $dirloc) || die "cannot open directory $dirloc";
print "<!-- Table generated by script ", basename ($0), " from Zonemaster/Zonemaster utils directory -->\n\n";
# Table header
print '|Test Plan/Test Case |Test Case Description|', "\n";
print '|:-------------------|:--------------------|', "\n";
my @files = readdir $dir;
@files = sort {$a cmp $b} @files;
foreach my $f (@files) {
print "$dirloc/$f\n" if $DEBUG;
next if $f =~ /^\./;
if (-d "$dirloc/$f") {
tcList("$dirloc/$f");
}
}
print "No test cases found\nUse -d to specify directory\n" if !$tcCounter;
}
sub tcList
{
my $tcDir = shift;
my $tcLevel = basename($tcDir);
# For each level
print '|**',$tcLevel,'**| |', "\n";
opendir(my $dir, $tcDir);
my @files = readdir $dir;
@files = sort {$a cmp $b} @files;
foreach my $f (@files) {
next if $f =~ /^\./;
next if $f eq "README.md";
unless ($f =~ /^[a-z]+[0-9]+\.md$/) {
warn "Skip file with unknown file name pattern: $f\n";
next;
}
tcName("$tcDir/$f", $tcLevel);
}
}
sub tcName
{
my $tcPath = shift;
my $tcLevel = shift;
my $basename = basename($tcPath);
my $testcase = uc (basename($tcPath, ".md"));
open my $file, $tcPath or die "Cannot open file $tcPath: $!";
my $header = <$file>;
if (defined $header) {
if ($header =~ /^#+ +([A-Z]+[0-9]+): +(.*)/) {
# For each test case
print "|[$testcase]($tcLevel/$basename)|$2|\n";
warn "$tcPath: Test case ID does not match on first line\n" unless $1 eq $testcase;
} else {
warn "$tcPath: mismatch on first line\n";
};
} else {
warn "$tcPath: empty file or empty first line\n";
}
$tcCounter++; # increase the global counter
close $file;
}
main();
=head1 NAME
testcase.pl
=head1 DESCRIPTION
This tools extracts all implemented test cases and prints the header of each.
=head1 USAGE
testcase.pl --dir .
Optional arguments:
--dir Directory path of the test case directory
--help This help text
=head1 AUTHOR
Patrik Wallstrom <pawal@iis.se>
=cut

View File

@@ -0,0 +1,36 @@
#!/usr/bin/env perl
use 5.16.0;
use warnings;
use Zonemaster::Engine;
use File::Basename;
# page header
print '<!-- File generated by ', basename ($0), ", script in zonemaster/zonemaster util directory.\n";
print "Use that script to generate a new file for each release of Zonemaster when \n";
print "Zonemaster-Engine also has been updated.-->\n\n";
print "# Mapping test messages to Test Cases\n\n";
print "Index of Text Cases are found in [README](README.md).\n\n";
# table header
print "| Message tag from [Zonemaster-Engine] | Module | Method (implemented test case) |\n";
print "|:-------------------------------------|:-------|:-------------------------------|\n";
# table content
foreach my $module ( sort { fc $a cmp fc $b } Zonemaster::Engine::Test->modules ) {
my $full = "Zonemaster::Engine::Test::$module";
my $ref = $full->metadata;
for my $key (sort keys %$ref) {
my $list = $ref->{$key};
for my $tag (sort { $a cmp $b } @$list) {
my $testlevel = "${module}-TP";
my $testlevellink = "$module-TP/README.md";
my $testcasefile = "$key.md";
my $testcaselink = "$testlevel/$testcasefile";
printf "| %-40s | [%s](%s) | [%s](%s) |\n", $tag, $module, $testlevellink, $key, $testcaselink;
}
}
}
print "\n[Zonemaster-Engine]: https://github.com/zonemaster/zonemaster-engine\n";

View File

@@ -0,0 +1,181 @@
#!/usr/bin/env perl
use strict;
use warnings;
use Pod::Usage;
use Getopt::Long;
use File::Basename;
# command line options
my $dirloc = '.'; # directory for searching test cases
my $help;
my $DEBUG = 0;
sub main {
my $tcCounter = 0;
GetOptions(
'help|?' => \$help,
'dir|d=s' => \$dirloc,
'debug' => \$DEBUG,
) or pod2usage(2);
if ($help) {
pod2usage(1);
exit;
}
opendir(my $dir, $dirloc) || die "cannot open directory $dirloc";
my @directories;
for my $f (readdir $dir) {
my $d = "$dirloc/$f";
if (! -d $d) {
say STDERR qq{Skipping non-directory: $d} if $DEBUG;
next;
}
if ($f =~ /^\./) {
say STDERR qq{Skipping hidden directory: $d} if $DEBUG;
next;
}
if ($f !~ /-TP$/) {
say STDERR qq{Skipping directory not ending in "-TP": $d} if $DEBUG;
next;
}
push @directories, $d;
}
close $dir;
@directories = sort { $a cmp $b } @directories;
foreach my $d (@directories) {
say STDERR "Processing directory $d" if $DEBUG;
$tcCounter += tcList($d);
}
if ($tcCounter == 0) {
say STDERR qq{No test cases found};
say STDERR qq{Use -d to specify directory where all test plans (i.e. directories ending in "-TP") reside};
exit 1;
}
}
sub tcList
{
my $tcDir = shift;
my $tcCount = 0;
my $script_name = basename($0);
my $output = <<"HEADER";
<!-- Content until EOF generated by script $script_name from Zonemaster/Zonemaster utils directory -->
## Test cases list
|Test Case |Test Case Description|
|:---------|:--------------------|
HEADER
opendir(my $dir, $tcDir);
my @files = grep { ! /^\./ } readdir $dir;
close $dir;
@files = sort {$a cmp $b} @files;
foreach my $f (@files) {
next if $f eq "README.md";
unless ($f =~ /^[a-z]+[0-9]+\.md$/) {
warn "Skip file with unknown file name pattern: $f\n";
next;
}
$output .= tcName("$tcDir/$f");
$tcCount++;
}
if ( ! grep( /^README\.md$/, @files ) ) {
warn "No README.md file in folder $tcDir\n";
}
else {
writeReadme( "$tcDir/README.md", $output );
}
return $tcCount;
}
sub writeReadme
{
my ( $tcPath, $tcTable ) = @_;
# slurp README content until pattern
my $content = "";
open( my $in, '<', $tcPath ) or die "Cannot open file $tcPath: $!";
while( <$in> ) {
last if /^## Test cases list$/;
last if /^<!-- Content until EOF generated by script .* -->$/;
$content .= $_;
}
close $in;
$content .= $tcTable;
open( my $file, '>', $tcPath ) or die "Cannot open file $tcPath: $!";
print $file $content;
close $file;
}
sub tcName
{
my $tcPath = shift;
my $basename = basename($tcPath);
my $testcase = uc (basename($tcPath, ".md"));
my $output = "";
open my $file, $tcPath or die "Cannot open file $tcPath: $!";
my $header = <$file>;
if (defined $header) {
if ($header =~ /^#+ +([A-Z]+[0-9]+): +(.*)/) {
# For each test case
$output .= "|[$testcase]($basename)|$2|\n";
warn "$tcPath: Test case ID does not match on first line\n" unless $1 eq $testcase;
} else {
warn "$tcPath: mismatch on first line\n";
};
} else {
warn "$tcPath: empty file or empty first line\n";
}
close $file;
return $output;
}
main();
=head1 NAME
updateTestPlanReadme
=head1 DESCRIPTION
This tools updates all TestPlans README files with the TestPlan's test cases.
=head1 USAGE
From the root of the project:
./utils/updateTestPlanReadme.pl -d docs/public/specifications/tests
Optional arguments:
--dir Directory path of the test case directory
--help This help text
=cut