diff --git a/aws/provider.go b/aws/provider.go index d17a805318f..ea4bc1a74bc 100644 --- a/aws/provider.go +++ b/aws/provider.go @@ -598,6 +598,7 @@ func Provider() terraform.ResourceProvider { "aws_lambda_event_source_mapping": resourceAwsLambdaEventSourceMapping(), "aws_lambda_alias": resourceAwsLambdaAlias(), "aws_lambda_permission": resourceAwsLambdaPermission(), + "aws_lambda_provisioned_concurrency_config": resourceAwsLambdaProvisionedConcurrencyConfig(), "aws_lambda_layer_version": resourceAwsLambdaLayerVersion(), "aws_launch_configuration": resourceAwsLaunchConfiguration(), "aws_launch_template": resourceAwsLaunchTemplate(), diff --git a/aws/resource_aws_lambda_function_test.go b/aws/resource_aws_lambda_function_test.go index e6d1b1d1dab..4a51d6082d0 100644 --- a/aws/resource_aws_lambda_function_test.go +++ b/aws/resource_aws_lambda_function_test.go @@ -96,6 +96,29 @@ func TestAccAWSLambdaFunction_basic(t *testing.T) { }) } +func TestAccAWSLambdaFunction_disappears(t *testing.T) { + var function lambda.GetFunctionOutput + + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_lambda_function.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckLambdaFunctionDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSLambdaConfigBasic(rName, rName, rName, rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsLambdaFunctionExists(resourceName, rName, &function), + testAccCheckAwsLambdaFunctionDisappears(&function), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + func TestAccAWSLambdaFunction_concurrency(t *testing.T) { var conf lambda.GetFunctionOutput @@ -1681,6 +1704,20 @@ func testAccCheckLambdaFunctionDestroy(s *terraform.State) error { } +func testAccCheckAwsLambdaFunctionDisappears(function *lambda.GetFunctionOutput) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).lambdaconn + + input := &lambda.DeleteFunctionInput{ + FunctionName: function.Configuration.FunctionName, + } + + _, err := conn.DeleteFunction(input) + + return err + } +} + func testAccCheckAwsLambdaFunctionExists(res, funcName string, function *lambda.GetFunctionOutput) resource.TestCheckFunc { // Wait for IAM role return func(s *terraform.State) error { diff --git a/aws/resource_aws_lambda_provisioned_concurrency_config.go b/aws/resource_aws_lambda_provisioned_concurrency_config.go new file mode 100644 index 00000000000..c78f4e08d6e --- /dev/null +++ b/aws/resource_aws_lambda_provisioned_concurrency_config.go @@ -0,0 +1,211 @@ +package aws + +import ( + "fmt" + "log" + "strings" + "time" + + "github.com/aws/aws-sdk-go/aws" + "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 resourceAwsLambdaProvisionedConcurrencyConfig() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsLambdaProvisionedConcurrencyConfigCreate, + Read: resourceAwsLambdaProvisionedConcurrencyConfigRead, + Update: resourceAwsLambdaProvisionedConcurrencyConfigUpdate, + Delete: resourceAwsLambdaProvisionedConcurrencyConfigDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(15 * time.Minute), + Update: schema.DefaultTimeout(15 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "function_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.NoZeroValues, + }, + "provisioned_concurrent_executions": { + Type: schema.TypeInt, + Required: true, + ValidateFunc: validation.IntAtLeast(1), + }, + "qualifier": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.NoZeroValues, + }, + }, + } +} + +func resourceAwsLambdaProvisionedConcurrencyConfigCreate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).lambdaconn + functionName := d.Get("function_name").(string) + qualifier := d.Get("qualifier").(string) + + input := &lambda.PutProvisionedConcurrencyConfigInput{ + FunctionName: aws.String(functionName), + ProvisionedConcurrentExecutions: aws.Int64(int64(d.Get("provisioned_concurrent_executions").(int))), + Qualifier: aws.String(qualifier), + } + + _, err := conn.PutProvisionedConcurrencyConfig(input) + + if err != nil { + return fmt.Errorf("error putting Lambda Provisioned Concurrency Config (%s:%s): %s", functionName, qualifier, err) + } + + d.SetId(fmt.Sprintf("%s:%s", functionName, qualifier)) + + if err := waitForLambdaProvisionedConcurrencyConfigStatusReady(conn, functionName, qualifier, d.Timeout(schema.TimeoutCreate)); err != nil { + return fmt.Errorf("error waiting for Lambda Provisioned Concurrency Config (%s) to be ready: %s", d.Id(), err) + } + + return resourceAwsLambdaProvisionedConcurrencyConfigRead(d, meta) +} + +func resourceAwsLambdaProvisionedConcurrencyConfigRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).lambdaconn + + functionName, qualifier, err := resourceAwsLambdaProvisionedConcurrencyConfigParseId(d.Id()) + + if err != nil { + return err + } + + input := &lambda.GetProvisionedConcurrencyConfigInput{ + FunctionName: aws.String(functionName), + Qualifier: aws.String(qualifier), + } + + output, err := conn.GetProvisionedConcurrencyConfig(input) + + if isAWSErr(err, lambda.ErrCodeProvisionedConcurrencyConfigNotFoundException, "") || isAWSErr(err, lambda.ErrCodeResourceNotFoundException, "") { + log.Printf("[WARN] Lambda Provisioned Concurrency Config (%s) not found, removing from state", d.Id()) + d.SetId("") + return nil + } + + if err != nil { + return fmt.Errorf("error getting Lambda Provisioned Concurrency Config (%s): %s", d.Id(), err) + } + + d.Set("function_name", functionName) + d.Set("provisioned_concurrent_executions", aws.Int64Value(output.AllocatedProvisionedConcurrentExecutions)) + d.Set("qualifier", qualifier) + + return nil +} + +func resourceAwsLambdaProvisionedConcurrencyConfigUpdate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).lambdaconn + + functionName, qualifier, err := resourceAwsLambdaProvisionedConcurrencyConfigParseId(d.Id()) + + if err != nil { + return err + } + + input := &lambda.PutProvisionedConcurrencyConfigInput{ + FunctionName: aws.String(functionName), + ProvisionedConcurrentExecutions: aws.Int64(int64(d.Get("provisioned_concurrent_executions").(int))), + Qualifier: aws.String(qualifier), + } + + _, err = conn.PutProvisionedConcurrencyConfig(input) + + if err != nil { + return fmt.Errorf("error putting Lambda Provisioned Concurrency Config (%s:%s): %s", functionName, qualifier, err) + } + + if err := waitForLambdaProvisionedConcurrencyConfigStatusReady(conn, functionName, qualifier, d.Timeout(schema.TimeoutUpdate)); err != nil { + return fmt.Errorf("error waiting for Lambda Provisioned Concurrency Config (%s) to be ready: %s", d.Id(), err) + } + + return resourceAwsLambdaProvisionedConcurrencyConfigRead(d, meta) +} + +func resourceAwsLambdaProvisionedConcurrencyConfigDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).lambdaconn + + functionName, qualifier, err := resourceAwsLambdaProvisionedConcurrencyConfigParseId(d.Id()) + + if err != nil { + return err + } + + input := &lambda.DeleteProvisionedConcurrencyConfigInput{ + FunctionName: aws.String(functionName), + Qualifier: aws.String(qualifier), + } + + _, err = conn.DeleteProvisionedConcurrencyConfig(input) + + if isAWSErr(err, lambda.ErrCodeProvisionedConcurrencyConfigNotFoundException, "") || isAWSErr(err, lambda.ErrCodeResourceNotFoundException, "") { + return nil + } + + if err != nil { + return fmt.Errorf("error putting Lambda Provisioned Concurrency Config (%s:%s): %s", functionName, qualifier, err) + } + + return nil +} + +func resourceAwsLambdaProvisionedConcurrencyConfigParseId(id string) (string, string, error) { + parts := strings.SplitN(id, ":", 2) + + if len(parts) != 2 || parts[0] == "" || parts[1] == "" { + return "", "", fmt.Errorf("unexpected format of ID (%s), expected FUNCTION_NAME:QUALIFIER", id) + } + + return parts[0], parts[1], nil +} + +func refreshLambdaProvisionedConcurrencyConfigStatus(conn *lambda.Lambda, functionName, qualifier string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + input := &lambda.GetProvisionedConcurrencyConfigInput{ + FunctionName: aws.String(functionName), + Qualifier: aws.String(qualifier), + } + + output, err := conn.GetProvisionedConcurrencyConfig(input) + + if err != nil { + return "", "", err + } + + status := aws.StringValue(output.Status) + + if status == lambda.ProvisionedConcurrencyStatusEnumFailed { + return output, status, fmt.Errorf("status reason: %s", aws.StringValue(output.StatusReason)) + } + + return output, status, nil + } +} + +func waitForLambdaProvisionedConcurrencyConfigStatusReady(conn *lambda.Lambda, functionName, qualifier string, timeout time.Duration) error { + stateConf := &resource.StateChangeConf{ + Pending: []string{lambda.ProvisionedConcurrencyStatusEnumInProgress}, + Target: []string{lambda.ProvisionedConcurrencyStatusEnumReady}, + Refresh: refreshLambdaProvisionedConcurrencyConfigStatus(conn, functionName, qualifier), + Timeout: timeout, + Delay: 5 * time.Second, + } + + _, err := stateConf.WaitForState() + + return err +} diff --git a/aws/resource_aws_lambda_provisioned_concurrency_config_test.go b/aws/resource_aws_lambda_provisioned_concurrency_config_test.go new file mode 100644 index 00000000000..d841827f58c --- /dev/null +++ b/aws/resource_aws_lambda_provisioned_concurrency_config_test.go @@ -0,0 +1,331 @@ +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 TestAccAWSLambdaProvisionedConcurrencyConfig_basic(t *testing.T) { + rName := acctest.RandomWithPrefix("tf-acc-test") + lambdaFunctionResourceName := "aws_lambda_function.test" + resourceName := "aws_lambda_provisioned_concurrency_config.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckLambdaProvisionedConcurrencyConfigDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSLambdaProvisionedConcurrencyConfigQualifierFunctionVersion(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsLambdaProvisionedConcurrencyConfigExists(resourceName), + resource.TestCheckResourceAttrPair(resourceName, "function_name", lambdaFunctionResourceName, "function_name"), + resource.TestCheckResourceAttr(resourceName, "provisioned_concurrent_executions", "1"), + resource.TestCheckResourceAttrPair(resourceName, "qualifier", lambdaFunctionResourceName, "version"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAWSLambdaProvisionedConcurrencyConfig_disappears_LambdaFunction(t *testing.T) { + var function lambda.GetFunctionOutput + rName := acctest.RandomWithPrefix("tf-acc-test") + lambdaFunctionResourceName := "aws_lambda_function.test" + resourceName := "aws_lambda_provisioned_concurrency_config.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckLambdaProvisionedConcurrencyConfigDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSLambdaProvisionedConcurrencyConfigProvisionedConcurrentExecutions(rName, 1), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsLambdaFunctionExists(lambdaFunctionResourceName, rName, &function), + testAccCheckAwsLambdaProvisionedConcurrencyConfigExists(resourceName), + testAccCheckAwsLambdaFunctionDisappears(&function), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func TestAccAWSLambdaProvisionedConcurrencyConfig_disappears_LambdaProvisionedConcurrencyConfig(t *testing.T) { + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_lambda_provisioned_concurrency_config.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckLambdaProvisionedConcurrencyConfigDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSLambdaProvisionedConcurrencyConfigProvisionedConcurrentExecutions(rName, 1), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsLambdaProvisionedConcurrencyConfigExists(resourceName), + testAccCheckAwsLambdaProvisionedConcurrencyConfigDisappears(resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func TestAccAWSLambdaProvisionedConcurrencyConfig_ProvisionedConcurrentExecutions(t *testing.T) { + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_lambda_provisioned_concurrency_config.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckLambdaProvisionedConcurrencyConfigDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSLambdaProvisionedConcurrencyConfigProvisionedConcurrentExecutions(rName, 1), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsLambdaProvisionedConcurrencyConfigExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "function_name", rName), + resource.TestCheckResourceAttr(resourceName, "provisioned_concurrent_executions", "1"), + resource.TestCheckResourceAttr(resourceName, "qualifier", "1"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccAWSLambdaProvisionedConcurrencyConfigProvisionedConcurrentExecutions(rName, 2), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsLambdaProvisionedConcurrencyConfigExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "function_name", rName), + resource.TestCheckResourceAttr(resourceName, "provisioned_concurrent_executions", "2"), + resource.TestCheckResourceAttr(resourceName, "qualifier", "1"), + ), + }, + }, + }) +} + +func TestAccAWSLambdaProvisionedConcurrencyConfig_Qualifier_AliasName(t *testing.T) { + rName := acctest.RandomWithPrefix("tf-acc-test") + lambdaAliasResourceName := "aws_lambda_alias.test" + resourceName := "aws_lambda_provisioned_concurrency_config.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckLambdaProvisionedConcurrencyConfigDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSLambdaProvisionedConcurrencyConfigQualifierAliasName(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsLambdaProvisionedConcurrencyConfigExists(resourceName), + resource.TestCheckResourceAttrPair(resourceName, "qualifier", lambdaAliasResourceName, "name"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccCheckLambdaProvisionedConcurrencyConfigDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).lambdaconn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_lambda_provisioned_concurrency_config" { + continue + } + + functionName, qualifier, err := resourceAwsLambdaProvisionedConcurrencyConfigParseId(rs.Primary.ID) + + if err != nil { + return err + } + + input := &lambda.GetProvisionedConcurrencyConfigInput{ + FunctionName: aws.String(functionName), + Qualifier: aws.String(qualifier), + } + + output, err := conn.GetProvisionedConcurrencyConfig(input) + + if isAWSErr(err, lambda.ErrCodeProvisionedConcurrencyConfigNotFoundException, "") || isAWSErr(err, lambda.ErrCodeResourceNotFoundException, "") { + continue + } + + if err != nil { + return err + } + + if output != nil { + return fmt.Errorf("Lambda Provisioned Concurrency Config (%s) still exists", rs.Primary.ID) + } + } + + return nil + +} + +func testAccCheckAwsLambdaProvisionedConcurrencyConfigDisappears(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 := resourceAwsLambdaProvisionedConcurrencyConfigParseId(rs.Primary.ID) + + if err != nil { + return err + } + + input := &lambda.DeleteProvisionedConcurrencyConfigInput{ + FunctionName: aws.String(functionName), + Qualifier: aws.String(qualifier), + } + + _, err = conn.DeleteProvisionedConcurrencyConfig(input) + + return err + } +} + +func testAccCheckAwsLambdaProvisionedConcurrencyConfigExists(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 := resourceAwsLambdaProvisionedConcurrencyConfigParseId(rs.Primary.ID) + + if err != nil { + return err + } + + input := &lambda.GetProvisionedConcurrencyConfigInput{ + FunctionName: aws.String(functionName), + Qualifier: aws.String(qualifier), + } + + output, err := conn.GetProvisionedConcurrencyConfig(input) + + if err != nil { + return err + } + + if got, want := aws.StringValue(output.Status), lambda.ProvisionedConcurrencyStatusEnumReady; got != want { + return fmt.Errorf("Lambda Provisioned Concurrency Config (%s) expected status (%s), got: %s", rs.Primary.ID, want, got) + } + + return nil + } +} + +func testAccAWSLambdaProvisionedConcurrencyConfigBase(rName string) string { + return fmt.Sprintf(` +data "aws_partition" "current" {} + +resource "aws_iam_role" "test" { + name = %[1]q + + assume_role_policy = < aws_lambda_permission +
  • + aws_lambda_provisioned_concurrency_config +
  • diff --git a/website/docs/r/lambda_provisioned_concurrency_config.html.markdown b/website/docs/r/lambda_provisioned_concurrency_config.html.markdown new file mode 100644 index 00000000000..6664ad06a17 --- /dev/null +++ b/website/docs/r/lambda_provisioned_concurrency_config.html.markdown @@ -0,0 +1,62 @@ +--- +subcategory: "Lambda" +layout: "aws" +page_title: "AWS: aws_lambda_provisioned_concurrency_config" +description: |- + Manages a Lambda Provisioned Concurrency Configuration +--- + +# Resource: aws_lambda_provisioned_concurrency_config + +Manages a Lambda Provisioned Concurrency Configuration. + +## Example Usage + +### Alias Name + +```hcl +resource "aws_lambda_provisioned_concurrency_config" "example" { + function_name = aws_lambda_alias.example.function_name + provisioned_concurrent_executions = 1 + qualifier = aws_lambda_alias.example.name +} +``` + +### Function Version + +```hcl +resource "aws_lambda_provisioned_concurrency_config" "example" { + function_name = aws_lambda_function.example.function_name + provisioned_concurrent_executions = 1 + qualifier = aws_lambda_function.example.version +} +``` + +## Argument Reference + +The following arguments are required: + +* `function_name` - (Required) Name or Amazon Resource Name (ARN) of the Lambda Function. +* `provisioned_concurrent_executions` - (Required) Amount of capacity to allocate. Must be greater than or equal to `1`. +* `qualifier` - (Required) Lambda Function version or Lambda Alias name. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `id` - Lambda Function name and qualifier separated by a colon (`:`). + +## Timeouts + +`aws_lambda_provisioned_concurrency_config` provides the following [Timeouts](/docs/configuration/resources.html#timeouts) configuration options: + +* `create` - (Default `15 minutes`) How long to wait for the Lambda Provisioned Concurrency Config to be ready on creation. +* `update` - (Default `15 minutes`) How long to wait for the Lambda Provisioned Concurrency Config to be ready on update. + +## Import + +Lambda Provisioned Concurrency Configs can be imported using the `function_name` and `qualifier` separated by a colon (`:`), e.g. + +``` +$ terraform import aws_lambda_provisioned_concurrency_config.example my_function:production +```