Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ Flags:
--help Show context-sensitive help (also try --help-long and --help-man).
--config="./deployman.json" [OPTIONAL] Configuration file path. By default, this value is './deployman.json'. If this file does not exist, an error will occur.
--verbose [OPTIONAL] A detailed log containing call stacks will be error messages.
--output="table" [OPTIONAL] Output format (table, json). Default is table.
```
- output sample: This example shows that the bundle deployed in blue-AutoScalingGroup is #1 and the bundle deployed in green-AutoScaling is #2.
```shell
Expand All @@ -168,7 +169,7 @@ Flags:
+----+---------------------------+-----------------------------+----------------+
```

### bundle acrivate
### bundle activate
```shell
usage: deployman bundle activate --target=TARGET --name=NAME

Expand Down Expand Up @@ -205,6 +206,7 @@ Flags:
--help Show context-sensitive help (also try --help-long and --help-man).
--config="./deployman.json" [OPTIONAL] Configuration file path. By default, this value is './deployman.json'. If this file does not exist, an error will occur.
--verbose [OPTIONAL] A detailed log containing call stacks will be error messages.
--output="table" [OPTIONAL] Output format (table, json). Default is table.
```
- output sample: TARGET is a blue/green classification. It displays the percentage of each traffic weight and the status of the associated AutoScalingGroup and TargetGroup.
```shell
Expand Down
14 changes: 8 additions & 6 deletions cmd/deployman/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ var (
bundleRegisterName = bundleRegister.Flag("name", "[REQUIRED] Name of bundle to be registered").Required().String()
bundleRegisterActivate = bundleRegister.Flag("with-activate", "[OPTIONAL] Associate (activate) this bundle with an idle AutoScalingGroup.").Bool()

bundleList = bundle.Command("list", "List registered application bundles.")
bundleList = bundle.Command("list", "List registered application bundles.")
bundleListOutput = bundleList.Flag("output", "Output format (table, json). Default is table.").Default("table").Enum("table", "json")

bundleActivate = bundle.Command("activate", "Activate one of the registered bundles. The active bundle will be used for the next deployment or scale-out.")
bundleActivateTarget = bundleActivate.Flag("target", "[REQUIRED] Target type for bundle. Valid values are either 'blue' or 'green'. The 'ec2 status' command allows you to check the target details.").Required().Enum("blue", "green")
Expand All @@ -40,7 +41,8 @@ var (

ec2 = app.Command("ec2", "")

ec2status = ec2.Command("status", "Show current deployment status.")
ec2status = ec2.Command("status", "Show current deployment status.")
ec2statusOutput = ec2status.Flag("output", "Output format (table, json). Default is table.").Default("table").Enum("table", "json")

ec2deploy = ec2.Command("deploy", "Deploy a new application to an idling AutoScalingGroup.")
ec2deploySilent = ec2deploy.Flag("silent", "[OPTIONAL] Skip confirmation before process.").Bool()
Expand Down Expand Up @@ -122,7 +124,7 @@ func main() {
}

case bundleList.FullCommand():
err = bundler.ListBundles(ctx)
err = bundler.ListBundles(ctx, *bundleListOutput)

case bundleActivate.FullCommand():
err = bundler.Activate(ctx, internal.TargetType(*bundleActivateTarget), *bundleActivateName)
Expand All @@ -131,10 +133,10 @@ func main() {
err = bundler.Download(ctx, internal.TargetType(*bundleDownloadTarget))

case ec2status.FullCommand():
err = deployer.ShowStatus(ctx)
err = deployer.ShowStatus(ctx, *ec2statusOutput)

case ec2deploy.FullCommand():
if err = deployer.ShowStatus(ctx); err != nil {
if err = deployer.ShowStatus(ctx, "table"); err != nil {
break
}
if *ec2deploySilent == false && internal.AskToContinue() == false {
Expand All @@ -146,7 +148,7 @@ func main() {
}

case ec2rollback.FullCommand():
if err = deployer.ShowStatus(ctx); err != nil {
if err = deployer.ShowStatus(ctx, "table"); err != nil {
break
}
if *ec2rollbackSilent == false && internal.AskToContinue() == false {
Expand Down
110 changes: 78 additions & 32 deletions internal/bundler.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ package internal
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"os"
"sort"
"strconv"
Expand All @@ -28,11 +30,53 @@ type Bundler struct {
logger Logger
}

type BundleInfo struct {
type ActiveBundle struct {
Value string
LastModified *time.Time
}

type BundleListItem struct {
Number int `json:"number"`
LastUpdated string `json:"lastUpdated"`
BundleName string `json:"bundleName"`
ActiveTargets []string `json:"activeTargets"`
}

type BundleListOutput struct {
BucketName string `json:"bucket"`
Bundles []BundleListItem `json:"bundles"`
}

func (b *BundleListOutput) AsJSON(w io.Writer) error {
encoder := json.NewEncoder(w)
encoder.SetIndent("", " ")
return encoder.Encode(b)
}

func (b *BundleListOutput) AsTable(w io.Writer) error {
var data [][]string
for _, item := range b.Bundles {
status := ""
if len(item.ActiveTargets) > 0 {
status = "active:[" + strings.Join(item.ActiveTargets, ", ") + "]"
}
data = append(data, []string{
strconv.Itoa(item.Number),
item.LastUpdated,
item.BundleName,
status,
})
}

fmt.Fprintf(w, "Bucket: %s\n", b.BucketName)
table := tablewriter.NewWriter(w)
table.SetHeader([]string{"#", "last updated", "bundle name", "status"})
table.AppendBulk(data)
table.Render()

return nil
}

func NewBundler(deployConfig *Config, awsClient AwsClient, logger Logger) *Bundler {
return &Bundler{
config: deployConfig,
Expand All @@ -55,25 +99,26 @@ func (b *Bundler) listBundles(ctx context.Context, bucket string) ([]s3Types.Obj
return objects, nil
}

func (b *Bundler) ListBundles(ctx context.Context) error {
hasError := func(err error) bool {
if err == nil {
return false
}
var apiErr smithy.APIError
if errors.As(err, &apiErr) && apiErr.ErrorCode() == "NoSuchKey" {
return false
func (b *Bundler) ListBundles(ctx context.Context, outputFormat string) error {
Comment thread
yyoda marked this conversation as resolved.
getActiveBundleOrNil := func(targetType TargetType) (*ActiveBundle, error) {
bundle, err := b.getActiveBundle(ctx, targetType)
if err != nil {
var apiErr smithy.APIError
if errors.As(err, &apiErr) && apiErr.ErrorCode() == "NoSuchKey" {
return nil, nil
}
return nil, err
}
return true
return bundle, nil
}

blueBundle, err := b.getActiveBundle(ctx, BlueTargetType)
if hasError(err) {
blueBundle, err := getActiveBundleOrNil(BlueTargetType)
if err != nil {
return err
}

greenBundle, err := b.getActiveBundle(ctx, GreenTargetType)
if hasError(err) {
greenBundle, err := getActiveBundleOrNil(GreenTargetType)
if err != nil {
return err
}

Expand All @@ -82,7 +127,7 @@ func (b *Bundler) ListBundles(ctx context.Context) error {
return err
}

var data [][]string
var bundles []BundleListItem
for i, bundleObject := range bundleObjects {
var targets []string
if blueBundle != nil && strings.Contains(*bundleObject.Key, blueBundle.Value) {
Expand All @@ -91,26 +136,27 @@ func (b *Bundler) ListBundles(ctx context.Context) error {
if greenBundle != nil && strings.Contains(*bundleObject.Key, greenBundle.Value) {
targets = append(targets, "green")
}
status := ""
if len(targets) > 0 {
status = "active:[" + strings.Join(targets, ", ") + "]"
}
location := b.config.TimeZone.CurrentLocation()
data = append(data, []string{
strconv.Itoa(i + 1),
bundleObject.LastModified.In(location).Format(time.RFC3339),
strings.Replace(*bundleObject.Key, BundlePrefix, "", 1),
status,
lastUpdated := bundleObject.LastModified.In(location).Format(time.RFC3339)
bundleName := strings.Replace(*bundleObject.Key, BundlePrefix, "", 1)

bundles = append(bundles, BundleListItem{
Number: i + 1,
LastUpdated: lastUpdated,
BundleName: bundleName,
ActiveTargets: targets,
})
}

fmt.Printf("Bucket: %s\n", b.config.BundleBucket)
table := tablewriter.NewWriter(os.Stdout)
table.SetHeader([]string{"#", "last updated", "bundle name", "status"})
table.AppendBulk(data)
table.Render()
output := &BundleListOutput{
BucketName: b.config.BundleBucket,
Bundles: bundles,
}

return nil
if outputFormat == "json" {
return output.AsJSON(os.Stdout)
}
return output.AsTable(os.Stdout)
}

func (b *Bundler) Register(ctx context.Context, uploadFile string, bundleName string) error {
Expand Down Expand Up @@ -173,7 +219,7 @@ func (b *Bundler) Register(ctx context.Context, uploadFile string, bundleName st
return nil
}

func (b *Bundler) getActiveBundle(ctx context.Context, targetType TargetType) (*BundleInfo, error) {
func (b *Bundler) getActiveBundle(ctx context.Context, targetType TargetType) (*ActiveBundle, error) {
output, err := b.client.GetS3BucketObject(ctx, b.config.BundleBucket, ActiveBundleKeyPrefix+string(targetType))
if err != nil {
return nil, err
Expand All @@ -185,7 +231,7 @@ func (b *Bundler) getActiveBundle(ctx context.Context, targetType TargetType) (*
return nil, errors.WithStack(err)
}

return &BundleInfo{
return &ActiveBundle{
Value: buf.String(),
LastModified: output.LastModified,
}, nil
Expand Down
Loading