Skip to content

Commit

Permalink
Merge pull request #366 from fujiwara/feature/rollback-alias
Browse files Browse the repository at this point in the history
Support to `rollback --alias`.
  • Loading branch information
fujiwara authored Mar 29, 2024
2 parents ed4b815 + 722d7dd commit 6191f3c
Show file tree
Hide file tree
Showing 3 changed files with 90 additions and 51 deletions.
19 changes: 13 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -265,17 +265,24 @@ Usage: lambroll rollback
rollback function

Flags:
--dry-run dry run
--delete-version delete rolled back version
--dry-run dry run
--alias="current" alias to rollback
--version="" version to rollback (default: previous version auto detected)
--delete-version delete rolled back version
```
`lambroll deploy` create/update alias `current` to the published function version on deploy.
`lambroll deploy` create/update alias to the published function version on deploy.
`lambroll rollback` works as below.
1. Find previous one version of function.
2. Update alias `current` to the previous version.
3. When `--delete-version` specified, delete old version of function.
1. Find the previous version from the alias with no other aliases.
2. Update the alias to the previous version.
- If `--version` is specified, update the alias to the specified version.
3. When `--delete-version` is specified, delete the old version of the function.
If you add multiple aliases to the function, `lambroll rollback --alias={some-alias}` may not work as expected. Because the previous version that auto-detected may be the older version of other aliases.
So you should specify the version to rollback with `--version` flag to clear the ambiguity.
### Invoke
Expand Down
70 changes: 47 additions & 23 deletions rollback.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@ import (

// RollbackOption represents option for Rollback()
type RollbackOption struct {
DryRun bool `default:"false" help:"dry run"`
DeleteVersion bool `default:"false" help:"delete rolled back version"`
DryRun bool `default:"false" help:"dry run"`
Alias string `default:"current" help:"alias to rollback"`
Version string `default:"" help:"version to rollback (default: previous version auto detected)"`
DeleteVersion bool `default:"false" help:"delete rolled back version"`
}

func (opt RollbackOption) label() string {
Expand All @@ -33,20 +35,51 @@ func (app *App) Rollback(ctx context.Context, opt *RollbackOption) error {
return fmt.Errorf("failed to load function: %w", err)
}

log.Printf("[info] starting rollback function %s", *fn.FunctionName)
log.Printf("[info] starting rollback function %s:%s", *fn.FunctionName, opt.Alias)

res, err := app.lambda.GetAlias(ctx, &lambda.GetAliasInput{
FunctionName: fn.FunctionName,
Name: aws.String(CurrentAliasName),
Name: aws.String(opt.Alias),
})
if err != nil {
return fmt.Errorf("failed to get alias: %w", err)
}

currentVersion := *res.FunctionVersion
var prevVersion string
if opt.Version != "" {
prevVersion = opt.Version
} else {
prevVersion, err = app.findPreviousVersion(ctx, *fn.FunctionName, currentVersion)
if err != nil {
return fmt.Errorf("failed to find previous version: %w", err)
}
}

log.Printf("[info] rollbacking function version %s to %s %s", currentVersion, prevVersion, opt.label())
if opt.DryRun {
return nil
}
err = app.updateAliases(ctx, *fn.FunctionName, versionAlias{Version: prevVersion, Name: opt.Alias})
if err != nil {
return err
}

if !opt.DeleteVersion {
return nil
}

return app.deleteFunctionVersion(ctx, *fn.FunctionName, currentVersion)
}

func (app *App) findPreviousVersion(ctx context.Context, name, currentVersion string) (string, error) {
aliases, err := app.getAliases(ctx, name)
if err != nil {
return "", fmt.Errorf("failed to get aliases: %w", err)
}
cv, err := strconv.ParseInt(currentVersion, 10, 64)
if err != nil {
return fmt.Errorf("failed to pase %s as int: %w", currentVersion, err)
return "", fmt.Errorf("failed to pase %s as int: %w", currentVersion, err)
}

var prevVersion string
Expand All @@ -55,7 +88,7 @@ VERSIONS:
log.Printf("[debug] get function version %d", v)
vs := strconv.FormatInt(v, 10)
res, err := app.lambda.GetFunction(ctx, &lambda.GetFunctionInput{
FunctionName: fn.FunctionName,
FunctionName: aws.String(name),
Qualifier: aws.String(vs),
})
if err != nil {
Expand All @@ -64,30 +97,21 @@ VERSIONS:
log.Printf("[debug] version %s not found", vs)
continue VERSIONS
} else {
return fmt.Errorf("failed to get function: %w", err)
return "", fmt.Errorf("failed to get function: %w", err)
}
}
if pv := *res.Configuration.Version; aliases[pv] != nil {
// skip if the version has alias
log.Printf("[info] version %s has alias %v, skipping", pv, aliases[pv])
continue VERSIONS
}
prevVersion = *res.Configuration.Version
break
}
if prevVersion == "" {
return errors.New("unable to detect previous version of function")
}

log.Printf("[info] rollbacking function version %s to %s %s", currentVersion, prevVersion, opt.label())
if opt.DryRun {
return nil
return "", fmt.Errorf("unable to detect previous version of function")
}
err = app.updateAliases(ctx, *fn.FunctionName, versionAlias{Version: prevVersion, Name: CurrentAliasName})
if err != nil {
return err
}

if !opt.DeleteVersion {
return nil
}

return app.deleteFunctionVersion(ctx, *fn.FunctionName, currentVersion)
return prevVersion, nil
}

func (app *App) deleteFunctionVersion(ctx context.Context, functionName, version string) error {
Expand Down
52 changes: 30 additions & 22 deletions versions.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,28 +82,9 @@ func (app *App) Versions(ctx context.Context, opt *VersionsOption) error {
return app.deleteVersions(ctx, name, opt.KeepVersions)
}

aliases := make(map[string][]string)
var nextAliasMarker *string
for {
res, err := app.lambda.ListAliases(ctx, &lambda.ListAliasesInput{
FunctionName: &name,
Marker: nextAliasMarker,
})
if err != nil {
return fmt.Errorf("failed to list aliases: %w", err)
}
for _, alias := range res.Aliases {
aliases[*alias.FunctionVersion] = append(aliases[*alias.FunctionVersion], *alias.Name)
if alias.RoutingConfig == nil || alias.RoutingConfig.AdditionalVersionWeights == nil {
continue
}
for v := range alias.RoutingConfig.AdditionalVersionWeights {
aliases[v] = append(aliases[v], *alias.Name)
}
}
if nextAliasMarker = res.NextMarker; nextAliasMarker == nil {
break
}
aliases, err := app.getAliases(ctx, name)
if err != nil {
return fmt.Errorf("failed to get aliases: %w", err)
}

var versions []types.FunctionConfiguration
Expand Down Expand Up @@ -158,3 +139,30 @@ func (app *App) Versions(ctx context.Context, opt *VersionsOption) error {
}
return nil
}

func (app *App) getAliases(ctx context.Context, name string) (map[string][]string, error) {
aliases := make(map[string][]string)
var nextAliasMarker *string
for {
res, err := app.lambda.ListAliases(ctx, &lambda.ListAliasesInput{
FunctionName: &name,
Marker: nextAliasMarker,
})
if err != nil {
return nil, fmt.Errorf("failed to list aliases: %w", err)
}
for _, alias := range res.Aliases {
aliases[*alias.FunctionVersion] = append(aliases[*alias.FunctionVersion], *alias.Name)
if alias.RoutingConfig == nil || alias.RoutingConfig.AdditionalVersionWeights == nil {
continue
}
for v := range alias.RoutingConfig.AdditionalVersionWeights {
aliases[v] = append(aliases[v], *alias.Name)
}
}
if nextAliasMarker = res.NextMarker; nextAliasMarker == nil {
break
}
}
return aliases, nil
}

0 comments on commit 6191f3c

Please sign in to comment.