mirror of
https://tangled.org/evan.jarrett.net/at-container-registry
synced 2026-09-23 02:34:17 +00:00
build-trixie hardcoded `-tags billing`, so the only way to build a non-billing
appview was to bypass the Makefile and drive the trixie container by hand. Both
production deploys of this deployment did exactly that, and the workspace notes
record it as "the deploy tool cannot reproduce this build" — a tool that cannot
produce the artifact you actually ship is a tool nobody can safely use.
Worse, the failure is silent and one-directional. Nothing about a billing binary
looks different: same name, same version stamp, same vcs.revision. A deploy that
forgot the manual path would quietly switch billing on for a deployment whose
operator had chosen to run without it, exposing a live Stripe webhook endpoint
and a paid tier ladder on a service configured for neither. Production has been
running a non-billing appview since May precisely because someone did not use
`make build-trixie`.
Billing is now opt-in and off by default:
make build-trixie # no billing
make build-trixie BILLING=1 # billing
deploy/upcloud update appview # no billing
deploy/upcloud update appview --with-billing
Only the appview is affected; hold, scanner, labeler and the credential helper
never reference pkg/billing.
The flag alone is not enough, so verifyAppviewBilling reads the built binary
before anything is uploaded and refuses to ship a mismatch. It observes rather
than trusts, because the flag and the artifact can disagree for reasons the flag
cannot see: a stale bin/atcr-appview from an earlier build, a Makefile that
hardcodes the tag, a builder image that ignored it. The Stripe SDK links only
under the tag, so its symbols are a direct measurement — verified as a
discriminator here: 2338 stripe-go strings with the tag, 0 without.
A missing binary is an error rather than an absence of symbols, so a deploy
cannot proceed on a file that was never built by reading it as "no billing".
Verified: the default build-trixie output has 0 stripe-go symbols and does carry
the "Billing is not enabled on this deployment" stub. Guard covered both
directions by test, plus the missing-binary case. make lint 0 issues across
root, deploy and credential-helper; make test green across 44 packages.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AV6Mk2AgghFsNo4HWQEaBV
60 lines
2.0 KiB
Go
60 lines
2.0 KiB
Go
package main
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// The guard exists because the build tag is invisible in the binary's name and
|
|
// in its version stamp: a wrong one is otherwise only discovered in production.
|
|
// These cases pin the two directions that matter — shipping billing to a
|
|
// deployment that runs without it, and shipping a stub to one that expects it.
|
|
func TestVerifyAppviewBilling(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
for _, tc := range []struct {
|
|
name string
|
|
content string
|
|
want bool
|
|
wantErr bool
|
|
}{
|
|
{"billing build, billing wanted", "...github.com/stripe/stripe-go/v79...", true, false},
|
|
{"stub build, no billing wanted", "...Billing is not enabled on this deployment...", false, false},
|
|
{"billing build leaking into a no-billing deploy", "...stripe-go...", false, true},
|
|
{"stub build where billing was asked for", "...no payment code here...", true, true},
|
|
} {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
path := filepath.Join(t.TempDir(), "atcr-appview")
|
|
if err := os.WriteFile(path, []byte(tc.content), 0o600); err != nil {
|
|
t.Fatalf("write fixture: %v", err)
|
|
}
|
|
err := verifyAppviewBilling(path, tc.want)
|
|
if tc.wantErr && err == nil {
|
|
t.Fatalf("want=%v: expected a mismatch error, got nil", tc.want)
|
|
}
|
|
if !tc.wantErr && err != nil {
|
|
t.Fatalf("want=%v: unexpected error: %v", tc.want, err)
|
|
}
|
|
// The message has to say which way round the mismatch is, or the
|
|
// operator cannot tell whether to add or drop the flag.
|
|
if tc.wantErr && !strings.Contains(err.Error(), "billing mismatch") {
|
|
t.Errorf("error should name the mismatch, got: %v", err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// A missing binary must fail loudly rather than be read as "no stripe symbols,
|
|
// therefore billing is off" — which would let a deploy proceed on a file that
|
|
// was never built.
|
|
func TestVerifyAppviewBillingMissingBinary(t *testing.T) {
|
|
t.Parallel()
|
|
err := verifyAppviewBilling(filepath.Join(t.TempDir(), "does-not-exist"), false)
|
|
if err == nil {
|
|
t.Fatal("expected an error for a missing binary, got nil")
|
|
}
|
|
}
|