Skip to content

Commit

Permalink
Merge pull request #733 from kayac/fix/fail-by-cb
Browse files Browse the repository at this point in the history
Exit non-zero status when deployment is rolled back.
  • Loading branch information
fujiwara authored Aug 2, 2024
2 parents 9b0e226 + 84732a1 commit ea4bc12
Show file tree
Hide file tree
Showing 9 changed files with 74 additions and 40 deletions.
2 changes: 1 addition & 1 deletion create.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ func (d *App) createService(ctx context.Context, opt DeployOption) error {
return err
}

doWait, err := d.WaitFunc(sv)
doWait, err := d.WaitFunc(sv, nil)
if err != nil {
return err
}
Expand Down
8 changes: 5 additions & 3 deletions deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"errors"
"fmt"
"io"
"os"
"os/exec"
"strings"
Expand Down Expand Up @@ -95,12 +96,13 @@ func (d *App) Deploy(ctx context.Context, opt DeployOption) error {
if err != nil {
return err
}
doWait, err := d.WaitFunc(sv)

tdArn, err := d.taskDefinitionArnForDeploy(ctx, sv, opt)
if err != nil {
return err
}

tdArn, err := d.taskDefinitionArnForDeploy(ctx, sv, opt)
doWait, err := d.WaitFunc(sv, d.confirmPrimaryTD(tdArn))
if err != nil {
return err
}
Expand All @@ -112,7 +114,7 @@ func (d *App) Deploy(ctx context.Context, opt DeployOption) error {
return err
}
addedTags, updatedTags, deletedTags := CompareTags(sv.Tags, newSv.Tags)
differ, err := diffServices(ctx, newSv, sv, d.config.ServiceDefinitionPath, &DiffOption{Unified: true, w: os.Stdout})
differ, err := diffServices(ctx, newSv, sv, d.config.ServiceDefinitionPath, &DiffOption{Unified: true, w: io.Discard})
if err != nil {
return fmt.Errorf("failed to diff of service definitions: %w", err)
}
Expand Down
6 changes: 6 additions & 0 deletions ecspresso.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,12 @@ func (sv *Service) SetTags(tags []types.Tag) {
sv.Tags = tags
}

func (sv *Service) PrimaryDeployment() (types.Deployment, bool) {
return lo.Find(sv.Deployments, func(dp types.Deployment) bool {
return aws.ToString(dp.Status) == "PRIMARY"
})
}

func (d *App) newServiceFromTypes(ctx context.Context, in types.Service) (*Service, error) {
sv := Service{
Service: in,
Expand Down
25 changes: 10 additions & 15 deletions rollback.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,14 +49,17 @@ func (d *App) Rollback(ctx context.Context, opt RollbackOption) error {
if err != nil {
return err
}

doWait, err := d.WaitFunc(sv)
targetArn, err := d.FindRollbackTarget(ctx, *sv.TaskDefinition)
if err != nil {
return err
}
doWait, err := d.WaitFunc(sv, d.confirmPrimaryTD(targetArn))
if err != nil {
return err
}

// doRollback returns the task definition arn to be rolled back
rollbackedTdArn, err := doRollback(ctx, sv, opt)
rollbackedTdArn, err := doRollback(ctx, sv, targetArn, opt)
if err != nil {
return err
}
Expand Down Expand Up @@ -111,12 +114,8 @@ func (d *App) rollbackTaskDefinition(ctx context.Context, rollbackedTdArn string
return nil
}

func (d *App) RollbackServiceTasks(ctx context.Context, sv *Service, opt RollbackOption) (string, error) {
func (d *App) RollbackServiceTasks(ctx context.Context, sv *Service, targetArn string, opt RollbackOption) (string, error) {
currentArn := *sv.TaskDefinition
targetArn, err := d.FindRollbackTarget(ctx, currentArn)
if err != nil {
return "", err
}

d.Log("Rolling back to %s %s", arnToName(targetArn), opt.DryRunString())
if opt.DryRun {
Expand All @@ -138,7 +137,7 @@ func (d *App) RollbackServiceTasks(ctx context.Context, sv *Service, opt Rollbac
return currentArn, nil
}

func (d *App) RollbackByCodeDeploy(ctx context.Context, sv *Service, opt RollbackOption) (string, error) {
func (d *App) RollbackByCodeDeploy(ctx context.Context, sv *Service, targetArn string, opt RollbackOption) (string, error) {
dp, err := d.findDeploymentInfo(ctx)
if err != nil {
return "", err
Expand Down Expand Up @@ -168,10 +167,6 @@ func (d *App) RollbackByCodeDeploy(ctx context.Context, sv *Service, opt Rollbac
switch currentDeployment.Status {
case cdTypes.DeploymentStatusSucceeded, cdTypes.DeploymentStatusFailed, cdTypes.DeploymentStatusStopped:
currentTdArn := *sv.TaskDefinition
targetArn, err := d.FindRollbackTarget(ctx, currentTdArn)
if err != nil {
return "", err
}
d.Log("the deployment in progress is not found, creating a new deployment with %s %s", targetArn, opt.DryRunString())
if opt.DryRun {
return currentTdArn, nil
Expand Down Expand Up @@ -216,7 +211,7 @@ func (d *App) FindRollbackTarget(ctx context.Context, taskDefinitionArn string)
},
)
if err != nil {
return "", fmt.Errorf("failed to list taskdefinitions: %w", err)
return "", fmt.Errorf("failed to list task definitions: %w", err)
}
if len(out.TaskDefinitionArns) == 0 {
return "", ErrNotFound(fmt.Sprintf("rollback target is not found: %s", err))
Expand All @@ -237,7 +232,7 @@ func (d *App) FindRollbackTarget(ctx context.Context, taskDefinitionArn string)
return "", ErrNotFound("rollback target is not found")
}

type rollbackFunc func(ctx context.Context, sv *Service, opt RollbackOption) (string, error)
type rollbackFunc func(ctx context.Context, sv *Service, targetArn string, opt RollbackOption) (string, error)

func (d *App) RollbackFunc(sv *Service) (rollbackFunc, error) {
defaultFunc := d.RollbackServiceTasks
Expand Down
8 changes: 4 additions & 4 deletions tests/ci/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ ECSPRESSO := ecspresso --envfile envfile

help:

test: deploy rollback refresh deploy-no-update-service scale down up delete
test: deploy deploy rollback refresh deploy-no-update-service scale down up delete

status:
$(ECSPRESSO) status --events 10
Expand Down Expand Up @@ -58,13 +58,13 @@ run-task:
--overrides '{"containerOverrides":[{"name":"nginx", "command":["nginx", "-V"]}]}' \
--client-token $(CLIENT_TOKEN)
OPTION=-v $(ECSPRESSO) --envfile envfile.override run \
--overrides-file=overrides.json \
--overrides-file=overrides.jsonnet \
--dry-run
OPTION=-v $(ECSPRESSO) --envfile envfile.override run \
--overrides-file=overrides.json \
--overrides-file=overrides.jsonnet \
--wait-until=running
OPTION=-v $(ECSPRESSO) --envfile envfile.override run \
--overrides-file=overrides.json \
--overrides-file=overrides.jsonnet \
--wait-until=stopped

wait:
Expand Down
4 changes: 2 additions & 2 deletions tests/ci/ecs-service-def.jsonnet
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ local isCodeDeploy = env('DEPLOYMENT_CONTROLLER', 'ECS') == 'CODE_DEPLOY';
],
deploymentConfiguration: {
deploymentCircuitBreaker: if isCodeDeploy then null else {
enable: false,
rollback: false,
enable: true,
rollback: true,
},
maximumPercent: 200,
minimumHealthyPercent: 100,
Expand Down
11 changes: 0 additions & 11 deletions tests/ci/overrides.json

This file was deleted.

9 changes: 9 additions & 0 deletions tests/ci/overrides.jsonnet
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
local must_env = std.native('must_env');
{
containerOverrides: [
{
name: 'nginx',
command: ['nginx', must_env('OPTION')],
},
],
}
41 changes: 37 additions & 4 deletions wait.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,22 @@ import (

type waitFunc func(ctx context.Context, sv *Service) error

func (d *App) WaitFunc(sv *Service) (waitFunc, error) {
defaultFunc := d.WaitServiceStable
type confirmFunc func(ctx context.Context) error

func (confirm confirmFunc) wrap(wait waitFunc) waitFunc {
if confirm == nil {
return wait
}
return func(ctx context.Context, sv *Service) error {
if err := wait(ctx, sv); err != nil {
return err
}
return confirm(ctx)
}
}

func (d *App) WaitFunc(sv *Service, confirm confirmFunc) (waitFunc, error) {
defaultFunc := confirm.wrap(d.WaitServiceStable)
if sv == nil || sv.DeploymentController == nil {
return defaultFunc, nil
}
Expand All @@ -29,14 +43,33 @@ func (d *App) WaitFunc(sv *Service) (waitFunc, error) {
case types.DeploymentControllerTypeCodeDeploy:
return d.WaitForCodeDeploy, nil
case types.DeploymentControllerTypeEcs:
return d.WaitServiceStable, nil
return defaultFunc, nil
default:
return nil, fmt.Errorf("unsupported deployment controller type: %s", dc.Type)
}
}
return defaultFunc, nil
}

func (d *App) confirmPrimaryTD(tdArn string) confirmFunc {
return func(ctx context.Context) error {
sv, err := d.DescribeService(ctx)
if err != nil {
return err
}
if dp, ok := sv.PrimaryDeployment(); ok {
current := aws.ToString(dp.TaskDefinition)
d.Log("[DEBUG] checking primary deployment %s %s == %s", *dp.Id, current, tdArn)
if arnToName(current) != arnToName(tdArn) {
return fmt.Errorf("task definition %s is not deployed yet. PRIMARY deployment is %s", tdArn, current)
}
d.Log("[DEBUG] task definition %s is deployed", tdArn)
return nil
}
return fmt.Errorf("no primary deployment found")
}
}

type WaitOption struct {
}

Expand All @@ -51,7 +84,7 @@ func (d *App) Wait(ctx context.Context, opt WaitOption) error {
return err
}
d.LogJSON(sv.DeploymentController)
doWait, err := d.WaitFunc(sv)
doWait, err := d.WaitFunc(sv, nil)
if err != nil {
return err
}
Expand Down

0 comments on commit ea4bc12

Please sign in to comment.