code restructure

Signed-off-by: aiordache <anca.iordache@docker.com>
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
This commit is contained in:
aiordache 2020-06-11 19:22:12 +02:00 committed by Nicolas De Loof
parent d36b9b104e
commit bb98dae082
No known key found for this signature in database
GPG Key ID: 9858809D6F8F6E7E
35 changed files with 464 additions and 421 deletions

View File

@ -3,9 +3,12 @@ package commands
import ( import (
"context" "context"
"fmt" "fmt"
"io"
"os"
"strings"
"github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command"
"github.com/docker/ecs-plugin/pkg/amazon" amazon "github.com/docker/ecs-plugin/pkg/amazon/backend"
"github.com/docker/ecs-plugin/pkg/compose" "github.com/docker/ecs-plugin/pkg/compose"
"github.com/docker/ecs-plugin/pkg/docker" "github.com/docker/ecs-plugin/pkg/docker"
"github.com/spf13/cobra" "github.com/spf13/cobra"
@ -47,11 +50,11 @@ func ConvertCommand(dockerCli command.Cli, projectOpts *compose.ProjectOptions)
if err != nil { if err != nil {
return err return err
} }
client, err := amazon.NewClient(clusteropts.Profile, clusteropts.Cluster, clusteropts.Region) backend, err := amazon.NewBackend(clusteropts.Profile, clusteropts.Cluster, clusteropts.Region)
if err != nil { if err != nil {
return err return err
} }
template, err := client.Convert(project) template, err := backend.Convert(project)
if err != nil { if err != nil {
return err return err
} }
@ -77,11 +80,11 @@ func UpCommand(dockerCli command.Cli, projectOpts *compose.ProjectOptions) *cobr
if err != nil { if err != nil {
return err return err
} }
client, err := amazon.NewClient(clusteropts.Profile, clusteropts.Cluster, clusteropts.Region) backend, err := amazon.NewBackend(clusteropts.Profile, clusteropts.Cluster, clusteropts.Region)
if err != nil { if err != nil {
return err return err
} }
return client.ComposeUp(context.Background(), project) return backend.ComposeUp(context.Background(), project)
}), }),
} }
cmd.Flags().StringVar(&opts.loadBalancerArn, "load-balancer", "", "") cmd.Flags().StringVar(&opts.loadBalancerArn, "load-balancer", "", "")
@ -97,11 +100,20 @@ func PsCommand(dockerCli command.Cli, projectOpts *compose.ProjectOptions) *cobr
if err != nil { if err != nil {
return err return err
} }
client, err := amazon.NewClient(clusteropts.Profile, clusteropts.Cluster, clusteropts.Region) backend, err := amazon.NewBackend(clusteropts.Profile, clusteropts.Cluster, clusteropts.Region)
if err != nil { if err != nil {
return err return err
} }
return client.ComposePs(context.Background(), project) tasks, err := backend.ComposePs(context.Background(), project)
if err != nil {
return err
}
printSection(os.Stdout, len(tasks), func(w io.Writer) {
for _, task := range tasks {
fmt.Fprintf(w, "%s\t%s\t%s\n", task.Name, task.State, strings.Join(task.Ports, " "))
}
}, "NAME", "STATE", "PORTS")
return nil
}), }),
} }
cmd.Flags().StringVar(&opts.loadBalancerArn, "load-balancer", "", "") cmd.Flags().StringVar(&opts.loadBalancerArn, "load-balancer", "", "")
@ -117,7 +129,7 @@ func DownCommand(dockerCli command.Cli, projectOpts *compose.ProjectOptions) *co
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "down", Use: "down",
RunE: docker.WithAwsContext(dockerCli, func(clusteropts docker.AwsContext, args []string) error { RunE: docker.WithAwsContext(dockerCli, func(clusteropts docker.AwsContext, args []string) error {
client, err := amazon.NewClient(clusteropts.Profile, clusteropts.Cluster, clusteropts.Region) backend, err := amazon.NewBackend(clusteropts.Profile, clusteropts.Cluster, clusteropts.Region)
if err != nil { if err != nil {
return err return err
} }
@ -126,11 +138,11 @@ func DownCommand(dockerCli command.Cli, projectOpts *compose.ProjectOptions) *co
if err != nil { if err != nil {
return err return err
} }
return client.ComposeDown(context.Background(), project.Name, opts.DeleteCluster) return backend.ComposeDown(context.Background(), project.Name, opts.DeleteCluster)
} }
// project names passed as parameters // project names passed as parameters
for _, name := range args { for _, name := range args {
err := client.ComposeDown(context.Background(), name, opts.DeleteCluster) err := backend.ComposeDown(context.Background(), name, opts.DeleteCluster)
if err != nil { if err != nil {
return err return err
} }
@ -146,7 +158,7 @@ func LogsCommand(dockerCli command.Cli, projectOpts *compose.ProjectOptions) *co
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "logs [PROJECT NAME]", Use: "logs [PROJECT NAME]",
RunE: docker.WithAwsContext(dockerCli, func(clusteropts docker.AwsContext, args []string) error { RunE: docker.WithAwsContext(dockerCli, func(clusteropts docker.AwsContext, args []string) error {
client, err := amazon.NewClient(clusteropts.Profile, clusteropts.Cluster, clusteropts.Region) backend, err := amazon.NewBackend(clusteropts.Profile, clusteropts.Cluster, clusteropts.Region)
if err != nil { if err != nil {
return err return err
} }
@ -161,7 +173,7 @@ func LogsCommand(dockerCli command.Cli, projectOpts *compose.ProjectOptions) *co
} else { } else {
name = args[0] name = args[0]
} }
return client.ComposeLogs(context.Background(), name) return backend.ComposeLogs(context.Background(), name)
}), }),
} }
return cmd return cmd

View File

