From 3f184f7552b52ddb377f2e51ccd86b64bd773d69 Mon Sep 17 00:00:00 2001 From: Nicolas De Loof Date: Tue, 3 Nov 2020 09:51:33 +0100 Subject: [PATCH 1/3] store cloudformation template on s3 to workaround API limit Signed-off-by: Nicolas De Loof --- ecs/sdk.go | 124 ++++++++++++++++++++++++++++++++++++++++++----------- go.mod | 1 + go.sum | 1 + 3 files changed, 100 insertions(+), 26 deletions(-) diff --git a/ecs/sdk.go b/ecs/sdk.go index 6709a621..cfb578db 100644 --- a/ecs/sdk.go +++ b/ecs/sdk.go @@ -17,9 +17,15 @@ package ecs import ( + "bytes" "context" "encoding/json" "fmt" + "github.com/aws/aws-sdk-go/aws/awserr" + "github.com/aws/aws-sdk-go/service/s3" + "github.com/aws/aws-sdk-go/service/s3/s3iface" + "github.com/aws/aws-sdk-go/service/s3/s3manager" + "github.com/hashicorp/go-uuid" "strings" "time" @@ -68,6 +74,8 @@ type sdk struct { SM secretsmanageriface.SecretsManagerAPI SSM ssmiface.SSMAPI AG autoscalingiface.AutoScalingAPI + S3 s3iface.S3API + uploader *s3manager.Uploader } // sdk implement API @@ -88,6 +96,8 @@ func newSDK(sess *session.Session) sdk { SM: secretsmanager.New(sess), SSM: ssm.New(sess), AG: autoscaling.New(sess), + S3: s3.New(sess), + uploader: s3manager.NewUploader(sess), } } @@ -226,39 +236,101 @@ func (s sdk) StackExists(ctx context.Context, name string) (bool, error) { return len(stacks.Stacks) > 0, nil } -func (s sdk) CreateStack(ctx context.Context, name string, template []byte) error { - logrus.Debug("Create CloudFormation stack") +type uploadedTemplateFunc func(ctx context.Context, name string, url string) (string, error) - _, err := s.CF.CreateStackWithContext(ctx, &cloudformation.CreateStackInput{ - OnFailure: aws.String("DELETE"), - StackName: aws.String(name), - TemplateBody: aws.String(string(template)), - TimeoutInMinutes: nil, - Capabilities: []*string{ - aws.String(cloudformation.CapabilityCapabilityIam), - }, - Tags: []*cloudformation.Tag{ - { - Key: aws.String(compose.ProjectTag), - Value: aws.String(name), +func (s sdk) withTemplate(ctx context.Context, name string, template []byte, fn uploadedTemplateFunc) (string, error) { + logrus.Debug("Create s3 bucket to store cloudformation template") + _, err := s.S3.CreateBucket(&s3.CreateBucketInput{ + Bucket: aws.String("com.docker.compose"), + }) + if err != nil { + ae, ok := err.(awserr.Error) + if !ok { + return "", err + } + if ae.Code() != s3.ErrCodeBucketAlreadyOwnedByYou { + return "", err + } + } + + key, err := uuid.GenerateUUID() + if err != nil { + return "", err + } + + upload, err := s.uploader.UploadWithContext(ctx, &s3manager.UploadInput{ + Key: aws.String(key), + Body: bytes.NewReader(template), + Bucket: aws.String("com.docker.compose"), + ContentType: aws.String("application/json"), + Tagging: aws.String(name), + }) + + if err != nil { + return "", err + } + + defer s.S3.DeleteObjects(&s3.DeleteObjectsInput{ + Bucket: aws.String("com.docker.compose"), + Delete: &s3.Delete{ + Objects: []*s3.ObjectIdentifier{ + { + Key: aws.String(key), + VersionId: upload.VersionID, + }, }, }, }) + + return fn(ctx, name, upload.Location) +} + +func (s sdk) CreateStack(ctx context.Context, name string, template []byte) error { + logrus.Debug("Create CloudFormation stack") + + stackId, err := s.withTemplate(ctx, name, template, func(ctx context.Context, name string, url string) (string, error) { + stack, err := s.CF.CreateStackWithContext(ctx, &cloudformation.CreateStackInput{ + OnFailure: aws.String("DELETE"), + StackName: aws.String(name), + TemplateURL: aws.String(url), + TimeoutInMinutes: nil, + Capabilities: []*string{ + aws.String(cloudformation.CapabilityCapabilityIam), + }, + Tags: []*cloudformation.Tag{ + { + Key: aws.String(compose.ProjectTag), + Value: aws.String(name), + }, + }, + }) + if err != nil { + return "", err + } + return aws.StringValue(stack.StackId), nil + }) + logrus.Debugf("Stack %s created", stackId) return err } func (s sdk) CreateChangeSet(ctx context.Context, name string, template []byte) (string, error) { logrus.Debug("Create CloudFormation Changeset") - update := fmt.Sprintf("Update%s", time.Now().Format("2006-01-02-15-04-05")) - changeset, err := s.CF.CreateChangeSetWithContext(ctx, &cloudformation.CreateChangeSetInput{ - ChangeSetName: aws.String(update), - ChangeSetType: aws.String(cloudformation.ChangeSetTypeUpdate), - StackName: aws.String(name), - TemplateBody: aws.String(string(template)), - Capabilities: []*string{ - aws.String(cloudformation.CapabilityCapabilityIam), - }, + changeset, err := s.withTemplate(ctx, name, template, func(ctx context.Context, name string, url string) (string, error) { + update := fmt.Sprintf("Update%s", time.Now().Format("2006-01-02-15-04-05")) + changeset, err := s.CF.CreateChangeSetWithContext(ctx, &cloudformation.CreateChangeSetInput{ + ChangeSetName: aws.String(update), + ChangeSetType: aws.String(cloudformation.ChangeSetTypeUpdate), + StackName: aws.String(name), + TemplateBody: aws.String(string(template)), + Capabilities: []*string{ + aws.String(cloudformation.CapabilityCapabilityIam), + }, + }) + if err != nil { + return "", err + } + return aws.StringValue(changeset.Id), err }) if err != nil { return "", err @@ -267,7 +339,7 @@ func (s sdk) CreateChangeSet(ctx context.Context, name string, template []byte) // we have to WaitUntilChangeSetCreateComplete even this in fail with error `ResourceNotReady` // so that we can invoke DescribeChangeSet to check status, and then we can know about the actual creation failure cause. s.CF.WaitUntilChangeSetCreateCompleteWithContext(ctx, &cloudformation.DescribeChangeSetInput{ // nolint:errcheck - ChangeSetName: changeset.Id, + ChangeSetName: aws.String(changeset), }) desc, err := s.CF.DescribeChangeSetWithContext(ctx, &cloudformation.DescribeChangeSetInput{ @@ -275,10 +347,10 @@ func (s sdk) CreateChangeSet(ctx context.Context, name string, template []byte) StackName: aws.String(name), }) if aws.StringValue(desc.Status) == "FAILED" { - return *changeset.Id, fmt.Errorf(aws.StringValue(desc.StatusReason)) + return changeset, fmt.Errorf(aws.StringValue(desc.StatusReason)) } - return *changeset.Id, err + return changeset, err } func (s sdk) UpdateStack(ctx context.Context, changeset string) error { diff --git a/go.mod b/go.mod index ba7c297c..1e05d104 100644 --- a/go.mod +++ b/go.mod @@ -40,6 +40,7 @@ require ( github.com/google/uuid v1.1.2 github.com/gorilla/mux v1.7.4 // indirect github.com/hashicorp/go-multierror v1.1.0 + github.com/hashicorp/go-uuid v1.0.1 github.com/iancoleman/strcase v0.1.2 github.com/joho/godotenv v1.3.0 github.com/labstack/echo v3.3.10+incompatible diff --git a/go.sum b/go.sum index 14e8633a..fb7ab35a 100644 --- a/go.sum +++ b/go.sum @@ -298,6 +298,7 @@ github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1 h1:fv1ep09latC32wFoVwnqcnKJGnMSdBanPczbHAYm1BE= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= From 10a384d35b0c792f9f7c0cdbc32fc5fb7bdba4fa Mon Sep 17 00:00:00 2001 From: Nicolas De Loof Date: Tue, 3 Nov 2020 15:34:46 +0100 Subject: [PATCH 2/3] Pass region to create s3 bucket into Signed-off-by: Nicolas De Loof --- ecs/aws.go | 4 +-- ecs/aws_mock.go | 16 +++++----- ecs/sdk.go | 83 ++++++++++++++++++++++++++----------------------- ecs/up.go | 4 +-- 4 files changed, 56 insertions(+), 51 deletions(-) diff --git a/ecs/aws.go b/ecs/aws.go index b030c7aa..782c25c8 100644 --- a/ecs/aws.go +++ b/ecs/aws.go @@ -42,8 +42,8 @@ type API interface { GetSubNets(ctx context.Context, vpcID string) ([]awsResource, error) GetRoleArn(ctx context.Context, name string) (string, error) StackExists(ctx context.Context, name string) (bool, error) - CreateStack(ctx context.Context, name string, template []byte) error - CreateChangeSet(ctx context.Context, name string, template []byte) (string, error) + CreateStack(ctx context.Context, name string, region string, template []byte) error + CreateChangeSet(ctx context.Context, name string, region string, template []byte) (string, error) UpdateStack(ctx context.Context, changeset string) error WaitStackComplete(ctx context.Context, name string, operation int) error GetStackID(ctx context.Context, name string) (string, error) diff --git a/ecs/aws_mock.go b/ecs/aws_mock.go index bfdaed14..428bc8c8 100644 --- a/ecs/aws_mock.go +++ b/ecs/aws_mock.go @@ -66,18 +66,18 @@ func (mr *MockAPIMockRecorder) CheckVPC(arg0, arg1 interface{}) *gomock.Call { } // CreateChangeSet mocks base method -func (m *MockAPI) CreateChangeSet(arg0 context.Context, arg1 string, arg2 []byte) (string, error) { +func (m *MockAPI) CreateChangeSet(arg0 context.Context, arg1, arg2 string, arg3 []byte) (string, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "CreateChangeSet", arg0, arg1, arg2) + ret := m.ctrl.Call(m, "CreateChangeSet", arg0, arg1, arg2, arg3) ret0, _ := ret[0].(string) ret1, _ := ret[1].(error) return ret0, ret1 } // CreateChangeSet indicates an expected call of CreateChangeSet -func (mr *MockAPIMockRecorder) CreateChangeSet(arg0, arg1, arg2 interface{}) *gomock.Call { +func (mr *MockAPIMockRecorder) CreateChangeSet(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateChangeSet", reflect.TypeOf((*MockAPI)(nil).CreateChangeSet), arg0, arg1, arg2) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateChangeSet", reflect.TypeOf((*MockAPI)(nil).CreateChangeSet), arg0, arg1, arg2, arg3) } // CreateCluster mocks base method @@ -126,17 +126,17 @@ func (mr *MockAPIMockRecorder) CreateSecret(arg0, arg1 interface{}) *gomock.Call } // CreateStack mocks base method -func (m *MockAPI) CreateStack(arg0 context.Context, arg1 string, arg2 []byte) error { +func (m *MockAPI) CreateStack(arg0 context.Context, arg1, arg2 string, arg3 []byte) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "CreateStack", arg0, arg1, arg2) + ret := m.ctrl.Call(m, "CreateStack", arg0, arg1, arg2, arg3) ret0, _ := ret[0].(error) return ret0 } // CreateStack indicates an expected call of CreateStack -func (mr *MockAPIMockRecorder) CreateStack(arg0, arg1, arg2 interface{}) *gomock.Call { +func (mr *MockAPIMockRecorder) CreateStack(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateStack", reflect.TypeOf((*MockAPI)(nil).CreateStack), arg0, arg1, arg2) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateStack", reflect.TypeOf((*MockAPI)(nil).CreateStack), arg0, arg1, arg2, arg3) } // DeleteAutoscalingGroup mocks base method diff --git a/ecs/sdk.go b/ecs/sdk.go index cfb578db..383f8fee 100644 --- a/ecs/sdk.go +++ b/ecs/sdk.go @@ -21,11 +21,6 @@ import ( "context" "encoding/json" "fmt" - "github.com/aws/aws-sdk-go/aws/awserr" - "github.com/aws/aws-sdk-go/service/s3" - "github.com/aws/aws-sdk-go/service/s3/s3iface" - "github.com/aws/aws-sdk-go/service/s3/s3manager" - "github.com/hashicorp/go-uuid" "strings" "time" @@ -36,6 +31,7 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/arn" + "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/aws/request" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/autoscaling" @@ -54,27 +50,31 @@ import ( "github.com/aws/aws-sdk-go/service/elbv2/elbv2iface" "github.com/aws/aws-sdk-go/service/iam" "github.com/aws/aws-sdk-go/service/iam/iamiface" + "github.com/aws/aws-sdk-go/service/s3" + "github.com/aws/aws-sdk-go/service/s3/s3iface" + "github.com/aws/aws-sdk-go/service/s3/s3manager" "github.com/aws/aws-sdk-go/service/secretsmanager" "github.com/aws/aws-sdk-go/service/secretsmanager/secretsmanageriface" "github.com/aws/aws-sdk-go/service/ssm" "github.com/aws/aws-sdk-go/service/ssm/ssmiface" "github.com/hashicorp/go-multierror" + "github.com/hashicorp/go-uuid" "github.com/pkg/errors" "github.com/sirupsen/logrus" ) type sdk struct { - ECS ecsiface.ECSAPI - EC2 ec2iface.EC2API - EFS efsiface.EFSAPI - ELB elbv2iface.ELBV2API - CW cloudwatchlogsiface.CloudWatchLogsAPI - IAM iamiface.IAMAPI - CF cloudformationiface.CloudFormationAPI - SM secretsmanageriface.SecretsManagerAPI - SSM ssmiface.SSMAPI - AG autoscalingiface.AutoScalingAPI - S3 s3iface.S3API + ECS ecsiface.ECSAPI + EC2 ec2iface.EC2API + EFS efsiface.EFSAPI + ELB elbv2iface.ELBV2API + CW cloudwatchlogsiface.CloudWatchLogsAPI + IAM iamiface.IAMAPI + CF cloudformationiface.CloudFormationAPI + SM secretsmanageriface.SecretsManagerAPI + SSM ssmiface.SSMAPI + AG autoscalingiface.AutoScalingAPI + S3 s3iface.S3API uploader *s3manager.Uploader } @@ -86,17 +86,17 @@ func newSDK(sess *session.Session) sdk { request.AddToUserAgent(r, internal.ECSUserAgentName+"/"+internal.Version) }) return sdk{ - ECS: ecs.New(sess), - EC2: ec2.New(sess), - EFS: efs.New(sess), - ELB: elbv2.New(sess), - CW: cloudwatchlogs.New(sess), - IAM: iam.New(sess), - CF: cloudformation.New(sess), - SM: secretsmanager.New(sess), - SSM: ssm.New(sess), - AG: autoscaling.New(sess), - S3: s3.New(sess), + ECS: ecs.New(sess), + EC2: ec2.New(sess), + EFS: efs.New(sess), + ELB: elbv2.New(sess), + CW: cloudwatchlogs.New(sess), + IAM: iam.New(sess), + CF: cloudformation.New(sess), + SM: secretsmanager.New(sess), + SSM: ssm.New(sess), + AG: autoscaling.New(sess), + S3: s3.New(sess), uploader: s3manager.NewUploader(sess), } } @@ -197,11 +197,9 @@ func (s sdk) GetSubNets(ctx context.Context, vpcID string) ([]awsResource, error return nil, err } for _, subnet := range subnets.Subnets { - id := aws.StringValue(subnet.SubnetId) - logrus.Debugf("Found SubNet %s", id) ids = append(ids, existingAWSResource{ arn: aws.StringValue(subnet.SubnetArn), - id: id, + id: aws.StringValue(subnet.SubnetId), }) } @@ -238,10 +236,17 @@ func (s sdk) StackExists(ctx context.Context, name string) (bool, error) { type uploadedTemplateFunc func(ctx context.Context, name string, url string) (string, error) -func (s sdk) withTemplate(ctx context.Context, name string, template []byte, fn uploadedTemplateFunc) (string, error) { +func (s sdk) withTemplate(ctx context.Context, name string, template []byte, region string, fn uploadedTemplateFunc) (string, error) { logrus.Debug("Create s3 bucket to store cloudformation template") + var configuration *s3.CreateBucketConfiguration + if region != "us-east-1" { + configuration = &s3.CreateBucketConfiguration{ + LocationConstraint: aws.String(region), + } + } _, err := s.S3.CreateBucket(&s3.CreateBucketInput{ - Bucket: aws.String("com.docker.compose"), + Bucket: aws.String("com.docker.compose." + region), + CreateBucketConfiguration: configuration, }) if err != nil { ae, ok := err.(awserr.Error) @@ -261,7 +266,7 @@ func (s sdk) withTemplate(ctx context.Context, name string, template []byte, fn upload, err := s.uploader.UploadWithContext(ctx, &s3manager.UploadInput{ Key: aws.String(key), Body: bytes.NewReader(template), - Bucket: aws.String("com.docker.compose"), + Bucket: aws.String("com.docker.compose." + region), ContentType: aws.String("application/json"), Tagging: aws.String(name), }) @@ -270,7 +275,7 @@ func (s sdk) withTemplate(ctx context.Context, name string, template []byte, fn return "", err } - defer s.S3.DeleteObjects(&s3.DeleteObjectsInput{ + defer s.S3.DeleteObjects(&s3.DeleteObjectsInput{ //nolint: errcheck Bucket: aws.String("com.docker.compose"), Delete: &s3.Delete{ Objects: []*s3.ObjectIdentifier{ @@ -285,10 +290,10 @@ func (s sdk) withTemplate(ctx context.Context, name string, template []byte, fn return fn(ctx, name, upload.Location) } -func (s sdk) CreateStack(ctx context.Context, name string, template []byte) error { +func (s sdk) CreateStack(ctx context.Context, name string, region string, template []byte) error { logrus.Debug("Create CloudFormation stack") - stackId, err := s.withTemplate(ctx, name, template, func(ctx context.Context, name string, url string) (string, error) { + stackID, err := s.withTemplate(ctx, name, template, region, func(ctx context.Context, name string, url string) (string, error) { stack, err := s.CF.CreateStackWithContext(ctx, &cloudformation.CreateStackInput{ OnFailure: aws.String("DELETE"), StackName: aws.String(name), @@ -309,14 +314,14 @@ func (s sdk) CreateStack(ctx context.Context, name string, template []byte) erro } return aws.StringValue(stack.StackId), nil }) - logrus.Debugf("Stack %s created", stackId) + logrus.Debugf("Stack %s created", stackID) return err } -func (s sdk) CreateChangeSet(ctx context.Context, name string, template []byte) (string, error) { +func (s sdk) CreateChangeSet(ctx context.Context, name string, region string, template []byte) (string, error) { logrus.Debug("Create CloudFormation Changeset") - changeset, err := s.withTemplate(ctx, name, template, func(ctx context.Context, name string, url string) (string, error) { + changeset, err := s.withTemplate(ctx, name, template, region, func(ctx context.Context, name string, url string) (string, error) { update := fmt.Sprintf("Update%s", time.Now().Format("2006-01-02-15-04-05")) changeset, err := s.CF.CreateChangeSetWithContext(ctx, &cloudformation.CreateChangeSetInput{ ChangeSetName: aws.String(update), diff --git a/ecs/up.go b/ecs/up.go index 308683e8..373407e2 100644 --- a/ecs/up.go +++ b/ecs/up.go @@ -44,7 +44,7 @@ func (b *ecsAPIService) Up(ctx context.Context, project *types.Project, detach b operation := stackCreate if update { operation = stackUpdate - changeset, err := b.aws.CreateChangeSet(ctx, project.Name, template) + changeset, err := b.aws.CreateChangeSet(ctx, project.Name, b.Region, template) if err != nil { return err } @@ -53,7 +53,7 @@ func (b *ecsAPIService) Up(ctx context.Context, project *types.Project, detach b return err } } else { - err = b.aws.CreateStack(ctx, project.Name, template) + err = b.aws.CreateStack(ctx, project.Name, b.Region, template) if err != nil { return err } From 71ecbda48f85e8b3b5e7cc0e2023cd317007366a Mon Sep 17 00:00:00 2001 From: Nicolas De Loof Date: Tue, 10 Nov 2020 08:07:23 +0100 Subject: [PATCH 3/3] Publish on s3 if payload is > API limit Signed-off-by: Nicolas De Loof --- ecs/sdk.go | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/ecs/sdk.go b/ecs/sdk.go index 383f8fee..d12e6171 100644 --- a/ecs/sdk.go +++ b/ecs/sdk.go @@ -234,9 +234,15 @@ func (s sdk) StackExists(ctx context.Context, name string) (bool, error) { return len(stacks.Stacks) > 0, nil } -type uploadedTemplateFunc func(ctx context.Context, name string, url string) (string, error) +type uploadedTemplateFunc func(body *string, url *string) (string, error) + +const cloudformationBytesLimit = 51200 func (s sdk) withTemplate(ctx context.Context, name string, template []byte, region string, fn uploadedTemplateFunc) (string, error) { + if len(template) < cloudformationBytesLimit { + return fn(aws.String(string(template)), nil) + } + logrus.Debug("Create s3 bucket to store cloudformation template") var configuration *s3.CreateBucketConfiguration if region != "us-east-1" { @@ -244,8 +250,11 @@ func (s sdk) withTemplate(ctx context.Context, name string, template []byte, reg LocationConstraint: aws.String(region), } } + // CloudFormation will only allow URL from a same-region bucket + // to avoid conflicts we suffix bucket name by region, so we can create comparable buckets in other regions. + bucket := "com.docker.compose." + region _, err := s.S3.CreateBucket(&s3.CreateBucketInput{ - Bucket: aws.String("com.docker.compose." + region), + Bucket: aws.String(bucket), CreateBucketConfiguration: configuration, }) if err != nil { @@ -266,7 +275,7 @@ func (s sdk) withTemplate(ctx context.Context, name string, template []byte, reg upload, err := s.uploader.UploadWithContext(ctx, &s3manager.UploadInput{ Key: aws.String(key), Body: bytes.NewReader(template), - Bucket: aws.String("com.docker.compose." + region), + Bucket: aws.String(bucket), ContentType: aws.String("application/json"), Tagging: aws.String(name), }) @@ -276,7 +285,7 @@ func (s sdk) withTemplate(ctx context.Context, name string, template []byte, reg } defer s.S3.DeleteObjects(&s3.DeleteObjectsInput{ //nolint: errcheck - Bucket: aws.String("com.docker.compose"), + Bucket: aws.String(bucket), Delete: &s3.Delete{ Objects: []*s3.ObjectIdentifier{ { @@ -287,17 +296,18 @@ func (s sdk) withTemplate(ctx context.Context, name string, template []byte, reg }, }) - return fn(ctx, name, upload.Location) + return fn(nil, aws.String(upload.Location)) } func (s sdk) CreateStack(ctx context.Context, name string, region string, template []byte) error { logrus.Debug("Create CloudFormation stack") - stackID, err := s.withTemplate(ctx, name, template, region, func(ctx context.Context, name string, url string) (string, error) { + stackID, err := s.withTemplate(ctx, name, template, region, func(body *string, url *string) (string, error) { stack, err := s.CF.CreateStackWithContext(ctx, &cloudformation.CreateStackInput{ OnFailure: aws.String("DELETE"), StackName: aws.String(name), - TemplateURL: aws.String(url), + TemplateBody: body, + TemplateURL: url, TimeoutInMinutes: nil, Capabilities: []*string{ aws.String(cloudformation.CapabilityCapabilityIam), @@ -320,14 +330,15 @@ func (s sdk) CreateStack(ctx context.Context, name string, region string, templa func (s sdk) CreateChangeSet(ctx context.Context, name string, region string, template []byte) (string, error) { logrus.Debug("Create CloudFormation Changeset") + update := fmt.Sprintf("Update%s", time.Now().Format("2006-01-02-15-04-05")) - changeset, err := s.withTemplate(ctx, name, template, region, func(ctx context.Context, name string, url string) (string, error) { - update := fmt.Sprintf("Update%s", time.Now().Format("2006-01-02-15-04-05")) + changeset, err := s.withTemplate(ctx, name, template, region, func(body *string, url *string) (string, error) { changeset, err := s.CF.CreateChangeSetWithContext(ctx, &cloudformation.CreateChangeSetInput{ ChangeSetName: aws.String(update), ChangeSetType: aws.String(cloudformation.ChangeSetTypeUpdate), StackName: aws.String(name), - TemplateBody: aws.String(string(template)), + TemplateBody: body, + TemplateURL: url, Capabilities: []*string{ aws.String(cloudformation.CapabilityCapabilityIam), },