Explorar o código

`up` can update an existing stack using CloudFormation Changeset

Signed-off-by: Nicolas De Loof <[email protected]>
Nicolas De Loof %!s(int64=5) %!d(string=hai) anos
pai
achega
2d931dab9d
Modificáronse 4 ficheiros con 78 adicións e 13 borrados
  1. 19 10
      ecs/pkg/amazon/backend/up.go
  2. 2 0
      ecs/pkg/amazon/sdk/api.go
  3. 56 3
      ecs/pkg/amazon/sdk/sdk.go
  4. 1 0
      ecs/pkg/compose/types.go

+ 19 - 10
ecs/pkg/amazon/backend/up.go

@@ -26,14 +26,6 @@ func (b *Backend) Up(ctx context.Context, options cli.ProjectOptions) error {
 		return err
 	}
 
-	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
@@ -62,17 +54,34 @@ func (b *Backend) Up(ctx context.Context, options cli.ProjectOptions) error {
 		ParameterLoadBalancerARN: lb,
 	}
 
-	err = b.api.CreateStack(ctx, project.Name, template, parameters)
+	update, err := b.api.StackExists(ctx, project.Name)
 	if err != nil {
 		return err
 	}
+	operation := compose.StackCreate
+	if update {
+		operation = compose.StackUpdate
+		changeset, err := b.api.CreateChangeSet(ctx, project.Name, template, parameters)
+		if err != nil {
+			return err
+		}
+		err = b.api.UpdateStack(ctx, changeset)
+		if err != nil {
+			return err
+		}
+	} else {
+		err = b.api.CreateStack(ctx, project.Name, template, parameters)
+		if err != nil {
+			return err
+		}
+	}
 
 	fmt.Println()
 	w := console.NewProgressWriter()
 	for k := range template.Resources {
 		w.ResourceEvent(k, "PENDING", "")
 	}
-	return b.WaitStackCompletion(ctx, project.Name, compose.StackCreate, w)
+	return b.WaitStackCompletion(ctx, project.Name, operation, w)
 }
 
 func (b Backend) GetVPC(ctx context.Context, project *types.Project) (string, error) {

+ 2 - 0
ecs/pkg/amazon/sdk/api.go

@@ -23,6 +23,8 @@ type API 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)
+	CreateChangeSet(ctx context.Context, name string, template *cloudformation.Template, parameters map[string]string) (string, error)
+	UpdateStack(ctx context.Context, changeset string) error
 
 	DescribeServices(ctx context.Context, cluster string, arns []string) ([]compose.ServiceStatus, error)
 

+ 56 - 3
ecs/pkg/amazon/sdk/sdk.go

@@ -175,9 +175,8 @@ func (s sdk) CreateStack(ctx context.Context, name string, template *cf.Template
 	param := []*cloudformation.Parameter{}
 	for name, value := range parameters {
 		param = append(param, &cloudformation.Parameter{
-			ParameterKey:     aws.String(name),
-			ParameterValue:   aws.String(value),
-			UsePreviousValue: aws.Bool(true),
+			ParameterKey:   aws.String(name),
+			ParameterValue: aws.String(value),
 		})
 	}
 
@@ -194,6 +193,60 @@ func (s sdk) CreateStack(ctx context.Context, name string, template *cf.Template
 	return err
 }
 
+func (s sdk) CreateChangeSet(ctx context.Context, name string, template *cf.Template, parameters map[string]string) (string, error) {
+	logrus.Debug("Create CloudFormation Changeset")
+	json, err := template.JSON()
+	if err != nil {
+		return "", err
+	}
+
+	param := []*cloudformation.Parameter{}
+	for name := range parameters {
+		param = append(param, &cloudformation.Parameter{
+			ParameterKey:     aws.String(name),
+			UsePreviousValue: aws.Bool(true),
+		})
+	}
+
+	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(json)),
+		Parameters:    param,
+		Capabilities: []*string{
+			aws.String(cloudformation.CapabilityCapabilityIam),
+		},
+	})
+	if err != nil {
+		return "", err
+	}
+
+	err = s.CF.WaitUntilChangeSetCreateCompleteWithContext(ctx, &cloudformation.DescribeChangeSetInput{
+		ChangeSetName: changeset.Id,
+	})
+	return *changeset.Id, err
+}
+
+func (s sdk) UpdateStack(ctx context.Context, changeset string) error {
+	desc, err := s.CF.DescribeChangeSetWithContext(ctx, &cloudformation.DescribeChangeSetInput{
+		ChangeSetName: aws.String(changeset),
+	})
+	if err != nil {
+		return err
+	}
+
+	if strings.HasPrefix(aws.StringValue(desc.StatusReason), "The submitted information didn't contain changes.") {
+		return nil
+	}
+
+	_, err = s.CF.ExecuteChangeSet(&cloudformation.ExecuteChangeSetInput{
+		ChangeSetName: aws.String(changeset),
+	})
+	return err
+}
+
 func (s sdk) WaitStackComplete(ctx context.Context, name string, operation int) error {
 	input := &cloudformation.DescribeStacksInput{
 		StackName: aws.String(name),

+ 1 - 0
ecs/pkg/compose/types.go

@@ -19,6 +19,7 @@ type ServiceStatus struct {
 
 const (
 	StackCreate = iota
+	StackUpdate
 	StackDelete
 )