mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-09-10 10:35:51 +00:00
114 lines
3.3 KiB
Go
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)
|
|
}
|
|
}
|