diff --git a/aws/provider.go b/aws/provider.go index b52a7b1670f..f104c9f7178 100644 --- a/aws/provider.go +++ b/aws/provider.go @@ -594,12 +594,13 @@ func Provider() terraform.ResourceProvider { "aws_kms_grant": resourceAwsKmsGrant(), "aws_kms_key": resourceAwsKmsKey(), "aws_kms_ciphertext": resourceAwsKmsCiphertext(), - "aws_lambda_function": resourceAwsLambdaFunction(), - "aws_lambda_event_source_mapping": resourceAwsLambdaEventSourceMapping(), "aws_lambda_alias": resourceAwsLambdaAlias(), + "aws_lambda_event_source_mapping": resourceAwsLambdaEventSourceMapping(), + "aws_lambda_function_event_invoke_config": resourceAwsLambdaFunctionEventInvokeConfig(), + "aws_lambda_function": resourceAwsLambdaFunction(), + "aws_lambda_layer_version": resourceAwsLambdaLayerVersion(), "aws_lambda_permission": resourceAwsLambdaPermission(), "aws_lambda_provisioned_concurrency_config": resourceAwsLambdaProvisionedConcurrencyConfig(), - "aws_lambda_layer_version": resourceAwsLambdaLayerVersion(), "aws_launch_configuration": resourceAwsLaunchConfiguration(), "aws_launch_template": resourceAwsLaunchTemplate(), "aws_licensemanager_association": resourceAwsLicenseManagerAssociation(), diff --git a/aws/resource_aws_lambda_function_event_invoke_config.go b/aws/resource_aws_lambda_function_event_invoke_config.go new file mode 100644 index 00000000000..2b28507ded3 --- /dev/null +++ b/aws/resource_aws_lambda_function_event_invoke_config.go @@ -0,0 +1,415 @@ +package aws + +import ( + "fmt" + "log" + "strings" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/arn" + "github.com/aws/aws-sdk-go/service/lambda" + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/helper/validation" +) + +func resourceAwsLambdaFunctionEventInvokeConfig() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsLambdaFunctionEventInvokeConfigCreate, + Read: resourceAwsLambdaFunctionEventInvokeConfigRead, + Update: resourceAwsLambdaFunctionEventInvokeConfigUpdate, + Delete: resourceAwsLambdaFunctionEventInvokeConfigDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "destination_config": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "on_failure": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "destination": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validateArn, + }, + }, + }, + }, + "on_success": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "destination": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validateArn, + }, + }, + }, + }, + }, + }, + }, + "function_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.NoZeroValues, + }, + "maximum_event_age_in_seconds": { + Type: schema.TypeInt, + Optional: true, + ValidateFunc: validation.IntBetween(60, 21600), + }, + "maximum_retry_attempts": { + Type: schema.TypeInt, + Optional: true, + Default: 2, + ValidateFunc: validation.IntBetween(0, 2), + }, + "qualifier": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ValidateFunc: validation.NoZeroValues, + }, + }, + } +} + +func resourceAwsLambdaFunctionEventInvokeConfigCreate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).lambdaconn + functionName := d.Get("function_name").(string) + qualifier := d.Get("qualifier").(string) + + id := functionName + + if qualifier != "" { + id = fmt.Sprintf("%s:%s", functionName, qualifier) + } + + input := &lambda.PutFunctionEventInvokeConfigInput{ + DestinationConfig: expandLambdaFunctionEventInvokeConfigDestinationConfig(d.Get("destination_config").([]interface{})), + FunctionName: aws.String(functionName), + MaximumRetryAttempts: aws.Int64(int64(d.Get("maximum_retry_attempts").(int))), + } + + if qualifier != "" { + input.Qualifier = aws.String(qualifier) + } + + if v, ok := d.GetOk("maximum_event_age_in_seconds"); ok { + input.MaximumEventAgeInSeconds = aws.Int64(int64(v.(int))) + } + + // Retry for destination validation eventual consistency errors + err := resource.Retry(2*time.Minute, func() *resource.RetryError { + _, err := conn.PutFunctionEventInvokeConfig(input) + + // InvalidParameterValueException: The destination ARN arn:PARTITION:SERVICE:REGION:ACCOUNT:RESOURCE is invalid. + if isAWSErr(err, lambda.ErrCodeInvalidParameterValueException, "destination ARN") { + return resource.RetryableError(err) + } + + if err != nil { + return resource.NonRetryableError(err) + } + + return nil + }) + + if isResourceTimeoutError(err) { + _, err = conn.PutFunctionEventInvokeConfig(input) + } + + if err != nil { + return fmt.Errorf("error putting Lambda Function Event Invoke Config (%s): %s", id, err) + } + + d.SetId(id) + + return resourceAwsLambdaFunctionEventInvokeConfigRead(d, meta) +} + +func resourceAwsLambdaFunctionEventInvokeConfigRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).lambdaconn + + functionName, qualifier, err := resourceAwsLambdaFunctionEventInvokeConfigParseId(d.Id()) + + if err != nil { + return err + } + + input := &lambda.GetFunctionEventInvokeConfigInput{ + FunctionName: aws.String(functionName), + } + + if qualifier != "" { + input.Qualifier = aws.String(qualifier) + } + + output, err := conn.GetFunctionEventInvokeConfig(input) + + if isAWSErr(err, lambda.ErrCodeResourceNotFoundException, "") { + log.Printf("[WARN] Lambda Function Event Invoke Config (%s) not found, removing from state", d.Id()) + d.SetId("") + return nil + } + + if err != nil { + return fmt.Errorf("error getting Lambda Function Event Invoke Config (%s): %s", d.Id(), err) + } + + if err := d.Set("destination_config", flattenLambdaFunctionEventInvokeConfigDestinationConfig(output.DestinationConfig)); err != nil { + return fmt.Errorf("error setting destination_config: %s", err) + } + + d.Set("function_name", functionName) + d.Set("maximum_event_age_in_seconds", aws.Int64Value(output.MaximumEventAgeInSeconds)) + d.Set("maximum_retry_attempts", aws.Int64Value(output.MaximumRetryAttempts)) + d.Set("qualifier", qualifier) + + return nil +} + +func resourceAwsLambdaFunctionEventInvokeConfigUpdate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).lambdaconn + + functionName, qualifier, err := resourceAwsLambdaFunctionEventInvokeConfigParseId(d.Id()) + + if err != nil { + return err + } + + input := &lambda.PutFunctionEventInvokeConfigInput{ + DestinationConfig: expandLambdaFunctionEventInvokeConfigDestinationConfig(d.Get("destination_config").([]interface{})), + FunctionName: aws.String(functionName), + MaximumRetryAttempts: aws.Int64(int64(d.Get("maximum_retry_attempts").(int))), + } + + if qualifier != "" { + input.Qualifier = aws.String(qualifier) + } + + if v, ok := d.GetOk("maximum_event_age_in_seconds"); ok { + input.MaximumEventAgeInSeconds = aws.Int64(int64(v.(int))) + } + + // Retry for destination validation eventual consistency errors + err = resource.Retry(2*time.Minute, func() *resource.RetryError { + _, err := conn.PutFunctionEventInvokeConfig(input) + + // InvalidParameterValueException: The destination ARN arn:PARTITION:SERVICE:REGION:ACCOUNT:RESOURCE is invalid. + if isAWSErr(err, lambda.ErrCodeInvalidParameterValueException, "destination ARN") { + return resource.RetryableError(err) + } + + if err != nil { + return resource.NonRetryableError(err) + } + + return nil + }) + + if isResourceTimeoutError(err) { + _, err = conn.PutFunctionEventInvokeConfig(input) + } + + if err != nil { + return fmt.Errorf("error putting Lambda Function Event Invoke Config (%s): %s", d.Id(), err) + } + + return resourceAwsLambdaFunctionEventInvokeConfigRead(d, meta) +} + +func resourceAwsLambdaFunctionEventInvokeConfigDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).lambdaconn + + functionName, qualifier, err := resourceAwsLambdaFunctionEventInvokeConfigParseId(d.Id()) + + if err != nil { + return err + } + + input := &lambda.DeleteFunctionEventInvokeConfigInput{ + FunctionName: aws.String(functionName), + } + + if qualifier != "" { + input.Qualifier = aws.String(qualifier) + } + + _, err = conn.DeleteFunctionEventInvokeConfig(input) + + if isAWSErr(err, lambda.ErrCodeResourceNotFoundException, "") { + return nil + } + + if err != nil { + return fmt.Errorf("error putting Lambda Function Event Invoke Config (%s): %s", d.Id(), err) + } + + return nil +} + +func resourceAwsLambdaFunctionEventInvokeConfigParseId(id string) (string, string, error) { + if arn.IsARN(id) { + parsedARN, err := arn.Parse(id) + + if err != nil { + return "", "", fmt.Errorf("error parsing ARN (%s): %s", id, err) + } + + function := strings.TrimPrefix(parsedARN.Resource, "function:") + + if !strings.Contains(function, ":") { + // Return ARN for function name to match configuration + return id, "", nil + } + + functionParts := strings.Split(id, ":") + + if len(functionParts) != 2 || functionParts[0] == "" || functionParts[1] == "" { + return "", "", fmt.Errorf("unexpected format of function resource (%s), expected name:qualifier", id) + } + + qualifier := functionParts[1] + // Return ARN minus qualifier for function name to match configuration + functionName := strings.TrimSuffix(id, fmt.Sprintf(":%s", qualifier)) + + return functionName, qualifier, nil + } + + if !strings.Contains(id, ":") { + return id, "", nil + } + + idParts := strings.Split(id, ":") + + if len(idParts) != 2 || idParts[0] == "" || idParts[1] == "" { + return "", "", fmt.Errorf("unexpected format of ID (%s), expected name or name:qualifier", id) + } + + return idParts[0], idParts[1], nil +} + +func expandLambdaFunctionEventInvokeConfigDestinationConfig(l []interface{}) *lambda.DestinationConfig { + if len(l) == 0 || l[0] == nil { + return nil + } + + m := l[0].(map[string]interface{}) + + destinationConfig := &lambda.DestinationConfig{} + + if v, ok := m["on_failure"].([]interface{}); ok { + destinationConfig.OnFailure = expandLambdaFunctionEventInvokeConfigDestinationConfigOnFailure(v) + } + + if v, ok := m["on_success"].([]interface{}); ok { + destinationConfig.OnSuccess = expandLambdaFunctionEventInvokeConfigDestinationConfigOnSuccess(v) + } + + return destinationConfig +} + +func expandLambdaFunctionEventInvokeConfigDestinationConfigOnFailure(l []interface{}) *lambda.OnFailure { + if len(l) == 0 || l[0] == nil { + return nil + } + + m := l[0].(map[string]interface{}) + + onFailure := &lambda.OnFailure{} + + if v, ok := m["destination"].(string); ok { + onFailure.Destination = aws.String(v) + } + + return onFailure +} + +func expandLambdaFunctionEventInvokeConfigDestinationConfigOnSuccess(l []interface{}) *lambda.OnSuccess { + if len(l) == 0 || l[0] == nil { + return nil + } + + m := l[0].(map[string]interface{}) + + onSuccess := &lambda.OnSuccess{} + + if v, ok := m["destination"].(string); ok { + onSuccess.Destination = aws.String(v) + } + + return onSuccess +} + +func flattenLambdaFunctionEventInvokeConfigDestinationConfig(destinationConfig *lambda.DestinationConfig) []interface{} { + // The API will respond with empty OnFailure and OnSuccess destinations when unconfigured: + // "DestinationConfig":{"OnFailure":{"Destination":null},"OnSuccess":{"Destination":null}} + // Return no destination configuration to prevent Terraform state difference + + if destinationConfig == nil { + return []interface{}{} + } + + if destinationConfig.OnFailure == nil && destinationConfig.OnSuccess == nil { + return []interface{}{} + } + + if (destinationConfig.OnFailure != nil && destinationConfig.OnFailure.Destination == nil) && (destinationConfig.OnSuccess != nil && destinationConfig.OnSuccess.Destination == nil) { + return []interface{}{} + } + + m := map[string]interface{}{ + "on_failure": flattenLambdaFunctionEventInvokeConfigDestinationConfigOnFailure(destinationConfig.OnFailure), + "on_success": flattenLambdaFunctionEventInvokeConfigDestinationConfigOnSuccess(destinationConfig.OnSuccess), + } + + return []interface{}{m} +} + +func flattenLambdaFunctionEventInvokeConfigDestinationConfigOnFailure(onFailure *lambda.OnFailure) []interface{} { + // The API will respond with empty OnFailure destination when unconfigured: + // "DestinationConfig":{"OnFailure":{"Destination":null},"OnSuccess":{"Destination":null}} + // Return no on failure configuration to prevent Terraform state difference + + if onFailure == nil || onFailure.Destination == nil { + return []interface{}{} + } + + m := map[string]interface{}{ + "destination": aws.StringValue(onFailure.Destination), + } + + return []interface{}{m} +} + +func flattenLambdaFunctionEventInvokeConfigDestinationConfigOnSuccess(onSuccess *lambda.OnSuccess) []interface{} { + // The API will respond with empty OnSuccess destination when unconfigured: + // "DestinationConfig":{"OnFailure":{"Destination":null},"OnSuccess":{"Destination":null}} + // Return no on success configuration to prevent Terraform state difference + + if onSuccess == nil || onSuccess.Destination == nil { + return []interface{}{} + } + + m := map[string]interface{}{ + "destination": aws.StringValue(onSuccess.Destination), + } + + return []interface{}{m} +} diff --git a/aws/resource_aws_lambda_function_event_invoke_config_test.go b/aws/resource_aws_lambda_function_event_invoke_config_test.go new file mode 100644 index 00000000000..4e6e209d6b4 --- /dev/null +++ b/aws/resource_aws_lambda_function_event_invoke_config_test.go @@ -0,0 +1,733 @@ +package aws + +import ( + "fmt" + "testing" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/lambda" + "github.com/hashicorp/terraform-plugin-sdk/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/terraform" +) + +func TestAccAWSLambdaFunctionEventInvokeConfig_basic(t *testing.T) { + rName := acctest.RandomWithPrefix("tf-acc-test") + lambdaFunctionResourceName := "aws_lambda_function.test" + resourceName := "aws_lambda_function_event_invoke_config.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckLambdaFunctionEventInvokeConfigDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSLambdaFunctionEventInvokeConfigFunctionName(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsLambdaFunctionEventInvokeConfigExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "destination_config.#", "0"), + resource.TestCheckResourceAttrPair(resourceName, "function_name", lambdaFunctionResourceName, "function_name"), + resource.TestCheckResourceAttr(resourceName, "maximum_event_age_in_seconds", "0"), + resource.TestCheckResourceAttr(resourceName, "maximum_retry_attempts", "2"), + resource.TestCheckResourceAttr(resourceName, "qualifier", ""), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAWSLambdaFunctionEventInvokeConfig_disappears_LambdaFunction(t *testing.T) { + var function lambda.GetFunctionOutput + rName := acctest.RandomWithPrefix("tf-acc-test") + lambdaFunctionResourceName := "aws_lambda_function.test" + resourceName := "aws_lambda_function_event_invoke_config.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckLambdaFunctionEventInvokeConfigDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSLambdaFunctionEventInvokeConfigFunctionName(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsLambdaFunctionExists(lambdaFunctionResourceName, rName, &function), + testAccCheckAwsLambdaFunctionEventInvokeConfigExists(resourceName), + testAccCheckAwsLambdaFunctionDisappears(&function), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func TestAccAWSLambdaFunctionEventInvokeConfig_disappears_LambdaFunctionEventInvokeConfig(t *testing.T) { + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_lambda_function_event_invoke_config.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckLambdaFunctionEventInvokeConfigDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSLambdaFunctionEventInvokeConfigFunctionName(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsLambdaFunctionEventInvokeConfigExists(resourceName), + testAccCheckAwsLambdaFunctionEventInvokeConfigDisappears(resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func TestAccAWSLambdaFunctionEventInvokeConfig_DestinationConfig_OnFailure_Destination(t *testing.T) { + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_lambda_function_event_invoke_config.test" + sqsQueueResourceName := "aws_sqs_queue.test" + snsTopicResourceName := "aws_sns_topic.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckLambdaFunctionEventInvokeConfigDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSLambdaFunctionEventInvokeConfigDestinationConfigOnFailureDestinationSqsQueue(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsLambdaFunctionEventInvokeConfigExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "destination_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "destination_config.0.on_failure.#", "1"), + resource.TestCheckResourceAttrPair(resourceName, "destination_config.0.on_failure.0.destination", sqsQueueResourceName, "arn"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccAWSLambdaFunctionEventInvokeConfigDestinationConfigOnFailureDestinationSnsTopic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsLambdaFunctionEventInvokeConfigExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "destination_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "destination_config.0.on_failure.#", "1"), + resource.TestCheckResourceAttrPair(resourceName, "destination_config.0.on_failure.0.destination", snsTopicResourceName, "arn"), + ), + }, + }, + }) +} + +func TestAccAWSLambdaFunctionEventInvokeConfig_DestinationConfig_OnSuccess_Destination(t *testing.T) { + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_lambda_function_event_invoke_config.test" + sqsQueueResourceName := "aws_sqs_queue.test" + snsTopicResourceName := "aws_sns_topic.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckLambdaFunctionEventInvokeConfigDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSLambdaFunctionEventInvokeConfigDestinationConfigOnSuccessDestinationSqsQueue(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsLambdaFunctionEventInvokeConfigExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "destination_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "destination_config.0.on_success.#", "1"), + resource.TestCheckResourceAttrPair(resourceName, "destination_config.0.on_success.0.destination", sqsQueueResourceName, "arn"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccAWSLambdaFunctionEventInvokeConfigDestinationConfigOnSuccessDestinationSnsTopic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsLambdaFunctionEventInvokeConfigExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "destination_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "destination_config.0.on_success.#", "1"), + resource.TestCheckResourceAttrPair(resourceName, "destination_config.0.on_success.0.destination", snsTopicResourceName, "arn"), + ), + }, + }, + }) +} + +func TestAccAWSLambdaFunctionEventInvokeConfig_DestinationConfig_Remove(t *testing.T) { + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_lambda_function_event_invoke_config.test" + sqsQueueResourceName := "aws_sqs_queue.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckLambdaFunctionEventInvokeConfigDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSLambdaFunctionEventInvokeConfigDestinationConfigOnFailureDestinationSqsQueue(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsLambdaFunctionEventInvokeConfigExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "destination_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "destination_config.0.on_failure.#", "1"), + resource.TestCheckResourceAttrPair(resourceName, "destination_config.0.on_failure.0.destination", sqsQueueResourceName, "arn"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccAWSLambdaFunctionEventInvokeConfigQualifierFunctionVersion(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsLambdaFunctionEventInvokeConfigExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "destination_config.#", "0"), + ), + }, + }, + }) +} + +func TestAccAWSLambdaFunctionEventInvokeConfig_DestinationConfig_Swap(t *testing.T) { + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_lambda_function_event_invoke_config.test" + sqsQueueResourceName := "aws_sqs_queue.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckLambdaFunctionEventInvokeConfigDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSLambdaFunctionEventInvokeConfigDestinationConfigOnFailureDestinationSqsQueue(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsLambdaFunctionEventInvokeConfigExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "destination_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "destination_config.0.on_failure.#", "1"), + resource.TestCheckResourceAttrPair(resourceName, "destination_config.0.on_failure.0.destination", sqsQueueResourceName, "arn"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccAWSLambdaFunctionEventInvokeConfigDestinationConfigOnSuccessDestinationSqsQueue(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsLambdaFunctionEventInvokeConfigExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "destination_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "destination_config.0.on_success.#", "1"), + resource.TestCheckResourceAttrPair(resourceName, "destination_config.0.on_success.0.destination", sqsQueueResourceName, "arn"), + ), + }, + }, + }) +} + +func TestAccAWSLambdaFunctionEventInvokeConfig_FunctionName_Arn(t *testing.T) { + rName := acctest.RandomWithPrefix("tf-acc-test") + lambdaFunctionResourceName := "aws_lambda_function.test" + resourceName := "aws_lambda_function_event_invoke_config.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckLambdaFunctionEventInvokeConfigDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSLambdaFunctionEventInvokeConfigFunctionNameArn(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsLambdaFunctionEventInvokeConfigExists(resourceName), + resource.TestCheckResourceAttrPair(resourceName, "function_name", lambdaFunctionResourceName, "arn"), + resource.TestCheckResourceAttr(resourceName, "qualifier", ""), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAWSLambdaFunctionEventInvokeConfig_MaximumEventAgeInSeconds(t *testing.T) { + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_lambda_function_event_invoke_config.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckLambdaFunctionEventInvokeConfigDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSLambdaFunctionEventInvokeConfigMaximumEventAgeInSeconds(rName, 100), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsLambdaFunctionEventInvokeConfigExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "maximum_event_age_in_seconds", "100"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccAWSLambdaFunctionEventInvokeConfigMaximumEventAgeInSeconds(rName, 200), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsLambdaFunctionEventInvokeConfigExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "maximum_event_age_in_seconds", "200"), + ), + }, + }, + }) +} + +func TestAccAWSLambdaFunctionEventInvokeConfig_MaximumRetryAttempts(t *testing.T) { + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_lambda_function_event_invoke_config.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckLambdaFunctionEventInvokeConfigDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSLambdaFunctionEventInvokeConfigMaximumRetryAttempts(rName, 0), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsLambdaFunctionEventInvokeConfigExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "maximum_retry_attempts", "0"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccAWSLambdaFunctionEventInvokeConfigMaximumRetryAttempts(rName, 1), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsLambdaFunctionEventInvokeConfigExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "maximum_retry_attempts", "1"), + ), + }, + { + Config: testAccAWSLambdaFunctionEventInvokeConfigMaximumRetryAttempts(rName, 0), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsLambdaFunctionEventInvokeConfigExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "maximum_retry_attempts", "0"), + ), + }, + }, + }) +} + +func TestAccAWSLambdaFunctionEventInvokeConfig_Qualifier_AliasName(t *testing.T) { + rName := acctest.RandomWithPrefix("tf-acc-test") + lambdaAliasResourceName := "aws_lambda_alias.test" + resourceName := "aws_lambda_function_event_invoke_config.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckLambdaFunctionEventInvokeConfigDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSLambdaFunctionEventInvokeConfigQualifierAliasName(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsLambdaFunctionEventInvokeConfigExists(resourceName), + resource.TestCheckResourceAttrPair(resourceName, "qualifier", lambdaAliasResourceName, "name"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAWSLambdaFunctionEventInvokeConfig_Qualifier_FunctionVersion(t *testing.T) { + rName := acctest.RandomWithPrefix("tf-acc-test") + lambdaFunctionResourceName := "aws_lambda_function.test" + resourceName := "aws_lambda_function_event_invoke_config.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckLambdaFunctionEventInvokeConfigDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSLambdaFunctionEventInvokeConfigQualifierFunctionVersion(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsLambdaFunctionEventInvokeConfigExists(resourceName), + resource.TestCheckResourceAttrPair(resourceName, "function_name", lambdaFunctionResourceName, "function_name"), + resource.TestCheckResourceAttrPair(resourceName, "qualifier", lambdaFunctionResourceName, "version"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAWSLambdaFunctionEventInvokeConfig_Qualifier_Latest(t *testing.T) { + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_lambda_function_event_invoke_config.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckLambdaFunctionEventInvokeConfigDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSLambdaFunctionEventInvokeConfigQualifierLatest(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsLambdaFunctionEventInvokeConfigExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "qualifier", "$LATEST"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccCheckLambdaFunctionEventInvokeConfigDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).lambdaconn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_lambda_function_event_invoke_config" { + continue + } + + functionName, qualifier, err := resourceAwsLambdaFunctionEventInvokeConfigParseId(rs.Primary.ID) + + if err != nil { + return err + } + + input := &lambda.GetFunctionEventInvokeConfigInput{ + FunctionName: aws.String(functionName), + } + + if qualifier != "" { + input.Qualifier = aws.String(qualifier) + } + + output, err := conn.GetFunctionEventInvokeConfig(input) + + if isAWSErr(err, lambda.ErrCodeResourceNotFoundException, "") { + continue + } + + if err != nil { + return err + } + + if output != nil { + return fmt.Errorf("Lambda Function Event Invoke Config (%s) still exists", rs.Primary.ID) + } + } + + return nil + +} + +func testAccCheckAwsLambdaFunctionEventInvokeConfigDisappears(resourceName string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("Resource not found: %s", resourceName) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("Resource (%s) ID not set", resourceName) + } + + conn := testAccProvider.Meta().(*AWSClient).lambdaconn + + functionName, qualifier, err := resourceAwsLambdaFunctionEventInvokeConfigParseId(rs.Primary.ID) + + if err != nil { + return err + } + + input := &lambda.DeleteFunctionEventInvokeConfigInput{ + FunctionName: aws.String(functionName), + } + + if qualifier != "" { + input.Qualifier = aws.String(qualifier) + } + + _, err = conn.DeleteFunctionEventInvokeConfig(input) + + return err + } +} + +func testAccCheckAwsLambdaFunctionEventInvokeConfigExists(resourceName string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("Resource not found: %s", resourceName) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("Resource (%s) ID not set", resourceName) + } + + conn := testAccProvider.Meta().(*AWSClient).lambdaconn + + functionName, qualifier, err := resourceAwsLambdaFunctionEventInvokeConfigParseId(rs.Primary.ID) + + if err != nil { + return err + } + + input := &lambda.GetFunctionEventInvokeConfigInput{ + FunctionName: aws.String(functionName), + } + + if qualifier != "" { + input.Qualifier = aws.String(qualifier) + } + + _, err = conn.GetFunctionEventInvokeConfig(input) + + if err != nil { + return err + } + + return nil + } +} + +func testAccAWSLambdaFunctionEventInvokeConfigBase(rName string) string { + return fmt.Sprintf(` +data "aws_partition" "current" {} + +resource "aws_iam_role" "test" { + name = %[1]q + + assume_role_policy = < aws_lambda_function +
  • + aws_lambda_function_event_invoke_config +
  • aws_lambda_layer_version
  • diff --git a/website/docs/r/lambda_function_event_invoke_config.html.markdown b/website/docs/r/lambda_function_event_invoke_config.html.markdown new file mode 100644 index 00000000000..38d368c23c0 --- /dev/null +++ b/website/docs/r/lambda_function_event_invoke_config.html.markdown @@ -0,0 +1,144 @@ +--- +subcategory: "Lambda" +layout: "aws" +page_title: "AWS: aws_lambda_function_event_invoke_config" +description: |- + Manages an asynchronous invocation configuration for a Lambda Function or Alias. +--- + +# Resource: aws_lambda_function_event_invoke_config + +Manages an asynchronous invocation configuration for a Lambda Function or Alias. More information about asynchronous invocations and the configurable values can be found in the [Lambda Developer Guide](https://docs.aws.amazon.com/lambda/latest/dg/invocation-async.html). + +## Example Usage + +### Destination Configuration + +~> **NOTE:** Ensure the Lambda Function IAM Role has necessary permissions for the destination, such as `sqs:SendMessage` or `sns:Publish`, otherwise the API will return a generic `InvalidParameterValueException: The destination ARN arn:PARTITION:SERVICE:REGION:ACCOUNT:RESOURCE is invalid.` error. + +```hcl +resource "aws_lambda_function_event_invoke_config" "example" { + function_name = aws_lambda_alias.example.function_name + + destination_config { + on_failure { + destination = aws_sqs_queue.example.arn + } + + on_success { + destination = aws_sns_topic.example.arn + } + } +} +``` + +### Error Handling Configuration + +```hcl +resource "aws_lambda_function_event_invoke_config" "example" { + function_name = aws_lambda_alias.example.function_name + maximum_event_age_in_seconds = 60 + maximum_retry_attempts = 0 +} +``` + +### Configuration for Alias Name + +```hcl +resource "aws_lambda_function_event_invoke_config" "example" { + function_name = aws_lambda_alias.example.function_name + qualifier = aws_lambda_alias.example.name + + # ... other configuration ... +} +``` + +### Configuration for Function Latest Unpublished Version + +```hcl +resource "aws_lambda_function_event_invoke_config" "example" { + function_name = aws_lambda_function.example.function_name + qualifier = "$LATEST" + + # ... other configuration ... +} +``` + +### Configuration for Function Published Version + +```hcl +resource "aws_lambda_function_event_invoke_config" "example" { + function_name = aws_lambda_function.example.function_name + qualifier = aws_lambda_function.example.version + + # ... other configuration ... +} +``` + +## Argument Reference + +The following arguments are required: + +* `function_name` - (Required) Name or Amazon Resource Name (ARN) of the Lambda Function, omitting any version or alias qualifier. + +The following arguments are optional: + +* `destination_config` - (Optional) Configuration block with destination configuration. See below for details. +* `maximum_event_age_in_seconds` - (Optional) Maximum age of a request that Lambda sends to a function for processing in seconds. Valid values between 60 and 21600. +* `maximum_retry_attempts` - (Optional) Maximum number of times to retry when the function returns an error. Valid values between 0 and 2. Defaults to 2. +* `qualifier` - (Optional) Lambda Function published version, `$LATEST`, or Lambda Alias name. + +### destination_config Configuration Block + +~> **NOTE:** At least one of `on_failure` or `on_success` must be configured when using this configuration block, otherwise remove it completely to prevent perpetual differences in Terraform runs. + +The following arguments are optional: + +* `on_failure` - (Optional) Configuration block with destination configuration for failed asynchronous invocations. See below for details. +* `on_success` - (Optional) Configuration block with destination configuration for successful asynchronous invocations. See below for details. + +#### destination_config on_failure Configuration Block + +The following arguments are required: + +* `destination` - (Required) Amazon Resource Name (ARN) of the destination resource. See the [Lambda Developer Guide](https://docs.aws.amazon.com/lambda/latest/dg/invocation-async.html#invocation-async-destinations) for acceptable resource types and associated IAM permissions. + +#### destination_config on_success Configuration Block + +The following arguments are required: + +* `destination` - (Required) Amazon Resource Name (ARN) of the destination resource. See the [Lambda Developer Guide](https://docs.aws.amazon.com/lambda/latest/dg/invocation-async.html#invocation-async-destinations) for acceptable resource types and associated IAM permissions. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `id` - Fully qualified Lambda Function name or Amazon Resource Name (ARN) + +## Import + +Lambda Function Event Invoke Configs can be imported using the fully qualified Function name or Amazon Resource Name (ARN), e.g. + +ARN without qualifier (all versions and aliases): + +``` +$ terraform import aws_lambda_function_event_invoke_config.example arn:aws:us-east-1:123456789012:function:my_function +``` + +ARN with qualifier: + +``` +$ terraform import aws_lambda_function_event_invoke_config.example arn:aws:us-east-1:123456789012:function:my_function:production +``` + +Name without qualifier (all versions and aliases): + +``` +$ terraform import aws_lambda_function_event_invoke_config.example my_function +``` + +Name with qualifier: + +``` +$ terraform import aws_lambda_function_event_invoke_config.example my_function:production +```