diff --git a/aci/compose.go b/aci/compose.go index 2b9f47b4b..92b5d76c2 100644 --- a/aci/compose.go +++ b/aci/compose.go @@ -64,7 +64,7 @@ func (cs *aciComposeService) Start(ctx context.Context, project *types.Project, return errdefs.ErrNotImplemented } -func (cs *aciComposeService) Up(ctx context.Context, project *types.Project, detach bool) error { +func (cs *aciComposeService) Up(ctx context.Context, project *types.Project, options compose.UpOptions) error { logrus.Debugf("Up on project with name %q", project.Name) if err := autocreateFileshares(ctx, project); err != nil { @@ -102,14 +102,14 @@ func (cs aciComposeService) warnKeepVolumeOnDown(ctx context.Context, projectNam return nil } -func (cs *aciComposeService) Down(ctx context.Context, project string) error { - logrus.Debugf("Down on project with name %q", project) +func (cs *aciComposeService) Down(ctx context.Context, projectName string, options compose.DownOptions) error { + logrus.Debugf("Down on projectName with name %q", projectName) - if err := cs.warnKeepVolumeOnDown(ctx, project); err != nil { + if err := cs.warnKeepVolumeOnDown(ctx, projectName); err != nil { return err } - cg, err := deleteACIContainerGroup(ctx, cs.ctx, project) + cg, err := deleteACIContainerGroup(ctx, cs.ctx, projectName) if err != nil { return err } @@ -198,6 +198,6 @@ func (cs *aciComposeService) Logs(ctx context.Context, projectName string, consu return errdefs.ErrNotImplemented } -func (cs *aciComposeService) Convert(ctx context.Context, project *types.Project, format string) ([]byte, error) { +func (cs *aciComposeService) Convert(ctx context.Context, project *types.Project, options compose.ConvertOptions) ([]byte, error) { return nil, errdefs.ErrNotImplemented } diff --git a/api/client/compose.go b/api/client/compose.go index b069a6ab4..aac8d56eb 100644 --- a/api/client/compose.go +++ b/api/client/compose.go @@ -48,11 +48,11 @@ func (c *composeService) Start(ctx context.Context, project *types.Project, cons return errdefs.ErrNotImplemented } -func (c *composeService) Up(context.Context, *types.Project, bool) error { +func (c *composeService) Up(context.Context, *types.Project, compose.UpOptions) error { return errdefs.ErrNotImplemented } -func (c *composeService) Down(context.Context, string) error { +func (c *composeService) Down(context.Context, string, compose.DownOptions) error { return errdefs.ErrNotImplemented } @@ -68,6 +68,6 @@ func (c *composeService) List(context.Context, string) ([]compose.Stack, error) return nil, errdefs.ErrNotImplemented } -func (c *composeService) Convert(context.Context, *types.Project, string) ([]byte, error) { +func (c *composeService) Convert(context.Context, *types.Project, compose.ConvertOptions) ([]byte, error) { return nil, errdefs.ErrNotImplemented } diff --git a/api/compose/api.go b/api/compose/api.go index 1a2b6f234..5687cc019 100644 --- a/api/compose/api.go +++ b/api/compose/api.go @@ -35,9 +35,9 @@ type Service interface { // Start executes the equivalent to a `compose start` Start(ctx context.Context, project *types.Project, consumer LogConsumer) error // Up executes the equivalent to a `compose up` - Up(ctx context.Context, project *types.Project, detach bool) error + Up(ctx context.Context, project *types.Project, options UpOptions) error // Down executes the equivalent to a `compose down` - Down(ctx context.Context, projectName string) error + Down(ctx context.Context, projectName string, options DownOptions) error // Logs executes the equivalent to a `compose logs` Logs(ctx context.Context, projectName string, consumer LogConsumer, options LogOptions) error // Ps executes the equivalent to a `compose ps` @@ -45,7 +45,25 @@ type Service interface { // List executes the equivalent to a `docker stack ls` List(ctx context.Context, projectName string) ([]Stack, error) // Convert translate compose model into backend's native format - Convert(ctx context.Context, project *types.Project, format string) ([]byte, error) + Convert(ctx context.Context, project *types.Project, options ConvertOptions) ([]byte, error) +} + +// UpOptions group options of the Up API +type UpOptions struct { + // Detach will create services and return immediately + Detach bool +} + +// DownOptions group options of the Down API +type DownOptions struct { + // RemoveOrphans will cleanup containers that are not declared on the compose model but own the same labels + RemoveOrphans bool +} + +// ConvertOptions group options of the Convert API +type ConvertOptions struct { + // Format define the output format used to dump converted application model (json|yaml) + Format string } // PortPublisher hold status about published port diff --git a/cli/cmd/compose/convert.go b/cli/cmd/compose/convert.go index 29207ad56..5128155a0 100644 --- a/cli/cmd/compose/convert.go +++ b/cli/cmd/compose/convert.go @@ -20,6 +20,8 @@ import ( "context" "fmt" + "github.com/docker/compose-cli/api/compose" + "github.com/compose-spec/compose-go/cli" "github.com/spf13/cobra" @@ -61,7 +63,9 @@ func runConvert(ctx context.Context, opts composeOptions) error { return err } - json, err = c.ComposeService().Convert(ctx, project, opts.Format) + json, err = c.ComposeService().Convert(ctx, project, compose.ConvertOptions{ + Format: opts.Format, + }) if err != nil { return err } diff --git a/cli/cmd/compose/down.go b/cli/cmd/compose/down.go index 3b05c038b..05715760f 100644 --- a/cli/cmd/compose/down.go +++ b/cli/cmd/compose/down.go @@ -19,6 +19,8 @@ package compose import ( "context" + "github.com/docker/compose-cli/api/compose" + "github.com/spf13/cobra" "github.com/docker/compose-cli/api/client" @@ -52,7 +54,9 @@ func runDown(ctx context.Context, opts composeOptions) error { if err != nil { return "", err } - return projectName, c.ComposeService().Down(ctx, projectName) + return projectName, c.ComposeService().Down(ctx, projectName, compose.DownOptions{ + RemoveOrphans: false, + }) }) return err } diff --git a/cli/cmd/compose/up.go b/cli/cmd/compose/up.go index 0be7eb9b1..a74e352ba 100644 --- a/cli/cmd/compose/up.go +++ b/cli/cmd/compose/up.go @@ -40,7 +40,7 @@ func upCommand(contextType string) *cobra.Command { Short: "Create and start containers", RunE: func(cmd *cobra.Command, args []string) error { switch contextType { - case store.LocalContextType, store.DefaultContextType: + case store.LocalContextType, store.DefaultContextType, store.EcsLocalSimulationContextType: return runCreateStart(cmd.Context(), opts, args) default: return runUp(cmd.Context(), opts, args) @@ -68,7 +68,9 @@ func runUp(ctx context.Context, opts composeOptions, services []string) error { } _, err = progress.Run(ctx, func(ctx context.Context) (string, error) { - return "", c.ComposeService().Up(ctx, project, opts.Detach) + return "", c.ComposeService().Up(ctx, project, compose.UpOptions{ + Detach: opts.Detach, + }) }) return err } @@ -96,7 +98,7 @@ func runCreateStart(ctx context.Context, opts composeOptions, services []string) fmt.Println("Gracefully stopping...") ctx = context.Background() _, err = progress.Run(ctx, func(ctx context.Context) (string, error) { - return "", c.ComposeService().Down(ctx, project.Name) + return "", c.ComposeService().Down(ctx, project.Name, compose.DownOptions{}) }) } return err diff --git a/ecs/cloudformation.go b/ecs/cloudformation.go index 0c52fba2d..f82b0b5f5 100644 --- a/ecs/cloudformation.go +++ b/ecs/cloudformation.go @@ -23,6 +23,8 @@ import ( "regexp" "strings" + "github.com/docker/compose-cli/api/compose" + ecsapi "github.com/aws/aws-sdk-go/service/ecs" "github.com/aws/aws-sdk-go/service/elbv2" cloudmapapi "github.com/aws/aws-sdk-go/service/servicediscovery" @@ -37,13 +39,13 @@ import ( "github.com/compose-spec/compose-go/types" ) -func (b *ecsAPIService) Convert(ctx context.Context, project *types.Project, format string) ([]byte, error) { +func (b *ecsAPIService) Convert(ctx context.Context, project *types.Project, options compose.ConvertOptions) ([]byte, error) { template, err := b.convert(ctx, project) if err != nil { return nil, err } - return marshall(template, format) + return marshall(template, options.Format) } func (b *ecsAPIService) convert(ctx context.Context, project *types.Project) (*cloudformation.Template, error) { diff --git a/ecs/down.go b/ecs/down.go index bd1c434f5..047613adc 100644 --- a/ecs/down.go +++ b/ecs/down.go @@ -19,11 +19,13 @@ package ecs import ( "context" + "github.com/docker/compose-cli/api/compose" + "github.com/docker/compose-cli/progress" ) -func (b *ecsAPIService) Down(ctx context.Context, project string) error { - resources, err := b.aws.ListStackResources(ctx, project) +func (b *ecsAPIService) Down(ctx context.Context, projectName string, options compose.DownOptions) error { + resources, err := b.aws.ListStackResources(ctx, projectName) if err != nil { return err } @@ -38,16 +40,16 @@ func (b *ecsAPIService) Down(ctx context.Context, project string) error { return err } - previousEvents, err := b.previousStackEvents(ctx, project) + previousEvents, err := b.previousStackEvents(ctx, projectName) if err != nil { return err } - err = b.aws.DeleteStack(ctx, project) + err = b.aws.DeleteStack(ctx, projectName) if err != nil { return err } - return b.WaitStackCompletion(ctx, project, stackDelete, previousEvents...) + return b.WaitStackCompletion(ctx, projectName, stackDelete, previousEvents...) } func (b *ecsAPIService) previousStackEvents(ctx context.Context, project string) ([]string, error) { diff --git a/ecs/local/backend.go b/ecs/local/backend.go index 934bf0b86..952c48ca0 100644 --- a/ecs/local/backend.go +++ b/ecs/local/backend.go @@ -19,6 +19,8 @@ package local import ( "context" + local_compose "github.com/docker/compose-cli/local/compose" + "github.com/docker/docker/client" "github.com/docker/compose-cli/api/compose" @@ -38,17 +40,19 @@ func init() { } type ecsLocalSimulation struct { - moby *client.Client + moby *client.Client + compose compose.Service } func service(ctx context.Context) (backend.Service, error) { - apiClient, err := client.NewClientWithOpts(client.FromEnv) + apiClient, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) if err != nil { return nil, err } return &ecsLocalSimulation{ - moby: apiClient, + moby: apiClient, + compose: local_compose.NewComposeService(apiClient), }, nil } diff --git a/ecs/local/compose.go b/ecs/local/compose.go index abe9812d5..1dbaa7fe4 100644 --- a/ecs/local/compose.go +++ b/ecs/local/compose.go @@ -17,80 +17,76 @@ package local import ( - "bufio" - "bytes" "context" "encoding/json" "fmt" "os" - "os/exec" "path/filepath" - "strings" "github.com/aws/aws-sdk-go/aws" "github.com/compose-spec/compose-go/types" - types2 "github.com/docker/docker/api/types" - "github.com/docker/docker/api/types/filters" - "github.com/pkg/errors" - "github.com/sanathkr/go-yaml" - "golang.org/x/mod/semver" - "github.com/docker/compose-cli/api/compose" "github.com/docker/compose-cli/errdefs" + "github.com/sanathkr/go-yaml" ) func (e ecsLocalSimulation) Build(ctx context.Context, project *types.Project) error { - return errdefs.ErrNotImplemented + return e.compose.Build(ctx, project) } func (e ecsLocalSimulation) Push(ctx context.Context, project *types.Project) error { - return errdefs.ErrNotImplemented + return e.compose.Push(ctx, project) } func (e ecsLocalSimulation) Pull(ctx context.Context, project *types.Project) error { - return errdefs.ErrNotImplemented + return e.compose.Pull(ctx, project) } func (e ecsLocalSimulation) Create(ctx context.Context, project *types.Project) error { - return errdefs.ErrNotImplemented -} - -func (e ecsLocalSimulation) Start(ctx context.Context, project *types.Project, consumer compose.LogConsumer) error { - return errdefs.ErrNotImplemented -} - -func (e ecsLocalSimulation) Up(ctx context.Context, project *types.Project, detach bool) error { - - cmd := exec.Command("docker-compose", "version", "--short") - b := bytes.Buffer{} - b.WriteString("v") - cmd.Stdout = bufio.NewWriter(&b) - err := cmd.Run() - if err != nil { - return errors.Wrap(err, "ECS simulation mode require Docker-compose 1.27") - } - version := semver.MajorMinor(strings.TrimSpace(b.String())) - if version == "" { - return fmt.Errorf("can't parse docker-compose version: %s", b.String()) - } - if semver.Compare(version, "v1.27") < 0 { - return fmt.Errorf("ECS simulation mode require Docker-compose 1.27, found %s", version) - } - - converted, err := e.Convert(ctx, project, "json") + enhanced, err := e.enhanceForLocalSimulation(project) if err != nil { return err } - cmd = exec.Command("docker-compose", "--context", "default", "--project-directory", project.WorkingDir, "--project-name", project.Name, "-f", "-", "up") - cmd.Stdin = strings.NewReader(string(converted)) - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - return cmd.Run() + return e.compose.Create(ctx, enhanced) } -func (e ecsLocalSimulation) Convert(ctx context.Context, project *types.Project, format string) ([]byte, error) { +func (e ecsLocalSimulation) Start(ctx context.Context, project *types.Project, consumer compose.LogConsumer) error { + return e.compose.Start(ctx, project, consumer) +} + +func (e ecsLocalSimulation) Up(ctx context.Context, project *types.Project, options compose.UpOptions) error { + return errdefs.ErrNotImplemented +} + +func (e ecsLocalSimulation) Convert(ctx context.Context, project *types.Project, options compose.ConvertOptions) ([]byte, error) { + enhanced, err := e.enhanceForLocalSimulation(project) + if err != nil { + return nil, err + } + + delete(enhanced.Networks, "default") + config := map[string]interface{}{ + "services": enhanced.Services, + "networks": enhanced.Networks, + "volumes": enhanced.Volumes, + "secrets": enhanced.Secrets, + "configs": enhanced.Configs, + } + switch options.Format { + case "json": + return json.MarshalIndent(config, "", " ") + case "yaml": + return yaml.Marshal(config) + default: + return nil, fmt.Errorf("unsupported format %q", options) + } + +} + +func (e ecsLocalSimulation) enhanceForLocalSimulation(project *types.Project) (*types.Project, error) { project.Networks["credentials_network"] = types.NetworkConfig{ + Name: "credentials_network", Driver: "bridge", Ipam: types.IPAMConfig{ Config: []*types.IPAMPool{ @@ -148,68 +144,21 @@ func (e ecsLocalSimulation) Convert(ctx context.Context, project *types.Project, }, }, }) - - delete(project.Networks, "default") - config := map[string]interface{}{ - "services": project.Services, - "networks": project.Networks, - "volumes": project.Volumes, - "secrets": project.Secrets, - "configs": project.Configs, - } - switch format { - case "json": - return json.MarshalIndent(config, "", " ") - case "yaml": - return yaml.Marshal(config) - default: - return nil, fmt.Errorf("unsupported format %q", format) - } - + return project, nil } -func (e ecsLocalSimulation) Down(ctx context.Context, projectName string) error { - cmd := exec.Command("docker-compose", "--context", "default", "--project-name", projectName, "-f", "-", "down", "--remove-orphans") - cmd.Stdin = strings.NewReader(string(` -services: - ecs-local-endpoints: - image: "amazon/amazon-ecs-local-container-endpoints" -`)) - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - return cmd.Run() +func (e ecsLocalSimulation) Down(ctx context.Context, projectName string, options compose.DownOptions) error { + options.RemoveOrphans = true + return e.compose.Down(ctx, projectName, options) } func (e ecsLocalSimulation) Logs(ctx context.Context, projectName string, consumer compose.LogConsumer, options compose.LogOptions) error { - list, err := e.moby.ContainerList(ctx, types2.ContainerListOptions{ - Filters: filters.NewArgs(filters.Arg("label", "com.docker.compose.project="+projectName)), - }) - if err != nil { - return err - } - services := map[string]types.ServiceConfig{} - for _, c := range list { - services[c.Labels["com.docker.compose.service"]] = types.ServiceConfig{ - Image: "unused", - } - } - - marshal, err := yaml.Marshal(map[string]interface{}{ - "services": services, - }) - if err != nil { - return err - } - cmd := exec.Command("docker-compose", "--context", "default", "--project-name", projectName, "-f", "-", "logs", "-f") - cmd.Stdin = strings.NewReader(string(marshal)) - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - return cmd.Run() + return e.compose.Logs(ctx, projectName, consumer, options) } func (e ecsLocalSimulation) Ps(ctx context.Context, projectName string) ([]compose.ContainerSummary, error) { - return nil, errors.Wrap(errdefs.ErrNotImplemented, "use docker-compose ps") + return e.compose.Ps(ctx, projectName) } func (e ecsLocalSimulation) List(ctx context.Context, projectName string) ([]compose.Stack, error) { - return nil, errors.Wrap(errdefs.ErrNotImplemented, "use docker-compose ls") + return e.compose.List(ctx, projectName) } diff --git a/ecs/up.go b/ecs/up.go index 0af2fe70c..a15d9303a 100644 --- a/ecs/up.go +++ b/ecs/up.go @@ -49,14 +49,14 @@ func (b *ecsAPIService) Start(ctx context.Context, project *types.Project, consu return errdefs.ErrNotImplemented } -func (b *ecsAPIService) Up(ctx context.Context, project *types.Project, detach bool) error { +func (b *ecsAPIService) Up(ctx context.Context, project *types.Project, options compose.UpOptions) error { err := b.aws.CheckRequirements(ctx, b.Region) if err != nil { return err } - template, err := b.Convert(ctx, project, "yaml") + template, err := b.Convert(ctx, project, compose.ConvertOptions{Format: "yaml"}) if err != nil { return err } @@ -82,7 +82,7 @@ func (b *ecsAPIService) Up(ctx context.Context, project *types.Project, detach b return err } } - if detach { + if options.Detach { return nil } signalChan := make(chan os.Signal, 1) @@ -90,7 +90,7 @@ func (b *ecsAPIService) Up(ctx context.Context, project *types.Project, detach b go func() { <-signalChan fmt.Println("user interrupted deployment. Deleting stack...") - b.Down(ctx, project.Name) // nolint:errcheck + b.Down(ctx, project.Name, compose.DownOptions{}) // nolint:errcheck }() err = b.WaitStackCompletion(ctx, project.Name, operation) diff --git a/example/backend.go b/example/backend.go index 646a1d5bc..9e585bd39 100644 --- a/example/backend.go +++ b/example/backend.go @@ -158,14 +158,14 @@ func (cs *composeService) Start(ctx context.Context, project *types.Project, con return errdefs.ErrNotImplemented } -func (cs *composeService) Up(ctx context.Context, project *types.Project, detach bool) error { +func (cs *composeService) Up(ctx context.Context, project *types.Project, options compose.UpOptions) error { fmt.Printf("Up command on project %q", project.Name) return nil } -func (cs *composeService) Down(ctx context.Context, project string) error { - fmt.Printf("Down command on project %q", project) +func (cs *composeService) Down(ctx context.Context, projectName string, options compose.DownOptions) error { + fmt.Printf("Down command on project %q", projectName) return nil } @@ -179,6 +179,6 @@ func (cs *composeService) Logs(ctx context.Context, projectName string, consumer return errdefs.ErrNotImplemented } -func (cs *composeService) Convert(ctx context.Context, project *types.Project, format string) ([]byte, error) { +func (cs *composeService) Convert(ctx context.Context, project *types.Project, options compose.ConvertOptions) ([]byte, error) { return nil, errdefs.ErrNotImplemented } diff --git a/go.mod b/go.mod index 59dc7d083..dab8c3d3e 100644 --- a/go.mod +++ b/go.mod @@ -51,7 +51,6 @@ require ( github.com/spf13/pflag v1.0.5 github.com/stretchr/testify v1.6.1 github.com/valyala/fasttemplate v1.2.1 // indirect - golang.org/x/mod v0.3.0 golang.org/x/net v0.0.0-20201110031124-69a78807bb2b golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58 golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 diff --git a/local/compose/compose.go b/local/compose/compose.go index 6d220c649..402f3f0e0 100644 --- a/local/compose/compose.go +++ b/local/compose/compose.go @@ -40,7 +40,7 @@ type composeService struct { apiClient *client.Client } -func (s *composeService) Up(ctx context.Context, project *types.Project, detach bool) error { +func (s *composeService) Up(ctx context.Context, project *types.Project, options compose.UpOptions) error { return errdefs2.ErrNotImplemented } @@ -54,13 +54,13 @@ func getContainerName(c moby.Container) string { return c.Names[0][1:] } -func (s *composeService) Convert(ctx context.Context, project *types.Project, format string) ([]byte, error) { - switch format { +func (s *composeService) Convert(ctx context.Context, project *types.Project, options compose.ConvertOptions) ([]byte, error) { + switch options.Format { case "json": return json.MarshalIndent(project, "", " ") case "yaml": return yaml.Marshal(project) default: - return nil, fmt.Errorf("unsupported format %q", format) + return nil, fmt.Errorf("unsupported format %q", options) } } diff --git a/local/compose/down.go b/local/compose/down.go index a9ada09e0..0227ddb2d 100644 --- a/local/compose/down.go +++ b/local/compose/down.go @@ -21,6 +21,8 @@ import ( "path/filepath" "strings" + "github.com/docker/compose-cli/api/compose" + "github.com/docker/compose-cli/progress" "github.com/compose-spec/compose-go/cli" @@ -30,7 +32,7 @@ import ( "golang.org/x/sync/errgroup" ) -func (s *composeService) Down(ctx context.Context, projectName string) error { +func (s *composeService) Down(ctx context.Context, projectName string, options compose.DownOptions) error { eg, _ := errgroup.WithContext(ctx) w := progress.ContextWriter(ctx) @@ -39,10 +41,27 @@ func (s *composeService) Down(ctx context.Context, projectName string) error { return err } - err = InReverseDependencyOrder(ctx, project, func(c context.Context, service types.ServiceConfig) error { - filter := filters.NewArgs(projectFilter(project.Name), serviceFilter(service.Name)) - return s.removeContainers(ctx, w, eg, filter) + containers, err := s.apiClient.ContainerList(ctx, moby.ContainerListOptions{ + Filters: filters.NewArgs(projectFilter(project.Name)), + All: true, }) + if err != nil { + return err + } + + err = InReverseDependencyOrder(ctx, project, func(c context.Context, service types.ServiceConfig) error { + serviceContainers, others := split(containers, isService(service.Name)) + err := s.removeContainers(ctx, w, eg, serviceContainers) + containers = others + return err + }) + + if options.RemoveOrphans { + err := s.removeContainers(ctx, w, eg, containers) + if err != nil { + return err + } + } if err != nil { return err @@ -70,14 +89,7 @@ func (s *composeService) Down(ctx context.Context, projectName string) error { return eg.Wait() } -func (s *composeService) removeContainers(ctx context.Context, w progress.Writer, eg *errgroup.Group, filter filters.Args) error { - containers, err := s.apiClient.ContainerList(ctx, moby.ContainerListOptions{ - Filters: filter, - All: true, - }) - if err != nil { - return err - } +func (s *composeService) removeContainers(ctx context.Context, w progress.Writer, eg *errgroup.Group, containers []moby.Container) error { for _, container := range containers { eg.Go(func() error { eventName := "Container " + getContainerName(container) @@ -147,3 +159,25 @@ func loadProjectOptionsFromLabels(c moby.Container) (*cli.ProjectOptions, error) cli.WithWorkingDirectory(c.Labels[workingDirLabel]), cli.WithName(c.Labels[projectLabel])) } + +type containerPredicate func(c moby.Container) bool + +func isService(service string) containerPredicate { + return func(c moby.Container) bool { + return c.Labels[serviceLabel] == service + } +} + +// split return a container slice with elements to match predicate +func split(containers []moby.Container, predicate containerPredicate) ([]moby.Container, []moby.Container) { + var right []moby.Container + var left []moby.Container + for _, c := range containers { + if predicate(c) { + right = append(right, c) + } else { + left = append(left, c) + } + } + return right, left +} diff --git a/local/compose/logs.go b/local/compose/logs.go index c3c395d3c..3b8d34ac3 100644 --- a/local/compose/logs.go +++ b/local/compose/logs.go @@ -34,6 +34,7 @@ func (s *composeService) Logs(ctx context.Context, projectName string, consumer Filters: filters.NewArgs( projectFilter(projectName), ), + All: true, }) ignore := func(string) bool { diff --git a/server/proxy/compose.go b/server/proxy/compose.go index 3c584d201..b3b5f8eec 100644 --- a/server/proxy/compose.go +++ b/server/proxy/compose.go @@ -19,6 +19,8 @@ package proxy import ( "context" + "github.com/docker/compose-cli/api/compose" + "github.com/compose-spec/compose-go/cli" "github.com/compose-spec/compose-go/types" @@ -30,7 +32,8 @@ func (p *proxy) Up(ctx context.Context, request *composev1.ComposeUpRequest) (*c if err != nil { return nil, err } - return &composev1.ComposeUpResponse{ProjectName: project.Name}, Client(ctx).ComposeService().Up(ctx, project, true) + err = Client(ctx).ComposeService().Up(ctx, project, compose.UpOptions{Detach: true}) + return &composev1.ComposeUpResponse{ProjectName: project.Name}, err } func (p *proxy) Down(ctx context.Context, request *composev1.ComposeDownRequest) (*composev1.ComposeDownResponse, error) { @@ -42,7 +45,8 @@ func (p *proxy) Down(ctx context.Context, request *composev1.ComposeDownRequest) } projectName = project.Name } - return &composev1.ComposeDownResponse{ProjectName: projectName}, Client(ctx).ComposeService().Down(ctx, projectName) + err := Client(ctx).ComposeService().Down(ctx, projectName, compose.DownOptions{}) + return &composev1.ComposeDownResponse{ProjectName: projectName}, err } func (p *proxy) Services(ctx context.Context, request *composev1.ComposeServicesRequest) (*composev1.ComposeServicesResponse, error) { diff --git a/tests/compose-e2e/fixtures/logs-test/compose.yaml b/tests/compose-e2e/fixtures/logs-test/compose.yaml new file mode 100644 index 000000000..dac5610bf --- /dev/null +++ b/tests/compose-e2e/fixtures/logs-test/compose.yaml @@ -0,0 +1,7 @@ +services: + ping: + image: busybox:1.27.2 + command: ping localhost -c 1 + hello: + image: busybox:1.31.0-uclibc + command: echo hello diff --git a/tests/compose-e2e/logs_test.go b/tests/compose-e2e/logs_test.go new file mode 100644 index 000000000..95a60f0b6 --- /dev/null +++ b/tests/compose-e2e/logs_test.go @@ -0,0 +1,60 @@ +/* + Copyright 2020 Docker Compose CLI authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package e2e + +import ( + "strings" + "testing" + + "gotest.tools/v3/assert" + + "gotest.tools/v3/icmd" + + . "github.com/docker/compose-cli/tests/framework" +) + +func TestLocalComposeLogs(t *testing.T) { + c := NewParallelE2eCLI(t, binDir) + + const projectName = "compose-e2e-logs" + + t.Run("up", func(t *testing.T) { + c.RunDockerCmd("compose", "up", "-d", "-f", "./fixtures/logs-test/compose.yaml", "--project-name", projectName, "-d") + }) + + t.Run("logs", func(t *testing.T) { + res := c.RunDockerCmd("compose", "logs", "--project-name", projectName) + res.Assert(t, icmd.Expected{Out: `PING localhost (127.0.0.1)`}) + res.Assert(t, icmd.Expected{Out: `hello`}) + }) + + t.Run("logs ping", func(t *testing.T) { + res := c.RunDockerCmd("compose", "logs", "--project-name", projectName, "ping") + res.Assert(t, icmd.Expected{Out: `PING localhost (127.0.0.1)`}) + assert.Assert(t, !strings.Contains(res.Stdout(), "hello")) + }) + + t.Run("logs hello", func(t *testing.T) { + res := c.RunDockerCmd("compose", "logs", "--project-name", projectName, "hello", "ping") + res.Assert(t, icmd.Expected{Out: `PING localhost (127.0.0.1)`}) + res.Assert(t, icmd.Expected{Out: `hello`}) + }) + + t.Run("down", func(t *testing.T) { + _ = c.RunDockerCmd("compose", "down", "--project-name", projectName) + }) +}