cfn/wrap.go (63 lines of code) (raw):
// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
package cfn
import (
"context"
"encoding/json"
"errors"
"log"
"net/http"
"github.com/aws/aws-lambda-go/events"
"github.com/aws/aws-lambda-go/lambdacontext"
)
// CustomResourceLambdaFunction is a standard form Lambda for a Custom Resource.
type CustomResourceLambdaFunction func(context.Context, Event) (reason string, err error)
// SNSCustomResourceLambdaFunction is a standard form Lambda for a Custom Resource
// that is triggered via a SNS topic.
type SNSCustomResourceLambdaFunction func(context.Context, events.SNSEvent) (reason string, err error)
// CustomResourceFunction is a representation of the customer's Custom Resource function.
// LambdaWrap will take the returned values and turn them into a response to be sent
// to CloudFormation.
type CustomResourceFunction func(context.Context, Event) (physicalResourceID string, data map[string]interface{}, err error)
func lambdaWrapWithClient(lambdaFunction CustomResourceFunction, client httpClient) (fn CustomResourceLambdaFunction) {
fn = func(ctx context.Context, event Event) (reason string, err error) {
r := NewResponse(&event)
funcDidPanic := true
defer func() {
if funcDidPanic {
r.Status = StatusFailed
r.Reason = "Function panicked, see log stream for details"
// FIXME: something should be done if an error is returned here
_ = r.sendWith(client)
}
}()
r.PhysicalResourceID, r.Data, err = lambdaFunction(ctx, event)
funcDidPanic = false
if err != nil {
r.Status = StatusFailed
r.Reason = err.Error()
log.Printf("sending status failed: %s", r.Reason)
} else {
r.Status = StatusSuccess
if r.PhysicalResourceID == "" {
log.Println("PhysicalResourceID must exist on creation, copying Log Stream name")
r.PhysicalResourceID = lambdacontext.LogStreamName
}
}
err = r.sendWith(client)
if err != nil {
reason = err.Error()
}
return
}
return
}
// LambdaWrap returns a CustomResourceLambdaFunction which is something lambda.Start()
// will understand. The purpose of doing this is so that Response Handling boiler
// plate is taken away from the customer and it makes writing a Custom Resource
// simpler.
//
// func myLambda(ctx context.Context, event cfn.Event) (physicalResourceID string, data map[string]interface{}, err error) {
// physicalResourceID = "arn:...."
// return
// }
//
// func main() {
// lambda.Start(cfn.LambdaWrap(myLambda))
// }
func LambdaWrap(lambdaFunction CustomResourceFunction) (fn CustomResourceLambdaFunction) {
return lambdaWrapWithClient(lambdaFunction, http.DefaultClient)
}
// LambdaWrapSNS wraps a Lambda handler with support for SNS-based custom
// resources. Usage and purpose otherwise same as LambdaWrap().
func LambdaWrapSNS(lambdaFunction CustomResourceFunction) SNSCustomResourceLambdaFunction {
inner := LambdaWrap(lambdaFunction)
return func(ctx context.Context, event events.SNSEvent) (reason string, err error) {
if len(event.Records) != 1 {
err = errors.New("expected exactly 1 incoming record")
return
}
message := event.Records[0].SNS.Message
var innerEvent Event
if err = json.Unmarshal([]byte(message), &innerEvent); err != nil {
return
}
return inner(ctx, innerEvent)
}
}