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  }