introduce ecs-local context

Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
This commit is contained in:
Nicolas De Loof 2020-08-31 14:26:00 +02:00
parent c1ecb2b7be
commit fed50d79f2
No known key found for this signature in database
GPG Key ID: 9858809D6F8F6E7E
13 changed files with 189 additions and 67 deletions

View File

@ -396,10 +396,6 @@ func (cs *aciComposeService) Up(ctx context.Context, project *types.Project) err
return createOrUpdateACIContainers(ctx, cs.ctx, groupDefinition) 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 { func (cs *aciComposeService) Down(ctx context.Context, project string) error {
logrus.Debugf("Down on project with name %q\n", project) logrus.Debugf("Down on project with name %q\n", project)

View File

@ -60,7 +60,7 @@ func (o *composeOptions) toProjectOptions() (*cli.ProjectOptions, error) {
} }
// Command returns the compose command with its child commands // Command returns the compose command with its child commands
func Command(contextType string) *cobra.Command { func Command() *cobra.Command {
command := &cobra.Command{ command := &cobra.Command{
Short: "Docker Compose", Short: "Docker Compose",
Use: "compose", Use: "compose",
@ -70,7 +70,7 @@ func Command(contextType string) *cobra.Command {
} }
command.AddCommand( command.AddCommand(
upCommand(contextType), upCommand(),
downCommand(), downCommand(),
psCommand(), psCommand(),
logsCommand(), logsCommand(),

View File

@ -24,17 +24,15 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/docker/compose-cli/client" "github.com/docker/compose-cli/client"
"github.com/docker/compose-cli/context/store"
"github.com/docker/compose-cli/progress" "github.com/docker/compose-cli/progress"
) )
func upCommand(contextType string) *cobra.Command { func upCommand() *cobra.Command {
opts := composeOptions{} opts := composeOptions{}
var simulation bool
upCmd := &cobra.Command{ upCmd := &cobra.Command{
Use: "up", Use: "up",
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
return runUp(cmd.Context(), opts, simulation) return runUp(cmd.Context(), opts)
}, },
} }
upCmd.Flags().StringVarP(&opts.Name, "project-name", "p", "", "Project name") upCmd.Flags().StringVarP(&opts.Name, "project-name", "p", "", "Project name")
@ -42,14 +40,10 @@ func upCommand(contextType string) *cobra.Command {
upCmd.Flags().StringArrayVarP(&opts.ConfigPaths, "file", "f", []string{}, "Compose configuration files") upCmd.Flags().StringArrayVarP(&opts.ConfigPaths, "file", "f", []string{}, "Compose configuration files")
upCmd.Flags().StringArrayVarP(&opts.Environment, "environment", "e", []string{}, "Environment variables") upCmd.Flags().StringArrayVarP(&opts.Environment, "environment", "e", []string{}, "Environment variables")
upCmd.Flags().BoolP("detach", "d", true, " Detached mode: Run containers in the background") 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 return upCmd
} }
func runUp(ctx context.Context, opts composeOptions, simulation bool) error { func runUp(ctx context.Context, opts composeOptions) error {
c, err := client.New(ctx) c, err := client.New(ctx)
if err != nil { if err != nil {
return err return err
@ -65,9 +59,6 @@ func runUp(ctx context.Context, opts composeOptions, simulation bool) error {
return err return err
} }
if simulation {
return c.ComposeService().Emulate(ctx, options)
}
return c.ComposeService().Up(ctx, project) return c.ComposeService().Up(ctx, project)
}) })
} }

View File

