diff --git a/aws/data_source_aws_lambda_invocation.go b/aws/data_source_aws_lambda_invocation.go new file mode 100644 index 00000000000..6e532686fa9 --- /dev/null +++ b/aws/data_source_aws_lambda_invocation.go @@ -0,0 +1,94 @@ +package aws + +import ( + "crypto/md5" + "encoding/json" + "fmt" + "log" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/lambda" + "github.com/hashicorp/terraform/helper/schema" +) + +func dataSourceAwsLambdaInvocation() *schema.Resource { + return &schema.Resource{ + Read: dataSourceAwsLambdaInvocationRead, + + Schema: map[string]*schema.Schema{ + "function_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "qualifier": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Default: "$LATEST", + }, + + "input": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validateJsonString, + }, + + "result": { + Type: schema.TypeString, + Computed: true, + }, + + "result_map": { + Type: schema.TypeMap, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + }, + } +} + +func dataSourceAwsLambdaInvocationRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).lambdaconn + + functionName := d.Get("function_name").(string) + qualifier := d.Get("qualifier").(string) + input := []byte(d.Get("input").(string)) + + res, err := conn.Invoke(&lambda.InvokeInput{ + FunctionName: aws.String(functionName), + InvocationType: aws.String(lambda.InvocationTypeRequestResponse), + Payload: input, + Qualifier: aws.String(qualifier), + }) + + if err != nil { + return err + } + + if res.FunctionError != nil { + return fmt.Errorf("Lambda function (%s) returned error: (%s)", functionName, string(res.Payload)) + } + + if err = d.Set("result", string(res.Payload)); err != nil { + return err + } + + var result map[string]interface{} + + if err = json.Unmarshal(res.Payload, &result); err != nil { + return err + } + + if err = d.Set("result_map", result); err != nil { + log.Printf("[WARN] Cannot use the result invocation as a string map: %s", err) + } + + d.SetId(fmt.Sprintf("%s_%s_%x", functionName, qualifier, md5.Sum(input))) + + return nil +} diff --git a/aws/data_source_aws_lambda_invocation_test.go b/aws/data_source_aws_lambda_invocation_test.go new file mode 100644 index 00000000000..6ade20a3365 --- /dev/null +++ b/aws/data_source_aws_lambda_invocation_test.go @@ -0,0 +1,215 @@ +package aws + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func testAccCheckLambdaInvocationResult(name, expectedResult string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[name] + if !ok { + return fmt.Errorf("Not found: %s", name) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No ID is set") + } + + result, ok := rs.Primary.Attributes["result"] + + if !ok { + return fmt.Errorf("No result is set") + } + + if !suppressEquivalentJsonDiffs("", result, expectedResult, nil) { + return fmt.Errorf("%s: Attribute 'result' expected %s, got %s", name, expectedResult, result) + } + + return nil + } +} + +func TestAccDataSourceAwsLambdaInvocation_basic(t *testing.T) { + testData := "value3" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccDataSourceAwsLambdaInvocation_basic_config("tf-test-lambda-role", "tf-test-lambda-invocation", testData), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("data.aws_lambda_invocation.invocation_test", "result_map.%", "3"), + resource.TestCheckResourceAttr("data.aws_lambda_invocation.invocation_test", "result_map.key1", "value1"), + resource.TestCheckResourceAttr("data.aws_lambda_invocation.invocation_test", "result_map.key2", "value2"), + resource.TestCheckResourceAttr("data.aws_lambda_invocation.invocation_test", "result_map.key3", testData), + testAccCheckLambdaInvocationResult("data.aws_lambda_invocation.invocation_test", `{"key1":"value1","key2":"value2","key3":"`+testData+`"}`), + ), + }, + }, + }) +} + +func TestAccDataSourceAwsLambdaInvocation_qualifier(t *testing.T) { + testData := "value3" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccDataSourceAwsLambdaInvocation_qualifier_config("tf-test-lambda-role-qualifier", "tf-test-lambda-invocation-qualifier", testData), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("data.aws_lambda_invocation.invocation_test", "result_map.%", "3"), + resource.TestCheckResourceAttr("data.aws_lambda_invocation.invocation_test", "result_map.key1", "value1"), + resource.TestCheckResourceAttr("data.aws_lambda_invocation.invocation_test", "result_map.key2", "value2"), + resource.TestCheckResourceAttr("data.aws_lambda_invocation.invocation_test", "result_map.key3", testData), + ), + }, + }, + }) +} + +func TestAccDataSourceAwsLambdaInvocation_complex(t *testing.T) { + testData := "value3" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccDataSourceAwsLambdaInvocation_complex_config("tf-test-lambda-role-complex", "tf-test-lambda-invocation-complex", testData), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckNoResourceAttr("data.aws_lambda_invocation.invocation_test", "result_map"), + testAccCheckLambdaInvocationResult("data.aws_lambda_invocation.invocation_test", `{"key1":{"subkey1":"subvalue1"},"key2":{"subkey2":"subvalue2","subkey3":{"a": "b"}},"key3":"`+testData+`"}`), + ), + }, + }, + }) +} + +func testAccDataSourceAwsLambdaInvocation_base_config(roleName string) string { + return fmt.Sprintf(` +data "aws_iam_policy_document" "lambda_assume_role_policy" { + statement { + effect = "Allow" + actions = ["sts:AssumeRole"] + + principals { + type = "Service" + identifiers = ["lambda.amazonaws.com"] + } + } +} + +resource "aws_iam_role" "lambda_role" { + name = "%s" + assume_role_policy = "${data.aws_iam_policy_document.lambda_assume_role_policy.json}" +} + +resource "aws_iam_role_policy_attachment" "lambda_role_policy" { + policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + role = "${aws_iam_role.lambda_role.name}" +} +`, roleName) +} + +func testAccDataSourceAwsLambdaInvocation_basic_config(roleName, lambdaName, testData string) string { + return fmt.Sprintf(testAccDataSourceAwsLambdaInvocation_base_config(roleName)+` +resource "aws_lambda_function" "lambda" { + depends_on = ["aws_iam_role_policy_attachment.lambda_role_policy"] + + filename = "test-fixtures/lambda_invocation.zip" + function_name = "%s" + role = "${aws_iam_role.lambda_role.arn}" + handler = "lambda_invocation.handler" + runtime = "nodejs8.10" + + environment { + variables = { + TEST_DATA = "%s" + } + } +} + +data "aws_lambda_invocation" "invocation_test" { + function_name = "${aws_lambda_function.lambda.function_name}" + + input = < { + if (process.env.TEST_DATA) { + event.key3 = process.env.TEST_DATA; + } + return event; +} diff --git a/aws/test-fixtures/lambda_invocation.zip b/aws/test-fixtures/lambda_invocation.zip new file mode 100644 index 00000000000..b2bc4cde4e1 Binary files /dev/null and b/aws/test-fixtures/lambda_invocation.zip differ diff --git a/website/aws.erb b/website/aws.erb index 91f132be189..44eafce3c4e 100644 --- a/website/aws.erb +++ b/website/aws.erb @@ -202,6 +202,9 @@ > aws_kms_secret + > + aws_lambda_invocation + > aws_nat_gateway diff --git a/website/docs/d/lambda_invocation.html.markdown b/website/docs/d/lambda_invocation.html.markdown new file mode 100644 index 00000000000..b472a71e32e --- /dev/null +++ b/website/docs/d/lambda_invocation.html.markdown @@ -0,0 +1,49 @@ +--- +layout: "aws" +page_title: "AWS: aws_lambda_invocation" +sidebar_current: "docs-aws-datasource-lambda-invocation" +description: |- + Invoke AWS Lambda Function as data source +--- + +# Data Source: aws_lambda_invocation + +Use this data source to invoke custom lambda functions as data source. +The lambda function is invoked with [RequestResponse](https://docs.aws.amazon.com/lambda/latest/dg/API_Invoke.html#API_Invoke_RequestSyntax) +invocation type. + +## Example Usage + +```hcl +data "aws_lambda_invocation" "example" { + function_name = "${aws_lambda_function.lambda_function_test.function_name}" + + input = <