@ -10,7 +10,8 @@ import (
"text/tabwriter" "text/tabwriter"
"github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command"
"github.com/docker/ecs-plugin/pkg/amazon" amazon "github.com/docker/ecs-plugin/pkg/amazon/backend"
"github.com/docker/ecs-plugin/pkg/amazon/types"
"github.com/docker/ecs-plugin/pkg/docker" "github.com/docker/ecs-plugin/pkg/docker"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -47,7 +48,7 @@ func CreateSecret(dockerCli command.Cli) *cobra.Command {
Use: "create NAME", Use: "create NAME",
Short: "Creates a secret.", Short: "Creates a secret.",
RunE: docker.WithAwsContext(dockerCli, func(clusteropts docker.AwsContext, args []string) error { RunE: docker.WithAwsContext(dockerCli, func(clusteropts docker.AwsContext, args []string) error {
client, err := amazon.NewClient(clusteropts.Profile, clusteropts.Cluster, clusteropts.Region) backend, err := amazon.NewBackend(clusteropts.Profile, clusteropts.Cluster, clusteropts.Region)
if err != nil { if err != nil {
return err return err
} }
@ -56,8 +57,8 @@ func CreateSecret(dockerCli command.Cli) *cobra.Command {
} }
name := args[0] name := args[0]
secret := docker.NewSecret(name, opts.Username, opts.Password, opts.Description) secret := types.NewSecret(name, opts.Username, opts.Password, opts.Description)
id, err := client.CreateSecret(context.Background(), secret) id, err := backend.CreateSecret(context.Background(), secret)
fmt.Println(id) fmt.Println(id)
return err return err
}), }),
@ -73,7 +74,7 @@ func InspectSecret(dockerCli command.Cli) *cobra.Command {
Use: "inspect ID", Use: "inspect ID",
Short: "Displays secret details", Short: "Displays secret details",
RunE: docker.WithAwsContext(dockerCli, func(clusteropts docker.AwsContext, args []string) error { RunE: docker.WithAwsContext(dockerCli, func(clusteropts docker.AwsContext, args []string) error {
client, err := amazon.NewClient(clusteropts.Profile, clusteropts.Cluster, clusteropts.Region) backend, err := amazon.NewBackend(clusteropts.Profile, clusteropts.Cluster, clusteropts.Region)
if err != nil { if err != nil {
return err return err
} }
@ -81,7 +82,7 @@ func InspectSecret(dockerCli command.Cli) *cobra.Command {
return errors.New("Missing mandatory parameter: ID") return errors.New("Missing mandatory parameter: ID")
} }
id := args[0] id := args[0]
secret, err := client.InspectSecret(context.Background(), id) secret, err := backend.InspectSecret(context.Background(), id)
if err != nil { if err != nil {
return err return err
} }
@ -102,11 +103,11 @@ func ListSecrets(dockerCli command.Cli) *cobra.Command {
Aliases: []string{"ls"}, Aliases: []string{"ls"},
Short: "List secrets stored for the existing account.", Short: "List secrets stored for the existing account.",
RunE: docker.WithAwsContext(dockerCli, func(clusteropts docker.AwsContext, args []string) error { RunE: docker.WithAwsContext(dockerCli, func(clusteropts docker.AwsContext, args []string) error {
client, err := amazon.NewClient(clusteropts.Profile, clusteropts.Cluster, clusteropts.Region) backend, err := amazon.NewBackend(clusteropts.Profile, clusteropts.Cluster, clusteropts.Region)
if err != nil { if err != nil {
return err return err
} }
secrets, err := client.ListSecrets(context.Background()) secrets, err := backend.ListSecrets(context.Background())
if err != nil { if err != nil {
return err return err
} }
@ -125,21 +126,21 @@ func DeleteSecret(dockerCli command.Cli) *cobra.Command {
Aliases: []string{"rm", "remove"}, Aliases: []string{"rm", "remove"},
Short: "Removes a secret.", Short: "Removes a secret.",
RunE: docker.WithAwsContext(dockerCli, func(clusteropts docker.AwsContext, args []string) error { RunE: docker.WithAwsContext(dockerCli, func(clusteropts docker.AwsContext, args []string) error {
client, err := amazon.NewClient(clusteropts.Profile, clusteropts.Cluster, clusteropts.Region) backend, err := amazon.NewBackend(clusteropts.Profile, clusteropts.Cluster, clusteropts.Region)
if err != nil { if err != nil {
return err return err
} }
if len(args) == 0 { if len(args) == 0 {
return errors.New("Missing mandatory parameter: [NAME]") return errors.New("Missing mandatory parameter: [NAME]")
} }
return client.DeleteSecret(context.Background(), args[0], opts.recover) return backend.DeleteSecret(context.Background(), args[0], opts.recover)
}), }),
} }
cmd.Flags().BoolVar(&opts.recover, "recover", false, "Enable recovery.") cmd.Flags().BoolVar(&opts.recover, "recover", false, "Enable recovery.")
return cmd return cmd
} }
func printList(out io.Writer, secrets []docker.Secret) { func printList(out io.Writer, secrets []types.Secret) {
printSection(out, len(secrets), func(w io.Writer) { printSection(out, len(secrets), func(w io.Writer) {
for _, secret := range secrets { for _, secret := range secrets {
fmt.Fprintf(w, "%s\t%s\t%s\n", secret.ID, secret.Name, secret.Description) fmt.Fprintf(w, "%s\t%s\t%s\n", secret.ID, secret.Name, secret.Description)

8
ecs/pkg/amazon/amazon.go Normal file
View File

@ -0,0 +1,8 @@
package amazon
import (
"github.com/docker/ecs-plugin/pkg/amazon/backend"
"github.com/docker/ecs-plugin/pkg/compose"
)
var _ compose.API = &backend.Backend{}

View File

@ -1,11 +0,0 @@
package amazon
//go:generate mockgen -destination=./api_mock.go -self_package "github.com/docker/ecs-plugin/pkg/amazon" -package=amazon . API
type API interface {
downAPI
upAPI
logsAPI
secretsAPI
listAPI
}

View File

@ -1,9 +1,9 @@
package amazon package backend
import ( 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/ecs-plugin/pkg/compose" "github.com/docker/ecs-plugin/pkg/amazon/sdk"
) )
const ( const (
@ -12,7 +12,7 @@ const (
ServiceTag = "com.docker.compose.service" ServiceTag = "com.docker.compose.service"
) )
func NewClient(profile string, cluster string, region string) (compose.API, error) { func NewBackend(profile string, cluster string, region string) (*Backend, error) {
sess, err := session.NewSessionWithOptions(session.Options{ sess, err := session.NewSessionWithOptions(session.Options{
Profile: profile, Profile: profile,
Config: aws.Config{ Config: aws.Config{
@ -22,17 +22,15 @@ func NewClient(profile string, cluster string, region string) (compose.API, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &client{ return &Backend{
Cluster: cluster, Cluster: cluster,
Region: region, Region: region,
api: NewAPI(sess), api: sdk.NewAPI(sess),
}, nil }, nil
} }
type client struct { type Backend struct {
Cluster string Cluster string
Region string Region string
api API api sdk.API
} }
var _ compose.API = &client{}

View File

@ -1,4 +1,4 @@
package amazon package backend
import ( import (
"fmt" "fmt"
@ -21,6 +21,9 @@ import (
"github.com/awslabs/goformation/v4/cloudformation/logs" "github.com/awslabs/goformation/v4/cloudformation/logs"
cloudmap "github.com/awslabs/goformation/v4/cloudformation/servicediscovery" cloudmap "github.com/awslabs/goformation/v4/cloudformation/servicediscovery"
"github.com/awslabs/goformation/v4/cloudformation/tags" "github.com/awslabs/goformation/v4/cloudformation/tags"
"github.com/docker/ecs-plugin/pkg/amazon/compatibility"
sdk "github.com/docker/ecs-plugin/pkg/amazon/sdk"
btypes "github.com/docker/ecs-plugin/pkg/amazon/types"
"github.com/docker/ecs-plugin/pkg/compose" "github.com/docker/ecs-plugin/pkg/compose"
) )
@ -33,8 +36,8 @@ const (
) )
// Convert a compose project into a CloudFormation template // Convert a compose project into a CloudFormation template
func (c client) Convert(project *compose.Project) (*cloudformation.Template, error) { func (b Backend) Convert(project *compose.Project) (*cloudformation.Template, error) {
warnings := Check(project) warnings := compatibility.Check(project)
for _, w := range warnings { for _, w := range warnings {
logrus.Warn(w) logrus.Warn(w)
} }
@ -75,7 +78,7 @@ func (c client) Convert(project *compose.Project) (*cloudformation.Template, err
// Create Cluster is `ParameterClusterName` parameter is not set // Create Cluster is `ParameterClusterName` parameter is not set
template.Conditions["CreateCluster"] = cloudformation.Equals("", cloudformation.Ref(ParameterClusterName)) template.Conditions["CreateCluster"] = cloudformation.Equals("", cloudformation.Ref(ParameterClusterName))
cluster := c.createCluster(project, template) cluster := createCluster(project, template)
networks := map[string]string{} networks := map[string]string{}
for _, net := range project.Networks { for _, net := range project.Networks {
@ -88,17 +91,18 @@ func (c client) Convert(project *compose.Project) (*cloudformation.Template, err
} }
// Private DNS namespace will allow DNS name for the services to be <service>.<project>.local // Private DNS namespace will allow DNS name for the services to be <service>.<project>.local
c.createCloudMap(project, template) createCloudMap(project, template)
loadBalancerARN := c.createLoadBalancer(project, template) loadBalancerARN := createLoadBalancer(project, template)
for _, service := range project.Services { for _, service := range project.Services {
definition, err := Convert(project, service)
definition, err := sdk.Convert(project, service)
if err != nil { if err != nil {
return nil, err return nil, err
} }
taskExecutionRole, err := c.createTaskExecutionRole(service, err, definition, template) taskExecutionRole, err := createTaskExecutionRole(service, err, definition, template)
if err != nil { if err != nil {
return template, err return template, err
} }
@ -112,7 +116,7 @@ func (c client) Convert(project *compose.Project) (*cloudformation.Template, err
// FIXME ECS only support HTTP(s) health checks, while Docker only support CMD // FIXME ECS only support HTTP(s) health checks, while Docker only support CMD
} }
serviceRegistry := c.createServiceRegistry(service, template, healthCheck) serviceRegistry := createServiceRegistry(service, template, healthCheck)
serviceSecurityGroups := []string{} serviceSecurityGroups := []string{}
for net := range service.Networks { for net := range service.Networks {
@ -124,14 +128,14 @@ func (c client) Convert(project *compose.Project) (*cloudformation.Template, err
if len(service.Ports) > 0 { if len(service.Ports) > 0 {
for _, port := range service.Ports { for _, port := range service.Ports {
protocol := strings.ToUpper(port.Protocol) protocol := strings.ToUpper(port.Protocol)
if c.getLoadBalancerType(project) == elbv2.LoadBalancerTypeEnumApplication { if getLoadBalancerType(project) == elbv2.LoadBalancerTypeEnumApplication {
protocol = elbv2.ProtocolEnumHttps protocol = elbv2.ProtocolEnumHttps
if port.Published == 80 { if port.Published == 80 {
protocol = elbv2.ProtocolEnumHttp protocol = elbv2.ProtocolEnumHttp
} }
} }
targetGroupName := c.createTargetGroup(project, service, port, template, protocol) targetGroupName := createTargetGroup(project, service, port, template, protocol)
listenerName := c.createListener(service, port, template, targetGroupName, loadBalancerARN, protocol) listenerName := createListener(service, port, template, targetGroupName, loadBalancerARN, protocol)
dependsOn = append(dependsOn, listenerName) dependsOn = append(dependsOn, listenerName)
serviceLB = append(serviceLB, ecs.Service_LoadBalancer{ serviceLB = append(serviceLB, ecs.Service_LoadBalancer{
ContainerName: service.Name, ContainerName: service.Name,
@ -184,7 +188,7 @@ func (c client) Convert(project *compose.Project) (*cloudformation.Template, err
return template, nil return template, nil
} }
func (c client) getLoadBalancerType(project *compose.Project) string { func getLoadBalancerType(project *compose.Project) string {
for _, service := range project.Services { for _, service := range project.Services {
for _, port := range service.Ports { for _, port := range service.Ports {
if port.Published != 80 && port.Published != 443 { if port.Published != 80 && port.Published != 443 {
@ -195,7 +199,7 @@ func (c client) getLoadBalancerType(project *compose.Project) string {
return elbv2.LoadBalancerTypeEnumApplication return elbv2.LoadBalancerTypeEnumApplication
} }
func (c client) getLoadBalancerSecurityGroups(project *compose.Project, template *cloudformation.Template) []string { func getLoadBalancerSecurityGroups(project *compose.Project, template *cloudformation.Template) []string {
securityGroups := []string{} securityGroups := []string{}
for _, network := range project.Networks { for _, network := range project.Networks {
if !network.Internal { if !network.Internal {
@ -206,15 +210,15 @@ func (c client) getLoadBalancerSecurityGroups(project *compose.Project, template
return uniqueStrings(securityGroups) return uniqueStrings(securityGroups)
} }
func (c client) createLoadBalancer(project *compose.Project, template *cloudformation.Template) string { func createLoadBalancer(project *compose.Project, template *cloudformation.Template) string {
loadBalancerName := fmt.Sprintf("%sLoadBalancer", strings.Title(project.Name)) loadBalancerName := fmt.Sprintf("%sLoadBalancer", strings.Title(project.Name))
// Create LoadBalancer if `ParameterLoadBalancerName` is not set // Create LoadBalancer if `ParameterLoadBalancerName` is not set
template.Conditions["CreateLoadBalancer"] = cloudformation.Equals("", cloudformation.Ref(ParameterLoadBalancerARN)) template.Conditions["CreateLoadBalancer"] = cloudformation.Equals("", cloudformation.Ref(ParameterLoadBalancerARN))
loadBalancerType := c.getLoadBalancerType(project) loadBalancerType := getLoadBalancerType(project)
securityGroups := []string{} securityGroups := []string{}
if loadBalancerType == elbv2.LoadBalancerTypeEnumApplication { if loadBalancerType == elbv2.LoadBalancerTypeEnumApplication {
securityGroups = c.getLoadBalancerSecurityGroups(project, template) securityGroups = getLoadBalancerSecurityGroups(project, template)
} }
template.Resources[loadBalancerName] = &elasticloadbalancingv2.LoadBalancer{ template.Resources[loadBalancerName] = &elasticloadbalancingv2.LoadBalancer{
@ -237,7 +241,7 @@ func (c client) createLoadBalancer(project *compose.Project, template *cloudform
return cloudformation.If("CreateLoadBalancer", cloudformation.Ref(loadBalancerName), cloudformation.Ref(ParameterLoadBalancerARN)) return cloudformation.If("CreateLoadBalancer", cloudformation.Ref(loadBalancerName), cloudformation.Ref(ParameterLoadBalancerARN))
} }
func (c client) createListener(service types.ServiceConfig, port types.ServicePortConfig, template *cloudformation.Template, targetGroupName string, loadBalancerARN string, protocol string) string { func createListener(service types.ServiceConfig, port types.ServicePortConfig, template *cloudformation.Template, targetGroupName string, loadBalancerARN string, protocol string) string {
listenerName := fmt.Sprintf( listenerName := fmt.Sprintf(
"%s%s%dListener", "%s%s%dListener",
normalizeResourceName(service.Name), normalizeResourceName(service.Name),
@ -266,7 +270,7 @@ func (c client) createListener(service types.ServiceConfig, port types.ServicePo
return listenerName return listenerName
} }
func (c client) createTargetGroup(project *compose.Project, service types.ServiceConfig, port types.ServicePortConfig, template *cloudformation.Template, protocol string) string { func createTargetGroup(project *compose.Project, service types.ServiceConfig, port types.ServicePortConfig, template *cloudformation.Template, protocol string) string {
targetGroupName := fmt.Sprintf( targetGroupName := fmt.Sprintf(
"%s%s%dTargetGroup", "%s%s%dTargetGroup",
normalizeResourceName(service.Name), normalizeResourceName(service.Name),
@ -289,7 +293,7 @@ func (c client) createTargetGroup(project *compose.Project, service types.Servic
return targetGroupName return targetGroupName
} }
func (c client) createServiceRegistry(service types.ServiceConfig, template *cloudformation.Template, healthCheck *cloudmap.Service_HealthCheckConfig) ecs.Service_ServiceRegistry { func createServiceRegistry(service types.ServiceConfig, template *cloudformation.Template, healthCheck *cloudmap.Service_HealthCheckConfig) ecs.Service_ServiceRegistry {
serviceRegistration := fmt.Sprintf("%sServiceDiscoveryEntry", normalizeResourceName(service.Name)) serviceRegistration := fmt.Sprintf("%sServiceDiscoveryEntry", normalizeResourceName(service.Name))
serviceRegistry := ecs.Service_ServiceRegistry{ serviceRegistry := ecs.Service_ServiceRegistry{
RegistryArn: cloudformation.GetAtt(serviceRegistration, "Arn"), RegistryArn: cloudformation.GetAtt(serviceRegistration, "Arn"),
@ -316,9 +320,9 @@ func (c client) createServiceRegistry(service types.ServiceConfig, template *clo
return serviceRegistry return serviceRegistry
} }
func (c client) createTaskExecutionRole(service types.ServiceConfig, err error, definition *ecs.TaskDefinition, template *cloudformation.Template) (string, error) { func createTaskExecutionRole(service types.ServiceConfig, err error, definition *ecs.TaskDefinition, template *cloudformation.Template) (string, error) {
taskExecutionRole := fmt.Sprintf("%sTaskExecutionRole", normalizeResourceName(service.Name)) taskExecutionRole := fmt.Sprintf("%sTaskExecutionRole", normalizeResourceName(service.Name))
policy, err := c.getPolicy(definition) policy, err := getPolicy(definition)
if err != nil { if err != nil {
return taskExecutionRole, err return taskExecutionRole, err
} }
@ -341,7 +345,7 @@ func (c client) createTaskExecutionRole(service types.ServiceConfig, err error,
return taskExecutionRole, nil return taskExecutionRole, nil
} }
func (c client) createCluster(project *compose.Project, template *cloudformation.Template) string { func createCluster(project *compose.Project, template *cloudformation.Template) string {
template.Resources["Cluster"] = &ecs.Cluster{ template.Resources["Cluster"] = &ecs.Cluster{
ClusterName: project.Name, ClusterName: project.Name,
Tags: []tags.Tag{ Tags: []tags.Tag{
@ -356,7 +360,7 @@ func (c client) createCluster(project *compose.Project, template *cloudformation
return cluster return cluster
} }
func (c client) createCloudMap(project *compose.Project, template *cloudformation.Template) { func createCloudMap(project *compose.Project, template *cloudformation.Template) {
template.Resources["CloudMap"] = &cloudmap.PrivateDnsNamespace{ template.Resources["CloudMap"] = &cloudmap.PrivateDnsNamespace{
Description: fmt.Sprintf("Service Map for Docker Compose project %s", project.Name), Description: fmt.Sprintf("Service Map for Docker Compose project %s", project.Name),
Name: fmt.Sprintf("%s.local", project.Name), Name: fmt.Sprintf("%s.local", project.Name),
@ -365,7 +369,7 @@ func (c client) createCloudMap(project *compose.Project, template *cloudformatio
} }
func convertNetwork(project *compose.Project, net types.NetworkConfig, vpc string, template *cloudformation.Template) string { func convertNetwork(project *compose.Project, net types.NetworkConfig, vpc string, template *cloudformation.Template) string {
if sg, ok := net.Extras[ExtensionSecurityGroup]; ok { if sg, ok := net.Extras[btypes.ExtensionSecurityGroup]; ok {
logrus.Debugf("Security Group for network %q set by user to %q", net.Name, sg) logrus.Debugf("Security Group for network %q set by user to %q", net.Name, sg)
return sg.(string) return sg.(string)
} }
@ -428,7 +432,7 @@ func normalizeResourceName(s string) string {
return strings.Title(regexp.MustCompile("[^a-zA-Z0-9]+").ReplaceAllString(s, "")) return strings.Title(regexp.MustCompile("[^a-zA-Z0-9]+").ReplaceAllString(s, ""))
} }
func (c client) getPolicy(taskDef *ecs.TaskDefinition) (*PolicyDocument, error) { func getPolicy(taskDef *ecs.TaskDefinition) (*PolicyDocument, error) {
arns := []string{} arns := []string{}
for _, container := range taskDef.ContainerDefinitions { for _, container := range taskDef.ContainerDefinitions {
if container.RepositoryCredentials != nil { if container.RepositoryCredentials != nil {

View File

@ -1,4 +1,4 @@
package amazon package backend
import ( import (
"fmt" "fmt"
@ -13,6 +13,7 @@ import (
"github.com/compose-spec/compose-go/loader" "github.com/compose-spec/compose-go/loader"
"github.com/compose-spec/compose-go/types" "github.com/compose-spec/compose-go/types"
"github.com/docker/ecs-plugin/pkg/compose" "github.com/docker/ecs-plugin/pkg/compose"
"gotest.tools/v3/assert" "gotest.tools/v3/assert"
"gotest.tools/v3/golden" "gotest.tools/v3/golden"
) )
@ -103,7 +104,7 @@ services:
} }
func convertResultAsString(t *testing.T, project *compose.Project, clusterName string) string { func convertResultAsString(t *testing.T, project *compose.Project, clusterName string) string {
client, err := NewClient("", clusterName, "") client, err := NewBackend("", clusterName, "")
assert.NilError(t, err) assert.NilError(t, err)
result, err := client.Convert(project) result, err := client.Convert(project)
assert.NilError(t, err) assert.NilError(t, err)
@ -133,7 +134,7 @@ func convertYaml(t *testing.T, yaml string) *cloudformation.Template {
assert.NilError(t, err) assert.NilError(t, err)
err = compose.Normalize(model) err = compose.Normalize(model)
assert.NilError(t, err) assert.NilError(t, err)
template, err := client{}.Convert(&compose.Project{ template, err := Backend{}.Convert(&compose.Project{
Config: *model, Config: *model,
Name: "test", Name: "test",
}) })

View File

@ -0,0 +1,31 @@
package backend
import (
"context"
"fmt"
"github.com/docker/ecs-plugin/pkg/amazon/types"
)
func (b *Backend) ComposeDown(ctx context.Context, projectName string, deleteCluster bool) error {
err := b.api.DeleteStack(ctx, projectName)
if err != nil {
return err
}
err = b.WaitStackCompletion(ctx, projectName, types.StackDelete)
if err != nil {
return err
}
if !deleteCluster {
return nil
}
fmt.Printf("Delete cluster %s", b.Cluster)
if err = b.api.DeleteCluster(ctx, b.Cluster); err != nil {
return err
}
fmt.Printf("... done. \n")
return nil
}

View File

@ -1,17 +1,19 @@
package amazon package backend
import ( import (
"context" "context"
"testing" "testing"
"github.com/docker/ecs-plugin/pkg/amazon/sdk"
btypes "github.com/docker/ecs-plugin/pkg/amazon/types"
"github.com/golang/mock/gomock" "github.com/golang/mock/gomock"
) )
func TestDownDontDeleteCluster(t *testing.T) { func TestDownDontDeleteCluster(t *testing.T) {
ctrl := gomock.NewController(t) ctrl := gomock.NewController(t)
defer ctrl.Finish() defer ctrl.Finish()
m := NewMockAPI(ctrl) m := sdk.NewMockAPI(ctrl)
c := &client{ c := &Backend{
Cluster: "test_cluster", Cluster: "test_cluster",
Region: "region", Region: "region",
api: m, api: m,
@ -20,7 +22,7 @@ func TestDownDontDeleteCluster(t *testing.T) {
recorder := m.EXPECT() recorder := m.EXPECT()
recorder.DeleteStack(ctx, "test_project").Return(nil) recorder.DeleteStack(ctx, "test_project").Return(nil)
recorder.GetStackID(ctx, "test_project").Return("stack-123", nil) recorder.GetStackID(ctx, "test_project").Return("stack-123", nil)
recorder.WaitStackComplete(ctx, "stack-123", StackDelete).Return(nil) recorder.WaitStackComplete(ctx, "stack-123", btypes.StackDelete).Return(nil)
recorder.DescribeStackEvents(ctx, "stack-123").Return(nil, nil) recorder.DescribeStackEvents(ctx, "stack-123").Return(nil, nil)
c.ComposeDown(ctx, "test_project", false) c.ComposeDown(ctx, "test_project", false)
@ -29,8 +31,8 @@ func TestDownDontDeleteCluster(t *testing.T) {
func TestDownDeleteCluster(t *testing.T) { func TestDownDeleteCluster(t *testing.T) {
ctrl := gomock.NewController(t) ctrl := gomock.NewController(t)
defer ctrl.Finish() defer ctrl.Finish()
m := NewMockAPI(ctrl) m := sdk.NewMockAPI(ctrl)
c := &client{ c := &Backend{
Cluster: "test_cluster", Cluster: "test_cluster",
Region: "region", Region: "region",
api: m, api: m,
@ -40,7 +42,7 @@ func TestDownDeleteCluster(t *testing.T) {
recorder := m.EXPECT() recorder := m.EXPECT()
recorder.DeleteStack(ctx, "test_project").Return(nil) recorder.DeleteStack(ctx, "test_project").Return(nil)
recorder.GetStackID(ctx, "test_project").Return("stack-123", nil) recorder.GetStackID(ctx, "test_project").Return("stack-123", nil)
recorder.WaitStackComplete(ctx, "stack-123", StackDelete).Return(nil) recorder.WaitStackComplete(ctx, "stack-123", btypes.StackDelete).Return(nil)
recorder.DescribeStackEvents(ctx, "stack-123").Return(nil, nil) recorder.DescribeStackEvents(ctx, "stack-123").Return(nil, nil)
recorder.DeleteCluster(ctx, "test_cluster").Return(nil) recorder.DeleteCluster(ctx, "test_cluster").Return(nil)

View File

@ -1,4 +1,4 @@
package amazon package backend
const ( const (
ECSTaskExecutionPolicy = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy" ECSTaskExecutionPolicy = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"

View File

@ -0,0 +1,63 @@
package backend
import (
"context"
"fmt"
"sort"
"strings"
"github.com/docker/ecs-plugin/pkg/amazon/types"
"github.com/docker/ecs-plugin/pkg/compose"
)
func (b *Backend) ComposePs(ctx context.Context, project *compose.Project) ([]types.TaskStatus, error) {
cluster := b.Cluster
if cluster == "" {
cluster = project.Name
}
arns := []string{}
for _, service := range project.Services {
tasks, err := b.api.ListTasks(ctx, cluster, service.Name)
if err != nil {
return []types.TaskStatus{}, err
}
arns = append(arns, tasks...)
}
if len(arns) == 0 {
return []types.TaskStatus{}, nil
}
tasks, err := b.api.DescribeTasks(ctx, cluster, arns...)
if err != nil {
return []types.TaskStatus{}, err
}
networkInterfaces := []string{}
for _, t := range tasks {
if t.NetworkInterface != "" {
networkInterfaces = append(networkInterfaces, t.NetworkInterface)
}
}
publicIps, err := b.api.GetPublicIPs(ctx, networkInterfaces...)
if err != nil {
return []types.TaskStatus{}, err
}
sort.Slice(tasks, func(i, j int) bool {
return strings.Compare(tasks[i].Service, tasks[j].Service) < 0
})
for i, t := range tasks {
ports := []string{}
s, err := project.GetService(t.Service)
if err != nil {
return []types.TaskStatus{}, err
}
for _, p := range s.Ports {
ports = append(ports, fmt.Sprintf("%s:%d->%d/%s", publicIps[t.NetworkInterface], p.Published, p.Target, p.Protocol))
}
tasks[i].Name = s.Name
tasks[i].Ports = ports
}
return tasks, nil
}

View File

@ -1,4 +1,4 @@
package amazon package backend
import ( import (
"context" "context"
@ -11,8 +11,8 @@ import (
"github.com/docker/ecs-plugin/pkg/console" "github.com/docker/ecs-plugin/pkg/console"
) )
func (c *client) ComposeLogs(ctx context.Context, projectName string) error { func (b *Backend) ComposeLogs(ctx context.Context, projectName string) error {
err := c.api.GetLogs(ctx, projectName, &logConsumer{ err := b.api.GetLogs(ctx, projectName, &logConsumer{
colors: map[string]console.ColorFunc{}, colors: map[string]console.ColorFunc{},
width: 0, width: 0,
}) })
@ -26,11 +26,6 @@ func (c *client) ComposeLogs(ctx context.Context, projectName string) error {
return nil return nil
} }
type logConsumer struct {
colors map[string]console.ColorFunc
width int
}
func (l *logConsumer) Log(service, container, message string) { func (l *logConsumer) Log(service, container, message string) {
cf, ok := l.colors[service] cf, ok := l.colors[service]
if !ok { if !ok {
@ -54,10 +49,7 @@ func (l *logConsumer) computeWidth() {
l.width = width + 3 l.width = width + 3
} }
type LogConsumer interface { type logConsumer struct {
Log(service, container, message string) colors map[string]console.ColorFunc
} width int
type logsAPI interface {
GetLogs(ctx context.Context, name string, consumer LogConsumer) error
} }

View File

@ -0,0 +1,23 @@
package backend
import (
"context"
"github.com/docker/ecs-plugin/pkg/amazon/types"
)
func (b Backend) CreateSecret(ctx context.Context, secret types.Secret) (string, error) {
return b.api.CreateSecret(ctx, secret)
}
func (b Backend) InspectSecret(ctx context.Context, id string) (types.Secret, error) {
return b.api.InspectSecret(ctx, id)
}
func (b Backend) ListSecrets(ctx context.Context) ([]types.Secret, error) {
return b.api.ListSecrets(ctx)
}
func (b Backend) DeleteSecret(ctx context.Context, id string, recover bool) error {
return b.api.DeleteSecret(ctx, id, recover)
}

View File

@ -0,0 +1,100 @@
package backend
import (
"context"
"fmt"
"github.com/docker/ecs-plugin/pkg/amazon/types"
"github.com/docker/ecs-plugin/pkg/compose"
)
func (b *Backend) ComposeUp(ctx context.Context, project *compose.Project) error {
if b.Cluster != "" {
ok, err := b.api.ClusterExists(ctx, b.Cluster)
if err != nil {
return err
}
if !ok {
return fmt.Errorf("configured cluster %q does not exist", b.Cluster)
}
}
update, err := b.api.StackExists(ctx, project.Name)
if err != nil {
return err
}
if update {
return fmt.Errorf("we do not (yet) support updating an existing CloudFormation stack")
}
template, err := b.Convert(project)
if err != nil {
return err
}
vpc, err := b.GetVPC(ctx, project)
if err != nil {
return err
}
subNets, err := b.api.GetSubNets(ctx, vpc)
if err != nil {
return err
}
lb, err := b.GetLoadBalancer(ctx, project)
if err != nil {
return err
}
parameters := map[string]string{
ParameterClusterName: b.Cluster,
ParameterVPCId: vpc,
ParameterSubnet1Id: subNets[0],
ParameterSubnet2Id: subNets[1],
ParameterLoadBalancerARN: lb,
}
err = b.api.CreateStack(ctx, project.Name, template, parameters)
if err != nil {
return err
}
fmt.Println()
return b.WaitStackCompletion(ctx, project.Name, types.StackCreate)
}
func (b Backend) GetVPC(ctx context.Context, project *compose.Project) (string, error) {
//check compose file for custom VPC selected
if vpc, ok := project.Extras[types.ExtensionVPC]; ok {
vpcID := vpc.(string)
ok, err := b.api.VpcExists(ctx, vpcID)
if err != nil {
return "", err
}
if !ok {
return "", fmt.Errorf("VPC does not exist: %s", vpc)
}
}
defaultVPC, err := b.api.GetDefaultVPC(ctx)
if err != nil {
return "", err
}
return defaultVPC, nil
}
func (b Backend) GetLoadBalancer(ctx context.Context, project *compose.Project) (string, error) {
//check compose file for custom VPC selected
if lb, ok := project.Extras[types.ExtensionLB]; ok {
lbName := lb.(string)
ok, err := b.api.LoadBalancerExists(ctx, lbName)
if err != nil {
return "", err
}
if !ok {
return "", fmt.Errorf("Load Balancer does not exist: %s", lb)
}
return b.api.GetLoadBalancerARN(ctx, lbName)
}
return "", nil
}

View File

@ -1,4 +1,4 @@
package amazon package backend
import ( import (
"context" "context"
@ -8,16 +8,15 @@ import (
"time" "time"
"github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/cloudformation"
"github.com/docker/ecs-plugin/pkg/console" "github.com/docker/ecs-plugin/pkg/console"
) )
func (c *client) WaitStackCompletion(ctx context.Context, name string, operation int) error { func (b *Backend) WaitStackCompletion(ctx context.Context, name string, operation int) error {
w := console.NewProgressWriter() w := console.NewProgressWriter()
knownEvents := map[string]struct{}{} knownEvents := map[string]struct{}{}
// Get the unique Stack ID so we can collect events without getting some from previous deployments with same name // Get the unique Stack ID so we can collect events without getting some from previous deployments with same name
stackID, err := c.api.GetStackID(ctx, name) stackID, err := b.api.GetStackID(ctx, name)
if err != nil { if err != nil {
return err return err
} }
@ -26,7 +25,7 @@ func (c *client) WaitStackCompletion(ctx context.Context, name string, operation
done := make(chan bool) done := make(chan bool)
go func() { go func() {
c.api.WaitStackComplete(ctx, stackID, operation) //nolint:errcheck b.api.WaitStackComplete(ctx, stackID, operation) //nolint:errcheck
ticker.Stop() ticker.Stop()
done <- true done <- true
}() }()
@ -39,7 +38,7 @@ func (c *client) WaitStackCompletion(ctx context.Context, name string, operation
completed = true completed = true
case <-ticker.C: case <-ticker.C:
} }
events, err := c.api.DescribeStackEvents(ctx, stackID) events, err := b.api.DescribeStackEvents(ctx, stackID)
if err != nil { if err != nil {
return err return err
} }
@ -65,14 +64,3 @@ func (c *client) WaitStackCompletion(ctx context.Context, name string, operation
} }
return stackErr return stackErr
} }
type waitAPI interface {
GetStackID(ctx context.Context, name string) (string, error)
WaitStackComplete(ctx context.Context, name string, operation int) error
DescribeStackEvents(ctx context.Context, stackID string) ([]*cloudformation.StackEvent, error)
}
const (
StackCreate = iota
StackDelete
)

View File

@ -1,13 +0,0 @@
package amazon
import (
"testing"
"gotest.tools/v3/assert"
)
func TestInvalidNetworkMode(t *testing.T) {
project := load(t, "testdata/invalid_network_mode.yaml")
err := Check(project)
assert.Error(t, err[0], "'network_mode' \"bridge\" is not supported")
}

View File

@ -1,4 +1,4 @@
package amazon package compatibility
import ( import (
"github.com/compose-spec/compose-go/types" "github.com/compose-spec/compose-go/types"

View File

@ -0,0 +1,23 @@
package compatibility
import (
"testing"
"github.com/docker/ecs-plugin/pkg/compose"
"gotest.tools/v3/assert"
)
func load(t *testing.T, paths ...string) *compose.Project {
options := compose.ProjectOptions{
Name: t.Name(),
ConfigPaths: paths,
}
project, err := compose.ProjectFromOptions(&options)
assert.NilError(t, err)
return project
}
func TestInvalidNetworkMode(t *testing.T) {
project := load(t, "../backend/testdata/invalid_network_mode.yaml")
err := Check(project)
assert.Error(t, err[0], "'network_mode' \"bridge\" is not supported")
}

View File

@ -1,4 +1,4 @@
package amazon package compatibility
import ( import (
"fmt" "fmt"

View File

@ -1,34 +0,0 @@
package amazon
import (
"context"
"fmt"
)
func (c *client) ComposeDown(ctx context.Context, projectName string, deleteCluster bool) error {
err := c.api.DeleteStack(ctx, projectName)
if err != nil {
return err
}
err = c.WaitStackCompletion(ctx, projectName, StackDelete)
if err != nil {
return err
}
if !deleteCluster {
return nil
}
fmt.Printf("Delete cluster %s", c.Cluster)
if err = c.api.DeleteCluster(ctx, c.Cluster); err != nil {
return err
}
fmt.Printf("... done. \n")
return nil
}
type downAPI interface {
DeleteStack(ctx context.Context, name string) error
DeleteCluster(ctx context.Context, name string) error
}

View File

@ -1,80 +0,0 @@
package amazon
import (
"context"
"fmt"
"os"
"sort"
"strings"
"text/tabwriter"
"github.com/docker/ecs-plugin/pkg/compose"
)
func (c *client) ComposePs(ctx context.Context, project *compose.Project) error {
cluster := c.Cluster
if cluster == "" {
cluster = project.Name
}
w := tabwriter.NewWriter(os.Stdout, 20, 2, 3, ' ', 0)
fmt.Fprintf(w, "Name\tState\tPorts\n")
defer w.Flush()
arns := []string{}
for _, service := range project.Services {
tasks, err := c.api.ListTasks(ctx, cluster, service.Name)
if err != nil {
return err
}
arns = append(arns, tasks...)
}
if len(arns) == 0 {
return nil
}
tasks, err := c.api.DescribeTasks(ctx, cluster, arns...)
if err != nil {
return err
}
networkInterfaces := []string{}
for _, t := range tasks {
if t.NetworkInterface != "" {
networkInterfaces = append(networkInterfaces, t.NetworkInterface)
}
}
publicIps, err := c.api.GetPublicIPs(ctx, networkInterfaces...)
if err != nil {
return err
}
sort.Slice(tasks, func(i, j int) bool {
return strings.Compare(tasks[i].Service, tasks[j].Service) < 0
})
for _, t := range tasks {
ports := []string{}
s, err := project.GetService(t.Service)
if err != nil {
return err
}
for _, p := range s.Ports {
ports = append(ports, fmt.Sprintf("%s:%d->%d/%s", publicIps[t.NetworkInterface], p.Published, p.Target, p.Protocol))
}
fmt.Fprintf(w, "%s\t%s\t%s\n", s.Name, t.State, strings.Join(ports, ", "))
}
return nil
}
type TaskStatus struct {
State string
Service string
NetworkInterface string
PublicIP string
}
type listAPI interface {
ListTasks(ctx context.Context, cluster string, name string) ([]string, error)
DescribeTasks(ctx context.Context, cluster string, arns ...string) ([]TaskStatus, error)
GetPublicIPs(ctx context.Context, interfaces ...string) (map[string]string, error)
}

61
ecs/pkg/amazon/sdk/api.go Normal file
View File

@ -0,0 +1,61 @@
package sdk
import (
"context"
cf "github.com/aws/aws-sdk-go/service/cloudformation"
"github.com/awslabs/goformation/v4/cloudformation"
"github.com/docker/ecs-plugin/pkg/amazon/types"
)
//go:generate mockgen -destination=./api_mock.go -self_package "github.com/docker/ecs-plugin/pkg/amazon" -package=amazon . API
type API interface {
downAPI
upAPI
logsAPI
secretsAPI
listAPI
}
type upAPI interface {
waitAPI
GetDefaultVPC(ctx context.Context) (string, error)
VpcExists(ctx context.Context, vpcID string) (bool, error)
GetSubNets(ctx context.Context, vpcID string) ([]string, error)
ClusterExists(ctx context.Context, name string) (bool, error)
StackExists(ctx context.Context, name string) (bool, error)
CreateStack(ctx context.Context, name string, template *cloudformation.Template, parameters map[string]string) error
LoadBalancerExists(ctx context.Context, name string) (bool, error)
GetLoadBalancerARN(ctx context.Context, name string) (string, error)
}
type downAPI interface {
DeleteStack(ctx context.Context, name string) error
DeleteCluster(ctx context.Context, name string) error
}
type logsAPI interface {
GetLogs(ctx context.Context, name string, consumer types.LogConsumer) error
}
type secretsAPI interface {
CreateSecret(ctx context.Context, secret types.Secret) (string, error)
InspectSecret(ctx context.Context, id string) (types.Secret, error)
ListSecrets(ctx context.Context) ([]types.Secret, error)
DeleteSecret(ctx context.Context, id string, recover bool) error
}
type listAPI interface {
ListTasks(ctx context.Context, cluster string, name string) ([]string, error)
DescribeTasks(ctx context.Context, cluster string, arns ...string) ([]types.TaskStatus, error)
GetPublicIPs(ctx context.Context, interfaces ...string) (map[string]string, error)
}
type waitAPI interface {
GetStackID(ctx context.Context, name string) (string, error)
WaitStackComplete(ctx context.Context, name string, operation int) error
DescribeStackEvents(ctx context.Context, stackID string) ([]*cf.StackEvent, error)
}

View File

@ -2,7 +2,7 @@
// Source: github.com/docker/ecs-plugin/pkg/amazon (interfaces: API) // Source: github.com/docker/ecs-plugin/pkg/amazon (interfaces: API)
// Package amazon is a generated GoMock package. // Package amazon is a generated GoMock package.
package amazon package sdk
import ( import (
context "context" context "context"
@ -10,7 +10,7 @@ import (
cloudformation "github.com/aws/aws-sdk-go/service/cloudformation" cloudformation "github.com/aws/aws-sdk-go/service/cloudformation"
cloudformation0 "github.com/awslabs/goformation/v4/cloudformation" cloudformation0 "github.com/awslabs/goformation/v4/cloudformation"
docker "github.com/docker/ecs-plugin/pkg/docker" btypes "github.com/docker/ecs-plugin/pkg/amazon/types"
gomock "github.com/golang/mock/gomock" gomock "github.com/golang/mock/gomock"
) )
@ -53,7 +53,7 @@ func (mr *MockAPIMockRecorder) ClusterExists(arg0, arg1 interface{}) *gomock.Cal
} }
// CreateSecret mocks base method // CreateSecret mocks base method
func (m *MockAPI) CreateSecret(arg0 context.Context, arg1 docker.Secret) (string, error) { func (m *MockAPI) CreateSecret(arg0 context.Context, arg1 btypes.Secret) (string, error) {
m.ctrl.T.Helper() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CreateSecret", arg0, arg1) ret := m.ctrl.Call(m, "CreateSecret", arg0, arg1)
ret0, _ := ret[0].(string) ret0, _ := ret[0].(string)
@ -139,14 +139,14 @@ func (mr *MockAPIMockRecorder) DescribeStackEvents(arg0, arg1 interface{}) *gomo
} }
// DescribeTasks mocks base method // DescribeTasks mocks base method
func (m *MockAPI) DescribeTasks(arg0 context.Context, arg1 string, arg2 ...string) ([]TaskStatus, error) { func (m *MockAPI) DescribeTasks(arg0 context.Context, arg1 string, arg2 ...string) ([]btypes.TaskStatus, error) {
m.ctrl.T.Helper() m.ctrl.T.Helper()
varargs := []interface{}{arg0, arg1} varargs := []interface{}{arg0, arg1}
for _, a := range arg2 { for _, a := range arg2 {
varargs = append(varargs, a) varargs = append(varargs, a)
} }
ret := m.ctrl.Call(m, "DescribeTasks", varargs...) ret := m.ctrl.Call(m, "DescribeTasks", varargs...)
ret0, _ := ret[0].([]TaskStatus) ret0, _ := ret[0].([]btypes.TaskStatus)
ret1, _ := ret[1].(error) ret1, _ := ret[1].(error)
return ret0, ret1 return ret0, ret1
} }
@ -174,7 +174,7 @@ func (mr *MockAPIMockRecorder) GetDefaultVPC(arg0 interface{}) *gomock.Call {
} }
// GetLogs mocks base method // GetLogs mocks base method
func (m *MockAPI) GetLogs(arg0 context.Context, arg1 string, arg2 LogConsumer) error { func (m *MockAPI) GetLogs(arg0 context.Context, arg1 string, arg2 btypes.LogConsumer) error {
m.ctrl.T.Helper() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetLogs", arg0, arg1, arg2) ret := m.ctrl.Call(m, "GetLogs", arg0, arg1, arg2)
ret0, _ := ret[0].(error) ret0, _ := ret[0].(error)
@ -238,10 +238,10 @@ func (mr *MockAPIMockRecorder) GetSubNets(arg0, arg1 interface{}) *gomock.Call {
} }
// InspectSecret mocks base method // InspectSecret mocks base method
func (m *MockAPI) InspectSecret(arg0 context.Context, arg1 string) (docker.Secret, error) { func (m *MockAPI) InspectSecret(arg0 context.Context, arg1 string) (btypes.Secret, error) {
m.ctrl.T.Helper() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "InspectSecret", arg0, arg1) ret := m.ctrl.Call(m, "InspectSecret", arg0, arg1)
ret0, _ := ret[0].(docker.Secret) ret0, _ := ret[0].(btypes.Secret)
ret1, _ := ret[1].(error) ret1, _ := ret[1].(error)
return ret0, ret1 return ret0, ret1
} }
@ -253,10 +253,10 @@ func (mr *MockAPIMockRecorder) InspectSecret(arg0, arg1 interface{}) *gomock.Cal
} }
// ListSecrets mocks base method // ListSecrets mocks base method
func (m *MockAPI) ListSecrets(arg0 context.Context) ([]docker.Secret, error) { func (m *MockAPI) ListSecrets(arg0 context.Context) ([]btypes.Secret, error) {
m.ctrl.T.Helper() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ListSecrets", arg0) ret := m.ctrl.Call(m, "ListSecrets", arg0)
ret0, _ := ret[0].([]docker.Secret) ret0, _ := ret[0].([]btypes.Secret)
ret1, _ := ret[1].(error) ret1, _ := ret[1].(error)
return ret0, ret1 return ret0, ret1
} }

View File

@ -1,4 +1,4 @@
package amazon package sdk
import ( import (
"fmt" "fmt"
@ -13,6 +13,7 @@ import (
"github.com/awslabs/goformation/v4/cloudformation/tags" "github.com/awslabs/goformation/v4/cloudformation/tags"
"github.com/compose-spec/compose-go/types" "github.com/compose-spec/compose-go/types"
"github.com/docker/cli/opts" "github.com/docker/cli/opts"
t "github.com/docker/ecs-plugin/pkg/amazon/types"
"github.com/docker/ecs-plugin/pkg/compose" "github.com/docker/ecs-plugin/pkg/compose"
) )
@ -318,7 +319,7 @@ func getImage(image string) string {
func getRepoCredentials(service types.ServiceConfig) *ecs.TaskDefinition_RepositoryCredentials { func getRepoCredentials(service types.ServiceConfig) *ecs.TaskDefinition_RepositoryCredentials {
// extract registry and namespace string from image name // extract registry and namespace string from image name
for key, value := range service.Extras { for key, value := range service.Extras {
if key == ExtensionPullCredentials { if key == t.ExtensionPullCredentials {
return &ecs.TaskDefinition_RepositoryCredentials{CredentialsParameter: value.(string)} return &ecs.TaskDefinition_RepositoryCredentials{CredentialsParameter: value.(string)}
} }
} }

View File

@ -1,4 +1,4 @@
package amazon package sdk
import ( import (
"context" "context"
@ -25,7 +25,8 @@ import (
cf "github.com/awslabs/goformation/v4/cloudformation" cf "github.com/awslabs/goformation/v4/cloudformation"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/docker/ecs-plugin/pkg/docker" "github.com/docker/ecs-plugin/pkg/amazon/types"
t "github.com/docker/ecs-plugin/pkg/amazon/types"
) )
type sdk struct { type sdk struct {
@ -188,9 +189,9 @@ func (s sdk) WaitStackComplete(ctx context.Context, name string, operation int)
StackName: aws.String(name), StackName: aws.String(name),
} }
switch operation { switch operation {
case StackCreate: case t.StackCreate:
return s.CF.WaitUntilStackCreateCompleteWithContext(ctx, input) return s.CF.WaitUntilStackCreateCompleteWithContext(ctx, input)
case StackDelete: case t.StackDelete:
return s.CF.WaitUntilStackDeleteCompleteWithContext(ctx, input) return s.CF.WaitUntilStackDeleteCompleteWithContext(ctx, input)
default: default:
return fmt.Errorf("internal error: unexpected stack operation %d", operation) return fmt.Errorf("internal error: unexpected stack operation %d", operation)
@ -235,7 +236,7 @@ func (s sdk) DeleteStack(ctx context.Context, name string) error {
return err return err
} }
func (s sdk) CreateSecret(ctx context.Context, secret docker.Secret) (string, error) { func (s sdk) CreateSecret(ctx context.Context, secret t.Secret) (string, error) {
logrus.Debug("Create secret " + secret.Name) logrus.Debug("Create secret " + secret.Name)
secretStr, err := secret.GetCredString() secretStr, err := secret.GetCredString()
if err != nil { if err != nil {
@ -253,17 +254,17 @@ func (s sdk) CreateSecret(ctx context.Context, secret docker.Secret) (string, er
return *response.ARN, nil return *response.ARN, nil
} }
func (s sdk) InspectSecret(ctx context.Context, id string) (docker.Secret, error) { func (s sdk) InspectSecret(ctx context.Context, id string) (t.Secret, error) {
logrus.Debug("Inspect secret " + id) logrus.Debug("Inspect secret " + id)
response, err := s.SM.DescribeSecret(&secretsmanager.DescribeSecretInput{SecretId: &id}) response, err := s.SM.DescribeSecret(&secretsmanager.DescribeSecretInput{SecretId: &id})
if err != nil { if err != nil {
return docker.Secret{}, err return t.Secret{}, err
} }
labels := map[string]string{} labels := map[string]string{}
for _, tag := range response.Tags { for _, tag := range response.Tags {
labels[*tag.Key] = *tag.Value labels[*tag.Key] = *tag.Value
} }
secret := docker.Secret{ secret := t.Secret{
ID: *response.ARN, ID: *response.ARN,
Name: *response.Name, Name: *response.Name,
Labels: labels, Labels: labels,
@ -274,14 +275,14 @@ func (s sdk) InspectSecret(ctx context.Context, id string) (docker.Secret, error
return secret, nil return secret, nil
} }
func (s sdk) ListSecrets(ctx context.Context) ([]docker.Secret, error) { func (s sdk) ListSecrets(ctx context.Context) ([]t.Secret, error) {
logrus.Debug("List secrets ...") logrus.Debug("List secrets ...")
response, err := s.SM.ListSecrets(&secretsmanager.ListSecretsInput{}) response, err := s.SM.ListSecrets(&secretsmanager.ListSecretsInput{})
if err != nil { if err != nil {
return []docker.Secret{}, err return []t.Secret{}, err
} }
var secrets []docker.Secret var secrets []t.Secret
for _, sec := range response.SecretList { for _, sec := range response.SecretList {
@ -293,7 +294,7 @@ func (s sdk) ListSecrets(ctx context.Context) ([]docker.Secret, error) {
if sec.Description != nil { if sec.Description != nil {
description = *sec.Description description = *sec.Description
} }
secrets = append(secrets, docker.Secret{ secrets = append(secrets, t.Secret{
ID: *sec.ARN, ID: *sec.ARN,
Name: *sec.Name, Name: *sec.Name,
Labels: labels, Labels: labels,
@ -310,7 +311,7 @@ func (s sdk) DeleteSecret(ctx context.Context, id string, recover bool) error {
return err return err
} }
func (s sdk) GetLogs(ctx context.Context, name string, consumer LogConsumer) error { func (s sdk) GetLogs(ctx context.Context, name string, consumer types.LogConsumer) error {
logGroup := fmt.Sprintf("/docker-compose/%s", name) logGroup := fmt.Sprintf("/docker-compose/%s", name)
var startTime int64 var startTime int64
for { for {
@ -356,7 +357,7 @@ func (s sdk) ListTasks(ctx context.Context, cluster string, service string) ([]s
return arns, nil return arns, nil
} }
func (s sdk) DescribeTasks(ctx context.Context, cluster string, arns ...string) ([]TaskStatus, error) { func (s sdk) DescribeTasks(ctx context.Context, cluster string, arns ...string) ([]t.TaskStatus, error) {
tasks, err := s.ECS.DescribeTasksWithContext(ctx, &ecs.DescribeTasksInput{ tasks, err := s.ECS.DescribeTasksWithContext(ctx, &ecs.DescribeTasksInput{
Cluster: aws.String(cluster), Cluster: aws.String(cluster),
Tasks: aws.StringSlice(arns), Tasks: aws.StringSlice(arns),
@ -364,7 +365,7 @@ func (s sdk) DescribeTasks(ctx context.Context, cluster string, arns ...string)
if err != nil { if err != nil {
return nil, err return nil, err
} }
result := []TaskStatus{} result := []t.TaskStatus{}
for _, task := range tasks.Tasks { for _, task := range tasks.Tasks {
var networkInterface string var networkInterface string
for _, attachement := range task.Attachments { for _, attachement := range task.Attachments {
@ -376,7 +377,7 @@ func (s sdk) DescribeTasks(ctx context.Context, cluster string, arns ...string)
} }
} }
} }
result = append(result, TaskStatus{ result = append(result, t.TaskStatus{
State: *task.LastStatus, State: *task.LastStatus,
Service: strings.Replace(*task.Group, "service:", "", 1), Service: strings.Replace(*task.Group, "service:", "", 1),
NetworkInterface: networkInterface, NetworkInterface: networkInterface,

View File

@ -1,30 +0,0 @@
package amazon
import (
"context"
"github.com/docker/ecs-plugin/pkg/docker"
)
type secretsAPI interface {
CreateSecret(ctx context.Context, secret docker.Secret) (string, error)
InspectSecret(ctx context.Context, id string) (docker.Secret, error)
ListSecrets(ctx context.Context) ([]docker.Secret, error)
DeleteSecret(ctx context.Context, id string, recover bool) error
}
func (c client) CreateSecret(ctx context.Context, secret docker.Secret) (string, error) {
return c.api.CreateSecret(ctx, secret)
}
func (c client) InspectSecret(ctx context.Context, id string) (docker.Secret, error) {
return c.api.InspectSecret(ctx, id)
}
func (c client) ListSecrets(ctx context.Context) ([]docker.Secret, error) {
return c.api.ListSecrets(ctx)
}
func (c client) DeleteSecret(ctx context.Context, id string, recover bool) error {
return c.api.DeleteSecret(ctx, id, recover)
}

View File

@ -1,9 +1,25 @@
package docker package types
import ( import "encoding/json"
"encoding/json"
type TaskStatus struct {
Name string
State string
Service string
NetworkInterface string
PublicIP string
Ports []string
}
const (
StackCreate = iota
StackDelete
) )
type LogConsumer interface {
Log(service, container, message string)
}
type Secret struct { type Secret struct {
ID string `json:"ID"` ID string `json:"ID"`
Name string `json:"Name"` Name string `json:"Name"`

View File

@ -1,4 +1,4 @@
package amazon package types
const ( const (
ExtensionSecurityGroup = "x-aws-securitygroup" ExtensionSecurityGroup = "x-aws-securitygroup"

View File

@ -1,114 +0,0 @@
package amazon
import (
"context"
"fmt"
"github.com/awslabs/goformation/v4/cloudformation"
"github.com/docker/ecs-plugin/pkg/compose"
)
func (c *client) ComposeUp(ctx context.Context, project *compose.Project) error {
if c.Cluster != "" {
ok, err := c.api.ClusterExists(ctx, c.Cluster)
if err != nil {
return err
}
if !ok {
return fmt.Errorf("configured cluster %q does not exist", c.Cluster)
}
}
update, err := c.api.StackExists(ctx, project.Name)
if err != nil {
return err
}
if update {
return fmt.Errorf("we do not (yet) support updating an existing CloudFormation stack")
}
template, err := c.Convert(project)
if err != nil {
return err
}
vpc, err := c.GetVPC(ctx, project)
if err != nil {
return err
}
subNets, err := c.api.GetSubNets(ctx, vpc)
if err != nil {
return err
}
lb, err := c.GetLoadBalancer(ctx, project)
if err != nil {
return err
}
parameters := map[string]string{
ParameterClusterName: c.Cluster,
ParameterVPCId: vpc,
ParameterSubnet1Id: subNets[0],
ParameterSubnet2Id: subNets[1],
ParameterLoadBalancerARN: lb,
}
err = c.api.CreateStack(ctx, project.Name, template, parameters)
if err != nil {
return err
}
fmt.Println()
return c.WaitStackCompletion(ctx, project.Name, StackCreate)
}
func (c client) GetVPC(ctx context.Context, project *compose.Project) (string, error) {
//check compose file for custom VPC selected
if vpc, ok := project.Extras[ExtensionVPC]; ok {
vpcID := vpc.(string)
ok, err := c.api.VpcExists(ctx, vpcID)
if err != nil {
return "", err
}
if !ok {
return "", fmt.Errorf("VPC does not exist: %s", vpc)
}
}
defaultVPC, err := c.api.GetDefaultVPC(ctx)
if err != nil {
return "", err
}
return defaultVPC, nil
}
func (c client) GetLoadBalancer(ctx context.Context, project *compose.Project) (string, error) {
//check compose file for custom VPC selected
if lb, ok := project.Extras[ExtensionLB]; ok {
lbName := lb.(string)
ok, err := c.api.LoadBalancerExists(ctx, lbName)
if err != nil {
return "", err
}
if !ok {
return "", fmt.Errorf("Load Balancer does not exist: %s", lb)
}
return c.api.GetLoadBalancerARN(ctx, lbName)
}
return "", nil
}
type upAPI interface {
waitAPI
GetDefaultVPC(ctx context.Context) (string, error)
VpcExists(ctx context.Context, vpcID string) (bool, error)
GetSubNets(ctx context.Context, vpcID string) ([]string, error)
ClusterExists(ctx context.Context, name string) (bool, error)
StackExists(ctx context.Context, name string) (bool, error)
CreateStack(ctx context.Context, name string, template *cloudformation.Template, parameters map[string]string) error
LoadBalancerExists(ctx context.Context, name string) (bool, error)
GetLoadBalancerARN(ctx context.Context, name string) (string, error)
}

View File

@ -4,7 +4,7 @@ import (
"context" "context"
"github.com/awslabs/goformation/v4/cloudformation" "github.com/awslabs/goformation/v4/cloudformation"
"github.com/docker/ecs-plugin/pkg/docker" "github.com/docker/ecs-plugin/pkg/amazon/types"
) )
type API interface { type API interface {
@ -13,9 +13,9 @@ type API interface {
ComposeDown(ctx context.Context, projectName string, deleteCluster bool) error ComposeDown(ctx context.Context, projectName string, deleteCluster bool) error
ComposeLogs(ctx context.Context, projectName string) error ComposeLogs(ctx context.Context, projectName string) error
CreateSecret(ctx context.Context, secret docker.Secret) (string, error) CreateSecret(ctx context.Context, secret types.Secret) (string, error)
InspectSecret(ctx context.Context, id string) (docker.Secret, error) InspectSecret(ctx context.Context, id string) (types.Secret, error)
ListSecrets(ctx context.Context) ([]docker.Secret, error) ListSecrets(ctx context.Context) ([]types.Secret, error)
DeleteSecret(ctx context.Context, id string, recover bool) error DeleteSecret(ctx context.Context, id string, recover bool) error
ComposePs(background context.Context, project *Project) error ComposePs(background context.Context, project *Project) ([]types.TaskStatus, error)
} }