Skip to content

Commit

Permalink
Improve stack event polling behaviour (closes #25 and #26)
Browse files Browse the repository at this point in the history
  • Loading branch information
aidansteele committed May 9, 2019
1 parent 8769221 commit 14643ef
Show file tree
Hide file tree
Showing 4 changed files with 73 additions and 27 deletions.
4 changes: 3 additions & 1 deletion cmd/up.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ var upCmd = &cobra.Command{
stackPolicy := viper.GetString("stack-policy")
template := viper.GetString("template")
previousTemplate := viper.GetBool("previous-template")
alwaysSucceed := viper.GetBool("always-succeed")
//noDestroy := viper.GetBool("no-destroy")
//cancelOnExit := !viper.GetBool("no-cancel-on-exit")

Expand Down Expand Up @@ -90,7 +91,7 @@ var upCmd = &cobra.Command{
}

stackId := *prepared.Output.StackId
if success, _ := sit.IsSuccessfulState(stackId); !success {
if success, _ := sit.IsSuccessfulState(stackId); !success && !alwaysSucceed {
os.Exit(1)
}

Expand Down Expand Up @@ -192,6 +193,7 @@ func init() {
upCmd.PersistentFlags().Bool("no-cancel-on-exit", false, "")
upCmd.PersistentFlags().Bool("no-timestamps", false, "")
upCmd.PersistentFlags().Bool("no-color", false, "")
upCmd.PersistentFlags().Bool("always-succeed", false, "Typically stackit will return a nonzero exit code on failure. This disables that.")

viper.BindPFlags(upCmd.PersistentFlags())
}
50 changes: 50 additions & 0 deletions cmd/up_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,63 @@ package cmd

import (
"bytes"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/cloudformation"
"github.com/magiconair/properties/assert"
"io"
"os"
"strings"
"testing"
"time"
)

func TestUp_DoesntHangWhenCreationCancelled(t *testing.T) {
if testing.Short() {
t.Skip("skip e2e tests in short mode")
}

stackName := "test-cancelled-stack"
RootCmd.SetArgs([]string{
"up",
"--always-succeed",
"--stack-name", stackName,
"--template", "../sample/sample.yml",
})

buf := &bytes.Buffer{}
out := io.MultiWriter(buf, os.Stderr)
RootCmd.SetOutput(out)

time.AfterFunc(5*time.Second, func() {
for !strings.Contains(buf.String(), "LogGroup - CREATE_IN_PROGRESS") {
time.Sleep(time.Second)
}

sess := session.Must(session.NewSession())
cfn := cloudformation.New(sess)
input := &cloudformation.DeleteStackInput{StackName: &stackName}
_, err := cfn.DeleteStack(input)
if err != nil {
panic(err)
}
})

_ = RootCmd.Execute()

assert.Matches(t, buf.String(), strings.TrimSpace(`
\[\d\d:\d\d:\d\d] test-cancelled-stack - REVIEW_IN_PROGRESS - User Initiated
\[\d\d:\d\d:\d\d] test-cancelled-stack - CREATE_IN_PROGRESS - User Initiated
\[\d\d:\d\d:\d\d] LogGroup - CREATE_IN_PROGRESS
\[\d\d:\d\d:\d\d] LogGroup - CREATE_IN_PROGRESS - Resource creation Initiated
\[\d\d:\d\d:\d\d] LogGroup - CREATE_COMPLETE
\[\d\d:\d\d:\d\d] test-cancelled-stack - DELETE_IN_PROGRESS - User Initiated
\[\d\d:\d\d:\d\d] LogGroup - DELETE_IN_PROGRESS
\[\d\d:\d\d:\d\d] LogGroup - DELETE_COMPLETE
\[\d\d:\d\d:\d\d] test-cancelled-stack - DELETE_COMPLETE
\{\}
`))
}

func TestUp(t *testing.T) {
if testing.Short() {
t.Skip("skip e2e tests in short mode")
Expand Down
44 changes: 19 additions & 25 deletions pkg/stackit/poller.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ type TailStackEvent struct {
}

func (s *Stackit) PollStackEvents(stackId, token string, callback func(event TailStackEvent)) (*TailStackEvent, error) {
lastSentEventId := ""
mostRecentEventTimestamp := time.Now().AddDate(0, 0, -1)
haveSeenExpectedToken := false

for {
time.Sleep(3 * time.Second)
Expand All @@ -22,56 +23,49 @@ func (s *Stackit) PollStackEvents(stackId, token string, callback func(event Tai
StackName: &stackId,
}, func(page *cloudformation.DescribeStackEventsOutput, lastPage bool) bool {
for _, event := range page.StackEvents {
crt := "nil"
if event.ClientRequestToken != nil {
crt = *event.ClientRequestToken
if event.ClientRequestToken != nil && *event.ClientRequestToken == token {
haveSeenExpectedToken = true
}

if token == "" {
token = crt
if haveSeenExpectedToken && event.Timestamp.After(mostRecentEventTimestamp) {
events = append(events, event)
}

if *event.EventId == lastSentEventId || crt != token {
return false
}

events = append(events, event)
}
return true

earliestEvent := page.StackEvents[len(page.StackEvents)-1]
shouldPaginate := earliestEvent.Timestamp.After(mostRecentEventTimestamp)
mostRecentEventTimestamp = *page.StackEvents[0].Timestamp
return shouldPaginate
})

if err != nil {
if awsErr, ok := err.(awserr.Error); ok {
code := awsErr.Code()
if code == "ThrottlingException" {
continue
if code != "ThrottlingException" {
return nil, err
}
} else {
return nil, err
}
return nil, err
}

if len(events) == 0 {
continue
}

lastSentEventId = *events[0].EventId
stack, err := s.Describe(*events[0].StackId)
if err != nil {
return nil, err
}
terminal := IsTerminalStatus(*stack.StackStatus)

for ev_i := len(events) - 1; ev_i >= 0; ev_i-- {
event := events[ev_i]
tailEvent := TailStackEvent{*event}

done := terminal && ev_i == 0
if done {
return &tailEvent, nil
}

callback(tailEvent)
}

if IsTerminalStatus(*stack.StackStatus) {
return &TailStackEvent{*events[0]}, nil
}
}
}

Expand Down
2 changes: 1 addition & 1 deletion sample/sample.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ Resources:
LogGroup:
Type: AWS::Logs::LogGroup
Properties:
LogGroupName: test-stack-LogGroup
LogGroupName: !Sub ${AWS::StackName}-LogGroup
Outputs:
LogGroup:
Value: !Ref LogGroup
Expand Down

0 comments on commit 14643ef

Please sign in to comment.