@@ -284,6 +284,8 @@ func (m mockConfig) ContainerDebugging() bool { return true }
284284func (m mockConfig ) GetInsecureRegistries () map [string ]bool { return nil }
285285func (m mockConfig ) GetKubeContext () string { return "" }
286286func (m mockConfig ) GlobalConfig () string { return "" }
287+ func (m mockConfig ) Mode () config.RunMode { return "" }
288+ func (m mockConfig ) Prune () bool { return false }
287289
288290// Tests for Docker Compose deployer
289291
@@ -407,6 +409,72 @@ func TestReplaceComposeImages(t *testing.T) {
407409 },
408410 shouldErr : false ,
409411 },
412+ {
413+ name : "reject false positive: 'app' should not match 'my-app'" ,
414+ composeConfig : map [string ]interface {}{
415+ "services" : map [string ]interface {}{
416+ "myapp" : map [string ]interface {}{
417+ "image" : "app" ,
418+ },
419+ },
420+ },
421+ artifact : graph.Artifact {
422+ ImageName : "my-app" ,
423+ Tag : "my-app:v1.0.0" ,
424+ },
425+ expectedConfig : map [string ]interface {}{
426+ "services" : map [string ]interface {}{
427+ "myapp" : map [string ]interface {}{
428+ "image" : "app" , // Should NOT be replaced
429+ },
430+ },
431+ },
432+ shouldErr : false ,
433+ },
434+ {
435+ name : "match with registry prefix" ,
436+ composeConfig : map [string ]interface {}{
437+ "services" : map [string ]interface {}{
438+ "frontend" : map [string ]interface {}{
439+ "image" : "frontend" ,
440+ },
441+ },
442+ },
443+ artifact : graph.Artifact {
444+ ImageName : "frontend" ,
445+ Tag : "gcr.io/project/frontend:sha256-abc" ,
446+ },
447+ expectedConfig : map [string ]interface {}{
448+ "services" : map [string ]interface {}{
449+ "frontend" : map [string ]interface {}{
450+ "image" : "gcr.io/project/frontend:sha256-abc" ,
451+ },
452+ },
453+ },
454+ shouldErr : false ,
455+ },
456+ {
457+ name : "match with tag in compose file" ,
458+ composeConfig : map [string ]interface {}{
459+ "services" : map [string ]interface {}{
460+ "backend" : map [string ]interface {}{
461+ "image" : "backend:latest" ,
462+ },
463+ },
464+ },
465+ artifact : graph.Artifact {
466+ ImageName : "backend" ,
467+ Tag : "backend:v2.0.0" ,
468+ },
469+ expectedConfig : map [string ]interface {}{
470+ "services" : map [string ]interface {}{
471+ "backend" : map [string ]interface {}{
472+ "image" : "backend:v2.0.0" ,
473+ },
474+ },
475+ },
476+ shouldErr : false ,
477+ },
410478 }
411479
412480 for _ , test := range tests {
@@ -542,146 +610,3 @@ func TestDeployWithComposeFileOperations(t *testing.T) {
542610 })
543611 }
544612}
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