forked from crewjam/go-cloudformation
/
watcher.go
135 lines (124 loc) · 4.16 KB
/
watcher.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
package deploycfn
import (
"fmt"
"strings"
"time"
log "github.com/Sirupsen/logrus"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/client"
"github.com/aws/aws-sdk-go/service/cloudformation"
)
// StackEventWatcher watches a CloudFormation stack for events and emits
// them to the log channel.
type StackEventWatcher struct {
Service *cloudformation.CloudFormation
StackName string
// seenEvents maps the events that have already been printed. If the stack
// already exists, then we want to get all the existing events into this
// set so we don't re-print old events.
seenEvents map[string]struct{}
}
// NewStackEventWatcher returns a new StackEventWatcher that emits events for the
// specified stack. It scans all the existing events and adds them to seenEvents
// so that events the occur prior to the invocation of this function will not be
// printed.
func NewStackEventWatcher(session client.ConfigProvider, stackName string) (*StackEventWatcher, error) {
sw := StackEventWatcher{
Service: cloudformation.New(session),
StackName: stackName,
seenEvents: map[string]struct{}{},
}
err := sw.Service.DescribeStackEventsPages(&cloudformation.DescribeStackEventsInput{
StackName: aws.String(sw.StackName),
}, func(p *cloudformation.DescribeStackEventsOutput, _ bool) bool {
for _, stackEvent := range p.StackEvents {
sw.seenEvents[*stackEvent.EventId] = struct{}{}
}
return true
})
if err != nil {
return nil, err
}
return &sw, nil
}
// Watch monitors the stack for events, reporting each unseen event to the
// log channel. Returns when the stack enters a non-transitional state. The
// return value is non-nil if the final state is an error state.
func (sw *StackEventWatcher) Watch() error {
if sw.seenEvents == nil {
sw.seenEvents = map[string]struct{}{}
}
lastStackStatus := ""
for {
// print the events for the stack
sw.Service.DescribeStackEventsPages(&cloudformation.DescribeStackEventsInput{
StackName: aws.String(sw.StackName),
}, func(p *cloudformation.DescribeStackEventsOutput, _ bool) bool {
for _, stackEvent := range p.StackEvents {
if _, ok := sw.seenEvents[*stackEvent.EventId]; ok {
continue
}
wrapStrPtr := func(s *string) string {
if s == nil {
return ""
}
return *s
}
l := log.WithField("Status", *stackEvent.ResourceStatus)
if stackEvent.ResourceType != nil {
l = l.WithField("Type", *stackEvent.ResourceType)
}
if stackEvent.ResourceType != nil {
l = l.WithField("Type", *stackEvent.ResourceType)
}
if stackEvent.PhysicalResourceId != nil {
l = l.WithField("PhysicalID", *stackEvent.PhysicalResourceId)
}
if stackEvent.LogicalResourceId != nil {
l = l.WithField("LogicalID", *stackEvent.LogicalResourceId)
}
if strings.Contains(*stackEvent.ResourceStatus, "FAIL") {
l.Error(wrapStrPtr(stackEvent.ResourceStatusReason))
} else {
l.Info(wrapStrPtr(stackEvent.ResourceStatusReason))
}
sw.seenEvents[*stackEvent.EventId] = struct{}{}
}
return true
})
// monitor the status of the stack
describeStacksResponse, err := sw.Service.DescribeStacks(&cloudformation.DescribeStacksInput{
StackName: aws.String(sw.StackName),
})
if err != nil {
// the stack might not exist yet
log.Errorf("DescribeStacks: %s", err)
time.Sleep(time.Second)
continue
}
stackStatus := *describeStacksResponse.Stacks[0].StackStatus
if stackStatus != lastStackStatus {
log.Infof("Stack: %s\n", stackStatus)
lastStackStatus = stackStatus
}
switch stackStatus {
case cloudformation.StackStatusCreateComplete:
return nil
case cloudformation.StackStatusCreateFailed:
return fmt.Errorf("%s", stackStatus)
case cloudformation.StackStatusRollbackComplete:
return fmt.Errorf("%s", stackStatus)
case cloudformation.StackStatusUpdateRollbackComplete:
return fmt.Errorf("%s", stackStatus)
case cloudformation.StackStatusRollbackFailed:
return fmt.Errorf("%s", stackStatus)
case cloudformation.StackStatusUpdateComplete:
return nil
case cloudformation.StackStatusUpdateRollbackFailed:
return fmt.Errorf("%s", stackStatus)
default:
time.Sleep(time.Second * 5)
continue
}
}
}