maidn-cli/internal/bootstrap/template_revisions_test.go

318 lines
11 KiB
Go

package bootstrap
import (
"errors"
"os"
"path/filepath"
"strings"
"testing"
"github.com/Pingu-Studio/MaidnCLI/internal/config"
)
func TestEnsureTemplateRevisionsLocksFirstCheckout(t *testing.T) {
workspace, cfg, git := templateRevisionTestConfig(t)
useTemplateRevisionGit(t, git)
if err := ensureTemplateRevisions(workspace, cfg); err != nil {
t.Fatal(err)
}
lock, err := readTemplateRevisionLock(filepath.Join(workspace, "maidn-template-revisions.yaml"))
if err != nil {
t.Fatal(err)
}
if lock.CICD.Commit != git.commits["cicd"] || lock.Manifests.Commit != git.commits["manifests"] || lock.Talos.Commit != git.commits["talos"] {
t.Fatalf("lock did not record checked-out commits: %#v", lock)
}
data, err := os.ReadFile(filepath.Join(workspace, "maidn-template-revisions.yaml"))
if err != nil {
t.Fatal(err)
}
if strings.Contains(string(data), "template-password") || strings.Contains(git.commands(), "template-password") {
t.Fatal("template credentials reached the lock or Git command arguments")
}
}
func TestEnsureTemplateRevisionsReusesLockedCommitAfterBranchDrift(t *testing.T) {
workspace, cfg, git := templateRevisionTestConfig(t)
useTemplateRevisionGit(t, git)
if err := ensureTemplateRevisions(workspace, cfg); err != nil {
t.Fatal(err)
}
locked := map[string]string{}
for name, commit := range git.commits {
locked[name] = commit
}
git.commits = map[string]string{"cicd": strings.Repeat("d", 40), "manifests": strings.Repeat("e", 40), "talos": strings.Repeat("f", 40)}
git.resetCalls()
if err := ensureTemplateRevisions(workspace, cfg); err != nil {
t.Fatal(err)
}
lock, err := readTemplateRevisionLock(filepath.Join(workspace, "maidn-template-revisions.yaml"))
if err != nil {
t.Fatal(err)
}
for name, commit := range locked {
if git.fetched[name] != commit || git.checkedOut[name] != commit {
t.Fatalf("branch drift changed locked %s revision: fetched %q, checked out %q", name, git.fetched[name], git.checkedOut[name])
}
}
if lock.CICD.Commit != locked["cicd"] || lock.Manifests.Commit != locked["manifests"] || lock.Talos.Commit != locked["talos"] {
t.Fatalf("normal bootstrap rewrote template lock: %#v", lock)
}
if strings.Contains(git.commands(), "fetch origin main") {
t.Fatal("later run fetched a mutable branch instead of the lock commit")
}
}
func TestRefreshTemplateRevisionsUpdatesExistingLockToConfiguredHeads(t *testing.T) {
workspace, cfg, git := templateRevisionTestConfig(t)
useTemplateRevisionGit(t, git)
if err := ensureTemplateRevisions(workspace, cfg); err != nil {
t.Fatal(err)
}
git.commits = map[string]string{"cicd": strings.Repeat("d", 40), "manifests": strings.Repeat("e", 40), "talos": strings.Repeat("f", 40)}
git.resetCalls()
if err := refreshTemplateRevisions(workspace, cfg); err != nil {
t.Fatal(err)
}
lock, err := readTemplateRevisionLock(filepath.Join(workspace, "maidn-template-revisions.yaml"))
if err != nil {
t.Fatal(err)
}
for name, commit := range git.commits {
if git.fetched[name] != commit || git.checkedOut[name] != commit {
t.Fatalf("refresh did not use configured %s branch head: fetched %q, checked out %q", name, git.fetched[name], git.checkedOut[name])
}
}
if lock.CICD.Commit != git.commits["cicd"] || lock.Manifests.Commit != git.commits["manifests"] || lock.Talos.Commit != git.commits["talos"] {
t.Fatalf("refresh did not update template lock: %#v", lock)
}
if !strings.Contains(git.commands(), "fetch origin main") || strings.Contains(git.commands(), "template-password") {
t.Fatal("refresh did not safely fetch configured branch heads")
}
}
func TestEnsureTemplateRevisionsRejectsChangedRefWithoutGit(t *testing.T) {
workspace, cfg, git := templateRevisionTestConfig(t)
useTemplateRevisionGit(t, git)
if err := ensureTemplateRevisions(workspace, cfg); err != nil {
t.Fatal(err)
}
git.resetCalls()
cfg.Templates.CICDRepoRef = "release"
err := ensureTemplateRevisions(workspace, cfg)
if err == nil || !strings.Contains(err.Error(), "workspace revision lock") || !strings.Contains(err.Error(), "new empty workspaceDir") {
t.Fatalf("changed ref error was not safe and actionable: %v", err)
}
if git.commands() != "" {
t.Fatal("changed ref touched Git before rejecting the lock mismatch")
}
}
func TestEnsureTemplateRevisionsHidesSourceWhenLockedCommitIsUnavailable(t *testing.T) {
workspace, cfg, git := templateRevisionTestConfig(t)
useTemplateRevisionGit(t, git)
if err := ensureTemplateRevisions(workspace, cfg); err != nil {
t.Fatal(err)
}
git.failFetch = true
err := ensureTemplateRevisions(workspace, cfg)
if err == nil || !strings.Contains(err.Error(), "locked template revision cannot be resolved") || !strings.Contains(err.Error(), "new empty workspaceDir") || strings.Contains(err.Error(), "template-password") {
t.Fatalf("locked revision failure exposed source details or lacked recovery guidance: %v", err)
}
}
func TestEnsureTemplateRevisionsAllowsOnlyGeneratedTalosChanges(t *testing.T) {
workspace, cfg, git := templateRevisionTestConfig(t)
useTemplateRevisionGit(t, git)
if err := ensureTemplateRevisions(workspace, cfg); err != nil {
t.Fatal(err)
}
git.dirty["talos"] = []string{" M generated/talsecret.yaml", "?? generated/clusterconfig/talosconfig"}
if err := ensureTemplateRevisions(workspace, cfg); err != nil {
t.Fatalf("generated Talos changes blocked reconciliation: %v", err)
}
git.dirty["talos"] = []string{" M terraform/main.tf"}
if err := ensureTemplateRevisions(workspace, cfg); err == nil {
t.Fatal("Talos changes outside generated directory were accepted")
}
if !hasUnexpectedTemplateChanges([]byte(" M generated/../terraform/main.tf\x00"), "generated") {
t.Fatal("Talos changes escaping the generated directory were accepted")
}
}
func TestTemplateGitRunnerUsesTemporaryAskPassForPrivateSource(t *testing.T) {
original := runGitEnvironment
t.Cleanup(func() { runGitEnvironment = original })
var environment []string
var command string
runGitEnvironment = func(_ string, env []string, args ...string) ([]byte, error) {
environment = env
command = strings.Join(args, " ")
return nil, nil
}
source := "https://reader:template-password@git.example.test/templates/cicd.git"
repository := config.RedactURL(source)
run, cleanup, err := templateGitRunner(config.Config{}, source, repository)
if err != nil {
t.Fatal(err)
}
defer cleanup()
if _, err := run("", "clone", repository, "checkout"); err != nil {
t.Fatal(err)
}
var askPass string
for _, item := range environment {
if strings.HasPrefix(item, "GIT_ASKPASS=") {
askPass = strings.TrimPrefix(item, "GIT_ASKPASS=")
}
}
if askPass == "" || strings.Contains(command, "template-password") || strings.Contains(command, "reader:") {
t.Fatalf("private template checkout did not isolate credentials: command=%q", command)
}
if _, err := os.Stat(askPass); err != nil {
t.Fatalf("temporary askpass script was not created: %v", err)
}
}
func TestEnsureTemplateRevisionsRejectsCredentialBearingExistingOrigin(t *testing.T) {
workspace, cfg, git := templateRevisionTestConfig(t)
useTemplateRevisionGit(t, git)
if err := ensureTemplateRevisions(workspace, cfg); err != nil {
t.Fatal(err)
}
git.origins[filepath.Join(workspace, "maidn-cicd-cluster-template")] = cfg.Templates.CICDRepoURL
if err := ensureTemplateRevisions(workspace, cfg); err == nil || strings.Contains(err.Error(), "template-password") {
t.Fatalf("credential-bearing checkout origin was accepted or exposed: %v", err)
}
}
func templateRevisionTestConfig(t *testing.T) (string, config.Config, *fakeTemplateGit) {
t.Helper()
workspace := t.TempDir()
cloneParent := filepath.Join(workspace, "checkouts")
if err := os.MkdirAll(cloneParent, 0755); err != nil {
t.Fatal(err)
}
git := &fakeTemplateGit{
commits: map[string]string{
"cicd": strings.Repeat("a", 40),
"manifests": strings.Repeat("b", 40),
"talos": strings.Repeat("c", 40),
},
origins: map[string]string{},
fetched: map[string]string{},
checkedOut: map[string]string{},
dirty: map[string][]string{},
}
return workspace, config.Config{
WorkspaceDir: workspace,
Git: config.GitConfig{CloneParent: cloneParent},
Talos: config.TalosConfig{RepoDirName: "talos", GeneratedDir: "generated"},
Templates: config.TemplateConfig{
CICDRepoURL: "https://reader:template-password@git.example.test/templates/cicd.git",
CICDRepoRef: "main",
ManifestsRepoURL: "https://git.example.test/templates/manifests.git",
ManifestsRepoRef: "main",
TalosRepoURL: "https://git.example.test/templates/talos.git",
TalosRepoRef: "main",
},
}, git
}
func useTemplateRevisionGit(t *testing.T, git *fakeTemplateGit) {
t.Helper()
original := runGit
runGit = git.run
originalEnvironment := runGitEnvironment
runGitEnvironment = func(dir string, _ []string, args ...string) ([]byte, error) { return git.run(dir, args...) }
t.Cleanup(func() {
runGit = original
runGitEnvironment = originalEnvironment
})
}
type fakeTemplateGit struct {
commits map[string]string
origins map[string]string
fetched map[string]string
checkedOut map[string]string
dirty map[string][]string
calls []string
failFetch bool
}
func (git *fakeTemplateGit) run(dir string, args ...string) ([]byte, error) {
git.calls = append(git.calls, strings.Join(args, " "))
if len(args) == 0 {
return nil, errors.New("missing Git command")
}
switch args[0] {
case "clone":
dir = args[len(args)-1]
if err := os.MkdirAll(dir, 0755); err != nil {
return nil, err
}
git.origins[dir] = args[len(args)-2]
return nil, nil
case "remote":
return []byte(git.origins[dir] + "\n"), nil
case "status":
if records := git.dirty[git.templateName(dir)]; len(records) != 0 {
return []byte(strings.Join(records, "\x00") + "\x00"), nil
}
return nil, nil
case "fetch":
if git.failFetch {
return nil, errors.New("not found")
}
target := args[len(args)-1]
name := git.templateName(dir)
if len(target) == 40 {
git.fetched[name] = target
} else {
git.fetched[name] = git.commits[name]
}
return nil, nil
case "checkout":
git.checkedOut[git.templateName(dir)] = args[len(args)-1]
return nil, nil
case "rev-parse":
if len(args) == 2 && args[1] == "--is-inside-work-tree" {
return []byte("true\n"), nil
}
target := args[len(args)-1]
if target == "FETCH_HEAD^{commit}" {
return []byte(git.fetched[git.templateName(dir)] + "\n"), nil
}
if target == "HEAD^{commit}" {
return []byte(git.checkedOut[git.templateName(dir)] + "\n"), nil
}
}
return nil, errors.New("unexpected Git command")
}
func (git *fakeTemplateGit) templateName(dir string) string {
switch filepath.Base(dir) {
case "maidn-cicd-cluster-template":
return "cicd"
case "cicd-deployment-manifests-template":
return "manifests"
default:
return "talos"
}
}
func (git *fakeTemplateGit) resetCalls() {
git.calls = nil
}
func (git *fakeTemplateGit) commands() string {
return strings.Join(git.calls, "\n")
}