Version and bind module build receipts
This commit is contained in:
@@ -3,65 +3,86 @@ package cli
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestBuildReceiptRoundTripBindsFilesAndBuildInputs(t *testing.T) {
|
||||
outputDir := t.TempDir()
|
||||
repositoryDir := t.TempDir()
|
||||
filename := "example-v1.2.3-linux-amd64"
|
||||
artifactPath := filepath.Join(outputDir, filename)
|
||||
contents := []byte("built application")
|
||||
if err := os.WriteFile(artifactPath, contents, 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
digest := sha256.Sum256(contents)
|
||||
receipt := BuildReceipt{
|
||||
RepositoryDir: repositoryDir,
|
||||
ModuleDir: repositoryDir,
|
||||
ModulePath: "example.com/receipt",
|
||||
Tag: "v1.2.3",
|
||||
CandidateCommit: strings.Repeat("a", 40),
|
||||
Version: "v1.2.3",
|
||||
MainPackage: "./cmd/example",
|
||||
BinaryName: "example",
|
||||
Targets: []ReceiptTarget{{OS: "linux", Arch: "amd64"}},
|
||||
Compiler: "go version go1.25.0 linux/amd64",
|
||||
Artifacts: []ReceiptArtifact{{
|
||||
Path: artifactPath,
|
||||
Filename: filename,
|
||||
Target: ReceiptTarget{OS: "linux", Arch: "amd64"},
|
||||
SHA256: hex.EncodeToString(digest[:]),
|
||||
}},
|
||||
}
|
||||
path, err := WriteBuildReceipt(outputDir, receipt)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
loaded, err := LoadBuildReceipt(path)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
settings := BuildSettings{
|
||||
MainPackage: "./cmd/example",
|
||||
BinaryName: "example",
|
||||
Targets: []string{"linux/amd64", "linux/amd64"},
|
||||
}
|
||||
if !loaded.MatchesBuild(repositoryDir, strings.Repeat("a", 40), "v1.2.3", settings) {
|
||||
t.Fatalf("receipt does not match normalized build settings: %#v", loaded)
|
||||
}
|
||||
settings.BinaryName = "other"
|
||||
if loaded.MatchesBuild(repositoryDir, strings.Repeat("a", 40), "v1.2.3", settings) {
|
||||
t.Fatal("receipt matched changed build settings")
|
||||
}
|
||||
if err := os.WriteFile(artifactPath, []byte("changed artifact"), 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := LoadBuildReceipt(path); err == nil || !strings.Contains(err.Error(), "hash changed") {
|
||||
t.Fatalf("LoadBuildReceipt() error = %v, want changed-file hash error", err)
|
||||
func TestBuildReceiptRoundTripBindsRootAndNestedModuleIdentity(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
name string
|
||||
moduleDir string
|
||||
modulePath string
|
||||
tag string
|
||||
}{
|
||||
{name: "root", moduleDir: ".", modulePath: "example.com/receipt", tag: "v1.2.3"},
|
||||
{name: "nested", moduleDir: "integrations/ckconfig", modulePath: "example.com/ckconfig", tag: "integrations/ckconfig/v1.2.3"},
|
||||
} {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
outputDir := t.TempDir()
|
||||
repositoryDir := t.TempDir()
|
||||
moduleDir := repositoryDir
|
||||
if test.moduleDir != "." {
|
||||
moduleDir = filepath.Join(repositoryDir, filepath.FromSlash(test.moduleDir))
|
||||
if err := os.MkdirAll(moduleDir, 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
filename := "example-v1.2.3-linux-amd64"
|
||||
artifactPath := filepath.Join(outputDir, filename)
|
||||
contents := []byte("built application")
|
||||
if err := os.WriteFile(artifactPath, contents, 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
digest := sha256.Sum256(contents)
|
||||
receipt := BuildReceipt{
|
||||
SchemaVersion: BuildReceiptSchemaVersion,
|
||||
RepositoryDir: repositoryDir,
|
||||
ModuleDir: moduleDir,
|
||||
ModulePath: test.modulePath,
|
||||
Tag: test.tag,
|
||||
CandidateCommit: strings.Repeat("a", 40),
|
||||
Version: "v1.2.3",
|
||||
MainPackage: "./cmd/example",
|
||||
BinaryName: "example",
|
||||
Targets: []ReceiptTarget{{OS: "linux", Arch: "amd64"}},
|
||||
Compiler: "go version go1.25.0 linux/amd64",
|
||||
Artifacts: []ReceiptArtifact{{
|
||||
Path: artifactPath, Filename: filename,
|
||||
Target: ReceiptTarget{OS: "linux", Arch: "amd64"}, SHA256: hex.EncodeToString(digest[:]),
|
||||
}},
|
||||
}
|
||||
path, err := WriteBuildReceipt(outputDir, receipt)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
loaded, err := LoadBuildReceipt(path)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
settings := BuildSettings{MainPackage: "./cmd/example", BinaryName: "example", Targets: []string{"linux/amd64", "linux/amd64"}}
|
||||
if !loaded.MatchesBuild(repositoryDir, strings.Repeat("a", 40), "v1.2.3", settings) {
|
||||
t.Fatalf("receipt does not match normalized build settings: %#v", loaded)
|
||||
}
|
||||
settings.BinaryName = "other"
|
||||
if loaded.MatchesBuild(repositoryDir, strings.Repeat("a", 40), "v1.2.3", settings) {
|
||||
t.Fatal("receipt matched changed build settings")
|
||||
}
|
||||
if err := os.WriteFile(artifactPath, []byte("changed artifact"), 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := LoadBuildReceipt(path); err != nil {
|
||||
t.Fatalf("receipt loading verified changed artifact bytes: %v", err)
|
||||
}
|
||||
if err := os.Remove(artifactPath); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := LoadBuildReceipt(path); err != nil {
|
||||
t.Fatalf("receipt loading required artifact bytes: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,7 +92,9 @@ func TestLoadBuildReceiptRejectsUnknownFieldsAndMultipleValues(t *testing.T) {
|
||||
body string
|
||||
want string
|
||||
}{
|
||||
{name: "unknown field", body: `{"unexpected":true}`, want: "unknown field"},
|
||||
{name: "unknown field", body: `{"schema_version":1,"unexpected":true}`, want: "unknown field"},
|
||||
{name: "unversioned receipt", body: `{}`, want: "regenerate it with the updated build command"},
|
||||
{name: "unknown schema", body: `{"schema_version":2}`, want: "regenerate it with the updated build command"},
|
||||
{name: "multiple values", body: `{} {}`, want: "multiple JSON values"},
|
||||
} {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
@@ -85,3 +108,66 @@ func TestLoadBuildReceiptRejectsUnknownFieldsAndMultipleValues(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteBuildReceiptRejectsInconsistentRecordedIdentity(t *testing.T) {
|
||||
repositoryDir := t.TempDir()
|
||||
moduleDir := filepath.Join(repositoryDir, "integrations", "ckconfig")
|
||||
if err := os.MkdirAll(moduleDir, 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
outputDir := t.TempDir()
|
||||
receipt := BuildReceipt{
|
||||
SchemaVersion: BuildReceiptSchemaVersion,
|
||||
RepositoryDir: repositoryDir, ModuleDir: moduleDir, ModulePath: "example.com/ckconfig", Tag: "v1.2.3",
|
||||
CandidateCommit: strings.Repeat("a", 40), Version: "v1.2.3", MainPackage: ".", BinaryName: "helper",
|
||||
OutputDir: outputDir, Targets: []ReceiptTarget{{OS: "linux", Arch: "amd64"}}, Compiler: "go version fixture",
|
||||
Artifacts: []ReceiptArtifact{{
|
||||
Path: filepath.Join(outputDir, "helper-v1.2.3-linux-amd64"), Filename: "helper-v1.2.3-linux-amd64",
|
||||
Target: ReceiptTarget{OS: "linux", Arch: "amd64"}, SHA256: strings.Repeat("a", 64),
|
||||
}},
|
||||
}
|
||||
if _, err := WriteBuildReceipt(outputDir, receipt); err == nil || !strings.Contains(err.Error(), "full tag") {
|
||||
t.Fatalf("WriteBuildReceipt() error = %v, want inconsistent full tag", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadBuildReceiptRejectsMismatchedModuleIdentity(t *testing.T) {
|
||||
repositoryDir := t.TempDir()
|
||||
outputDir := t.TempDir()
|
||||
artifactPath := filepath.Join(outputDir, "helper-v1.2.3-linux-amd64")
|
||||
contents := []byte("fixture")
|
||||
if err := os.WriteFile(artifactPath, contents, 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
digest := sha256.Sum256(contents)
|
||||
receipt := BuildReceipt{
|
||||
SchemaVersion: BuildReceiptSchemaVersion,
|
||||
RepositoryDir: repositoryDir, ModuleDir: repositoryDir, ModulePath: "example.com/receipt", Tag: "v1.2.3",
|
||||
CandidateCommit: strings.Repeat("a", 40), Version: "v1.2.3", MainPackage: ".", BinaryName: "helper",
|
||||
OutputDir: outputDir, Targets: []ReceiptTarget{{OS: "linux", Arch: "amd64"}}, Compiler: "go version fixture",
|
||||
Artifacts: []ReceiptArtifact{{
|
||||
Path: artifactPath, Filename: filepath.Base(artifactPath),
|
||||
Target: ReceiptTarget{OS: "linux", Arch: "amd64"}, SHA256: hex.EncodeToString(digest[:]),
|
||||
}},
|
||||
}
|
||||
contents, err := json.Marshal(receipt)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var fields map[string]any
|
||||
if err := json.Unmarshal(contents, &fields); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
fields["module_dir"] = filepath.Join(repositoryDir, "integrations", "ckconfig")
|
||||
contents, err = json.Marshal(fields)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
path := filepath.Join(outputDir, BuildReceiptName)
|
||||
if err := os.WriteFile(path, contents, 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := LoadBuildReceipt(path); err == nil || !strings.Contains(err.Error(), "inconsistent module identity") {
|
||||
t.Fatalf("LoadBuildReceipt() error = %v, want inconsistent module identity", err)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user