Skip to content

Commit

Permalink
r/aws_lambda_function: re-implement replace_security_groups_on_destroy
Browse files Browse the repository at this point in the history
This change implements an alternative approach for improving deletion times of security groups associated with deleted Lambda functions. Previously, the `replace_security_groups_on_destroy` and `replacement_security_group_ids` were deprecated as AWS began disallowing modification of the security groups assigned to orphaned ENI's left behind after destruction of a Lambda function.

The deprecations on these arguments will be removed with this re-implementation. Instead of attempting to modify orphaned ENI's after deletion of the function, this approach replaces the security groups assigned in the functions VPC configuration, waits for the configuration update to complete, and then deletes the function. This re-ordering allows for faster security group deletion as the lambda function ENI's left behind after deletion do not contain the configured security groups. This approach also does not violate AWS rules around mutability of Lambda function ENI's, as Lambda is handling replacement of the security groups associated with the ENI's internally. While improvements are not as significant as the previous approach, this option still offers a significant reduction in destroy time when compared to a configurations without this option enabled.

```console
% make testacc PKG=lambda TESTS="TestAccLambdaFunction_VPC_replaceSGWith"
==> Checking that code complies with gofmt requirements...
TF_ACC=1 go1.22.2 test ./internal/service/lambda/... -v -count 1 -parallel 20 -run='TestAccLambdaFunction_VPC_replaceSGWith'  -timeout 360m

--- PASS: TestAccLambdaFunction_VPC_replaceSGWithDefault (426.21s)
--- PASS: TestAccLambdaFunction_VPC_replaceSGWithCustom (648.47s)
PASS
ok      github.com/hashicorp/terraform-provider-aws/internal/service/lambda     653.553s
```

