mirror of https://github.com/docker/compose.git
delete CapacityProvider before we invoke DeleteStack
this is a workaround for CloudFormation issue to manage CapacityProvider <-> Cluster reverse dependency Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
This commit is contained in:
parent
3685cbbf8d
commit
eab140ee71
|
@ -0,0 +1,22 @@
|
||||||
|
/*
|
||||||
|
Copyright 2020 Docker Compose CLI authors
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ecs
|
||||||
|
|
||||||
|
const (
|
||||||
|
awsTypeCapacityProvider = "AWS::ECS::CapacityProvider"
|
||||||
|
awsTypeAutoscalingGroup = "AWS::AutoScaling::AutoScalingGroup"
|
||||||
|
)
|
50
ecs/down.go
50
ecs/down.go
|
@ -18,12 +18,58 @@ package ecs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
|
"github.com/docker/compose-cli/progress"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (b *ecsAPIService) Down(ctx context.Context, project string) error {
|
func (b *ecsAPIService) Down(ctx context.Context, project string) error {
|
||||||
err := b.SDK.DeleteStack(ctx, project)
|
resources, err := b.SDK.ListStackResources(ctx, project)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return b.WaitStackCompletion(ctx, project, stackDelete)
|
|
||||||
|
err = resources.apply(awsTypeCapacityProvider, delete(ctx, b.SDK.DeleteCapacityProvider))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = resources.apply(awsTypeAutoscalingGroup, delete(ctx, b.SDK.DeleteAutoscalingGroup))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
previousEvents, err := b.previousStackEvents(ctx, project)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = b.SDK.DeleteStack(ctx, project)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return b.WaitStackCompletion(ctx, project, stackDelete, previousEvents...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *ecsAPIService) previousStackEvents(ctx context.Context, project string) ([]string, error) {
|
||||||
|
events, err := b.SDK.DescribeStackEvents(ctx, project)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var previousEvents []string
|
||||||
|
for _, e := range events {
|
||||||
|
previousEvents = append(previousEvents, *e.EventId)
|
||||||
|
}
|
||||||
|
return previousEvents, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func delete(ctx context.Context, delete func(ctx context.Context, arn string) error) func(r stackResource) error {
|
||||||
|
return func(r stackResource) error {
|
||||||
|
w := progress.ContextWriter(ctx)
|
||||||
|
w.Event(progress.Event{
|
||||||
|
ID: r.LogicalID,
|
||||||
|
Status: progress.Working,
|
||||||
|
StatusText: "DELETE_IN_PROGRESS",
|
||||||
|
})
|
||||||
|
return delete(ctx, r.ARN)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
41
ecs/sdk.go
41
ecs/sdk.go
|
@ -32,6 +32,8 @@ import (
|
||||||
"github.com/aws/aws-sdk-go/aws"
|
"github.com/aws/aws-sdk-go/aws"
|
||||||
"github.com/aws/aws-sdk-go/aws/request"
|
"github.com/aws/aws-sdk-go/aws/request"
|
||||||
"github.com/aws/aws-sdk-go/aws/session"
|
"github.com/aws/aws-sdk-go/aws/session"
|
||||||
|
"github.com/aws/aws-sdk-go/service/autoscaling"
|
||||||
|
"github.com/aws/aws-sdk-go/service/autoscaling/autoscalingiface"
|
||||||
"github.com/aws/aws-sdk-go/service/cloudformation"
|
"github.com/aws/aws-sdk-go/service/cloudformation"
|
||||||
"github.com/aws/aws-sdk-go/service/cloudformation/cloudformationiface"
|
"github.com/aws/aws-sdk-go/service/cloudformation/cloudformationiface"
|
||||||
"github.com/aws/aws-sdk-go/service/cloudwatchlogs"
|
"github.com/aws/aws-sdk-go/service/cloudwatchlogs"
|
||||||
|
@ -48,6 +50,7 @@ import (
|
||||||
"github.com/aws/aws-sdk-go/service/iam/iamiface"
|
"github.com/aws/aws-sdk-go/service/iam/iamiface"
|
||||||
"github.com/aws/aws-sdk-go/service/secretsmanager"
|
"github.com/aws/aws-sdk-go/service/secretsmanager"
|
||||||
"github.com/aws/aws-sdk-go/service/secretsmanager/secretsmanageriface"
|
"github.com/aws/aws-sdk-go/service/secretsmanager/secretsmanageriface"
|
||||||
|
"github.com/hashicorp/go-multierror"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -61,6 +64,7 @@ type sdk struct {
|
||||||
CF cloudformationiface.CloudFormationAPI
|
CF cloudformationiface.CloudFormationAPI
|
||||||
SM secretsmanageriface.SecretsManagerAPI
|
SM secretsmanageriface.SecretsManagerAPI
|
||||||
SSM ssmiface.SSMAPI
|
SSM ssmiface.SSMAPI
|
||||||
|
AG autoscalingiface.AutoScalingAPI
|
||||||
}
|
}
|
||||||
|
|
||||||
func newSDK(sess *session.Session) sdk {
|
func newSDK(sess *session.Session) sdk {
|
||||||
|
@ -77,6 +81,7 @@ func newSDK(sess *session.Session) sdk {
|
||||||
CF: cloudformation.New(sess),
|
CF: cloudformation.New(sess),
|
||||||
SM: secretsmanager.New(sess),
|
SM: secretsmanager.New(sess),
|
||||||
SSM: ssm.New(sess),
|
SSM: ssm.New(sess),
|
||||||
|
AG: autoscaling.New(sess),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -364,7 +369,24 @@ type stackResource struct {
|
||||||
Status string
|
Status string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s sdk) ListStackResources(ctx context.Context, name string) ([]stackResource, error) {
|
type stackResourceFn func(r stackResource) error
|
||||||
|
|
||||||
|
type stackResources []stackResource
|
||||||
|
|
||||||
|
func (resources stackResources) apply(awsType string, fn stackResourceFn) error {
|
||||||
|
var errs *multierror.Error
|
||||||
|
for _, r := range resources {
|
||||||
|
if r.Type == awsType {
|
||||||
|
err := fn(r)
|
||||||
|
if err != nil {
|
||||||
|
errs = multierror.Append(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return errs.ErrorOrNil()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s sdk) ListStackResources(ctx context.Context, name string) (stackResources, error) {
|
||||||
// FIXME handle pagination
|
// FIXME handle pagination
|
||||||
res, err := s.CF.ListStackResourcesWithContext(ctx, &cloudformation.ListStackResourcesInput{
|
res, err := s.CF.ListStackResourcesWithContext(ctx, &cloudformation.ListStackResourcesInput{
|
||||||
StackName: aws.String(name),
|
StackName: aws.String(name),
|
||||||
|
@ -373,7 +395,7 @@ func (s sdk) ListStackResources(ctx context.Context, name string) ([]stackResour
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
resources := []stackResource{}
|
resources := stackResources{}
|
||||||
for _, r := range res.StackResourceSummaries {
|
for _, r := range res.StackResourceSummaries {
|
||||||
resources = append(resources, stackResource{
|
resources = append(resources, stackResource{
|
||||||
LogicalID: aws.StringValue(r.LogicalResourceId),
|
LogicalID: aws.StringValue(r.LogicalResourceId),
|
||||||
|
@ -714,3 +736,18 @@ func (s sdk) SecurityGroupExists(ctx context.Context, sg string) (bool, error) {
|
||||||
}
|
}
|
||||||
return len(desc.SecurityGroups) > 0, nil
|
return len(desc.SecurityGroups) > 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s sdk) DeleteCapacityProvider(ctx context.Context, arn string) error {
|
||||||
|
_, err := s.ECS.DeleteCapacityProvider(&ecs.DeleteCapacityProviderInput{
|
||||||
|
CapacityProvider: aws.String(arn),
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s sdk) DeleteAutoscalingGroup(ctx context.Context, arn string) error {
|
||||||
|
_, err := s.AG.DeleteAutoScalingGroup(&autoscaling.DeleteAutoScalingGroupInput{
|
||||||
|
AutoScalingGroupName: aws.String(arn),
|
||||||
|
ForceDelete: aws.Bool(true),
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
|
@ -28,8 +28,12 @@ import (
|
||||||
"github.com/aws/aws-sdk-go/aws"
|
"github.com/aws/aws-sdk-go/aws"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (b *ecsAPIService) WaitStackCompletion(ctx context.Context, name string, operation int) error { //nolint:gocyclo
|
func (b *ecsAPIService) WaitStackCompletion(ctx context.Context, name string, operation int, ignored ...string) error { //nolint:gocyclo
|
||||||
knownEvents := map[string]struct{}{}
|
knownEvents := map[string]struct{}{}
|
||||||
|
for _, id := range ignored {
|
||||||
|
knownEvents[id] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
// progress writer
|
// progress writer
|
||||||
w := progress.ContextWriter(ctx)
|
w := progress.ContextWriter(ctx)
|
||||||
// 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
|
||||||
|
@ -78,7 +82,6 @@ func (b *ecsAPIService) WaitStackCompletion(ctx context.Context, name string, op
|
||||||
case "CREATE_COMPLETE":
|
case "CREATE_COMPLETE":
|
||||||
if operation == stackCreate {
|
if operation == stackCreate {
|
||||||
progressStatus = progress.Done
|
progressStatus = progress.Done
|
||||||
|
|
||||||
}
|
}
|
||||||
case "UPDATE_COMPLETE":
|
case "UPDATE_COMPLETE":
|
||||||
if operation == stackUpdate {
|
if operation == stackUpdate {
|
||||||
|
|
Loading…
Reference in New Issue