github.com/darmach/terratest@v0.34.8-0.20210517103231-80931f95e3ff/modules/aws/lambda.go (about) 1 package aws 2 3 import ( 4 "encoding/json" 5 "errors" 6 "fmt" 7 8 "github.com/aws/aws-sdk-go/service/lambda" 9 "github.com/gruntwork-io/terratest/modules/testing" 10 "github.com/stretchr/testify/require" 11 ) 12 13 type InvocationTypeOption string 14 15 const ( 16 InvocationTypeRequestResponse InvocationTypeOption = "RequestResponse" 17 InvocationTypeDryRun = "DryRun" 18 ) 19 20 func (itype *InvocationTypeOption) Value() (string, error) { 21 if itype != nil { 22 switch *itype { 23 case 24 InvocationTypeRequestResponse, 25 InvocationTypeDryRun: 26 return string(*itype), nil 27 default: 28 msg := fmt.Sprintf("LambdaOptions.InvocationType, if specified, must either be \"%s\" or \"%s\"", 29 InvocationTypeRequestResponse, 30 InvocationTypeDryRun) 31 return "", errors.New(msg) 32 } 33 } 34 return string(InvocationTypeRequestResponse), nil 35 } 36 37 // LambdaOptions contains additional parameters for InvokeFunctionWithParams(). 38 // It contains a subset of the fields found in the lambda.InvokeInput struct. 39 type LambdaOptions struct { 40 // InvocationType can be one of InvocationTypeOption values: 41 // * InvocationTypeRequestResponse (default) - Invoke the function 42 // synchronously. Keep the connection open until the function 43 // returns a response or times out. 44 // * InvocationTypeDryRun - Validate parameter values and verify 45 // that the user or role has permission to invoke the function. 46 InvocationType *InvocationTypeOption 47 48 // Lambda function input; will be converted to JSON. 49 Payload interface{} 50 } 51 52 // LambdaOutput contains the output from InvokeFunctionWithParams(). The 53 // fields may or may not have a value depending on the invocation type and 54 // whether an error occurred or not. 55 type LambdaOutput struct { 56 // The response from the function, or an error object. 57 Payload []byte 58 59 // The HTTP status code for a successful request is in the 200 range. 60 // For RequestResponse invocation type, the status code is 200. 61 // For the DryRun invocation type, the status code is 204. 62 StatusCode *int64 63 } 64 65 // InvokeFunction invokes a lambda function. 66 func InvokeFunction(t testing.TestingT, region, functionName string, payload interface{}) []byte { 67 out, err := InvokeFunctionE(t, region, functionName, payload) 68 require.NoError(t, err) 69 return out 70 } 71 72 // InvokeFunctionE invokes a lambda function. 73 func InvokeFunctionE(t testing.TestingT, region, functionName string, payload interface{}) ([]byte, error) { 74 lambdaClient, err := NewLambdaClientE(t, region) 75 if err != nil { 76 return nil, err 77 } 78 79 invokeInput := &lambda.InvokeInput{ 80 FunctionName: &functionName, 81 } 82 83 if payload != nil { 84 payloadJson, err := json.Marshal(payload) 85 86 if err != nil { 87 return nil, err 88 } 89 invokeInput.Payload = payloadJson 90 } 91 92 out, err := lambdaClient.Invoke(invokeInput) 93 require.NoError(t, err) 94 if err != nil { 95 return nil, err 96 } 97 98 if out.FunctionError != nil { 99 return out.Payload, &FunctionError{Message: *out.FunctionError, StatusCode: *out.StatusCode, Payload: out.Payload} 100 } 101 102 return out.Payload, nil 103 } 104 105 // InvokeFunctionWithParams invokes a lambda function using parameters 106 // supplied in the LambdaOptions struct and returns values in a LambdaOutput 107 // struct. Checks for failure using "require". 108 func InvokeFunctionWithParams(t testing.TestingT, region, functionName string, input *LambdaOptions) *LambdaOutput { 109 out, err := InvokeFunctionWithParamsE(t, region, functionName, input) 110 require.NoError(t, err) 111 return out 112 } 113 114 // InvokeFunctionWithParamsE invokes a lambda function using parameters 115 // supplied in the LambdaOptions struct. Returns the status code and payload 116 // in a LambdaOutput struct and the error. A non-nil error will either reflect 117 // a problem with the parameters supplied to this function or an error returned 118 // by the Lambda. 119 func InvokeFunctionWithParamsE(t testing.TestingT, region, functionName string, input *LambdaOptions) (*LambdaOutput, error) { 120 lambdaClient, err := NewLambdaClientE(t, region) 121 if err != nil { 122 return nil, err 123 } 124 125 // Verify the InvocationType is one of the allowed values and report 126 // an error if it's not. By default the InvocationType will be 127 // "RequestResponse". 128 invocationType, err := input.InvocationType.Value() 129 if err != nil { 130 return nil, err 131 } 132 133 invokeInput := &lambda.InvokeInput{ 134 FunctionName: &functionName, 135 InvocationType: &invocationType, 136 } 137 138 if input.Payload != nil { 139 payloadJson, err := json.Marshal(input.Payload) 140 if err != nil { 141 return nil, err 142 } 143 invokeInput.Payload = payloadJson 144 } 145 146 out, err := lambdaClient.Invoke(invokeInput) 147 if err != nil { 148 return nil, err 149 } 150 151 // As this function supports different invocation types, it must 152 // then support different combinations of output other than just 153 // payload. 154 lambdaOutput := LambdaOutput{ 155 Payload: out.Payload, 156 StatusCode: out.StatusCode, 157 } 158 159 if out.FunctionError != nil { 160 return &lambdaOutput, errors.New(*out.FunctionError) 161 } 162 163 return &lambdaOutput, nil 164 } 165 166 type FunctionError struct { 167 Message string 168 StatusCode int64 169 Payload []byte 170 } 171 172 func (err *FunctionError) Error() string { 173 return fmt.Sprintf("%s error invoking lambda function: %v", err.Message, err.Payload) 174 } 175 176 // NewLambdaClient creates a new Lambda client. 177 func NewLambdaClient(t testing.TestingT, region string) *lambda.Lambda { 178 client, err := NewLambdaClientE(t, region) 179 require.NoError(t, err) 180 return client 181 } 182 183 // NewLambdaClientE creates a new Lambda client. 184 func NewLambdaClientE(t testing.TestingT, region string) (*lambda.Lambda, error) { 185 sess, err := NewAuthenticatedSession(region) 186 if err != nil { 187 return nil, err 188 } 189 190 return lambda.New(sess), nil 191 }