Files
at-container-registry/pkg/appview/installscript/installscript_test.go
T
Evan JarrettandClaude Opus 5 9d8bd513da appview: render the install scripts from config instead of shipping ATCR's
seamark.dev's /install and /settings/devices told users to pipe
seamark.dev/static/install.sh into bash. That file was the unmodified ATCR
script: it announced itself as the "ATCR Credential Helper Installer",
installed docker-credential-atcr, and finished by telling the user to configure
credHelpers for atcr.io, the wrong registry for that deployment. Anyone
following the documented setup ended up pointed at another service. The
templates hardcoded docker-credential-atcr, "atcr" and ~/.atcr/device.json
alongside a correctly themed {{ .RegistryURL }}.

The scripts are now rendered from config by a handler, rather than forked per
brand. A theme overlay was the alternative and was worse: it needed a full copy
of both install.sh and install.ps1 per brand, four scripts to keep in sync, and
the operator asked for these values to come from config.

credential_helper.name is the single knob. Docker resolves a credHelpers value
x by exec'ing docker-credential-x, so the credHelpers value, the binary suffix
and the config directory are genuinely one word, not three that can drift. It
is validated against a strict pattern because it is interpolated into a shell
script.

install.sh renders byte-identical to the deleted static file under the atcr
default, so existing installs are unaffected. install.ps1 differs by one line,
where a stale usage comment named a path the script is not served at.

Two behaviour changes worth noting: these two URLs drop from a one-year
Cache-Control to five minutes, since the body now depends on deployment config;
and credential_helper.tangled_repo becomes a real overridable default. It was
previously assigned over unconditionally and read by nothing, while the shipped
script used a different URL form.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PDqoCE1j3njokkZ9b1C5n9
2026-09-02 21:38:10 -05:00

219 lines
6.5 KiB
Go