@ -38,17 +38,22 @@ $ docker context create ecs CONTEXT [flags]
} }
func createEcsCommand() *cobra.Command { func createEcsCommand() *cobra.Command {
var localSimulation bool
var opts ecs.ContextParams var opts ecs.ContextParams
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "ecs CONTEXT [flags]", Use: "ecs CONTEXT [flags]",
Short: "Create a context for Amazon ECS", Short: "Create a context for Amazon ECS",
Args: cobra.ExactArgs(1), Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
if localSimulation {
return runCreateLocalSimulation(cmd.Context(), args[0], opts)
}
return runCreateEcs(cmd.Context(), args[0], opts) return runCreateEcs(cmd.Context(), args[0], opts)
}, },
} }
addDescriptionFlag(cmd, &opts.Description) addDescriptionFlag(cmd, &opts.Description)
cmd.Flags().BoolVar(&localSimulation, "local-simulation", false, "Create context for ECS local simulation endpoints")
cmd.Flags().StringVar(&opts.Profile, "profile", "", "Profile") cmd.Flags().StringVar(&opts.Profile, "profile", "", "Profile")
cmd.Flags().StringVar(&opts.Region, "region", "", "Region") cmd.Flags().StringVar(&opts.Region, "region", "", "Region")
cmd.Flags().StringVar(&opts.AwsID, "key-id", "", "AWS Access Key ID") cmd.Flags().StringVar(&opts.AwsID, "key-id", "", "AWS Access Key ID")
@ -56,6 +61,21 @@ func createEcsCommand() *cobra.Command {
return cmd return cmd
} }
func runCreateLocalSimulation(ctx context.Context, contextName string, opts ecs.ContextParams) error {
if contextExists(ctx, contextName) {
return errors.Wrapf(errdefs.ErrAlreadyExists, "context %q", contextName)
}
cs, err := client.GetCloudService(ctx, store.EcsLocalSimulationContextType)
if err != nil {
return errors.Wrap(err, "cannot connect to ECS backend")
}
data, description, err := cs.CreateContextData(ctx, opts)
if err != nil {
return err
}
return createDockerContext(ctx, contextName, store.EcsLocalSimulationContextType, description, data)
}
func runCreateEcs(ctx context.Context, contextName string, opts ecs.ContextParams) error { func runCreateEcs(ctx context.Context, contextName string, opts ecs.ContextParams) error {
if contextExists(ctx, contextName) { if contextExists(ctx, contextName) {
return errors.Wrapf(errdefs.ErrAlreadyExists, "context %q", contextName) return errors.Wrapf(errdefs.ErrAlreadyExists, "context %q", contextName)
@ -71,7 +91,7 @@ func runCreateEcs(ctx context.Context, contextName string, opts ecs.ContextParam
func getEcsContextData(ctx context.Context, opts ecs.ContextParams) (interface{}, string, error) { func getEcsContextData(ctx context.Context, opts ecs.ContextParams) (interface{}, string, error) {
cs, err := client.GetCloudService(ctx, store.EcsContextType) cs, err := client.GetCloudService(ctx, store.EcsContextType)
if err != nil { if err != nil {
return nil, "", errors.Wrap(err, "cannot connect to AWS backend") return nil, "", errors.Wrap(err, "cannot connect to ECS backend")
} }
return cs.CreateContextData(ctx, opts) return cs.CreateContextData(ctx, opts)
} }

View File

@ -27,6 +27,8 @@ import (
"syscall" "syscall"
"time" "time"
"github.com/docker/compose-cli/cli/cmd/compose"
"github.com/docker/compose-cli/cli/cmd/logout" "github.com/docker/compose-cli/cli/cmd/logout"
"github.com/docker/compose-cli/errdefs" "github.com/docker/compose-cli/errdefs"
@ -38,12 +40,12 @@ import (
// Backend registrations // Backend registrations
_ "github.com/docker/compose-cli/aci" _ "github.com/docker/compose-cli/aci"
_ "github.com/docker/compose-cli/ecs" _ "github.com/docker/compose-cli/ecs"
_ "github.com/docker/compose-cli/ecs/local"
_ "github.com/docker/compose-cli/example" _ "github.com/docker/compose-cli/example"
_ "github.com/docker/compose-cli/local" _ "github.com/docker/compose-cli/local"
"github.com/docker/compose-cli/metrics" "github.com/docker/compose-cli/metrics"
"github.com/docker/compose-cli/cli/cmd" "github.com/docker/compose-cli/cli/cmd"
"github.com/docker/compose-cli/cli/cmd/compose"
contextcmd "github.com/docker/compose-cli/cli/cmd/context" contextcmd "github.com/docker/compose-cli/cli/cmd/context"
"github.com/docker/compose-cli/cli/cmd/login" "github.com/docker/compose-cli/cli/cmd/login"
"github.com/docker/compose-cli/cli/cmd/run" "github.com/docker/compose-cli/cli/cmd/run"
@ -126,6 +128,7 @@ func main() {
cmd.VersionCommand(version), cmd.VersionCommand(version),
cmd.StopCommand(), cmd.StopCommand(),
cmd.SecretCommand(), cmd.SecretCommand(),
compose.Command(),
// Place holders // Place holders
cmd.EcsCommand(), cmd.EcsCommand(),
@ -183,8 +186,6 @@ func main() {
$ docker context create %s <name>`, cc.Type(), store.EcsContextType)) $ docker context create %s <name>`, cc.Type(), store.EcsContextType))
} }
root.AddCommand(compose.Command(ctype))
metrics.Track(ctype, os.Args[1:], root.PersistentFlags()) metrics.Track(ctype, os.Args[1:], root.PersistentFlags())
ctx = apicontext.WithCurrentContext(ctx, currentContext) ctx = apicontext.WithCurrentContext(ctx, currentContext)

View File

@ -34,11 +34,6 @@ func (c *composeService) Up(context.Context, *types.Project) error {
return errdefs.ErrNotImplemented 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` // Down executes the equivalent to a `compose down`
func (c *composeService) Down(context.Context, string) error { func (c *composeService) Down(context.Context, string) error {
return errdefs.ErrNotImplemented return errdefs.ErrNotImplemented

View File

@ -35,8 +35,6 @@ type Service interface {
Ps(ctx context.Context, projectName string) ([]ServiceStatus, error) Ps(ctx context.Context, projectName string) ([]ServiceStatus, error)
// Convert translate compose model into backend's native format // Convert translate compose model into backend's native format
Convert(ctx context.Context, project *types.Project) ([]byte, error) 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 // PortPublisher hold status about published port

View File

@ -44,6 +44,11 @@ const (
// EcsContextType is the endpoint key in the context endpoints for an ECS // EcsContextType is the endpoint key in the context endpoints for an ECS
// backend // backend
EcsContextType = "ecs" EcsContextType = "ecs"
// EcsLocalSimulationContextType is the endpoint key in the context endpoints for an ECS backend
// running local simulation endpoints
EcsLocalSimulationContextType = "ecs-local"
// AciContextType is the endpoint key in the context endpoints for an ACI // AciContextType is the endpoint key in the context endpoints for an ACI
// backend // backend
AciContextType = "aci" AciContextType = "aci"

View File

@ -22,8 +22,6 @@ import (
"github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/aws/session"
"github.com/docker/compose-cli/secrets"
"github.com/docker/compose-cli/backend" "github.com/docker/compose-cli/backend"
"github.com/docker/compose-cli/compose" "github.com/docker/compose-cli/compose"
"github.com/docker/compose-cli/containers" "github.com/docker/compose-cli/containers"
@ -31,6 +29,7 @@ import (
"github.com/docker/compose-cli/context/cloud" "github.com/docker/compose-cli/context/cloud"
"github.com/docker/compose-cli/context/store" "github.com/docker/compose-cli/context/store"
"github.com/docker/compose-cli/errdefs" "github.com/docker/compose-cli/errdefs"
"github.com/docker/compose-cli/secrets"
) )
const backendType = store.EcsContextType const backendType = store.EcsContextType

67
ecs/local/backend.go Normal file
View File

@ -0,0 +1,67 @@
/*
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 local
import (
"context"
"github.com/docker/compose-cli/compose"
"github.com/docker/compose-cli/containers"
"github.com/docker/compose-cli/secrets"
"github.com/docker/docker/client"
"github.com/docker/compose-cli/backend"
"github.com/docker/compose-cli/context/cloud"
"github.com/docker/compose-cli/context/store"
)
const backendType = store.EcsLocalSimulationContextType
func init() {
backend.Register(backendType, backendType, service, getCloudService)
}
type ecsLocalSimulation struct {
moby *client.Client
}
func service(ctx context.Context) (backend.Service, error) {
apiClient, err := client.NewClientWithOpts(client.FromEnv)
if err != nil {
return nil, err
}
return &ecsLocalSimulation{
moby: apiClient,
}, nil
}
func getCloudService() (cloud.Service, error) {
return ecsLocalSimulation{}, nil
}
func (e ecsLocalSimulation) ContainerService() containers.Service {
return nil
}
func (e ecsLocalSimulation) SecretsService() secrets.Service {
return nil
}
func (e ecsLocalSimulation) ComposeService() compose.Service {
return e
}

View File

@ -14,31 +14,59 @@
limitations under the License. limitations under the License.
*/ */
package ecs package local
import ( import (
"bufio" "bufio"
"bytes" "bytes"
"context" "context"
"fmt" "fmt"
"io"
"os" "os"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
"strings" "strings"
"github.com/docker/compose-cli/compose"
"github.com/docker/compose-cli/errdefs"
"github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws"
"github.com/compose-spec/compose-go/cli"
"github.com/compose-spec/compose-go/types" "github.com/compose-spec/compose-go/types"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/sanathkr/go-yaml" "github.com/sanathkr/go-yaml"
"golang.org/x/mod/semver" "golang.org/x/mod/semver"
) )
func (c *ecsAPIService) Emulate(ctx context.Context, options *cli.ProjectOptions) error { func (e ecsLocalSimulation) Up(ctx context.Context, project *types.Project) error {
project, err := cli.ProjectFromOptions(options) 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)
if err != nil { if err != nil {
return err 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()
}
func (e ecsLocalSimulation) Convert(ctx context.Context, project *types.Project) ([]byte, error) {
project.Networks["credentials_network"] = types.NetworkConfig{ project.Networks["credentials_network"] = types.NetworkConfig{
Driver: "bridge", Driver: "bridge",
Ipam: types.IPAMConfig{ Ipam: types.IPAMConfig{
@ -54,7 +82,7 @@ func (c *ecsAPIService) Emulate(ctx context.Context, options *cli.ProjectOptions
// On Windows, this directory can be found at "%UserProfile%\.aws" // On Windows, this directory can be found at "%UserProfile%\.aws"
home, err := os.UserHomeDir() home, err := os.UserHomeDir()
if err != nil { if err != nil {
return err return nil, err
} }
for i, service := range project.Services { for i, service := range project.Services {
@ -62,7 +90,6 @@ func (c *ecsAPIService) Emulate(ctx context.Context, options *cli.ProjectOptions
Ipv4Address: fmt.Sprintf("169.254.170.%d", i+3), Ipv4Address: fmt.Sprintf("169.254.170.%d", i+3),
} }
service.DependsOn = append(service.DependsOn, "ecs-local-endpoints") 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["AWS_CONTAINER_CREDENTIALS_RELATIVE_URI"] = aws.String("/creds")
service.Environment["ECS_CONTAINER_METADATA_URI"] = aws.String("http://169.254.170.2/v3") service.Environment["ECS_CONTAINER_METADATA_URI"] = aws.String("http://169.254.170.2/v3")
project.Services[i] = service project.Services[i] = service
@ -102,30 +129,17 @@ func (c *ecsAPIService) Emulate(ctx context.Context, options *cli.ProjectOptions
"secrets": project.Secrets, "secrets": project.Secrets,
"configs": project.Configs, "configs": project.Configs,
} }
marshal, err := yaml.Marshal(config) return yaml.Marshal(config)
if err != nil {
return err
} }
cmd := exec.Command("docker-compose", "version", "--short") func (e ecsLocalSimulation) Down(ctx context.Context, projectName string) error {
b := bytes.Buffer{} return errors.Wrap(errdefs.ErrNotImplemented, "use docker-compose down")
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)
} }
cmd = exec.Command("docker-compose", "--context", "default", "--project-directory", project.WorkingDir, "--project-name", project.Name, "-f", "-", "up") func (e ecsLocalSimulation) Logs(ctx context.Context, projectName string, w io.Writer) error {
cmd.Stdin = strings.NewReader(string(marshal)) return errors.Wrap(errdefs.ErrNotImplemented, "use docker-compose logs")
cmd.Stdout = os.Stdout }
cmd.Stderr = os.Stderr
return cmd.Run() func (e ecsLocalSimulation) Ps(ctx context.Context, projectName string) ([]compose.ServiceStatus, error) {
return nil, errors.Wrap(errdefs.ErrNotImplemented, "use docker-compose ps")
} }

40
ecs/local/context.go Normal file
View File

@ -0,0 +1,40 @@
/*
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 local
import (
"context"
"github.com/docker/compose-cli/context/cloud"
"github.com/docker/compose-cli/ecs"
"github.com/docker/compose-cli/errdefs"
)
var _ cloud.Service = ecsLocalSimulation{}
func (e ecsLocalSimulation) Login(ctx context.Context, params interface{}) error {
return errdefs.ErrNotImplemented
}
func (e ecsLocalSimulation) Logout(ctx context.Context) error {
return errdefs.ErrNotImplemented
}
func (e ecsLocalSimulation) CreateContextData(ctx context.Context, params interface{}) (contextData interface{}, description string, err error) {
opts := params.(ecs.ContextParams)
return struct{}{}, opts.Description, nil
}

View File

@ -132,10 +132,6 @@ func (cs *composeService) Down(ctx context.Context, project string) error {
return nil 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) { func (cs *composeService) Ps(ctx context.Context, project string) ([]compose.ServiceStatus, error) {
return nil, errdefs.ErrNotImplemented return nil, errdefs.ErrNotImplemented
} }