Skip to content

Commit

Permalink
Add build wait command and start --wait
Browse files Browse the repository at this point in the history
  • Loading branch information
ipmb committed Dec 17, 2020
1 parent 82b2a0a commit 7be5592
Show file tree
Hide file tree
Showing 3 changed files with 141 additions and 10 deletions.
68 changes: 61 additions & 7 deletions app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -172,22 +172,35 @@ func (a *App) LoadECSConfig() error {
return nil
}

// LoadDeployStatus will set the app.DeployStatus value from DDB
func (a *App) LoadDeployStatus() error {
if a.DeployStatus != nil {
return nil
// GetDeployStatus will get a DeployStatus value from DDB
func (a *App) GetDeployStatus(buildARN string) (*DeployStatus, error) {
key := "DEPLOYSTATUS"
if buildARN != "" {
key = strings.Join([]string{key, buildARN}, "#")
}
Item, err := a.ddbItem("DEPLOYSTATUS")
Item, err := a.ddbItem(key)
if err != nil {
return err
return nil, err
}
i := deployStatusItem{}

err = dynamodbattribute.UnmarshalMap(*Item, &i)
if err != nil {
return nil, err
}
return &i.DeployStatus, nil
}

// LoadDeployStatus will set the app.DeployStatus value from DDB
func (a *App) LoadDeployStatus() error {
if a.DeployStatus != nil {
return nil
}
deployStatus, err := a.GetDeployStatus("")
if err != nil {
return err
}
a.DeployStatus = &i.DeployStatus
a.DeployStatus = deployStatus
return nil
}

Expand Down Expand Up @@ -350,6 +363,21 @@ func (a *App) StartBuild() (*codebuild.Build, error) {
return build.Build, err
}

// BuildStatus checks the status of a CodeBuild run
func (a *App) BuildStatus(build *codebuild.Build) (*DeployStatus, error) {
deployStatus, err := a.GetDeployStatus(*build.Arn)
if err != nil {
deployStatus, err = a.GetDeployStatus("")
if err != nil || deployStatus.BuildID != *build.Arn {
return nil, fmt.Errorf("no status found for build #%d", *build.BuildNumber)
}
}
if deployStatus.Failed {
return nil, fmt.Errorf("build #%d failed in phase %s", *build.BuildNumber, deployStatus.Phase)
}
return deployStatus, nil
}

// ListBuilds lists recent CodeBuild runs
func (a *App) ListBuilds() ([]*codebuild.Build, error) {
codebuildSvc := codebuild.New(a.Session)
Expand All @@ -372,6 +400,32 @@ func (a *App) ListBuilds() ([]*codebuild.Build, error) {
return builds.Builds, nil
}

// LastBuild retrieves the most recent build
func (a *App) LastBuild() (*codebuild.Build, error) {
codebuildSvc := codebuild.New(a.Session)
err := a.LoadSettings()
if err != nil {
return nil, err
}
buildList, err := codebuildSvc.ListBuildsForProject(&codebuild.ListBuildsForProjectInput{
ProjectName: &a.Settings.CodebuildProject.Name,
SortOrder: aws.String("DESCENDING"),
})
if err != nil {
return nil, err
}
if len(buildList.Ids) == 0 {
return nil, fmt.Errorf("no builds have started for %s", a.Name)
}
builds, err := codebuildSvc.BatchGetBuilds(&codebuild.BatchGetBuildsInput{
Ids: buildList.Ids[0:1],
})
if err != nil {
return nil, err
}
return builds.Builds[0], nil
}

// GetBuildArtifact retrieves an artifact stored in S3
func (a *App) GetBuildArtifact(build *codebuild.Build, name string) ([]byte, error) {
artifactArn := build.Artifacts.Location
Expand Down
81 changes: 78 additions & 3 deletions cmd/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package cmd
import (
"fmt"
"strings"
"time"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/codebuild"
Expand Down Expand Up @@ -60,14 +61,59 @@ func printBuild(build *codebuild.Build, commitLog []byte) error {
fmt.Print(" ", aurora.Blue(*build.SourceVersion), icon[*build.BuildStatus])
fmt.Println()
if build.EndTime != nil {
fmt.Println(aurora.Faint(fmt.Sprintf("%s%s ~ %s", indentStr, build.EndTime.Local().Format("Jan 02, 2006 15:04:05 MST"), humanize.Time(*build.EndTime))))
fmt.Println(aurora.Faint(fmt.Sprintf("%s%s ~ %s", indentStr, build.EndTime.Local().Format(timeFmt), humanize.Time(*build.EndTime))))
} else {
fmt.Println(aurora.Faint(fmt.Sprintf("%sstarted %s ~ %s", indentStr, build.StartTime.Local().Format("Jan 02, 2006 15:04:05 MST"), humanize.Time(*build.EndTime))))
fmt.Println(aurora.Faint(fmt.Sprintf("%sstarted %s ~ %s", indentStr, build.StartTime.Local().Format(timeFmt), humanize.Time(*build.StartTime))))
}
fmt.Println(indent(fmt.Sprintf("%s", commitLog), indentStr))
return nil
}

func watchBuild(a *app.App, build *codebuild.Build) error {
var lastPhase string
var lastUpdate time.Time
var status string
startSpinner()
retries := 10
for {
// retry a few times for the initial status to show up
// once it does, stop retrying
deployStatus, err := a.BuildStatus(build)
if err != nil {
if retries > 0 {
retries--
time.Sleep(3 * time.Second)
continue
} else {
return err
}
}
retries = 0
lastUpdate = time.Unix(deployStatus.LastUpdate, 0)
if lastPhase != deployStatus.Phase {
Spinner.Stop()
if deployStatus.Phase == "running" {
status = "Deploy phase complete"
} else {
status = fmt.Sprintf("%s phase started", strings.Title(deployStatus.Phase))
}
fmt.Printf("%s\t%s\n", aurora.Yellow(status), aurora.Faint(lastUpdate.Local().Format(timeFmt)))
lastPhase = deployStatus.Phase
startSpinner()
// sleep for a second so the spinner doesn't show "X running for now"
time.Sleep(500 * time.Millisecond)
}
Spinner.Suffix = fmt.Sprintf(" %s running for %s", deployStatus.Phase, strings.Replace(humanize.Time(lastUpdate), " ago", "", 1))
if deployStatus.Phase == "running" {
Spinner.Stop()
printSuccess(fmt.Sprintf("build #%d deployed successfully", *build.BuildNumber))
break
}
time.Sleep(5 * time.Second)
}
return nil
}

// buildCmd represents the build command
var buildCmd = &cobra.Command{
Use: "build",
Expand All @@ -87,9 +133,33 @@ var buildStartCmd = &cobra.Command{
build, err := a.StartBuild()
checkErr(err)
Spinner.Stop()
printSuccess("Build started")
printSuccess("build started")
err = printBuild(build, []byte{})
checkErr(err)
if watchBuildFlag {
err = watchBuild(a, build)
checkErr(err)
}
},
}

// buildWaitCmd represents the start command
var buildWaitCmd = &cobra.Command{
Use: "wait",
Short: "wait for the most recent build to be deployed",
Long: `Bait for the most recent build to be deployed`,
Run: func(cmd *cobra.Command, args []string) {
startSpinner()
a, err := app.Init(AppName)
checkErr(err)
build, err := a.LastBuild()
checkErr(err)
Spinner.Stop()
commitLog, _ := a.GetBuildArtifact(build, "commit.txt")
err = printBuild(build, commitLog)
checkErr(err)
err = watchBuild(a, build)
checkErr(err)
},
}

Expand All @@ -113,11 +183,16 @@ var buildListCmd = &cobra.Command{
},
}

var watchBuildFlag bool

func init() {
rootCmd.AddCommand(buildCmd)
buildCmd.PersistentFlags().StringVarP(&AppName, "app-name", "a", "", "App name (required)")
buildCmd.MarkPersistentFlagRequired("app-name")

buildCmd.AddCommand(buildStartCmd)
buildStartCmd.Flags().BoolVarP(&watchBuildFlag, "wait", "w", false, "wait for build to complete")
buildCmd.AddCommand(buildListCmd)

buildCmd.AddCommand(buildWaitCmd)
}
2 changes: 2 additions & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ const ()

var cfgFile string

const timeFmt = "Jan 02, 2006 15:04:05 -0700"

// Spinner is the loading animation to use for all commands
var Spinner *spinner.Spinner = spinner.New(spinner.CharSets[14], 50*time.Millisecond)

Expand Down

0 comments on commit 7be5592

Please sign in to comment.