Skip to content

Commit b039a47

Browse files
leodidoona-agent
andcommitted
fix: prevent cache pollution from builds without git metadata
CRITICAL FIX: The previous silent fallback to epoch 0 when commit is empty caused cache pollution. Same source code would produce different checksums depending on whether it was built from git clone or source tarball. Changes: 1. **Fail fast in production**: Return error if no git commit and no SOURCE_DATE_EPOCH 2. **Support SOURCE_DATE_EPOCH**: Explicit override for reproducible builds 3. **Test environment detection**: Allow epoch fallback only in test binaries 4. **Clear error messages**: Guide users to fix their build environment Why this matters: - Prevents cache pollution (same source → different checksums) - Maintains cache hit rates across build environments - Forces proper git metadata in production builds - Supports reproducible builds via SOURCE_DATE_EPOCH Test environment detection: - Checks if binary ends with '.test' (go test) - Checks LEEWAY_TEST_MODE environment variable - Only allows epoch fallback in tests This ensures cache consistency and prevents silent failures that would degrade cache performance over time. Co-authored-by: Ona <[email protected]>
1 parent a1bf26d commit b039a47

File tree

2 files changed

+66
-3
lines changed

2 files changed

+66
-3
lines changed

pkg/leeway/build.go

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2401,12 +2401,30 @@ func createDockerExportMetadata(wd, version string, cfg DockerPkgConfig) error {
24012401

24022402
// getDeterministicMtime returns the Unix timestamp to use for tar --mtime flag.
24032403
// It uses the same timestamp source as SBOM normalization for consistency.
2404-
// If no git commit is available (e.g., in test fixtures), returns 0 (Unix epoch).
2404+
// For test environments, returns 0 (Unix epoch) when git is unavailable.
2405+
// For production, fails fast to prevent cache pollution.
24052406
func (p *Package) getDeterministicMtime() (int64, error) {
24062407
commit := p.C.Git().Commit
24072408
if commit == "" {
2408-
// No git commit available (e.g., test fixtures) - use Unix epoch for determinism
2409-
return 0, nil
2409+
// Try SOURCE_DATE_EPOCH first (explicit override for reproducible builds)
2410+
if epoch := os.Getenv("SOURCE_DATE_EPOCH"); epoch != "" {
2411+
timestamp, err := strconv.ParseInt(epoch, 10, 64)
2412+
if err != nil {
2413+
return 0, fmt.Errorf("invalid SOURCE_DATE_EPOCH: %w", err)
2414+
}
2415+
return timestamp, nil
2416+
}
2417+
2418+
// Check if we're in a test environment
2419+
if isTestEnvironment() {
2420+
// Test fixtures don't have git - use epoch for determinism
2421+
return 0, nil
2422+
}
2423+
2424+
// Production build without git is an error - prevents cache pollution
2425+
return 0, fmt.Errorf("no git commit available for deterministic mtime. "+
2426+
"Ensure repository is properly cloned with git history, or set SOURCE_DATE_EPOCH environment variable. "+
2427+
"Building from source tarballs without git metadata will cause cache inconsistencies")
24102428
}
24112429

24122430
timestamp, err := getGitCommitTimestamp(context.Background(), commit)
@@ -2418,6 +2436,22 @@ func (p *Package) getDeterministicMtime() (int64, error) {
24182436
return timestamp.Unix(), nil
24192437
}
24202438

2439+
// isTestEnvironment detects if we're running in a test context.
2440+
// This allows test fixtures to use epoch fallback without polluting production cache.
2441+
func isTestEnvironment() bool {
2442+
// Check if running as a test binary (compiled with go test)
2443+
if strings.HasSuffix(os.Args[0], ".test") {
2444+
return true
2445+
}
2446+
2447+
// Check for explicit test mode environment variable
2448+
if os.Getenv("LEEWAY_TEST_MODE") == "true" {
2449+
return true
2450+
}
2451+
2452+
return false
2453+
}
2454+
24212455
// Update buildGeneric to use compression arg helper
24222456
func (p *Package) buildGeneric(buildctx *buildContext, wd, result string) (res *packageBuild, err error) {
24232457
cfg, ok := p.Config.(GenericPkgConfig)

pkg/leeway/compression_test.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package leeway
22

33
import (
4+
"os"
45
"strings"
56
"testing"
67
)
@@ -163,3 +164,31 @@ func TestBuildTarCommand_MtimeWithOtherOptions(t *testing.T) {
163164
}
164165
}
165166
}
167+
168+
func TestIsTestEnvironment(t *testing.T) {
169+
// This test itself should be detected as a test environment
170+
if !isTestEnvironment() {
171+
t.Error("isTestEnvironment() should return true when running in test binary")
172+
}
173+
174+
// Test with explicit environment variable
175+
originalEnv := os.Getenv("LEEWAY_TEST_MODE")
176+
defer func() {
177+
if originalEnv != "" {
178+
os.Setenv("LEEWAY_TEST_MODE", originalEnv)
179+
} else {
180+
os.Unsetenv("LEEWAY_TEST_MODE")
181+
}
182+
}()
183+
184+
os.Setenv("LEEWAY_TEST_MODE", "true")
185+
if !isTestEnvironment() {
186+
t.Error("isTestEnvironment() should return true when LEEWAY_TEST_MODE=true")
187+
}
188+
189+
os.Setenv("LEEWAY_TEST_MODE", "false")
190+
// Should still be true because we're in a test binary
191+
if !isTestEnvironment() {
192+
t.Error("isTestEnvironment() should return true when running in test binary (even if LEEWAY_TEST_MODE=false)")
193+
}
194+
}

0 commit comments

Comments
 (0)