@@ -34,6 +34,7 @@ import (
3434 "time"
3535
3636 "github.com/docker/docker/api/types/mount"
37+ yaml "gopkg.in/yaml.v3"
3738 "gvisor.dev/gvisor/pkg/test/dockerutil"
3839 "gvisor.dev/gvisor/pkg/test/testutil"
3940)
@@ -411,6 +412,23 @@ type dockerCommandOptions struct {
411412 privileged bool
412413}
413414
415+ type dockerComposeConfig struct {
416+ Name string `yaml:"name,omitempty"`
417+ Services map [string ]dockerService `yaml:"services,omitempty"`
418+ }
419+
420+ type dockerService struct {
421+ Image string `yaml:"image,omitempty"`
422+ Build dockerBuild `yaml:"build,omitempty"`
423+ Privileged bool `yaml:"privileged,omitempty"`
424+ NetworkdMode string `yaml:"network_mode,omitempty"`
425+ }
426+
427+ type dockerBuild struct {
428+ Context string `yaml:"context,omitempty"`
429+ Network string `yaml:"network,omitempty"`
430+ }
431+
414432func testDockerMatrix (ctx context.Context , t * testing.T , d * dockerutil.Container ) {
415433 definitions := []struct {
416434 name string
@@ -421,6 +439,8 @@ func testDockerMatrix(ctx context.Context, t *testing.T, d *dockerutil.Container
421439 {"docker_run" , testDockerRun , true , true },
422440 {"docker_build" , testDockerBuild , true , false },
423441 {"docker_exec" , testDockerExec , false , true },
442+ {"docker_compose_run" , testDockerComposeRun , true , true },
443+ {"docker_compose_build" , testDockerComposeBuild , true , false },
424444 }
425445 for _ , def := range definitions {
426446 hostNetworkOpts := []bool {false }
@@ -653,6 +673,101 @@ func testDockerExec(ctx context.Context, t *testing.T, d *dockerutil.Container,
653673 }
654674}
655675
676+ func testDockerComposeBuild (ctx context.Context , t * testing.T , d * dockerutil.Container , opts dockerCommandOptions ) {
677+ dockerComposeFileName := strings .ToLower (strings .ReplaceAll (testutil .RandomID ("docker-compose-build" ), "/" , "-" ))
678+ // Letters in image name must be lowercase.
679+ imageName := strings .ToLower (strings .ReplaceAll (testutil .RandomID ("testdockercomposebuild" ), "/" , "-" ))
680+ network := ""
681+ if opts .hostNetwork {
682+ network = "host"
683+ }
684+ config := dockerComposeConfig {
685+ Name : "image_test" ,
686+ Services : map [string ]dockerService {
687+ imageName : dockerService {
688+ Image : imageName ,
689+ Build : dockerBuild {
690+ Context : "." ,
691+ Network : network ,
692+ },
693+ },
694+ },
695+ }
696+ buildConfig , err := yaml .Marshal (config )
697+ if err != nil {
698+ log .Fatalf ("error marshaling to docker-compose.yml: %v" , err )
699+ }
700+ dockerfileContent := fmt .Sprintf ("\" FROM %s\n RUN apk add curl\" " , testAlpineImage )
701+ cmd := []string {"echo" , dockerfileContent , ">" , "Dockerfile" }
702+ _ , err = d .ExecProcess (ctx , dockerutil.ExecOpts {},
703+ "/bin/sh" , "-c" , strings .Join (cmd , " " ))
704+ if err != nil {
705+ t .Fatalf ("failed to write Dockerfile: %v" , err )
706+ }
707+ cmd = []string {"echo" , fmt .Sprintf ("\" %s\" " , string (buildConfig )), ">" , dockerComposeFileName }
708+ // Write a config file for docker compose.
709+ _ , err = d .ExecProcess (ctx , dockerutil.ExecOpts {},
710+ "/bin/sh" , "-c" , strings .Join (cmd , " " ))
711+ if err != nil {
712+ t .Fatalf ("failed to write docker-compose.yml: %v" , err )
713+ }
714+ _ , err = d .ExecProcess (ctx , dockerutil.ExecOpts {}, "/bin/sh" , "-c" , fmt .Sprintf ("docker compose -f %s build" , dockerComposeFileName ))
715+ if err != nil {
716+ t .Fatalf ("docker compose build failed: %v" , err )
717+ }
718+ defer removeDockerImage (ctx , imageName , d )
719+ d .WaitForOutput (ctx , fmt .Sprintf ("%s Built" , imageName ), defaultWait )
720+ if err := checkDockerImage (ctx , imageName , d ); err != nil {
721+ t .Fatalf ("failed to find docker image: %v" , err )
722+ }
723+ }
724+
725+ func testDockerComposeRun (ctx context.Context , t * testing.T , d * dockerutil.Container , opts dockerCommandOptions ) {
726+ dockerComposeAppName := strings .ToLower (strings .ReplaceAll (testutil .RandomID ("docker-compose-run-test-app" ), "/" , "-" ))
727+ dockerComposeFileName := strings .ToLower (strings .ReplaceAll (testutil .RandomID ("docker-compose-run" ), "/" , "-" ))
728+ networkMode := ""
729+ // TODO(b/436936268): test bridge network driver in docker compose run.
730+ // The option now has no impact since the test command doesn't attempt to connect to internet.
731+ if opts .hostNetwork {
732+ networkMode = "host"
733+ }
734+ config := dockerComposeConfig {
735+ Name : "image_test" ,
736+ Services : map [string ]dockerService {
737+ dockerComposeAppName : dockerService {
738+ Image : testAlpineImage ,
739+ Privileged : opts .privileged ,
740+ NetworkdMode : networkMode ,
741+ },
742+ },
743+ }
744+ dockerComposeContent , err := yaml .Marshal (config )
745+ if err != nil {
746+ log .Fatalf ("error marshaling to docker-compose.yml: %v" , err )
747+ }
748+ cmd := []string {"echo" , fmt .Sprintf ("\" %s\" " , string (dockerComposeContent )), ">" , dockerComposeFileName }
749+ _ , err = d .ExecProcess (
750+ ctx ,
751+ dockerutil.ExecOpts {},
752+ "/bin/sh" , "-c" , strings .Join (cmd , " " ))
753+ if err != nil {
754+ t .Fatalf ("failed to write %s: %v" , dockerComposeFileName , err )
755+ }
756+ execProc , err := d .ExecProcess (ctx , dockerutil.ExecOpts {},
757+ []string {"docker" , "compose" , "-f" , dockerComposeFileName , "run" , "--rm" , dockerComposeAppName , "sh" , "-c" , "echo hello gVisor" }... )
758+ if err != nil {
759+ t .Fatalf ("docker compose run failed: %v" , err )
760+ }
761+ output , err := execProc .Logs ()
762+ if err != nil {
763+ t .Fatalf ("docker logs failed: %v" , err )
764+ }
765+ expectedOutput := "hello gVisor"
766+ if ! strings .Contains (output , expectedOutput ) {
767+ t .Fatalf ("docker didn't get output expected: %q, got: %q" , expectedOutput , output )
768+ }
769+ }
770+
656771func TestMain (m * testing.M ) {
657772 dockerutil .EnsureSupportedDockerVersion ()
658773 flag .Parse ()
0 commit comments