```console
% make testacc PKG=lambda TESTS="TestAccLambdaFunction_"
==> Checking that code complies with gofmt requirements...
TF_ACC=1 go1.22.2 test ./internal/service/lambda/... -v -count 1 -parallel 20 -run='TestAccLambdaFunction_'  -timeout 360m

--- PASS: TestAccLambdaFunction_expectFilenameAndS3Attributes (7.82s)
=== CONT  TestAccLambdaFunction_loggingConfig
--- PASS: TestAccLambdaFunction_nameValidation (8.23s)
=== CONT  TestAccLambdaFunction_ephemeralStorage
--- PASS: TestAccLambdaFunction_Zip_validation (13.86s)
=== CONT  TestAccLambdaFunction_architecturesWithLayer
--- PASS: TestAccLambdaFunction_versioned (49.69s)
=== CONT  TestAccLambdaFunction_architecturesUpdate
--- PASS: TestAccLambdaFunction_S3Update_unversioned (59.60s)
=== CONT  TestAccLambdaFunction_architectures
--- PASS: TestAccLambdaFunction_skipDestroyInconsistentPlan (60.70s)
=== CONT  TestAccLambdaFunction_EnvironmentVariables_noValue
--- PASS: TestAccLambdaFunction_S3Update_basic (62.64s)
=== CONT  TestAccLambdaFunction_tags
--- PASS: TestAccLambdaFunction_nilDeadLetter (68.23s)
=== CONT  TestAccLambdaFunction_unpublishedCodeUpdate
--- PASS: TestAccLambdaFunction_basic (71.08s)
=== CONT  TestAccLambdaFunction_envVariables
--- PASS: TestAccLambdaFunction_tracing (74.88s)
=== CONT  TestAccLambdaFunction_disappears
--- PASS: TestAccLambdaFunction_EnvironmentVariables_emptyUpgrade (76.27s)
=== CONT  TestAccLambdaFunction_disablePublish
--- PASS: TestAccLambdaFunction_skipDestroy (80.78s)
=== CONT  TestAccLambdaFunction_deadLetterUpdated
--- PASS: TestAccLambdaFunction_snapStart (81.34s)
=== CONT  TestAccLambdaFunction_deadLetter
--- PASS: TestAccLambdaFunction_concurrencyCycle (98.23s)
=== CONT  TestAccLambdaFunction_VPCPublishNo_changes
--- PASS: TestAccLambdaFunction_codeSigning (105.37s)
=== CONT  TestAccLambdaFunction_s3
--- PASS: TestAccLambdaFunction_ephemeralStorage (114.15s)
=== CONT  TestAccLambdaFunction_emptyVPC
--- PASS: TestAccLambdaFunction_s3 (26.56s)
=== CONT  TestAccLambdaFunction_VPC_replaceSGWithCustom
--- PASS: TestAccLambdaFunction_loggingConfig (126.46s)
=== CONT  TestAccLambdaFunction_VPC_replaceSGWithDefault
--- PASS: TestAccLambdaFunction_LocalUpdate_nameOnly (304.98s)
=== CONT  TestAccLambdaFunction_VPC_properIAMDependencies
--- PASS: TestAccLambdaFunction_localUpdate (310.81s)
=== CONT  TestAccLambdaFunction_VPCPublishHas_changes
--- PASS: TestAccLambdaFunction_EnvironmentVariables_noValue (273.32s)
=== CONT  TestAccLambdaFunction_enablePublish
--- PASS: TestAccLambdaFunction_architectures (276.70s)
=== CONT  TestAccLambdaFunction_versionedUpdate
--- PASS: TestAccLambdaFunction_disappears (281.36s)
=== CONT  TestAccLambdaFunction_encryptedEnvVariables
--- PASS: TestAccLambdaFunction_tags (297.75s)
=== CONT  TestAccLambdaFunction_vpc
--- PASS: TestAccLambdaFunction_disablePublish (297.83s)
=== CONT  TestAccLambdaFunction_VPC_withInvocation
--- PASS: TestAccLambdaFunction_deadLetterUpdated (309.54s)
=== CONT  TestAccLambdaFunction_vpcUpdate
--- PASS: TestAccLambdaFunction_deadLetter (310.61s)
=== CONT  TestAccLambdaFunction_vpcRemoval
--- PASS: TestAccLambdaFunction_envVariables (331.82s)
=== CONT  TestAccLambdaFunction_layers
--- PASS: TestAccLambdaFunction_runtimes (438.40s)
=== CONT  TestAccLambdaFunction_layersUpdate
--- PASS: TestAccLambdaFunction_architecturesWithLayer (562.33s)
=== CONT  TestAccLambdaFunction_concurrency
--- PASS: TestAccLambdaFunction_ipv6AllowedForDualStack (701.53s)
=== CONT  TestAccLambdaFunction_KMSKeyARN_noEnvironmentVariables
--- PASS: TestAccLambdaFunction_emptyVPC (643.83s)
--- PASS: TestAccLambdaFunction_fileSystem (907.72s)
--- PASS: TestAccLambdaFunction_VPCPublishNo_changes (938.12s)
--- PASS: TestAccLambdaFunction_VPC_replaceSGWithCustom (1056.70s)
--- PASS: TestAccLambdaFunction_architecturesUpdate (1295.23s)
--- PASS: TestAccLambdaFunction_VPC_properIAMDependencies (1056.01s)
--- PASS: TestAccLambdaFunction_VPC_replaceSGWithDefault (1226.73s)
--- PASS: TestAccLambdaFunction_enablePublish (1038.48s)
--- PASS: TestAccLambdaFunction_unpublishedCodeUpdate (1479.15s)
--- PASS: TestAccLambdaFunction_encryptedEnvVariables (1228.87s)
--- PASS: TestAccLambdaFunction_VPCPublishHas_changes (1424.59s)
--- PASS: TestAccLambdaFunction_vpc (1569.97s)
--- PASS: TestAccLambdaFunction_layers (1546.31s)
--- PASS: TestAccLambdaFunction_VPC_withInvocation (1588.27s)
--- PASS: TestAccLambdaFunction_KMSKeyARN_noEnvironmentVariables (1654.83s)
--- PASS: TestAccLambdaFunction_concurrency (1782.71s)
--- PASS: TestAccLambdaFunction_layersUpdate (1924.70s)
--- PASS: TestAccLambdaFunction_versionedUpdate (2040.51s)
--- PASS: TestAccLambdaFunction_vpcUpdate (2161.88s)
--- PASS: TestAccLambdaFunction_vpcRemoval (2161.44s)
PASS
ok      github.com/hashicorp/terraform-provider-aws/internal/service/lambda     2558.481s
```
  • Loading branch information