package installscript_test
import (
"bytes"
"strings"
"testing"
"atcr.io/pkg/appview/installscript"
)
func mustBrand(t *testing.T, name, display, releases string) installscript.Brand {
t.Helper()
b, err := installscript.NewBrand(name, display, releases)
if err != nil {
t.Fatalf("NewBrand(%q, %q, %q) error = %v", name, display, releases, err)
}
return b
}
func TestNewBrandDefaultsToATCR(t *testing.T) {
b := mustBrand(t, "", "", "")
if b.Name != "atcr" {
t.Errorf("Name = %q, want atcr", b.Name)
}
if b.BinaryName() != "docker-credential-atcr" {
t.Errorf("BinaryName() = %q", b.BinaryName())
}
if b.ConfigDir() != "~/.atcr" {
t.Errorf("ConfigDir() = %q", b.ConfigDir())
}
if b.DeviceFile() != "~/.atcr/device.json" {
t.Errorf("DeviceFile() = %q", b.DeviceFile())
}
if b.EnvPrefix() != "ATCR" {
t.Errorf("EnvPrefix() = %q", b.EnvPrefix())
}
if b.ReleasesBaseURL != installscript.DefaultReleasesBaseURL {
t.Errorf("ReleasesBaseURL = %q", b.ReleasesBaseURL)
}
}
func TestNewBrandSeamark(t *testing.T) {
b := mustBrand(t, "seamark", "Seamark", "")
if b.BinaryName() != "docker-credential-seamark" {
t.Errorf("BinaryName() = %q", b.BinaryName())
}
if b.DeviceFile() != "~/.seamark/device.json" {
t.Errorf("DeviceFile() = %q", b.DeviceFile())
}
if b.EnvPrefix() != "SEAMARK" {
t.Errorf("EnvPrefix() = %q", b.EnvPrefix())
}
}
// The name lands inside a shell script and a PowerShell script, so anything
// that is not a plain lowercase word has to be refused at config load.
func TestNewBrandRejectsUnsafeNames(t *testing.T) {
for _, name := range []string{
"ATCR",
"sea mark",
"sea/mark",
"$(id)",
"a`id`",
"-lead",
"sea\nmark",
} {
if _, err := installscript.NewBrand(name, "", ""); err == nil {
t.Errorf("NewBrand(%q) accepted an unsafe name", name)
}
}
}
func TestRenderShellSeamark(t *testing.T) {
var buf bytes.Buffer
err := installscript.RenderShell(&buf, installscript.Params{
Brand: mustBrand(t, "seamark", "Seamark", ""),
RegistryHost: "seamark.cr",
SiteHost: "seamark.dev",
})
if err != nil {
t.Fatalf("RenderShell() error = %v", err)
}
got := buf.String()
for _, want := range []string{
"# Seamark Credential Helper Installation Script",
"# Usage: curl -fsSL https://seamark.dev/static/install.sh | bash",
`BINARY_NAME="docker-credential-seamark"`,
`TANGLED_REPO="${SEAMARK_TANGLED_REPO:-` + installscript.DefaultReleasesBaseURL + `}"`,
`download/docker-credential-seamark_${version_without_v}_${OS}_${ARCH}.tar.gz`,
`{"credHelpers": {"seamark.cr": "seamark"}}`,
` "seamark.cr": "seamark"`,
`if [ -n "$SEAMARK_VERSION" ]; then`,
`VERSION="$SEAMARK_VERSION"`,
"Seamark Credential Helper Installer",
} {
if !strings.Contains(got, want) {
t.Errorf("rendered install.sh missing %q", want)
}
}
// The whole point of the fix: a Seamark deployment must not name atcr
// anywhere in the script it hands to `curl | bash`.
for _, forbidden := range []string{"atcr", "ATCR"} {
if strings.Contains(got, forbidden) {
t.Errorf("rendered install.sh still contains %q:\n%s", forbidden, offendingLines(got, forbidden))
}
}
}
func TestRenderPowerShellSeamark(t *testing.T) {
var buf bytes.Buffer
err := installscript.RenderPowerShell(&buf, installscript.Params{
Brand: mustBrand(t, "seamark", "Seamark", ""),
RegistryHost: "seamark.cr",
SiteHost: "seamark.dev",
})
if err != nil {
t.Fatalf("RenderPowerShell() error = %v", err)
}
got := buf.String()
for _, want := range []string{
`$BinaryName = "docker-credential-seamark.exe"`,
`$env:SEAMARK_INSTALL_DIR`,
`"$env:ProgramFiles\Seamark"`,
`docker-credential-seamark_${versionClean}_Windows_${Arch}.tar.gz`,
` "seamark.cr": "seamark"`,
} {
if !strings.Contains(got, want) {
t.Errorf("rendered install.ps1 missing %q", want)
}
}
for _, forbidden := range []string{"atcr", "ATCR"} {
if strings.Contains(got, forbidden) {
t.Errorf("rendered install.ps1 still contains %q:\n%s", forbidden, offendingLines(got, forbidden))
}
}
}
// The default deployment must keep producing exactly the script it shipped as
// a static file, so a rebrand cannot regress atcr.io's documented install.
func TestRenderDefaultIsUnchangedATCR(t *testing.T) {
p := installscript.Params{
Brand: mustBrand(t, "", "ATCR", ""),
RegistryHost: "atcr.io",
SiteHost: "atcr.io",
}
var sh bytes.Buffer
if err := installscript.RenderShell(&sh, p); err != nil {
t.Fatalf("RenderShell() error = %v", err)
}
for _, want := range []string{
"# ATCR Credential Helper Installation Script",
"# Usage: curl -fsSL https://atcr.io/static/install.sh | bash",
`BINARY_NAME="docker-credential-atcr"`,
`TANGLED_REPO="${ATCR_TANGLED_REPO:-https://tangled.org/did:plc:e3kzdezk5gsirzh7eoqplc64}"`,
`download/docker-credential-atcr_${version_without_v}_${OS}_${ARCH}.tar.gz`,
`{"credHelpers": {"atcr.io": "atcr"}}`,
` "atcr.io": "atcr"`,
`if [ -n "$ATCR_VERSION" ]; then`,
`VERSION="$ATCR_VERSION"`,
"ATCR Credential Helper Installer",
} {
if !strings.Contains(sh.String(), want) {
t.Errorf("rendered install.sh missing %q", want)
}
}
if !strings.HasPrefix(sh.String(), "#!/bin/bash\n") {
t.Error("rendered install.sh lost its shebang")
}
var ps bytes.Buffer
if err := installscript.RenderPowerShell(&ps, p); err != nil {
t.Fatalf("RenderPowerShell() error = %v", err)
}
for _, want := range []string{
`$BinaryName = "docker-credential-atcr.exe"`,
`$env:ATCR_INSTALL_DIR`,
`"$env:ProgramFiles\ATCR"`,
` "atcr.io": "atcr"`,
} {
if !strings.Contains(ps.String(), want) {
t.Errorf("rendered install.ps1 missing %q", want)
}
}
}
// A registry host that differs from the site host is the Seamark shape:
// the UI is seamark.dev, but Docker authenticates against seamark.cr, so the
// credHelpers key has to be the registry.
func TestCredHelpersKeyIsRegistryNotSite(t *testing.T) {
var buf bytes.Buffer
err := installscript.RenderShell(&buf, installscript.Params{
Brand: mustBrand(t, "seamark", "Seamark", ""),
RegistryHost: "seamark.cr",
SiteHost: "seamark.dev",
})
if err != nil {
t.Fatalf("RenderShell() error = %v", err)
}
if strings.Contains(buf.String(), `"seamark.dev": "seamark"`) {
t.Error("credHelpers key used the site host instead of the registry host")
}
}
func offendingLines(s, needle string) string {
var out []string
for _, line := range strings.Split(s, "\n") {
if strings.Contains(line, needle) {
out = append(out, " "+line)
}
}
return strings.Join(out, "\n")
}