-
Notifications
You must be signed in to change notification settings - Fork 48
/
resource_references.go
171 lines (152 loc) · 5.01 KB
/
resource_references.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
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
// +build !lambdabinary
package sparta
import (
"encoding/json"
"reflect"
"strings"
gocf "github.com/mweagle/go-cloudformation"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
type resourceRefType int
const (
resourceLiteral resourceRefType = iota
resourceRefFunc
resourceGetAttrFunc
resourceStringFunc
)
type resourceRef struct {
RefType resourceRefType
ResourceName string
}
// resolvedResourceVisitor represents the signature of a function that
// visits
type resolvedResourceVisitor func(lambdaAWSInfo *LambdaAWSInfo,
eventSourceMapping *EventSourceMapping,
mappingIndex int,
resource *resourceRef) error
// resolveResourceRef takes an interface representing a dynamic ARN
// and tries to determine the CloudFormation resource name it resolves to
func resolveResourceRef(expr interface{}) (*resourceRef, error) {
// Is there any chance it's just a string?
typedString, typedStringOk := expr.(string)
if typedStringOk {
return &resourceRef{
RefType: resourceLiteral,
ResourceName: typedString,
}, nil
}
// Some type of intrinsic function?
marshalled, marshalledErr := json.Marshal(expr)
if marshalledErr != nil {
return nil, errors.Errorf("Failed to unmarshal dynamic resource ref %v", expr)
}
var refFunc gocf.RefFunc
if json.Unmarshal(marshalled, &refFunc) == nil &&
len(refFunc.Name) != 0 {
return &resourceRef{
RefType: resourceRefFunc,
ResourceName: refFunc.Name,
}, nil
}
var getAttFunc gocf.GetAttFunc
if json.Unmarshal(marshalled, &getAttFunc) == nil && len(getAttFunc.Resource) != 0 {
return &resourceRef{
RefType: resourceGetAttrFunc,
ResourceName: getAttFunc.Resource,
}, nil
}
// Any chance it's a string?
var stringExprFunc gocf.StringExpr
if json.Unmarshal(marshalled, &stringExprFunc) == nil && len(stringExprFunc.Literal) != 0 {
return &resourceRef{
RefType: resourceStringFunc,
ResourceName: stringExprFunc.Literal,
}, nil
}
// Nope
return nil, nil
}
// isResolvedResourceType is a utility function to determine if a resolved
// reference is a given type. If it is a literal, the literalTokenIndicator
// substring match is used for the predicate. If it is a resource provisioned
// by this template, the &gocf.RESOURCE_TYPE{} will be used via reflection
// Example:
// isResolvedResourceType(resourceRef, template, ":dynamodb:", &gocf.DynamoDBTable{}) {
//
func isResolvedResourceType(resource *resourceRef,
template *gocf.Template,
literalTokenIndicator string,
templateType gocf.ResourceProperties) bool {
if resource.RefType == resourceLiteral ||
resource.RefType == resourceStringFunc {
return strings.Contains(resource.ResourceName, literalTokenIndicator)
}
// Dynamically provisioned resource included in the template definition?
existingResource, existingResourceExists := template.Resources[resource.ResourceName]
if existingResourceExists {
if reflect.TypeOf(existingResource.Properties) == reflect.TypeOf(templateType) {
return true
}
}
return false
}
// visitResolvedEventSourceMapping is a utility function that visits all the EventSourceMapping
// entries for the given lambdaAWSInfo struct
func visitResolvedEventSourceMapping(visitor resolvedResourceVisitor,
lambdaAWSInfos []*LambdaAWSInfo,
template *gocf.Template,
logger *logrus.Logger) error {
//
// BEGIN
// Inline closure to wrap the visitor function so that we can provide
// specific error messages
visitEventSourceMappingRef := func(lambdaAWSInfo *LambdaAWSInfo,
eventSourceMapping *EventSourceMapping,
mappingIndex int,
resource *resourceRef) error {
annotateStatementsErr := visitor(lambdaAWSInfo,
eventSourceMapping,
mappingIndex,
resource)
// Early exit?
if annotateStatementsErr != nil {
return errors.Wrapf(annotateStatementsErr,
"Visiting event source mapping: %#v",
eventSourceMapping)
}
return nil
}
//
// END
// Iterate through every lambda function. If there is an EventSourceMapping
// that points to a piece of infastructure provisioned by this stack,
// find the referred resource and supply it to the visitor
for _, eachLambda := range lambdaAWSInfos {
for eachIndex, eachEventSource := range eachLambda.EventSourceMappings {
resourceRef, resourceRefErr := resolveResourceRef(eachEventSource.EventSourceArn)
if resourceRefErr != nil {
return errors.Wrapf(resourceRefErr,
"Failed to resolve EventSourceArn: %#v", eachEventSource)
}
// At this point everything is a string, so we need to unmarshall
// and see if the Arn is supplied by either a Ref or a GetAttr
// function. In those cases, we need to look around in the template
// to go from: EventMapping -> Type -> Lambda -> LambdaIAMRole
// so that we can add the permissions
if resourceRef != nil {
annotationErr := visitEventSourceMappingRef(eachLambda,
eachEventSource,
eachIndex,
resourceRef)
// Anything go wrong?
if annotationErr != nil {
return errors.Wrapf(annotationErr,
"Failed to annotate template for EventSourceMapping: %#v",
eachEventSource)
}
}
}
}
return nil
}