Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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