Compare commits

..
1 Commits
Author SHA1 Message Date
sneak f6850b8c09 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 output of
cmd/asinfo-gen to a temporary file. Only when the generator succeeds
does it compress the list and move it into place. Otherwise it leaves
the file alone and exits non-zero. The generator now fails when as.csv
holds no AS.

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:32:38 +00:00
4 changed files with 31 additions and 15 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. When the generator fails or finds no ASes, the old list stays and the
script exits non-zero.
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.
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`
+9 -2
View File
@@ -26,7 +26,10 @@ const (
columnsRead = 3
)
var errUnexpectedHeader = errors.New("unexpected CSV header")
var (
errUnexpectedHeader = errors.New("unexpected CSV header")
errNoASes = errors.New("no AS in the CSV")
)
// ASInfo represents information about an Autonomous System
type ASInfo struct {
@@ -82,7 +85,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.
// does not parse is logged and skipped. A file left with no AS is an error.
func parseCSV(r io.Reader) ([]ASInfo, error) {
reader := csv.NewReader(r)
@@ -126,5 +129,9 @@ func parseCSV(r io.Reader) ([]ASInfo, error) {
})
}
if len(asInfos) == 0 {
return nil, errNoASes
}
return asInfos, nil
}
+18
View File
@@ -1,6 +1,7 @@
package main
import (
"errors"
"slices"
"strings"
"testing"
@@ -53,3 +54,20 @@ 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)
}
}
}
+2 -11
View File
@@ -1,7 +1,7 @@
#!/bin/sh
# script/asupdate: rebuild the AS list built into RouteWatch from
# 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
# 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
# the old list stays as it was and the script exits non-zero.
set -eu
@@ -25,15 +25,6 @@ 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"