-
Notifications
You must be signed in to change notification settings - Fork 0
/
plan.go
115 lines (99 loc) · 2.68 KB
/
plan.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
package clon
import (
"fmt"
"strconv"
"strings"
"github.com/juju/errors"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/arn"
"github.com/aws/aws-sdk-go/service/cloudformation"
"github.com/spirius/clon/pkg/cfn"
)
// DiffString is helper type for tracking
// changes in string types.
type DiffString struct {
old, new string
}
// String returns string representation of diff.
func (d DiffString) String() string {
if d.IsEqual() {
return strconv.Quote(d.old)
}
return fmt.Sprintf(`%s => %s`, strconv.Quote(d.old), strconv.Quote(d.new))
}
// IsEqual indicates if underlying strings are equal.
func (d DiffString) IsEqual() bool {
return d.old == d.new
}
// DiffStringMap is map of string diffs.
type DiffStringMap map[string]DiffString
// HasChange indicates if there is a change in
// any of strings in underlying map.
func (d DiffStringMap) HasChange() bool {
for _, diff := range d {
if !diff.IsEqual() {
return true
}
}
return false
}
func newDiffStringMap(src map[string]string, dst map[string]string) DiffStringMap {
res := make(map[string]DiffString)
for k, v := range src {
res[k] = DiffString{old: v}
}
for k, v := range dst {
r, ok := res[k]
if ok {
r.new = v
} else {
r = DiffString{new: v}
}
res[k] = r
}
return res
}
// Plan represents the plan of changes on stack.
type Plan struct {
ID string
ChangeSet *cfn.ChangeSetData
Stack *StackData
RoleARN DiffString
Parameters DiffStringMap
HasChange bool
}
func newPlan(cs *cfn.ChangeSetData, stack *StackData, ignoreNestedUpdates bool) (*Plan, error) {
csARN, err := arn.Parse(cs.ID)
if err != nil {
return nil, errors.Annotatef(err, "cannot parse change set id '%s'", cs.ID)
}
p := &Plan{
ID: strings.TrimPrefix(csARN.Resource, "changeSet/"),
ChangeSet: cs,
Stack: stack,
RoleARN: DiffString{stack.RoleARN, cs.StackData.RoleARN},
Parameters: newDiffStringMap(stack.Parameters, cs.StackData.Parameters),
}
// If Changes contain only Automatic updates on nested stacks,
// we consider it as no-change. We assume, that nested stack
// can contain changes only if input parameters or template URL are changed.
if ignoreNestedUpdates {
for _, c := range p.ChangeSet.Changes {
if len(c.Details) != 1 {
p.HasChange = true
break
}
d := c.Details[0]
if aws.StringValue(d.ChangeSource) != "Automatic" ||
aws.StringValue(d.Evaluation) != "Dynamic" ||
aws.StringValue(d.Target.Attribute) != "Properties" ||
aws.StringValue(d.Target.RequiresRecreation) != "Never" {
p.HasChange = true
break
}
}
} else {
p.HasChange = p.ChangeSet.ExecutionStatus == cloudformation.ExecutionStatusAvailable
}
return p, nil
}