@@ -19,7 +19,6 @@ package docker
1919import (
2020 "context"
2121 "os"
22- "strings"
2322 "testing"
2423
2524 "github.com/docker/docker/api/types/container"
@@ -30,10 +29,7 @@ import (
3029 "github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/debug/types"
3130 "github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/deploy/label"
3231 "github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/docker/debugger"
33- "github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/docker/tracker"
3432 "github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/graph"
35- "github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/kubernetes/manifest"
36- "github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/log"
3733 "github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/schema/latest"
3834 "github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/schema/util"
3935 "github.com/GoogleContainerTools/skaffold/v2/testutil"
@@ -284,6 +280,9 @@ func (m mockConfig) ContainerDebugging() bool { return true }
284280func (m mockConfig ) GetInsecureRegistries () map [string ]bool { return nil }
285281func (m mockConfig ) GetKubeContext () string { return "" }
286282func (m mockConfig ) GlobalConfig () string { return "" }
283+ func (m mockConfig ) MinikubeProfile () string { return "" }
284+ func (m mockConfig ) Mode () config.RunMode { return "" }
285+ func (m mockConfig ) Prune () bool { return false }
287286
288287// Tests for Docker Compose deployer
289288
@@ -407,6 +406,72 @@ func TestReplaceComposeImages(t *testing.T) {
407406 },
408407 shouldErr : false ,
409408 },
409+ {
410+ name : "reject false positive: 'app' should not match 'my-app'" ,
411+ composeConfig : map [string ]interface {}{
412+ "services" : map [string ]interface {}{
413+ "myapp" : map [string ]interface {}{
414+ "image" : "app" ,
415+ },
416+ },
417+ },
418+ artifact : graph.Artifact {
419+ ImageName : "my-app" ,
420+ Tag : "my-app:v1.0.0" ,
421+ },
422+ expectedConfig : map [string ]interface {}{
423+ "services" : map [string ]interface {}{
424+ "myapp" : map [string ]interface {}{
425+ "image" : "app" , // Should NOT be replaced
426+ },
427+ },
428+ },
429+ shouldErr : false ,
430+ },
431+ {
432+ name : "match with registry prefix" ,
433+ composeConfig : map [string ]interface {}{
434+ "services" : map [string ]interface {}{
435+ "frontend" : map [string ]interface {}{
436+ "image" : "frontend" ,
437+ },
438+ },
439+ },
440+ artifact : graph.Artifact {
441+ ImageName : "frontend" ,
442+ Tag : "gcr.io/project/frontend:sha256-abc" ,
443+ },
444+ expectedConfig : map [string ]interface {}{
445+ "services" : map [string ]interface {}{
446+ "frontend" : map [string ]interface {}{
447+ "image" : "gcr.io/project/frontend:sha256-abc" ,
448+ },
449+ },
450+ },
451+ shouldErr : false ,
452+ },
453+ {
454+ name : "match with tag in compose file" ,
455+ composeConfig : map [string ]interface {}{
456+ "services" : map [string ]interface {}{
457+ "backend" : map [string ]interface {}{
458+ "image" : "backend:latest" ,
459+ },
460+ },
461+ },
462+ artifact : graph.Artifact {
463+ ImageName : "backend" ,
464+ Tag : "backend:v2.0.0" ,
465+ },
466+ expectedConfig : map [string ]interface {}{
467+ "services" : map [string ]interface {}{
468+ "backend" : map [string ]interface {}{
469+ "image" : "backend:v2.0.0" ,
470+ },
471+ },
472+ },
473+ shouldErr : false ,
474+ },
410475 }
411476
412477 for _ , test := range tests {
@@ -542,146 +607,3 @@ func TestDeployWithComposeFileOperations(t *testing.T) {
542607 })
543608 }
544609}
545-
546- func (m mockConfig ) MinikubeProfile () string { return "" }
547- func (m mockConfig ) Mode () config.RunMode { return "" }
548- func (m mockConfig ) Prune () bool { return false }
549-
550- // createMockDockerCommand creates a fake 'docker' command that records invocations
551- // This allows us to test command execution without modifying production code
552- func createMockDockerCommand (t * testing.T , tmpDir string ) (binPath string , counterFile string ) {
553- t .Helper ()
554-
555- // Create a bin directory for our fake docker command
556- binDir := tmpDir + "/bin"
557- if err := os .MkdirAll (binDir , 0755 ); err != nil {
558- t .Fatalf ("Failed to create bin directory: %v" , err )
559- }
560-
561- // Counter file to track invocations
562- counterFile = tmpDir + "/docker-call-count.txt"
563-
564- // Create a fake docker script that records calls
565- dockerScript := binDir + "/docker"
566- scriptContent := `#!/bin/bash
567- # Mock docker command that tracks invocations
568- echo "1" >> "` + counterFile + `"
569-
570- # Log the arguments for debugging
571- echo "$@" >> "` + tmpDir + `/docker-args.log"
572-
573- # Exit successfully
574- exit 0
575- `
576- if err := os .WriteFile (dockerScript , []byte (scriptContent ), 0755 ); err != nil {
577- t .Fatalf ("Failed to create mock docker script: %v" , err )
578- }
579-
580- return binDir , counterFile
581- }
582-
583- // TestDeployWithCompose_MultipleArtifacts_CallsComposeUpOnce verifies that docker compose up
584- // is called only once when deploying multiple artifacts, with all image replacements done together.
585- // This test uses a mock docker command to track invocations without modifying production code.
586- func TestDeployWithCompose_MultipleArtifacts_CallsComposeUpOnce (t * testing.T ) {
587- testutil .Run (t , "multiple artifacts trigger compose up only once" , func (tt * testutil.T ) {
588- // Create temporary directory for test files
589- tmpDir := t .TempDir ()
590-
591- // Create mock docker command
592- binDir , counterFile := createMockDockerCommand (t , tmpDir )
593-
594- // Modify PATH to use our mock docker command
595- originalPath := os .Getenv ("PATH" )
596- os .Setenv ("PATH" , binDir + ":" + originalPath )
597- defer os .Setenv ("PATH" , originalPath )
598-
599- // Create a temporary compose file for testing
600- composeFile := tmpDir + "/docker-compose.yml"
601- composeContent := `version: '3'
602- services:
603- frontend:
604- image: frontend
605- backend:
606- image: backend
607- `
608- if err := os .WriteFile (composeFile , []byte (composeContent ), 0644 ); err != nil {
609- t .Fatalf ("Failed to write compose file: %v" , err )
610- }
611-
612- // Set environment variable to use our test compose file
613- originalEnv := os .Getenv ("SKAFFOLD_COMPOSE_FILE" )
614- os .Setenv ("SKAFFOLD_COMPOSE_FILE" , composeFile )
615- defer func () {
616- if originalEnv != "" {
617- os .Setenv ("SKAFFOLD_COMPOSE_FILE" , originalEnv )
618- } else {
619- os .Unsetenv ("SKAFFOLD_COMPOSE_FILE" )
620- }
621- }()
622-
623- // Create a minimal deployer
624- d := & Deployer {
625- cfg : & latest.DockerDeploy {
626- UseCompose : true ,
627- Images : []string {"frontend" , "backend" },
628- },
629- labeller : & label.DefaultLabeller {},
630- tracker : tracker .NewContainerTracker (),
631- logger : & log.NoopLogger {},
632- }
633-
634- // Skip network creation
635- d .once .Do (func () {})
636-
637- // Create multiple artifacts
638- builds := []graph.Artifact {
639- {
640- ImageName : "frontend" ,
641- Tag : "frontend:v1.0.0" ,
642- },
643- {
644- ImageName : "backend" ,
645- Tag : "backend:v1.0.0" ,
646- },
647- }
648-
649- // Call Deploy
650- err := d .Deploy (context .Background (), os .Stdout , builds , manifest.ManifestListByConfig {})
651- if err != nil {
652- t .Fatalf ("Deploy failed: %v" , err )
653- }
654-
655- // Read the counter file to check how many times docker was called
656- counterData , err := os .ReadFile (counterFile )
657- if err != nil {
658- t .Fatalf ("Failed to read counter file: %v" , err )
659- }
660-
661- // Count lines (each invocation adds one line)
662- lines := 0
663- for _ , b := range counterData {
664- if b == '\n' {
665- lines ++
666- }
667- }
668-
669- // FIXED: docker compose up should be called only once with all artifacts
670- if lines != 1 {
671- t .Errorf ("Expected docker compose up to be called 1 time, but was called %d times" , lines )
672- }
673-
674- // Log arguments for verification
675- argsData , _ := os .ReadFile (tmpDir + "/docker-args.log" )
676- t .Logf ("SUCCESS: docker compose up was called %d time for %d artifacts" , lines , len (builds ))
677- t .Logf ("Docker command arguments:\n %s" , string (argsData ))
678-
679- // Verify it was a compose up command
680- if lines == 1 {
681- argsStr := string (argsData )
682- if ! strings .Contains (argsStr , "compose" ) || ! strings .Contains (argsStr , "up" ) {
683- t .Errorf ("Expected 'docker compose up' command, got: %s" , argsStr )
684- }
685- }
686- })
687- }
0 commit comments