mirror of https://github.com/docker/compose.git
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:
parent
d36b9b104e
commit
bb98dae082
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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{}
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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{}
|
|
|
@ -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 {
|
|
@ -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",
|
||||||
})
|
})
|
|
@ -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
|
||||||
|
}
|
|
@ -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)
|
||||||
|
|
|
@ -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"
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
|
||||||
}
|
}
|
|
@ -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)
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
|
||||||
)
|
|
|
@ -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")
|
|
||||||
}
|
|
|
@ -1,4 +1,4 @@
|
||||||
package amazon
|
package compatibility
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/compose-spec/compose-go/types"
|
"github.com/compose-spec/compose-go/types"
|
|
@ -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")
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package amazon
|
package compatibility
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
|
@ -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)
|
||||||
|
}
|
|
@ -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
|
||||||
}
|
}
|
|
@ -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)}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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,
|
|
@ -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)
|
|
||||||
}
|
|
|
@ -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"`
|
|
@ -1,4 +1,4 @@
|
||||||
package amazon
|
package types
|
||||||
|
|
||||||
const (
|
const (
|
||||||
ExtensionSecurityGroup = "x-aws-securitygroup"
|
ExtensionSecurityGroup = "x-aws-securitygroup"
|
|
@ -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)
|
|
||||||
}
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue