github.com/mweagle/Sparta@v1.15.0/resource_references.go (about)

     1  // +build !lambdabinary
     2  
     3  package sparta
     4  
     5  import (
     6  	"encoding/json"
     7  	"reflect"
     8  	"strings"
     9  
    10  	gocf "github.com/mweagle/go-cloudformation"
    11  	"github.com/pkg/errors"
    12  	"github.com/sirupsen/logrus"
    13  )
    14  
    15  type resourceRefType int
    16  
    17  const (
    18  	resourceLiteral resourceRefType = iota
    19  	resourceRefFunc
    20  	resourceGetAttrFunc
    21  	resourceStringFunc
    22  )
    23  
    24  type resourceRef struct {
    25  	RefType      resourceRefType
    26  	ResourceName string
    27  }
    28  
    29  // resolvedResourceVisitor represents the signature of a function that
    30  // visits
    31  type resolvedResourceVisitor func(lambdaAWSInfo *LambdaAWSInfo,
    32  	eventSourceMapping *EventSourceMapping,
    33  	mappingIndex int,
    34  	resource *resourceRef) error
    35  
    36  // resolveResourceRef takes an interface representing a dynamic ARN
    37  // and tries to determine the CloudFormation resource name it resolves to
    38  func resolveResourceRef(expr interface{}) (*resourceRef, error) {
    39  
    40  	// Is there any chance it's just a string?
    41  	typedString, typedStringOk := expr.(string)
    42  	if typedStringOk {
    43  		return &resourceRef{
    44  			RefType:      resourceLiteral,
    45  			ResourceName: typedString,
    46  		}, nil
    47  	}
    48  	// Some type of intrinsic function?
    49  	marshalled, marshalledErr := json.Marshal(expr)
    50  	if marshalledErr != nil {
    51  		return nil, errors.Errorf("Failed to unmarshal dynamic resource ref %v", expr)
    52  	}
    53  	var refFunc gocf.RefFunc
    54  	if json.Unmarshal(marshalled, &refFunc) == nil &&
    55  		len(refFunc.Name) != 0 {
    56  		return &resourceRef{
    57  			RefType:      resourceRefFunc,
    58  			ResourceName: refFunc.Name,
    59  		}, nil
    60  	}
    61  
    62  	var getAttFunc gocf.GetAttFunc
    63  	if json.Unmarshal(marshalled, &getAttFunc) == nil && len(getAttFunc.Resource) != 0 {
    64  		return &resourceRef{
    65  			RefType:      resourceGetAttrFunc,
    66  			ResourceName: getAttFunc.Resource,
    67  		}, nil
    68  	}
    69  	// Any chance it's a string?
    70  	var stringExprFunc gocf.StringExpr
    71  	if json.Unmarshal(marshalled, &stringExprFunc) == nil && len(stringExprFunc.Literal) != 0 {
    72  		return &resourceRef{
    73  			RefType:      resourceStringFunc,
    74  			ResourceName: stringExprFunc.Literal,
    75  		}, nil
    76  	}
    77  
    78  	// Nope
    79  	return nil, nil
    80  }
    81  
    82  // isResolvedResourceType is a utility function to determine if a resolved
    83  // reference is a given type. If it is a literal, the literalTokenIndicator
    84  // substring match is used for the predicate. If it is a resource provisioned
    85  // by this template, the &gocf.RESOURCE_TYPE{} will be used via reflection
    86  // Example:
    87  // isResolvedResourceType(resourceRef, template, ":dynamodb:", &gocf.DynamoDBTable{}) {
    88  //
    89  func isResolvedResourceType(resource *resourceRef,
    90  	template *gocf.Template,
    91  	literalTokenIndicator string,
    92  	templateType gocf.ResourceProperties) bool {
    93  	if resource.RefType == resourceLiteral ||
    94  		resource.RefType == resourceStringFunc {
    95  		return strings.Contains(resource.ResourceName, literalTokenIndicator)
    96  	}
    97  
    98  	// Dynamically provisioned resource included in the template definition?
    99  	existingResource, existingResourceExists := template.Resources[resource.ResourceName]
   100  	if existingResourceExists {
   101  		if reflect.TypeOf(existingResource.Properties) == reflect.TypeOf(templateType) {
   102  			return true
   103  		}
   104  	}
   105  	return false
   106  }
   107  
   108  // visitResolvedEventSourceMapping is a utility function that visits all the EventSourceMapping
   109  // entries for the given lambdaAWSInfo struct
   110  func visitResolvedEventSourceMapping(visitor resolvedResourceVisitor,
   111  	lambdaAWSInfos []*LambdaAWSInfo,
   112  	template *gocf.Template,
   113  	logger *logrus.Logger) error {
   114  
   115  	//
   116  	// BEGIN
   117  	// Inline closure to wrap the visitor function so that we can provide
   118  	// specific error messages
   119  	visitEventSourceMappingRef := func(lambdaAWSInfo *LambdaAWSInfo,
   120  		eventSourceMapping *EventSourceMapping,
   121  		mappingIndex int,
   122  		resource *resourceRef) error {
   123  
   124  		annotateStatementsErr := visitor(lambdaAWSInfo,
   125  			eventSourceMapping,
   126  			mappingIndex,
   127  			resource)
   128  
   129  		// Early exit?
   130  		if annotateStatementsErr != nil {
   131  			return errors.Wrapf(annotateStatementsErr,
   132  				"Visiting event source mapping: %#v",
   133  				eventSourceMapping)
   134  		}
   135  		return nil
   136  	}
   137  	//
   138  	// END
   139  
   140  	// Iterate through every lambda function. If there is an EventSourceMapping
   141  	// that points to a piece of infastructure provisioned by this stack,
   142  	// find the referred resource and supply it to the visitor
   143  	for _, eachLambda := range lambdaAWSInfos {
   144  		for eachIndex, eachEventSource := range eachLambda.EventSourceMappings {
   145  			resourceRef, resourceRefErr := resolveResourceRef(eachEventSource.EventSourceArn)
   146  			if resourceRefErr != nil {
   147  				return errors.Wrapf(resourceRefErr,
   148  					"Failed to resolve EventSourceArn: %#v", eachEventSource)
   149  			}
   150  
   151  			// At this point everything is a string, so we need to unmarshall
   152  			// and see if the Arn is supplied by either a Ref or a GetAttr
   153  			// function. In those cases, we need to look around in the template
   154  			// to go from: EventMapping -> Type -> Lambda -> LambdaIAMRole
   155  			// so that we can add the permissions
   156  			if resourceRef != nil {
   157  				annotationErr := visitEventSourceMappingRef(eachLambda,
   158  					eachEventSource,
   159  					eachIndex,
   160  					resourceRef)
   161  				// Anything go wrong?
   162  				if annotationErr != nil {
   163  					return errors.Wrapf(annotationErr,
   164  						"Failed to annotate template for EventSourceMapping: %#v",
   165  						eachEventSource)
   166  				}
   167  			}
   168  		}
   169  	}
   170  	return nil
   171  }