diff --git a/README.md b/README.md index 9a3b675..9dad9e0 100644 --- a/README.md +++ b/README.md @@ -130,8 +130,8 @@ that is not one task (`taskgate`). module's defaults by `task reference`, and a page shows one with ``. - **A figure said in prose has a line** in `tools/refgen/figures.txt`: every duration, size and count a hand-written page says outside ``, with the constant of spin's that decides it - (`spin:# = 15m`), which `task reference:check` reads and holds the line to, or - why nothing holds it. A figure with no line fails, and so does a line its page no longer says. + (`spin:# = 15m`), which `task reference:check` reads and holds the line to; with the text of a file + that decides it where no constant does (`spin: has ""`); or with why nothing holds it. A figure with no line fails, and so does a line its page no longer says. - **What a release is made of**, `reference/release.md`, is written from spin's `versions.yaml` at the release installations run (its spin-machine), images.json and the module's newest release. - **Diagrams** are the ones spin and spin-terraform-aws draw beside their sources (`.dot`, `.d2`): diff --git a/tools/refgen/figures.go b/tools/refgen/figures.go index 550377b..1332e05 100644 --- a/tools/refgen/figures.go +++ b/tools/refgen/figures.go @@ -17,9 +17,14 @@ import ( // // | | spin:# = // -// for a figure a constant of spin's decides, which refgen reads and compares - 15m, 12h, 300, 3 - -// or, in the third place, why nothing holds it: a default of a file refgen cannot evaluate, or -// words that are no figure of the code's. A page that showed a figure with has no line: +// for a figure a constant of spin's decides, which refgen reads and compares - 15m, 12h, 300, 3; +// +// | | spin: has "" +// +// for one decided where no constant is - a field's default, a timer's unit, a template's line, a +// Terraform local - held by the text that decides it still being in the file, of spin or of +// spin-terraform-aws, whatever its spacing; or, in the third place, why nothing holds it: another +// party's fact, or words that are no figure of the code's. A page that showed a figure with has no line: // the component is the code's own number. // // Before it a page said "an hour" of a credential the code had made fifteen minutes, and "about @@ -29,8 +34,12 @@ const figuresList = "tools/refgen/figures.txt" // saidFigure is a duration, a size or a count of tries, in digits or in words. var saidFigure = regexp.MustCompile(`(?i)\b(?:(?:\d+(?:[.,]\d+)?|an?|one|two|three|four|five|six|seven|eight|nine|ten|twelve|fourteen|fifteen|twenty|thirty|sixty|ninety|half an?|a quarter of an|a few|few)[ -](?:(?:second|minute|hour|day|week|month|year)s?|times|attempts)|\d+(?:\.\d+)? ?[KMGT]i?B)\b`) -// held is a figure's third place when a constant decides it. -var held = regexp.MustCompile(`^spin:(\S+)#(\w+) = (\S+)$`) +// held is a figure's third place when a constant decides it, and has when a file's text does. +var ( + held = regexp.MustCompile(`^spin:(\S+)#(\w+) = (\S+)$`) + has = regexp.MustCompile(`^([\w-]+):(\S+) has "(.+)"$`) + gaps = regexp.MustCompile(`\s+`) +) type figureLine struct { n int @@ -38,10 +47,11 @@ type figureLine struct { } // checkFigures holds every figure in the prose of a hand-written page to a line of list, every -// line to words its page still says, and every line that names a constant to that constant's -// value in the checkout spin. spin is "" in a run without it, which leaves the constants to the -// run that has it. -func checkFigures(docs, list, spin string) ([]string, error) { +// line to words its page still says, and every line that names a constant or a file's text to +// the checkout that has it. from is the checkouts at hand: a line of a repository that is not +// among them - spin's, in a run without spin - is left to the run that has it. +func checkFigures(docs, list string, from map[string]checkout) ([]string, error) { + spin := from["spin"].root raw, err := os.ReadFile(list) if err != nil { return nil, err @@ -89,6 +99,20 @@ func checkFigures(docs, list, spin string) ([]string, error) { bad = append(bad, fmt.Sprintf("%s:%d: %s does not say %q: take the line out", list, l.n, l.page, l.words)) continue } + if m := has.FindStringSubmatch(l.what); m != nil { + c, ok := from[m[1]] + if !ok { + continue + } + raw, err := os.ReadFile(filepath.Join(c.root, m[2])) + switch { + case err != nil: + bad = append(bad, fmt.Sprintf("%s:%d: %s says %q, and %s no longer has %s: correct the page and the line", list, l.n, l.page, l.words, m[1], m[2])) + case !strings.Contains(gaps.ReplaceAllString(string(raw), " "), gaps.ReplaceAllString(m[3], " ")): + bad = append(bad, fmt.Sprintf("%s:%d: %s says %q, and %s's %s no longer says %q: correct the page and the line", list, l.n, l.page, l.words, m[1], m[2], m[3])) + } + continue + } m := held.FindStringSubmatch(l.what) if m == nil || spin == "" { continue diff --git a/tools/refgen/figures.txt b/tools/refgen/figures.txt index e5a90b5..16d6788 100644 --- a/tools/refgen/figures.txt +++ b/tools/refgen/figures.txt @@ -4,26 +4,27 @@ # | | spin:# = # # where a constant of spin's decides the figure - task reference:check reads it and fails when it -# is another value, naming the page - or, in the third place, why nothing holds it. A figure a +# is another value, naming the page; or `: has ""`, where the text that decides +# it is held to still being in the file; or, in the third place, why nothing holds it. A figure a # page says that has no line fails, and so does a line whose words its page no longer says. A # "not held" line is a debt: the figure is true only while somebody remembers it. -admin/configuration.md | seven days | not held: struct field default SessionDurationHours: 168 in DefaultSecurity() of internal/controlplane/settings/security.go, overridden by the session_duration_hours setting +admin/configuration.md | seven days | spin:internal/controlplane/settings/security.go has "SessionDurationHours: 168," admin/hosts.mdx | 24 hours | spin:internal/controlplane/server/runnertokenserver/runnertoken.go#registrationTokenExpiry = 24h admin/hosts.mdx | 30 days | spin:internal/controlplane/server/runnertokenserver/runnertoken.go#maxRegistrationTokenExpiry = 720h -admin/observability.md | fifteen minutes | not held: systemd timer OnUnitActiveSec=15min in internal/installation/install/units/spin-vmbackup.timer -admin/observability.md | 30 days | not held: var DefaultRetention (PlatformLogs: 30) in internal/logstore/logstore.go, overridden by the logs_platform_retention_days setting -admin/observability.md | 7 days | not held: var DefaultRetention (WorkspaceLogs: 7) in internal/logstore/logstore.go, overridden by the logs_workspace_retention_days setting -admin/observability.md | 7 days | not held: var DefaultRetention (Traces: 7) in internal/logstore/logstore.go, overridden by the traces_retention_days setting +admin/observability.md | fifteen minutes | spin:internal/installation/install/units/spin-vmbackup.timer has "OnUnitActiveSec=15min" +admin/observability.md | 30 days | spin:internal/logstore/logstore.go has "PlatformLogs: 30," +admin/observability.md | 7 days | spin:internal/logstore/logstore.go has "WorkspaceLogs: 7," +admin/observability.md | 7 days | spin:internal/logstore/logstore.go has "Traces: 7}" admin/observability.md | 30 days | spin:internal/metricstore/metricstore.go#RetentionDays = 30 -admin/observability.md | thirty seconds | not held: poll_frequency = "30s" in the Alloy template internal/installation/boot/files/alloy.alloy.tmpl +admin/observability.md | thirty seconds | spin:internal/installation/boot/files/alloy.alloy.tmpl has "poll_frequency = "30s"" admin/recovery.md | five minutes | spin:internal/pgserver/server.go#ArchiveTimeoutSeconds = 300 admin/recovery.md | five minutes | spin:internal/pgserver/server.go#ArchiveTimeoutSeconds = 300 -admin/users.md | seven days | not held: struct field default SessionDurationHours: 168 in DefaultSecurity() of internal/controlplane/settings/security.go, overridden by the session_duration_hours setting +admin/users.md | seven days | spin:internal/controlplane/settings/security.go has "SessionDurationHours: 168," admin/users.md | a year | spin:internal/controlplane/server/authserver/apikeys.go#maxAPIKeyLifetime = 8784h aws/cost.mdx | Three months | not a figure: a heading naming the period the cost table prices aws/cost.mdx | a month | not a figure: the unit of S3's price per GB a month aws/cost.mdx | a month | not a figure: the unit of Route 53's price per hosted zone -aws/network.mdx | ten-second | not held: Terraform local internal_record_ttl = 10 in modules/controlplane/dns.tf of spin-terraform-aws +aws/network.mdx | ten-second | spin-terraform-aws:modules/controlplane/dns.tf has "internal_record_ttl = 10" aws/setup.md | a few minutes | not a figure: an estimate of how long Let's Encrypt issuance takes aws/setup.md | ten minutes | not a figure: an estimate ("About") of the first bring-up time aws/setup.md | a few minutes | not a figure: an estimate of how long a first runner takes to come up @@ -39,16 +40,16 @@ concepts/workspaces.md | 512 MB | spin:internal/controlplane/server/workspaceser concepts/workspaces.md | 10 GB | spin:internal/controlplane/server/workspaceserver/workspace.go#DefaultDiskGB = 10 concepts/workspaces.md | three times | spin:internal/lifecycle/budget.go#GenerationAttempts = 3 concepts/workspaces.md | Ten minutes | spin:internal/controlplane/server/workspaceserver/idle.go#WarnMinutes = 10 -overview/evaluate.md | 30 GB | not held: flag default -free-gb 30 in hack/lanes/cmd/lanes/ci.go (the CI run's preflight); nothing checks it for the development stack +overview/evaluate.md | 30 GB | spin:hack/lanes/cmd/lanes/ci.go has "fs.Int("free-gb", 30," overview/how-it-works.mdx | five minutes | spin:internal/pgserver/server.go#ArchiveTimeoutSeconds = 300 overview/how-it-works.mdx | a minute | spin:internal/controlplane/domain/reconcile/reconcile.go#Every = 1m overview/how-it-works.mdx | three times | spin:internal/lifecycle/budget.go#GenerationAttempts = 3 overview/how-it-works.mdx | five minutes | spin:internal/controlplane/settings/settings.go#DefaultVolumeRPOSeconds = 300 -overview/one-machine.md | 30 GB | not held: flag default -free-gb 30 in hack/lanes/cmd/lanes/ci.go (the CI run's preflight); nothing checks it for the development stack +overview/one-machine.md | 30 GB | spin:hack/lanes/cmd/lanes/ci.go has "fs.Int("free-gb", 30," quickstart.md | few minutes | not a figure: an estimate of how long a runner takes to come up security/data.md | 64 KiB | spin:internal/storage/crypto/layer.go#LayerFrameBytes = 65536 security/data.md | fifteen minutes | spin:internal/controlplane/server/volumeserver/storagecreds.go#StorageCredentialTTL = 15m -security/identity.md | seven days | not held: struct field default SessionDurationHours: 168 in DefaultSecurity() of internal/controlplane/settings/security.go, overridden by the session_duration_hours setting +security/identity.md | seven days | spin:internal/controlplane/settings/security.go has "SessionDurationHours: 168," security/identity.md | a year | spin:internal/controlplane/server/authserver/apikeys.go#maxAPIKeyLifetime = 8784h security/identity.md | fifteen minutes | spin:internal/installation/provider/aws/stsjoin/stsjoin.go#maxExpiry = 15m security/identity.md | an hour | spin:internal/controlplane/server/runnertokenserver/runnertoken.go#runnerTokenExpiry = 1h diff --git a/tools/refgen/figures_test.go b/tools/refgen/figures_test.go index 8a391c1..e954339 100644 --- a/tools/refgen/figures_test.go +++ b/tools/refgen/figures_test.go @@ -19,6 +19,9 @@ func TestCheckFigures(t *testing.T) { "listed with why nothing holds it": {"About 220 MB.", "# sizes\np.md | 220 MB | not held: the size of Microsoft's server\n", ttl, ""}, "a link's target is not the page's": {"[It stops]() and [reads](/a/30-days/).", "", ttl, ""}, "a flag's own value is the CLI's": {"Pass `--expires 30 days`.\n\n```bash\nsleep 5 minutes\n```", "", ttl, ""}, + "held by a file's text": {"Lasts seven days.", "p.md | seven days | spin:p/p.go has \"Tries = 3\"\n", ttl, ""}, + "the text is gone": {"Lasts seven days.", "p.md | seven days | spin:p/p.go has \"Tries = 4\"\n", ttl, `no longer says "Tries = 4"`}, + "the text's file is gone": {"Lasts seven days.", "p.md | seven days | spin:q/q.go has \"Tries = 3\"\n", ttl, "spin no longer has q/q.go"}, "a figure with no line": {"Lasts an hour.", "", ttl, `says "an hour", which nothing holds`}, "the constant moved": {"Lasts fifteen minutes.", "p.md | fifteen minutes | spin:p/p.go#TTL = 15m\n", strings.Replace(ttl, "15", "10", 1), "TTL is now 10m0s, not 15m"}, "the count moved": {"Tried three times.", "p.md | three times | spin:p/p.go#Tries = 3\n", strings.Replace(ttl, "= 3", "= 5", 1), "Tries is now 5, not 3"}, @@ -42,7 +45,7 @@ func TestCheckFigures(t *testing.T) { if err := os.WriteFile(list, []byte(c.list), 0o644); err != nil { t.Fatal(err) } - bad, err := checkFigures(docs, list, spin) + bad, err := checkFigures(docs, list, map[string]checkout{"spin": {root: spin}}) if err != nil { t.Fatal(err) } @@ -64,7 +67,7 @@ func TestCheckFiguresWithoutSpin(t *testing.T) { if err := os.WriteFile(list, []byte("p.md | fifteen minutes | spin:gone.go#TTL = 15m\n"), 0o644); err != nil { t.Fatal(err) } - if bad, err := checkFigures(docs, list, ""); err != nil || len(bad) > 0 { + if bad, err := checkFigures(docs, list, nil); err != nil || len(bad) > 0 { t.Errorf("checkFigures without spin: %v, %v", bad, err) } } diff --git a/tools/refgen/main.go b/tools/refgen/main.go index 7044050..ad67cc0 100644 --- a/tools/refgen/main.go +++ b/tools/refgen/main.go @@ -174,7 +174,7 @@ func run() error { func() ([]string, error) { return checkDomains(*docs, *tf) }, func() ([]string, error) { return checkTofu(*docs, *tf) }, func() ([]string, error) { return checkReads(*docs, from) }, - func() ([]string, error) { return checkFigures(*docs, figuresList, *spin) }, + func() ([]string, error) { return checkFigures(*docs, figuresList, from) }, } { b, err := c() if err != nil {