Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optionally replace Lambda function ENI security groups on destroy #29289

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .changelog/29289.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:enhancement
resource/aws_lambda_function: Add `replace_security_groups_on_destroy` and `replacement_security_group_ids` attributes
```
18 changes: 18 additions & 0 deletions internal/service/ec2/find.go
Original file line number Diff line number Diff line change
Expand Up @@ -1572,6 +1572,24 @@ func FindNetworkInterfaceByID(ctx context.Context, conn *ec2.EC2, id string) (*e
return output, nil
}

func FindLambdaNetworkInterfacesBySecurityGroupIDsAndFunctionName(ctx context.Context, conn *ec2.EC2, securityGroupIDs []string, functionName string) ([]*ec2.NetworkInterface, error) {
// lambdaENIDescriptionPrefix is the common prefix used in the description for Lambda function
// elastic network interfaces (ENI). This can be used with a function name to filter to only
// ENIs associated with a single function.
lambdaENIDescriptionPrefix := "AWS Lambda VPC ENI-"
description := fmt.Sprintf("%s%s-*", lambdaENIDescriptionPrefix, functionName)

input := &ec2.DescribeNetworkInterfacesInput{
Filters: BuildAttributeFilterList(map[string]string{
"interface-type": ec2.NetworkInterfaceTypeLambda,
"description": description,
}),
}
input.Filters = append(input.Filters, NewFilter("group-id", securityGroupIDs))

return FindNetworkInterfaces(ctx, conn, input)
}

func FindNetworkInterfacesByAttachmentInstanceOwnerIDAndDescription(ctx context.Context, conn *ec2.EC2, attachmentInstanceOwnerID, description string) ([]*ec2.NetworkInterface, error) {
input := &ec2.DescribeNetworkInterfacesInput{
Filters: BuildAttributeFilterList(map[string]string{
Expand Down
68 changes: 68 additions & 0 deletions internal/service/lambda/function.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/arn"
"github.com/aws/aws-sdk-go/aws/endpoints"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/aws/aws-sdk-go/service/lambda"
"github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
Expand All @@ -21,6 +22,7 @@ import (
"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"
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 @@ -234,6 +236,16 @@ func ResourceFunction() *schema.Resource {
Type: schema.TypeString,
Computed: true,
},
"replace_security_groups_on_destroy": {
Type: schema.TypeBool,
Optional: true,
},
"replacement_security_group_ids": {
Type: schema.TypeSet,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
RequiredWith: []string{"replace_security_groups_on_destroy"},
},
"reserved_concurrent_executions": {
Type: schema.TypeInt,
Optional: true,
Expand Down Expand Up @@ -981,6 +993,12 @@ func resourceFunctionDelete(ctx context.Context, d *schema.ResourceData, meta in
return sdkdiag.AppendErrorf(diags, "deleting Lambda Function (%s): %s", d.Id(), err)
}

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

return diags
}

Expand Down Expand Up @@ -1044,6 +1062,56 @@ func findLatestFunctionVersionByName(ctx context.Context, conn *lambda.Lambda, n
return output, nil
}

// replaceSecurityGroups will replace the security groups on orphaned lambda ENI's
//
// If the replacement_security_group_ids attribute is set, those values will be used as
// replacements. Otherwise, the default security group is used.
func replaceSecurityGroups(ctx context.Context, d *schema.ResourceData, meta interface{}) error {
ec2Conn := meta.(*conns.AWSClient).EC2Conn()

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

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

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

networkInterfaces, err := tfec2.FindLambdaNetworkInterfacesBySecurityGroupIDsAndFunctionName(ctx, ec2Conn, sgIDs, d.Id())
if err != nil {
return fmt.Errorf("finding Lambda Function (%s) network interfaces: %s", d.Id(), err)
}

for _, ni := range networkInterfaces {
_, err := ec2Conn.ModifyNetworkInterfaceAttributeWithContext(ctx, &ec2.ModifyNetworkInterfaceAttributeInput{
NetworkInterfaceId: ni.NetworkInterfaceId,
Groups: replacmentSGIDs,
})
if err != nil {
return fmt.Errorf("modifying Lambda Function (%s) network interfaces: %s", d.Id(), err)
}
}

return nil
}

func statusFunctionLastUpdateStatus(ctx context.Context, conn *lambda.Lambda, name string) resource.StateRefreshFunc {
return func() (interface{}, string, error) {
output, err := FindFunctionByName(ctx, conn, name)
Expand Down
Loading