dockerimg: fix Docker image reuse for Dockerfile.sketch
Add sketch_context label to Docker images built from Dockerfile.sketch to enable
proper image reuse detection across runs, eliminating unnecessary rebuilds.
Co-Authored-By: sketch <hello@sketch.dev>
Change-ID: sfa4d61590e7e00f9k
diff --git a/dockerimg/dockerimg.go b/dockerimg/dockerimg.go
index e207904..5791083 100644
--- a/dockerimg/dockerimg.go
+++ b/dockerimg/dockerimg.go
@@ -887,14 +887,31 @@
}
start := time.Now()
- cmd := exec.CommandContext(ctx,
- "docker", "build",
+ cmdArgs := []string{
+ "build",
"-t", imgName,
"-f", dockerfilePath,
- "--build-arg", "GIT_USER_EMAIL="+gitUserEmail,
- "--build-arg", "GIT_USER_NAME="+gitUserName,
- ".",
- )
+ "--build-arg", "GIT_USER_EMAIL=" + gitUserEmail,
+ "--build-arg", "GIT_USER_NAME=" + gitUserName,
+ }
+
+ // Add the sketch_context label for image reuse detection
+ var contextHash string
+ if len(candidates) > 0 {
+ // Building from Dockerfile.sketch or similar static file
+ contents, err := os.ReadFile(dockerfilePath)
+ if err != nil {
+ return "", err
+ }
+ contextHash = hashInitFiles(map[string]string{dockerfilePath: string(contents)})
+ } else {
+ // Building from generated dockerfile
+ contextHash = hashInitFiles(initFiles)
+ }
+ cmdArgs = append(cmdArgs, "--label", "sketch_context="+contextHash)
+ cmdArgs = append(cmdArgs, ".")
+
+ cmd := exec.CommandContext(ctx, "docker", cmdArgs...)
cmd.Dir = gitRoot
// We print the docker build output whether or not the user
// has selected --verbose. Building an image takes a while
diff --git a/dockerimg/dockerimg_test.go b/dockerimg/dockerimg_test.go
index be5344a..c3cc65c 100644
--- a/dockerimg/dockerimg_test.go
+++ b/dockerimg/dockerimg_test.go
@@ -446,3 +446,79 @@
t.Errorf("expected Dockerfile.sketch to be prioritized, but got %s", prioritized)
}
}
+
+// TestDockerfileSketchImageReuse tests that Docker images built from Dockerfile.sketch
+// are properly reused when the Dockerfile content hasn't changed.
+func TestDockerfileSketchImageReuse(t *testing.T) {
+ // Create a temporary directory for the test repo
+ tmpDir := t.TempDir()
+
+ // Create a simple Dockerfile.sketch
+ dockerfileContent := `FROM ubuntu:24.04
+LABEL test=true
+CMD ["echo", "hello"]
+`
+ dockerfilePath := filepath.Join(tmpDir, "Dockerfile.sketch")
+ err := os.WriteFile(dockerfilePath, []byte(dockerfileContent), 0o644)
+ if err != nil {
+ t.Fatalf("Failed to write Dockerfile.sketch: %v", err)
+ }
+
+ // Test that hashInitFiles produces consistent results
+ initFiles := map[string]string{
+ dockerfilePath: dockerfileContent,
+ }
+ hash1 := hashInitFiles(initFiles)
+ hash2 := hashInitFiles(initFiles)
+
+ if hash1 != hash2 {
+ t.Errorf("hashInitFiles should be deterministic, got %s and %s", hash1, hash2)
+ }
+
+ // Test that hash changes when content changes
+ modifiedFiles := map[string]string{
+ dockerfilePath: dockerfileContent + "RUN echo modified\n",
+ }
+ hash3 := hashInitFiles(modifiedFiles)
+
+ if hash1 == hash3 {
+ t.Errorf("hashInitFiles should produce different hashes for different content, got %s for both", hash1)
+ }
+
+ t.Logf("Original hash: %s", hash1)
+ t.Logf("Modified hash: %s", hash3)
+}
+
+// TestPrioritizeDockerfiles tests the Dockerfile prioritization logic
+func TestDockerfileSketchPriority(t *testing.T) {
+ tests := []struct {
+ name string
+ candidates []string
+ expected string
+ }{
+ {
+ name: "dockerfile.sketch_wins_over_dockerfile",
+ candidates: []string{"/path/Dockerfile", "/path/Dockerfile.sketch"},
+ expected: "/path/Dockerfile.sketch",
+ },
+ {
+ name: "dockerfile.sketch_case_insensitive",
+ candidates: []string{"/path/dockerfile", "/path/dockerfile.sketch"},
+ expected: "/path/dockerfile.sketch",
+ },
+ {
+ name: "dockerfile_wins_over_variations",
+ candidates: []string{"/path/Dockerfile.dev", "/path/Dockerfile"},
+ expected: "/path/Dockerfile",
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ result := prioritizeDockerfiles(tt.candidates)
+ if result != tt.expected {
+ t.Errorf("prioritizeDockerfiles(%v) = %s, want %s", tt.candidates, result, tt.expected)
+ }
+ })
+ }
+}