From 4760ae334ddb1c83e4ac53cfbbb063a6bab4a49d Mon Sep 17 00:00:00 2001 From: Nicolas De Loof Date: Fri, 16 Oct 2020 10:48:19 +0200 Subject: [PATCH] Store external cluster as metadata Signed-off-by: Nicolas De Loof --- ecs/awsResources.go | 8 +++++--- ecs/cloudformation.go | 4 ++-- ecs/cloudformation_test.go | 12 ++++++++++++ ecs/sdk.go | 29 ++++++++++++++++++++++++++++- 4 files changed, 47 insertions(+), 6 deletions(-) diff --git a/ecs/awsResources.go b/ecs/awsResources.go index 566754eb6..980329d83 100644 --- a/ecs/awsResources.go +++ b/ecs/awsResources.go @@ -64,10 +64,10 @@ func (r *awsResources) allSecurityGroups() []string { } // parse look into compose project for configured resource to use, and check they are valid -func (b *ecsAPIService) parse(ctx context.Context, project *types.Project) (awsResources, error) { +func (b *ecsAPIService) parse(ctx context.Context, project *types.Project, template *cloudformation.Template) (awsResources, error) { r := awsResources{} var err error - r.cluster, err = b.parseClusterExtension(ctx, project) + r.cluster, err = b.parseClusterExtension(ctx, project, template) if err != nil { return r, err } @@ -90,7 +90,7 @@ func (b *ecsAPIService) parse(ctx context.Context, project *types.Project) (awsR return r, nil } -func (b *ecsAPIService) parseClusterExtension(ctx context.Context, project *types.Project) (string, error) { +func (b *ecsAPIService) parseClusterExtension(ctx context.Context, project *types.Project, template *cloudformation.Template) (string, error) { if x, ok := project.Extensions[extensionCluster]; ok { cluster := x.(string) ok, err := b.aws.ClusterExists(ctx, cluster) @@ -100,6 +100,8 @@ func (b *ecsAPIService) parseClusterExtension(ctx context.Context, project *type if !ok { return "", errors.Wrapf(errdefs.ErrNotFound, "cluster %q does not exist", cluster) } + + template.Metadata["Cluster"] = cluster return cluster, nil } return "", nil diff --git a/ecs/cloudformation.go b/ecs/cloudformation.go index 5c116fb41..e284dd514 100644 --- a/ecs/cloudformation.go +++ b/ecs/cloudformation.go @@ -52,12 +52,12 @@ func (b *ecsAPIService) convert(ctx context.Context, project *types.Project) (*c return nil, err } - resources, err := b.parse(ctx, project) + template := cloudformation.NewTemplate() + resources, err := b.parse(ctx, project, template) if err != nil { return nil, err } - template := cloudformation.NewTemplate() err = b.ensureResources(&resources, project, template) if err != nil { return nil, err diff --git a/ecs/cloudformation_test.go b/ecs/cloudformation_test.go index 06176822c..06881df02 100644 --- a/ecs/cloudformation_test.go +++ b/ecs/cloudformation_test.go @@ -492,6 +492,18 @@ services: } } +func TestTemplateMetadata(t *testing.T) { + template := convertYaml(t, ` +x-aws-cluster: "arn:aws:ecs:region:account:cluster/name" +services: + test: + image: nginx +`, useDefaultVPC, func(m *MockAPIMockRecorder) { + m.ClusterExists(gomock.Any(), "arn:aws:ecs:region:account:cluster/name").Return(true, nil) + }) + assert.Equal(t, template.Metadata["Cluster"], "arn:aws:ecs:region:account:cluster/name") +} + func convertYaml(t *testing.T, yaml string, fn ...func(m *MockAPIMockRecorder)) *cloudformation.Template { project := loadConfig(t, yaml) ctrl := gomock.NewController(t) diff --git a/ecs/sdk.go b/ecs/sdk.go index 81cd71897..77ee60d47 100644 --- a/ecs/sdk.go +++ b/ecs/sdk.go @@ -25,6 +25,7 @@ import ( "github.com/docker/compose-cli/api/compose" "github.com/docker/compose-cli/api/secrets" + "github.com/docker/compose-cli/errdefs" "github.com/docker/compose-cli/internal" "github.com/aws/aws-sdk-go/aws" @@ -51,6 +52,7 @@ import ( "github.com/aws/aws-sdk-go/service/ssm" "github.com/aws/aws-sdk-go/service/ssm/ssmiface" "github.com/hashicorp/go-multierror" + "github.com/pkg/errors" "github.com/sirupsen/logrus" ) @@ -332,6 +334,7 @@ func (s sdk) ListStacks(ctx context.Context, name string) ([]compose.Stack, erro } func (s sdk) GetStackClusterID(ctx context.Context, stack string) (string, error) { + // Note: could use DescribeStackResource but we only can detect `does not exist` case by matching string error message resources, err := s.CF.ListStackResourcesWithContext(ctx, &cloudformation.ListStackResourcesInput{ StackName: aws.String(stack), }) @@ -343,7 +346,28 @@ func (s sdk) GetStackClusterID(ctx context.Context, stack string) (string, error return aws.StringValue(r.PhysicalResourceId), nil } } - return "", nil + // stack is using user-provided cluster + res, err := s.CF.GetTemplateSummaryWithContext(ctx, &cloudformation.GetTemplateSummaryInput{ + StackName: aws.String(stack), + }) + if err != nil { + return "", err + } + c := aws.StringValue(res.Metadata) + var m templateMetadata + err = json.Unmarshal([]byte(c), &m) + if err != nil { + return "", err + } + if m.Cluster == "" { + return "", errors.Wrap(errdefs.ErrNotFound, "CloudFormation is missing cluster metadata") + } + + return m.Cluster, nil +} + +type templateMetadata struct { + Cluster string `json:",omitempty"` } func (s sdk) GetServiceTaskDefinition(ctx context.Context, cluster string, serviceArns []string) (map[string]string, error) { @@ -645,6 +669,9 @@ func (s sdk) DescribeService(ctx context.Context, cluster string, arn string) (c return compose.ServiceStatus{}, err } + for _, f := range services.Failures { + return compose.ServiceStatus{}, errors.Wrapf(errdefs.ErrNotFound, "can't get service status %s: %s", aws.StringValue(f.Detail), aws.StringValue(f.Reason)) + } service := services.Services[0] var name string for _, t := range service.Tags {