Files
pulse/scripts/installtests/container_dependency_contract_test.go
2026-08-30 06:40:25 +01:00

114 lines
3.3 KiB
Go

package installtests
import (
"bufio"
"io/fs"
"os"
"path/filepath"
"regexp"
"strings"
"testing"
)
var externalImageDigest = regexp.MustCompile(`@sha256:[0-9a-f]{64}$`)
func TestEveryDockerfilePinsExternalBases(t *testing.T) {
err := filepath.WalkDir(repoFile("."), func(path string, entry fs.DirEntry, walkErr error) error {
if walkErr != nil {
return walkErr
}
if entry.IsDir() {
if entry.Name() == "node_modules" || entry.Name() == ".git" {
return filepath.SkipDir
}
return nil
}
if !strings.HasPrefix(entry.Name(), "Dockerfile") {
return nil
}
file, err := os.Open(path)
if err != nil {
return err
}
defer file.Close()
stages := make(map[string]bool)
scanner := bufio.NewScanner(file)
lineNumber := 0
for scanner.Scan() {
lineNumber++
fields := strings.Fields(scanner.Text())
if len(fields) < 2 || !strings.EqualFold(fields[0], "FROM") {
continue
}
imageIndex := 1
if strings.HasPrefix(fields[imageIndex], "--platform=") {
imageIndex++
}
if imageIndex >= len(fields) {
t.Fatalf("%s:%d has malformed FROM instruction", path, lineNumber)
}
image := fields[imageIndex]
if !stages[image] && !externalImageDigest.MatchString(image) {
t.Fatalf("%s:%d external base image %q is not pinned to a full sha256 digest", path, lineNumber, image)
}
for index := imageIndex + 1; index+1 < len(fields); index++ {
if strings.EqualFold(fields[index], "AS") {
stages[fields[index+1]] = true
break
}
}
}
return scanner.Err()
})
if err != nil {
t.Fatalf("audit Dockerfiles: %v", err)
}
}
func TestNodeToolchainParity(t *testing.T) {
workflowPaths, err := filepath.Glob(repoFile(".github", "workflows", "*.yml"))
if err != nil {
t.Fatalf("list workflows: %v", err)
}
versionPattern := regexp.MustCompile(`node-version:\s*['"]?([^'"\s#]+)`)
setupCount := 0
versionCount := 0
for _, path := range workflowPaths {
contentBytes, err := os.ReadFile(path)
if err != nil {
t.Fatalf("read %s: %v", path, err)
}
content := string(contentBytes)
setupCount += strings.Count(content, "actions/setup-node@")
for _, match := range versionPattern.FindAllStringSubmatch(content, -1) {
versionCount++
if match[1] != nodeToolchainLine {
t.Fatalf("%s selects Node.js %s; all workflows must use governed line %s", path, match[1], nodeToolchainLine)
}
}
}
if setupCount == 0 || versionCount != setupCount {
t.Fatalf("every setup-node step must select the governed line: setup=%d versions=%d", setupCount, versionCount)
}
workerBytes, err := os.ReadFile(repoFile("scripts", "release-preflight-worker.sh"))
if err != nil {
t.Fatalf("read release preflight worker: %v", err)
}
if !strings.Contains(string(workerBytes), `!= "`+nodeToolchainLine+`"`) {
t.Fatalf("release preflight worker must enforce Node.js %s parity", nodeToolchainLine)
}
devcontainerBytes, err := os.ReadFile(repoFile(".devcontainer", "devcontainer.json"))
if err != nil {
t.Fatalf("read devcontainer config: %v", err)
}
devcontainer := string(devcontainerBytes)
if !strings.Contains(devcontainer, `"version": "`+nodeToolchainLine+`"`) ||
!regexp.MustCompile(`ghcr\.io/devcontainers/features/node@sha256:[0-9a-f]{64}`).MatchString(devcontainer) {
t.Fatalf("devcontainer must use Node.js %s from a digest-pinned feature", nodeToolchainLine)
}
}