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)
}
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)

View File

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

View File

@ -24,17 +24,15 @@ 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(contextType string) *cobra.Command {
func upCommand() *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, simulation)
return runUp(cmd.Context(), opts)
},
}
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.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, simulation bool) error {
func runUp(ctx context.Context, opts composeOptions) error {
c, err := client.New(ctx)
if err != nil {
return err
@ -65,9 +59,6 @@ func runUp(ctx context.Context, opts composeOptions, simulation bool) error {
return err
}
if simulation {
return c.ComposeService().Emulate(ctx, options)
}
return c.ComposeService().Up(ctx, project)
})
}

View File

@ -38,17 +38,22 @@ $ docker context create ecs CONTEXT [flags]
}
func createEcsCommand() *cobra.Command {
var localSimulation bool
var opts ecs.ContextParams
cmd := &cobra.Command{
Use: "ecs CONTEXT [flags]",
Short: "Create a context for Amazon ECS",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
if localSimulation {
return runCreateLocalSimulation(cmd.Context(), args[0], opts)
}
return runCreateEcs(cmd.Context(), args[0], opts)
},
}
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.Region, "region", "", "Region")
cmd.Flags().StringVar(&opts.AwsID, "key-id", "", "AWS Access Key ID")
@ -56,6 +61,21 @@ func createEcsCommand() *cobra.Command {
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 {
if contextExists(ctx, 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) {
cs, err := client.GetCloudService(ctx, store.EcsContextType)
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)
}

View File

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

View File

@ -34,11 +34,6 @@ 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

View File

@ -35,8 +35,6 @@ 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

View File

@ -44,6 +44,11 @@ const (
// EcsContextType is the endpoint key in the context endpoints for an ECS
// backend
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
// backend
AciContextType = "aci"

View File

@ -22,8 +22,6 @@ import (
"github.com/aws/aws-sdk-go/aws"
"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/compose"
"github.com/docker/compose-cli/containers"
@ -31,6 +29,7 @@ import (
"github.com/docker/compose-cli/context/cloud"
"github.com/docker/compose-cli/context/store"
"github.com/docker/compose-cli/errdefs"
"github.com/docker/compose-cli/secrets"
)
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.
*/
package ecs
package local
import (
"bufio"
"bytes"
"context"
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
"strings"
"github.com/docker/compose-cli/compose"
"github.com/docker/compose-cli/errdefs"
"github.com/aws/aws-sdk-go/aws"
"github.com/compose-spec/compose-go/cli"
"github.com/compose-spec/compose-go/types"
"github.com/pkg/errors"
"github.com/sanathkr/go-yaml"
"golang.org/x/mod/semver"
)
func (c *ecsAPIService) Emulate(ctx context.Context, options *cli.ProjectOptions) error {
project, err := cli.ProjectFromOptions(options)
func (e ecsLocalSimulation) Up(ctx context.Context, project *types.Project) 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)
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()
}
func (e ecsLocalSimulation) Convert(ctx context.Context, project *types.Project) ([]byte, error) {
project.Networks["credentials_network"] = types.NetworkConfig{
Driver: "bridge",
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"
home, err := os.UserHomeDir()
if err != nil {
return err
return nil, err
}
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),
}
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
@ -102,30 +129,17 @@ func (c *ecsAPIService) Emulate(ctx context.Context, options *cli.ProjectOptions
"secrets": project.Secrets,
"configs": project.Configs,
}
marshal, err := yaml.Marshal(config)
if err != nil {
return err
}
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)
}
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()
return yaml.Marshal(config)
}
func (e ecsLocalSimulation) Down(ctx context.Context, projectName string) error {
return errors.Wrap(errdefs.ErrNotImplemented, "use docker-compose down")
}
func (e ecsLocalSimulation) Logs(ctx context.Context, projectName string, w io.Writer) error {
return errors.Wrap(errdefs.ErrNotImplemented, "use docker-compose logs")
}
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
}
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
}