diff --git a/aci/backend.go b/aci/backend.go index 40ec0f427..80cf93708 100644 --- a/aci/backend.go +++ b/aci/backend.go @@ -396,6 +396,10 @@ func (cs *aciComposeService) Up(ctx context.Context, project *types.Project) err return createOrUpdateACIContainers(ctx, cs.ctx, groupDefinition) } +func (cs *aciComposeService) Emulate(context.Context, *cli.ProjectOptions) error { + return errdefs.ErrNotImplemented +} + func (cs *aciComposeService) Down(ctx context.Context, project string) error { logrus.Debugf("Down on project with name %q\n", project) diff --git a/cli/cmd/compose/compose.go b/cli/cmd/compose/compose.go index eee3bc16b..b93239441 100644 --- a/cli/cmd/compose/compose.go +++ b/cli/cmd/compose/compose.go @@ -60,7 +60,7 @@ func (o *composeOptions) toProjectOptions() (*cli.ProjectOptions, error) { } // Command returns the compose command with its child commands -func Command() *cobra.Command { +func Command(contextType string) *cobra.Command { command := &cobra.Command{ Short: "Docker Compose", Use: "compose", @@ -70,7 +70,7 @@ func Command() *cobra.Command { } command.AddCommand( - upCommand(), + upCommand(contextType), downCommand(), psCommand(), logsCommand(), diff --git a/cli/cmd/compose/up.go b/cli/cmd/compose/up.go index 4d5b964af..719680492 100644 --- a/cli/cmd/compose/up.go +++ b/cli/cmd/compose/up.go @@ -24,15 +24,17 @@ import ( "github.com/spf13/cobra" "github.com/docker/compose-cli/client" + "github.com/docker/compose-cli/context/store" "github.com/docker/compose-cli/progress" ) -func upCommand() *cobra.Command { +func upCommand(contextType string) *cobra.Command { opts := composeOptions{} + var simulation bool upCmd := &cobra.Command{ Use: "up", RunE: func(cmd *cobra.Command, args []string) error { - return runUp(cmd.Context(), opts) + return runUp(cmd.Context(), opts, simulation) }, } upCmd.Flags().StringVarP(&opts.Name, "project-name", "p", "", "Project name") @@ -40,11 +42,14 @@ func upCommand() *cobra.Command { upCmd.Flags().StringArrayVarP(&opts.ConfigPaths, "file", "f", []string{}, "Compose configuration files") upCmd.Flags().StringArrayVarP(&opts.Environment, "environment", "e", []string{}, "Environment variables") upCmd.Flags().BoolP("detach", "d", true, " Detached mode: Run containers in the background") + if contextType == store.EcsContextType { + upCmd.Flags().BoolVar(&simulation, "simulate", false, " Simulation mode: run compose app with ECS local container endpoints") + } return upCmd } -func runUp(ctx context.Context, opts composeOptions) error { +func runUp(ctx context.Context, opts composeOptions, simulation bool) error { c, err := client.New(ctx) if err != nil { return err @@ -60,6 +65,9 @@ func runUp(ctx context.Context, opts composeOptions) error { return err } + if simulation { + return c.ComposeService().Emulate(ctx, options) + } return c.ComposeService().Up(ctx, project) }) } diff --git a/cli/main.go b/cli/main.go index 293c1c8a8..6fd31d4d4 100644 --- a/cli/main.go +++ b/cli/main.go @@ -121,7 +121,6 @@ func main() { cmd.RmCommand(), cmd.StartCommand(), cmd.InspectCommand(), - compose.Command(), login.Command(), logout.Command(), cmd.VersionCommand(version), @@ -184,6 +183,8 @@ func main() { $ docker context create %s `, cc.Type(), store.EcsContextType)) } + root.AddCommand(compose.Command(ctype)) + metrics.Track(ctype, os.Args[1:], root.PersistentFlags()) ctx = apicontext.WithCurrentContext(ctx, currentContext) diff --git a/client/compose.go b/client/compose.go index bd554e230..4b4a7871b 100644 --- a/client/compose.go +++ b/client/compose.go @@ -34,6 +34,11 @@ func (c *composeService) Up(context.Context, *types.Project) error { return errdefs.ErrNotImplemented } +// Emulate executes the equivalent to a `compose up` in platform emulation mode +func (c *composeService) Emulate(context.Context, *cli.ProjectOptions) error { + return errdefs.ErrNotImplemented +} + // Down executes the equivalent to a `compose down` func (c *composeService) Down(context.Context, string) error { return errdefs.ErrNotImplemented diff --git a/compose/api.go b/compose/api.go index bb0b13c62..84b3143d9 100644 --- a/compose/api.go +++ b/compose/api.go @@ -35,6 +35,8 @@ type Service interface { Ps(ctx context.Context, projectName string) ([]ServiceStatus, error) // Convert translate compose model into backend's native format Convert(ctx context.Context, project *types.Project) ([]byte, error) + // Emulate executes the equivalent to a `compose up` in platform emulation mode + Emulate(ctx context.Context, options *cli.ProjectOptions) error } // PortPublisher hold status about published port diff --git a/ecs/emulate.go b/ecs/emulate.go new file mode 100644 index 000000000..f3859b485 --- /dev/null +++ b/ecs/emulate.go @@ -0,0 +1,112 @@ +/* + Copyright 2020 Docker, Inc. + + 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 ecs + +import ( + "context" + "fmt" + "os" + "os/exec" + "path/filepath" + "strings" + + "github.com/aws/aws-sdk-go/aws" + "github.com/compose-spec/compose-go/types" + "github.com/sanathkr/go-yaml" + + "github.com/compose-spec/compose-go/cli" +) + +func (c *ecsAPIService) Emulate(ctx context.Context, options *cli.ProjectOptions) error { + project, err := cli.ProjectFromOptions(options) + if err != nil { + return err + } + project.Networks["credentials_network"] = types.NetworkConfig{ + Driver: "bridge", + Ipam: types.IPAMConfig{ + Config: []*types.IPAMPool{ + { + Subnet: "169.254.170.0/24", + Gateway: "169.254.170.1", + }, + }, + }, + } + + // On Windows, this directory can be found at "%UserProfile%\.aws" + home, err := os.UserHomeDir() + if err != nil { + return err + } + + for i, service := range project.Services { + service.Networks["credentials_network"] = &types.ServiceNetworkConfig{ + Ipv4Address: fmt.Sprintf("169.254.170.%d", i+3), + } + service.DependsOn = append(service.DependsOn, "ecs-local-endpoints") + service.Environment["AWS_DEFAULT_REGION"] = aws.String(c.ctx.Region) + service.Environment["AWS_CONTAINER_CREDENTIALS_RELATIVE_URI"] = aws.String("/creds") + service.Environment["ECS_CONTAINER_METADATA_URI"] = aws.String("http://169.254.170.2/v3") + project.Services[i] = service + } + + project.Services = append(project.Services, types.ServiceConfig{ + Name: "ecs-local-endpoints", + Image: "amazon/amazon-ecs-local-container-endpoints", + Volumes: []types.ServiceVolumeConfig{ + { + Type: types.VolumeTypeBind, + Source: "/var/run", + Target: "/var/run", + }, + { + Type: types.VolumeTypeBind, + Source: filepath.Join(home, ".aws"), + Target: "/home/.aws", + }, + }, + Environment: map[string]*string{ + "HOME": aws.String("/home"), + "AWS_PROFILE": aws.String("default"), + }, + Networks: map[string]*types.ServiceNetworkConfig{ + "credentials_network": { + Ipv4Address: "169.254.170.2", + }, + }, + }) + + delete(project.Networks, "default") + config := map[string]interface{}{ + "services": project.Services, + "networks": project.Networks, + "volumes": project.Volumes, + "secrets": project.Secrets, + "configs": project.Configs, + } + marshal, err := yaml.Marshal(config) + 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(marshal)) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + return cmd.Run() +} diff --git a/example/backend.go b/example/backend.go index 83f9e8792..79428e52b 100644 --- a/example/backend.go +++ b/example/backend.go @@ -132,6 +132,10 @@ func (cs *composeService) Down(ctx context.Context, project string) error { return nil } +func (cs *composeService) Emulate(context.Context, *cli.ProjectOptions) error { + return errdefs.ErrNotImplemented +} + func (cs *composeService) Ps(ctx context.Context, project string) ([]compose.ServiceStatus, error) { return nil, errdefs.ErrNotImplemented } diff --git a/go.mod b/go.mod index 83af243d8..5f7ed8190 100644 --- a/go.mod +++ b/go.mod @@ -22,7 +22,7 @@ require ( github.com/aws/aws-sdk-go v1.34.8 github.com/awslabs/goformation/v4 v4.14.0 github.com/buger/goterm v0.0.0-20200322175922-2f3e71b85129 - github.com/compose-spec/compose-go v0.0.0-20200818070525-eb1188aae4a2 + github.com/compose-spec/compose-go v0.0.0-20200824075806-a70cd5945c25 github.com/containerd/console v1.0.0 github.com/containerd/containerd v1.3.5 // indirect github.com/docker/cli v0.0.0-20200528204125-dd360c7c0de8 @@ -46,6 +46,7 @@ require ( github.com/opencontainers/go-digest v1.0.0 github.com/opencontainers/runc v0.1.1 // indirect github.com/pkg/errors v0.9.1 + github.com/sanathkr/go-yaml v0.0.0-20170819195128-ed9d249f429b github.com/sirupsen/logrus v1.6.0 github.com/smartystreets/goconvey v1.6.4 // indirect github.com/spf13/cobra v1.0.0 diff --git a/go.sum b/go.sum index 480d8e19f..1b2ca12ef 100644 --- a/go.sum +++ b/go.sum @@ -87,8 +87,8 @@ github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/compose-spec/compose-go v0.0.0-20200818070525-eb1188aae4a2 h1:b3JmHJVJt8zXy112yGtRq74G32sPQ8XLJxfHKaP/DOg= -github.com/compose-spec/compose-go v0.0.0-20200818070525-eb1188aae4a2/go.mod h1:P7PZ0svgjrZ8nv/XvxObbl8o0DCIE9ZbL8pllg6uL4w= +github.com/compose-spec/compose-go v0.0.0-20200824075806-a70cd5945c25 h1:mVlGrHJuNGPJNEvCCIrDIZX5FYtNTwFd++y+fJaGTXM= +github.com/compose-spec/compose-go v0.0.0-20200824075806-a70cd5945c25/go.mod h1:P7PZ0svgjrZ8nv/XvxObbl8o0DCIE9ZbL8pllg6uL4w= github.com/containerd/cgroups v0.0.0-20190919134610-bf292b21730f h1:tSNMc+rJDfmYntojat8lljbt1mgKNpTxUZJsSzJ9Y1s= github.com/containerd/cgroups v0.0.0-20190919134610-bf292b21730f/go.mod h1:OApqhQ4XNSNC13gXIwDjhOQxjWa/NxkwZXJ1EvqT0ko= github.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw=