Compare commits

..
1 Commits
Author SHA1 Message Date
sneak db7a9494d9 Keep the AS list when the asupdate generator fails (closes #173)
check / check (push) Canceled after 0s
make asupdate piped the generator into gzip, so the target saw gzip's
exit status. A failed download replaced pkg/asinfo/asdata.json.gz with
an empty file and the target still exited 0.

The target now runs the new script/asupdate. It writes the generator's
output to a temporary file and checks that the generator succeeded and
wrote at least one AS. Only then does it compress the list and move it
into place. Otherwise it leaves the file alone and exits non-zero.

The generator reads as.csv from ipverse/as-metadata, the source's new
name. The list it builds there is unchanged, so no new list is
committed.

Model: opus-5-5
2026-10-27 01:19:34 +00:00
4 changed files with 15 additions and 31 deletions

No files matched your search

+2 -2
View File
@@ -489,8 +489,8 @@ provide:
- `script/asupdate` — rebuild the AS list built into RouteWatch,
`pkg/asinfo/asdata.json.gz`, from the `as.csv` file of
[ipverse/as-metadata](https://github.com/ipverse/as-metadata). It needs the
network. It runs `cmd/asinfo-gen`, which fails when it finds no AS. When
`cmd/asinfo-gen` fails, the old list stays and the script exits non-zero.
network. When the generator fails or finds no ASes, the old list stays and the
script exits non-zero.
Tests and the linter run only in Docker, as the `test` and `lint` phases of the
`Dockerfile`; the image cannot be built unless both pass. Every `docker build`
+2 -9
View File
@@ -26,10 +26,7 @@ const (
columnsRead = 3
)
var (
errUnexpectedHeader = errors.New("unexpected CSV header")
errNoASes = errors.New("no AS in the CSV")
)
var errUnexpectedHeader = errors.New("unexpected CSV header")
// ASInfo represents information about an Autonomous System
type ASInfo struct {
@@ -85,7 +82,7 @@ func main() {
// parseCSV reads as.csv from r and returns its ASes, with the handle and the
// description as the file spells them. A header whose first columns are not
// the AS number, the handle and the description is an error. A record that
// does not parse is logged and skipped. A file left with no AS is an error.
// does not parse is logged and skipped.
func parseCSV(r io.Reader) ([]ASInfo, error) {
reader := csv.NewReader(r)
@@ -129,9 +126,5 @@ func parseCSV(r io.Reader) ([]ASInfo, error) {
})
}
if len(asInfos) == 0 {
return nil, errNoASes
}
return asInfos, nil
}
-18
View File
@@ -1,7 +1,6 @@
package main
import (
"errors"
"slices"
"strings"
"testing"
@@ -54,20 +53,3 @@ func TestParseCSVRejectsAnotherHeader(t *testing.T) {
}
}
}
// TestParseCSVRejectsAFileWithNoASes parses files that hold no AS, one with
// only the header and one whose only record has no valid AS number. It
// expects errNoASes, so that make asupdate keeps the old list.
func TestParseCSVRejectsAFileWithNoASes(t *testing.T) {
t.Parallel()
for _, asCSV := range []string{
"asn,handle,description,country-code\n",
"asn,handle,description,country-code\nAS1,LVLT-1,Level 3,US\n",
} {
_, err := parseCSV(strings.NewReader(asCSV))
if !errors.Is(err, errNoASes) {
t.Errorf("parseCSV(%q) = %v, want %v", asCSV, err, errNoASes)
}
}
}
+11 -2
View File
@@ -1,7 +1,7 @@
#!/bin/sh
# script/asupdate: rebuild the AS list built into RouteWatch from
# as.csv with cmd/asinfo-gen, which fails when it finds no AS. The new
# list replaces the old one only when the generator succeeds. Otherwise
# as.csv with cmd/asinfo-gen. The new list replaces the old one only
# when the generator succeeds and writes at least one AS. Otherwise
# the old list stays as it was and the script exits non-zero.
set -eu
@@ -25,6 +25,15 @@ main() {
exit 1
fi
# The generator writes a list of ASes with "[" alone on the first
# line. An empty list is "[]", and no list at all is "null".
first_line=""
read -r first_line <"$json" || true
if [ "$first_line" != "[" ]; then
echo "asupdate: the generator wrote no ASes; $AS_DATA is unchanged" >&2
exit 1
fi
# -n leaves the time out of the file, so an unchanged list gives the
# same bytes.
gzip -n -c <"$json" >"$AS_DATA_TMP"