jar-b committed May 21, 2024
1 parent d5cdfff commit 7e52d3d
Show file tree
Hide file tree
Showing 4 changed files with 90 additions and 6 deletions.
3 changes: 3 additions & 0 deletions .changelog/37624.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:enhancement
resource/aws_lambda_function: Remove `replace_security_group_on_destory` and `replacement_security_group_ids` deprecations, re-implement alternate workflow
```
13 changes: 13 additions & 0 deletions internal/service/ec2/findv2.go
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,19 @@ func findSecurityGroupsV2(ctx context.Context, conn *ec2.Client, input *ec2.Desc
return output, nil
}

// FindSecurityGroupByNameAndVPCIDV2 looks up a security group by name, VPC ID. Returns a retry.NotFoundError if not found.
func FindSecurityGroupByNameAndVPCIDV2(ctx context.Context, conn *ec2.Client, name, vpcID string) (*awstypes.SecurityGroup, error) {
input := &ec2.DescribeSecurityGroupsInput{
Filters: newAttributeFilterListV2(
map[string]string{
"group-name": name,
"vpc-id": vpcID,
},
),
}
return findSecurityGroupV2(ctx, conn, input)
}

func findIPAMPoolAllocationsV2(ctx context.Context, conn *ec2.Client, input *ec2.GetIpamPoolAllocationsInput) ([]awstypes.IpamPoolAllocation, error) {
var output []awstypes.IpamPoolAllocation

Expand Down
72 changes: 68 additions & 4 deletions internal/service/lambda/function.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
"github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag"
"github.com/hashicorp/terraform-provider-aws/internal/flex"
"github.com/hashicorp/terraform-provider-aws/internal/sdkv2"
tfec2 "github.com/hashicorp/terraform-provider-aws/internal/service/ec2"
tftags "github.com/hashicorp/terraform-provider-aws/internal/tags"
"github.com/hashicorp/terraform-provider-aws/internal/tfresource"
"github.com/hashicorp/terraform-provider-aws/internal/verify"
Expand Down Expand Up @@ -292,14 +293,10 @@ func resourceFunction() *schema.Resource {
Computed: true,
},
"replace_security_groups_on_destroy": {
Deprecated: "AWS no longer supports this operation. This attribute now has " +
"no effect and will be removed in a future major version.",
Type: schema.TypeBool,
Optional: true,
},
"replacement_security_group_ids": {
Deprecated: "AWS no longer supports this operation. This attribute now has " +
"no effect and will be removed in a future major version.",
Type: schema.TypeSet,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
Expand Down Expand Up @@ -1045,6 +1042,12 @@ func resourceFunctionDelete(ctx context.Context, d *schema.ResourceData, meta in
return diags
}

if _, ok := d.GetOk("replace_security_groups_on_destroy"); ok {
if err := replaceSecurityGroupsOnDestroy(ctx, d, meta); err != nil {
return sdkdiag.AppendFromErr(diags, err)
}
}

log.Printf("[INFO] Deleting Lambda Function: %s", d.Id())
_, err := tfresource.RetryWhenIsAErrorMessageContains[*awstypes.InvalidParameterValueException](ctx, d.Timeout(schema.TimeoutDelete), func() (interface{}, error) {
return conn.DeleteFunction(ctx, &lambda.DeleteFunctionInput{
Expand Down Expand Up @@ -1120,6 +1123,67 @@ func findLatestFunctionVersionByName(ctx context.Context, conn *lambda.Client, n
return output, nil
}

// replaceSecurityGroupsOnDestroy sets the VPC configuration security groups
// prior to resource destruction
//
// This function is called when the replace_security_groups_on_destroy
// argument is set. If the replacement_security_group_ids attribute is set,
// those values will be used as replacements. Otherwise, the default
// security group is used.
//
// Configuring this option can decrease destroy times for the security
// groups included in the VPC configuration block during normal operation
// by freeing them from association with ENI's left behind after destruction
// of the function.
func replaceSecurityGroupsOnDestroy(ctx context.Context, d *schema.ResourceData, meta interface{}) error {
conn := meta.(*conns.AWSClient).LambdaClient(ctx)
ec2Conn := meta.(*conns.AWSClient).EC2Client(ctx)

var sgIDs []string
var vpcID string
if v, ok := d.GetOk(names.AttrVPCConfig); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil {
tfMap := v.([]interface{})[0].(map[string]interface{})
sgIDs = flex.ExpandStringValueSet(tfMap[names.AttrSecurityGroupIDs].(*schema.Set))
vpcID = tfMap[names.AttrVPCID].(string)
} else { // empty VPC config, nothing to do
return nil
}

if len(sgIDs) == 0 { // no security groups, nothing to do
return nil
}

var replacementSGIDs []string
if v, ok := d.GetOk("replacement_security_group_ids"); ok {
replacementSGIDs = flex.ExpandStringValueSet(v.(*schema.Set))
} else {
defaultSG, err := tfec2.FindSecurityGroupByNameAndVPCIDV2(ctx, ec2Conn, "default", vpcID)
if err != nil || defaultSG == nil {
return fmt.Errorf("finding VPC (%s) default security group: %s", vpcID, err)
}
replacementSGIDs = []string{aws.ToString(defaultSG.GroupId)}
}

input := &lambda.UpdateFunctionConfigurationInput{
FunctionName: aws.String(d.Id()),
VpcConfig: &awstypes.VpcConfig{
SecurityGroupIds: replacementSGIDs,
},
}

if _, err := retryFunctionOp(ctx, func() (*lambda.UpdateFunctionConfigurationOutput, error) {
return conn.UpdateFunctionConfiguration(ctx, input)
}); err != nil {
return fmt.Errorf("updating Lambda Function (%s) configuration: %s", d.Id(), err)
}

if _, err := waitFunctionUpdated(ctx, conn, d.Id(), d.Timeout(schema.TimeoutDelete)); err != nil {
return fmt.Errorf("waiting for Lambda Function (%s) configuration update: %s", d.Id(), err)
}

return nil
}

func statusFunctionLastUpdateStatus(ctx context.Context, conn *lambda.Client, name string) retry.StateRefreshFunc {
return func() (interface{}, string, error) {
output, err := findFunctionByName(ctx, conn, name)
Expand Down
8 changes: 6 additions & 2 deletions website/docs/r/lambda_function.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -280,8 +280,12 @@ The following arguments are optional:
* `package_type` - (Optional) Lambda deployment package type. Valid values are `Zip` and `Image`. Defaults to `Zip`.
* `publish` - (Optional) Whether to publish creation/change as new Lambda Function Version. Defaults to `false`.
* `reserved_concurrent_executions` - (Optional) Amount of reserved concurrent executions for this lambda function. A value of `0` disables lambda from being triggered and `-1` removes any concurrency limitations. Defaults to Unreserved Concurrency Limits `-1`. See [Managing Concurrency][9]
* `replace_security_groups_on_destroy` - (Optional, **Deprecated**) **AWS no longer supports this operation. This attribute now has no effect and will be removed in a future major version.** Whether to replace the security groups on associated lambda network interfaces upon destruction. Removing these security groups from orphaned network interfaces can speed up security group deletion times by avoiding a dependency on AWS's internal cleanup operations. By default, the ENI security groups will be replaced with the `default` security group in the function's VPC. Set the `replacement_security_group_ids` attribute to use a custom list of security groups for replacement.
* `replacement_security_group_ids` - (Optional, **Deprecated**) List of security group IDs to assign to orphaned Lambda function network interfaces upon destruction. `replace_security_groups_on_destroy` must be set to `true` to use this attribute.
* `replace_security_groups_on_destroy` - (Optional) Whether to replace the security groups on the function's VPC configuration upon destruction.
Removing these security group associations prior to function destruction can speed up security group deletion times of AWS's internal cleanup operations.
By default, the security groups will be replaced with the `default` security group in the function's configured VPC.
Set the `replacement_security_group_ids` attribute to use a custom list of security groups for replacement.
* `replacement_security_group_ids` - (Optional) List of security group IDs to assign to the function's VPC configuration upon destruction.
`replace_security_groups_on_destroy` must be set to `true` to use this attribute.
* `runtime` - (Optional) Identifier of the function's runtime. See [Runtimes][6] for valid values.
* `s3_bucket` - (Optional) S3 bucket location containing the function's deployment package. This bucket must reside in the same AWS region where you are creating the Lambda function. Exactly one of `filename`, `image_uri`, or `s3_bucket` must be specified. When `s3_bucket` is set, `s3_key` is required.
* `s3_key` - (Optional) S3 key of an object containing the function's deployment package. When `s3_bucket` is set, `s3_key` is required.
Expand Down

0 comments on commit 7e52d3d

Please sign in to comment.