diff --git a/.changelog/31933.txt b/.changelog/31933.txt new file mode 100644 index 000000000000..bc19467a49a5 --- /dev/null +++ b/.changelog/31933.txt @@ -0,0 +1,3 @@ +```release-note:bug +resource/aws_lambda_provisioned_concurrency_config: The `function_name` argument now properly handles ARN values +``` diff --git a/internal/service/lambda/provisioned_concurrency_config.go b/internal/service/lambda/provisioned_concurrency_config.go index b7824e678d16..0b2a31872daa 100644 --- a/internal/service/lambda/provisioned_concurrency_config.go +++ b/internal/service/lambda/provisioned_concurrency_config.go @@ -4,7 +4,6 @@ import ( "context" "fmt" "log" - "strings" "time" "github.com/aws/aws-sdk-go/aws" @@ -16,6 +15,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "github.com/hashicorp/terraform-provider-aws/internal/conns" "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/flex" ) // @SDKResource("aws_lambda_provisioned_concurrency_config") @@ -25,14 +25,25 @@ func ResourceProvisionedConcurrencyConfig() *schema.Resource { ReadWithoutTimeout: resourceProvisionedConcurrencyConfigRead, UpdateWithoutTimeout: resourceProvisionedConcurrencyConfigUpdate, DeleteWithoutTimeout: resourceProvisionedConcurrencyConfigDelete, + Importer: &schema.ResourceImporter{ StateContext: schema.ImportStatePassthroughContext, }, + Timeouts: &schema.ResourceTimeout{ Create: schema.DefaultTimeout(15 * time.Minute), Update: schema.DefaultTimeout(15 * time.Minute), }, + SchemaVersion: 1, + StateUpgraders: []schema.StateUpgrader{ + { + Type: resourceProvisionedConcurrencyConfigV0().CoreConfigSchema().ImpliedType(), + Upgrade: provisionedConcurrencyConfigStateUpgradeV0, + Version: 0, + }, + }, + Schema: map[string]*schema.Schema{ "function_name": { Type: schema.TypeString, @@ -60,6 +71,10 @@ func ResourceProvisionedConcurrencyConfig() *schema.Resource { } } +const ( + ProvisionedConcurrencyIDPartCount = 2 +) + func resourceProvisionedConcurrencyConfigCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics conn := meta.(*conns.AWSClient).LambdaConn(ctx) @@ -75,10 +90,15 @@ func resourceProvisionedConcurrencyConfigCreate(ctx context.Context, d *schema.R _, err := conn.PutProvisionedConcurrencyConfigWithContext(ctx, input) if err != nil { - return sdkdiag.AppendErrorf(diags, "putting Lambda Provisioned Concurrency Config (%s:%s): %s", functionName, qualifier, err) + return sdkdiag.AppendErrorf(diags, "putting Lambda Provisioned Concurrency Config (%s,%s): %s", functionName, qualifier, err) } - d.SetId(fmt.Sprintf("%s:%s", functionName, qualifier)) + parts := []string{functionName, qualifier} + id, err := flex.FlattenResourceId(parts, ProvisionedConcurrencyIDPartCount, false) + if err != nil { + return sdkdiag.AppendErrorf(diags, "setting Lambda Provisioned Concurrency Config ID (%s,%s): %s", functionName, qualifier, err) + } + d.SetId(id) if err := waitForProvisionedConcurrencyConfigStatusReady(ctx, conn, functionName, qualifier, d.Timeout(schema.TimeoutCreate)); err != nil { return sdkdiag.AppendErrorf(diags, "waiting for Lambda Provisioned Concurrency Config (%s) to be ready: %s", d.Id(), err) @@ -91,11 +111,12 @@ func resourceProvisionedConcurrencyConfigRead(ctx context.Context, d *schema.Res var diags diag.Diagnostics conn := meta.(*conns.AWSClient).LambdaConn(ctx) - functionName, qualifier, err := ProvisionedConcurrencyConfigParseID(d.Id()) - + parts, err := flex.ExpandResourceId(d.Id(), ProvisionedConcurrencyIDPartCount, false) if err != nil { return sdkdiag.AppendErrorf(diags, "reading Lambda Provisioned Concurrency Config (%s): %s", d.Id(), err) } + functionName := parts[0] + qualifier := parts[1] input := &lambda.GetProvisionedConcurrencyConfigInput{ FunctionName: aws.String(functionName), @@ -125,11 +146,12 @@ func resourceProvisionedConcurrencyConfigUpdate(ctx context.Context, d *schema.R var diags diag.Diagnostics conn := meta.(*conns.AWSClient).LambdaConn(ctx) - functionName, qualifier, err := ProvisionedConcurrencyConfigParseID(d.Id()) - + parts, err := flex.ExpandResourceId(d.Id(), ProvisionedConcurrencyIDPartCount, false) if err != nil { return sdkdiag.AppendErrorf(diags, "updating Lambda Provisioned Concurrency Config (%s): %s", d.Id(), err) } + functionName := parts[0] + qualifier := parts[1] input := &lambda.PutProvisionedConcurrencyConfigInput{ FunctionName: aws.String(functionName), @@ -159,15 +181,14 @@ func resourceProvisionedConcurrencyConfigDelete(ctx context.Context, d *schema.R conn := meta.(*conns.AWSClient).LambdaConn(ctx) - functionName, qualifier, err := ProvisionedConcurrencyConfigParseID(d.Id()) - + parts, err := flex.ExpandResourceId(d.Id(), ProvisionedConcurrencyIDPartCount, false) if err != nil { return sdkdiag.AppendErrorf(diags, "deleting Lambda Provisioned Concurrency Config (%s): %s", d.Id(), err) } input := &lambda.DeleteProvisionedConcurrencyConfigInput{ - FunctionName: aws.String(functionName), - Qualifier: aws.String(qualifier), + FunctionName: aws.String(parts[0]), + Qualifier: aws.String(parts[1]), } _, err = conn.DeleteProvisionedConcurrencyConfigWithContext(ctx, input) @@ -183,16 +204,6 @@ func resourceProvisionedConcurrencyConfigDelete(ctx context.Context, d *schema.R return diags } -func ProvisionedConcurrencyConfigParseID(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 refreshProvisionedConcurrencyConfigStatus(ctx context.Context, conn *lambda.Lambda, functionName, qualifier string) retry.StateRefreshFunc { return func() (interface{}, string, error) { input := &lambda.GetProvisionedConcurrencyConfigInput{ diff --git a/internal/service/lambda/provisioned_concurrency_config_migrate.go b/internal/service/lambda/provisioned_concurrency_config_migrate.go new file mode 100644 index 000000000000..e5b0cf00594c --- /dev/null +++ b/internal/service/lambda/provisioned_concurrency_config_migrate.go @@ -0,0 +1,59 @@ +package lambda + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + "github.com/hashicorp/terraform-provider-aws/internal/flex" +) + +func resourceProvisionedConcurrencyConfigV0() *schema.Resource { + // Resource with v0 schema (provider v5.3.0 and below) + return &schema.Resource{ + 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, + }, + "skip_destroy": { + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + }, + } +} + +func provisionedConcurrencyConfigStateUpgradeV0(ctx context.Context, rawState map[string]interface{}, meta interface{}) (map[string]interface{}, error) { + if rawState == nil { + rawState = map[string]interface{}{} + } + + // Convert id separator from ":" to "," + parts := []string{ + rawState["function_name"].(string), + rawState["qualifier"].(string), + } + + id, err := flex.FlattenResourceId(parts, ProvisionedConcurrencyIDPartCount, false) + if err != nil { + return rawState, err + } + rawState["id"] = id + + return rawState, nil +} diff --git a/internal/service/lambda/provisioned_concurrency_config_test.go b/internal/service/lambda/provisioned_concurrency_config_test.go index 48827ebcf0c8..2a33d40607b9 100644 --- a/internal/service/lambda/provisioned_concurrency_config_test.go +++ b/internal/service/lambda/provisioned_concurrency_config_test.go @@ -14,6 +14,7 @@ import ( "github.com/hashicorp/terraform-plugin-testing/terraform" "github.com/hashicorp/terraform-provider-aws/internal/acctest" "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/flex" tflambda "github.com/hashicorp/terraform-provider-aws/internal/service/lambda" "github.com/hashicorp/terraform-provider-aws/names" ) @@ -142,6 +143,50 @@ func TestAccLambdaProvisionedConcurrencyConfig_provisionedConcurrentExecutions(t }) } +func TestAccLambdaProvisionedConcurrencyConfig_FunctionName_arn(t *testing.T) { + ctx := acctest.Context(t) + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_lambda_provisioned_concurrency_config.test" + lambdaFunctionResourceName := "aws_lambda_function.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.LambdaEndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckProvisionedConcurrencyConfigDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccProvisionedConcurrencyConfigConfig_FunctionName_arn(rName, 1), + Check: resource.ComposeTestCheckFunc( + testAccCheckProvisionedConcurrencyConfigExists(ctx, resourceName), + resource.TestCheckResourceAttrPair(resourceName, "function_name", lambdaFunctionResourceName, "arn"), + resource.TestCheckResourceAttr(resourceName, "provisioned_concurrent_executions", "1"), + resource.TestCheckResourceAttr(resourceName, "qualifier", "1"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"skip_destroy"}, + }, + { + Config: testAccProvisionedConcurrencyConfigConfig_FunctionName_arn(rName, 2), + Check: resource.ComposeTestCheckFunc( + testAccCheckProvisionedConcurrencyConfigExists(ctx, resourceName), + resource.TestCheckResourceAttrPair(resourceName, "function_name", lambdaFunctionResourceName, "arn"), + resource.TestCheckResourceAttr(resourceName, "provisioned_concurrent_executions", "2"), + resource.TestCheckResourceAttr(resourceName, "qualifier", "1"), + ), + }, + }, + }) +} + func TestAccLambdaProvisionedConcurrencyConfig_Qualifier_aliasName(t *testing.T) { ctx := acctest.Context(t) rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -219,6 +264,51 @@ func TestAccLambdaProvisionedConcurrencyConfig_skipDestroy(t *testing.T) { }) } +func TestAccLambdaProvisionedConcurrencyConfig_idMigration530(t *testing.T) { + ctx := acctest.Context(t) + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + lambdaFunctionResourceName := "aws_lambda_function.test" + resourceName := "aws_lambda_provisioned_concurrency_config.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.LambdaEndpointID), + CheckDestroy: testAccCheckProvisionedConcurrencyConfigDestroy(ctx), + Steps: []resource.TestStep{ + { + // At v5.3.0 the resource's schema is v0 and id is colon-delimited + ExternalProviders: map[string]resource.ExternalProvider{ + "aws": { + Source: "hashicorp/aws", + VersionConstraint: "5.3.0", + }, + }, + Config: testAccProvisionedConcurrencyConfigConfig_concurrentExecutions(rName, 1), + Check: resource.ComposeTestCheckFunc( + testAccCheckProvisionedConcurrencyConfigExists_v0Schema(ctx, resourceName), + resource.TestCheckResourceAttrPair(resourceName, "function_name", lambdaFunctionResourceName, "function_name"), + resource.TestCheckResourceAttr(resourceName, "provisioned_concurrent_executions", "1"), + resource.TestCheckResourceAttrPair(resourceName, "qualifier", lambdaFunctionResourceName, "version"), + resource.TestCheckResourceAttr(resourceName, "skip_destroy", "false"), + resource.TestCheckResourceAttr(resourceName, "id", fmt.Sprintf("%s:1", rName)), + ), + }, + { + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + Config: testAccProvisionedConcurrencyConfigConfig_concurrentExecutions(rName, 1), + Check: resource.ComposeTestCheckFunc( + testAccCheckProvisionedConcurrencyConfigExists(ctx, resourceName), + resource.TestCheckResourceAttrPair(resourceName, "function_name", lambdaFunctionResourceName, "function_name"), + resource.TestCheckResourceAttr(resourceName, "provisioned_concurrent_executions", "1"), + resource.TestCheckResourceAttrPair(resourceName, "qualifier", lambdaFunctionResourceName, "version"), + resource.TestCheckResourceAttr(resourceName, "skip_destroy", "false"), + resource.TestCheckResourceAttr(resourceName, "id", fmt.Sprintf("%s,1", rName)), + ), + }, + }, + }) +} + func testAccCheckProvisionedConcurrencyConfigDestroy(ctx context.Context) resource.TestCheckFunc { return func(s *terraform.State) error { conn := acctest.Provider.Meta().(*conns.AWSClient).LambdaClient(ctx) @@ -228,15 +318,14 @@ func testAccCheckProvisionedConcurrencyConfigDestroy(ctx context.Context) resour continue } - functionName, qualifier, err := tflambda.ProvisionedConcurrencyConfigParseID(rs.Primary.ID) - + parts, err := flex.ExpandResourceId(rs.Primary.ID, tflambda.ProvisionedConcurrencyIDPartCount, false) if err != nil { return err } input := &lambda.GetProvisionedConcurrencyConfigInput{ - FunctionName: aws.String(functionName), - Qualifier: aws.String(qualifier), + FunctionName: aws.String(parts[0]), + Qualifier: aws.String(parts[1]), } output, err := conn.GetProvisionedConcurrencyConfig(ctx, input) @@ -274,15 +363,14 @@ func testAccCheckProvisionedConcurrencyDisappearsConfig(ctx context.Context, res conn := acctest.Provider.Meta().(*conns.AWSClient).LambdaClient(ctx) - functionName, qualifier, err := tflambda.ProvisionedConcurrencyConfigParseID(rs.Primary.ID) - + parts, err := flex.ExpandResourceId(rs.Primary.ID, tflambda.ProvisionedConcurrencyIDPartCount, false) if err != nil { return err } input := &lambda.DeleteProvisionedConcurrencyConfigInput{ - FunctionName: aws.String(functionName), - Qualifier: aws.String(qualifier), + FunctionName: aws.String(parts[0]), + Qualifier: aws.String(parts[1]), } _, err = conn.DeleteProvisionedConcurrencyConfig(ctx, input) @@ -291,7 +379,9 @@ func testAccCheckProvisionedConcurrencyDisappearsConfig(ctx context.Context, res } } -func testAccCheckProvisionedConcurrencyConfigExists(ctx context.Context, resourceName string) resource.TestCheckFunc { +// testAccCheckProvisionedConcurrencyConfigExists_v0Schema is a variant of the check +// exists functions for v0 schemas. +func testAccCheckProvisionedConcurrencyConfigExists_v0Schema(ctx context.Context, resourceName string) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[resourceName] if !ok { @@ -302,17 +392,59 @@ func testAccCheckProvisionedConcurrencyConfigExists(ctx context.Context, resourc return fmt.Errorf("Resource (%s) ID not set", resourceName) } + // flex.ExpandResourceId will fail for unmigrated (v0) schemas. For checking existence + // in the migration test, read the required attributes directly instead. + functionName, ok := rs.Primary.Attributes["function_name"] + if !ok { + return fmt.Errorf("Resource (%s) function_name attribute not set", resourceName) + } + qualifier, ok := rs.Primary.Attributes["qualifier"] + if !ok { + return fmt.Errorf("Resource (%s) qualifier attribute not set", resourceName) + } + conn := acctest.Provider.Meta().(*conns.AWSClient).LambdaClient(ctx) - functionName, qualifier, err := tflambda.ProvisionedConcurrencyConfigParseID(rs.Primary.ID) + input := &lambda.GetProvisionedConcurrencyConfigInput{ + FunctionName: aws.String(functionName), + Qualifier: aws.String(qualifier), + } + + output, err := conn.GetProvisionedConcurrencyConfig(ctx, input) if err != nil { return err } + if got, want := output.Status, types.ProvisionedConcurrencyStatusEnumReady; got != want { + return fmt.Errorf("Lambda Provisioned Concurrency Config (%s) expected status (%s), got: %s", rs.Primary.ID, want, got) + } + + return nil + } +} + +func testAccCheckProvisionedConcurrencyConfigExists(ctx context.Context, 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 := acctest.Provider.Meta().(*conns.AWSClient).LambdaClient(ctx) + + parts, err := flex.ExpandResourceId(rs.Primary.ID, tflambda.ProvisionedConcurrencyIDPartCount, false) + if err != nil { + return err + } + input := &lambda.GetProvisionedConcurrencyConfigInput{ - FunctionName: aws.String(functionName), - Qualifier: aws.String(qualifier), + FunctionName: aws.String(parts[0]), + Qualifier: aws.String(parts[1]), } output, err := conn.GetProvisionedConcurrencyConfig(ctx, input) @@ -415,6 +547,19 @@ resource "aws_lambda_provisioned_concurrency_config" "test" { ) } +func testAccProvisionedConcurrencyConfigConfig_FunctionName_arn(rName string, provisionedConcurrentExecutions int) string { + return acctest.ConfigCompose( + testAccProvisionedConcurrencyConfigConfigBase(rName), + fmt.Sprintf(` +resource "aws_lambda_provisioned_concurrency_config" "test" { + function_name = aws_lambda_function.test.arn + provisioned_concurrent_executions = %[1]d + qualifier = aws_lambda_function.test.version +} +`, provisionedConcurrentExecutions), + ) +} + func testAccProvisionedConcurrencyConfigConfig_qualifierAliasName(rName string) string { return acctest.ConfigCompose( testAccProvisionedConcurrencyConfigConfigBase(rName), diff --git a/website/docs/r/lambda_provisioned_concurrency_config.html.markdown b/website/docs/r/lambda_provisioned_concurrency_config.html.markdown index ee8802f2710b..421a2b674fec 100644 --- a/website/docs/r/lambda_provisioned_concurrency_config.html.markdown +++ b/website/docs/r/lambda_provisioned_concurrency_config.html.markdown @@ -50,7 +50,7 @@ The following arguments are optional: In addition to all arguments above, the following attributes are exported: -* `id` - Lambda Function name and qualifier separated by a colon (`:`). +* `id` - Lambda Function name and qualifier separated by a comma (`,`). ## Timeouts @@ -61,8 +61,8 @@ In addition to all arguments above, the following attributes are exported: ## Import -Lambda Provisioned Concurrency Configs can be imported using the `function_name` and `qualifier` separated by a colon (`:`), e.g., +A Lambda Provisioned Concurrency Configuration can be imported using the `function_name` and `qualifier` separated by a comma (`,`), e.g., ``` -$ terraform import aws_lambda_provisioned_concurrency_config.example my_function:production +$ terraform import aws_lambda_provisioned_concurrency_config.example my_function,production ```