Skip to content

Commit

Permalink
Merge pull request #34045 from DanielRieske/f/add-lambda-ipv6-dual-st…
Browse files Browse the repository at this point in the history
…ack-field

Add `ipv6_allowed_for_dual_stack` field for lambda function
  • Loading branch information
ewbankkit authored Oct 23, 2023
2 parents 256ffcc + b2d6af9 commit 20b530b
Show file tree
Hide file tree
Showing 6 changed files with 132 additions and 30 deletions.
3 changes: 3 additions & 0 deletions .changelog/34045.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:enhancement
resource/aws_lambda_function: Add `vpc_config.ipv6_allowed_for_dual_stack` argument
```
33 changes: 22 additions & 11 deletions internal/acctest/acctest.go
Original file line number Diff line number Diff line change
Expand Up @@ -2107,7 +2107,7 @@ func ConfigLambdaBase(policyName, roleName, sgName string) string {
data "aws_partition" "current" {}
resource "aws_iam_role_policy" "iam_policy_for_lambda" {
name = "%s"
name = %[1]q
role = aws_iam_role.iam_for_lambda.id
policy = <<EOF
Expand All @@ -2128,7 +2128,9 @@ resource "aws_iam_role_policy" "iam_policy_for_lambda" {
"Action": [
"ec2:CreateNetworkInterface",
"ec2:DescribeNetworkInterfaces",
"ec2:DeleteNetworkInterface"
"ec2:DeleteNetworkInterface",
"ec2:AssignPrivateIpAddresses",
"ec2:UnassignPrivateIpAddresses"
],
"Resource": [
"*"
Expand Down Expand Up @@ -2158,7 +2160,7 @@ EOF
}
resource "aws_iam_role" "iam_for_lambda" {
name = "%s"
name = %[2]q
assume_role_policy = <<EOF
{
Expand All @@ -2178,17 +2180,20 @@ EOF
}
resource "aws_vpc" "vpc_for_lambda" {
cidr_block = "10.0.0.0/16"
cidr_block = "10.0.0.0/16"
assign_generated_ipv6_cidr_block = true
tags = {
Name = "terraform-testacc-lambda-function"
}
}
resource "aws_subnet" "subnet_for_lambda" {
vpc_id = aws_vpc.vpc_for_lambda.id
cidr_block = "10.0.1.0/24"
availability_zone = data.aws_availability_zones.available.names[0]
vpc_id = aws_vpc.vpc_for_lambda.id
cidr_block = cidrsubnet(aws_vpc.vpc_for_lambda.cidr_block, 8, 1)
availability_zone = data.aws_availability_zones.available.names[1]
ipv6_cidr_block = cidrsubnet(aws_vpc.vpc_for_lambda.ipv6_cidr_block, 8, 1)
assign_ipv6_address_on_creation = true
tags = {
Name = "tf-acc-lambda-function-1"
Expand All @@ -2198,17 +2203,19 @@ resource "aws_subnet" "subnet_for_lambda" {
# This is defined here, rather than only in test cases where it's needed is to
# prevent a timeout issue when fully removing Lambda Filesystems
resource "aws_subnet" "subnet_for_lambda_az2" {
vpc_id = aws_vpc.vpc_for_lambda.id
cidr_block = "10.0.2.0/24"
availability_zone = data.aws_availability_zones.available.names[1]
vpc_id = aws_vpc.vpc_for_lambda.id
cidr_block = cidrsubnet(aws_vpc.vpc_for_lambda.cidr_block, 8, 2)
availability_zone = data.aws_availability_zones.available.names[1]
ipv6_cidr_block = cidrsubnet(aws_vpc.vpc_for_lambda.ipv6_cidr_block, 8, 2)
assign_ipv6_address_on_creation = true
tags = {
Name = "tf-acc-lambda-function-2"
}
}
resource "aws_security_group" "sg_for_lambda" {
name = "%s"
name = %[3]q
description = "Allow all inbound traffic for lambda test"
vpc_id = aws_vpc.vpc_for_lambda.id
Expand All @@ -2225,6 +2232,10 @@ resource "aws_security_group" "sg_for_lambda" {
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = %[3]q
}
}
`, policyName, roleName, sgName))
}
Expand Down
1 change: 1 addition & 0 deletions internal/service/lambda/flex.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ func flattenVPCConfigResponse(s *types.VpcConfigResponse) []map[string]interface
return nil
}

settings["ipv6_allowed_for_dual_stack"] = aws.BoolValue(s.Ipv6AllowedForDualStack)
settings["subnet_ids"] = flex.FlattenStringValueSet(s.SubnetIds)
settings["security_group_ids"] = flex.FlattenStringValueSet(s.SecurityGroupIds)
if s.VpcId != nil {
Expand Down
30 changes: 20 additions & 10 deletions internal/service/lambda/function.go
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,11 @@ func ResourceFunction() *schema.Resource {
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"ipv6_allowed_for_dual_stack": {
Type: schema.TypeBool,
Optional: true,
Default: false,
},
"security_group_ids": {
Type: schema.TypeSet,
Required: true,
Expand All @@ -397,15 +402,16 @@ func ResourceFunction() *schema.Resource {
// Suppress diffs if the VPC configuration is provided, but empty
// which is a valid Lambda function configuration. e.g.
// vpc_config {
// security_group_ids = []
// subnet_ids = []
// ipv6_allowed_for_dual_stack = false
// security_group_ids = []
// subnet_ids = []
// }
DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool {
if d.Id() == "" || old == "1" || new == "0" {
return false
}

if d.HasChanges("vpc_config.0.security_group_ids", "vpc_config.0.subnet_ids") {
if d.HasChanges("vpc_config.0.security_group_ids", "vpc_config.0.subnet_ids", "vpc_config.0.ipv6_allowed_for_dual_stack") {
return false
}

Expand Down Expand Up @@ -533,8 +539,9 @@ func resourceFunctionCreate(ctx context.Context, d *schema.ResourceData, meta in
if v, ok := d.GetOk("vpc_config"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil {
tfMap := v.([]interface{})[0].(map[string]interface{})
input.VpcConfig = &types.VpcConfig{
SecurityGroupIds: flex.ExpandStringValueSet(tfMap["security_group_ids"].(*schema.Set)),
SubnetIds: flex.ExpandStringValueSet(tfMap["subnet_ids"].(*schema.Set)),
Ipv6AllowedForDualStack: aws.Bool(tfMap["ipv6_allowed_for_dual_stack"].(bool)),
SecurityGroupIds: flex.ExpandStringValueSet(tfMap["security_group_ids"].(*schema.Set)),
SubnetIds: flex.ExpandStringValueSet(tfMap["subnet_ids"].(*schema.Set)),
}
}

Expand Down Expand Up @@ -849,17 +856,19 @@ func resourceFunctionUpdate(ctx context.Context, d *schema.ResourceData, meta in
}
}

if d.HasChanges("vpc_config.0.security_group_ids", "vpc_config.0.subnet_ids") {
if d.HasChanges("vpc_config.0.security_group_ids", "vpc_config.0.subnet_ids", "vpc_config.0.ipv6_allowed_for_dual_stack") {
if v, ok := d.GetOk("vpc_config"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil {
tfMap := v.([]interface{})[0].(map[string]interface{})
input.VpcConfig = &types.VpcConfig{
SecurityGroupIds: flex.ExpandStringValueSet(tfMap["security_group_ids"].(*schema.Set)),
SubnetIds: flex.ExpandStringValueSet(tfMap["subnet_ids"].(*schema.Set)),
Ipv6AllowedForDualStack: aws.Bool(tfMap["ipv6_allowed_for_dual_stack"].(bool)),
SecurityGroupIds: flex.ExpandStringValueSet(tfMap["security_group_ids"].(*schema.Set)),
SubnetIds: flex.ExpandStringValueSet(tfMap["subnet_ids"].(*schema.Set)),
}
} else {
input.VpcConfig = &types.VpcConfig{
SecurityGroupIds: []string{},
SubnetIds: []string{},
Ipv6AllowedForDualStack: aws.Bool(false),
SecurityGroupIds: []string{},
SubnetIds: []string{},
}
}
}
Expand Down Expand Up @@ -1250,6 +1259,7 @@ func needsFunctionConfigUpdate(d verify.ResourceDiffer) bool {
d.HasChange("dead_letter_config") ||
d.HasChange("snap_start") ||
d.HasChange("tracing_config") ||
d.HasChange("vpc_config.0.ipv6_allowed_for_dual_stack") ||
d.HasChange("vpc_config.0.security_group_ids") ||
d.HasChange("vpc_config.0.subnet_ids") ||
d.HasChange("runtime") ||
Expand Down
92 changes: 84 additions & 8 deletions internal/service/lambda/function_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1824,7 +1824,7 @@ func TestAccLambdaFunction_localUpdate(t *testing.T) {
Config: testAccFunctionConfig_local(path, rName),
Check: resource.ComposeTestCheckFunc(
testAccCheckFunctionExists(ctx, resourceName, &conf),
testAccCheckSourceCodeHash(&conf, "8DPiX+G1l2LQ8hjBkwRchQFf1TSCEvPrYGRKlM9UoyY="),
testAccCheckSourceCodeHash(&conf, "MbW0T1Pcy1QPtrFC9dT7hUfircj1NXss2uXgakqzAbk="),
),
},
{
Expand All @@ -1843,7 +1843,7 @@ func TestAccLambdaFunction_localUpdate(t *testing.T) {
Config: testAccFunctionConfig_local(path, rName),
Check: resource.ComposeTestCheckFunc(
testAccCheckFunctionExists(ctx, resourceName, &conf),
testAccCheckSourceCodeHash(&conf, "0tdaP9H9hsk9c2CycSwOG/sa/x5JyAmSYunA/ce99Pg="),
testAccCheckSourceCodeHash(&conf, "7qn3LZOWCpWK5nm49qjw+VrbPQHfdu2ZrDjBsSUveKM="),
func(s *terraform.State) error {
return testAccCheckAttributeIsDateAfter(s, resourceName, "last_modified", timeBeforeUpdate)
},
Expand Down Expand Up @@ -1890,7 +1890,7 @@ func TestAccLambdaFunction_LocalUpdate_nameOnly(t *testing.T) {
Config: testAccFunctionConfig_localNameOnly(path, rName),
Check: resource.ComposeTestCheckFunc(
testAccCheckFunctionExists(ctx, resourceName, &conf),
testAccCheckSourceCodeHash(&conf, "8DPiX+G1l2LQ8hjBkwRchQFf1TSCEvPrYGRKlM9UoyY="),
testAccCheckSourceCodeHash(&conf, "MbW0T1Pcy1QPtrFC9dT7hUfircj1NXss2uXgakqzAbk="),
),
},
{
Expand All @@ -1908,7 +1908,7 @@ func TestAccLambdaFunction_LocalUpdate_nameOnly(t *testing.T) {
Config: testAccFunctionConfig_localNameOnly(updatedPath, rName),
Check: resource.ComposeTestCheckFunc(
testAccCheckFunctionExists(ctx, resourceName, &conf),
testAccCheckSourceCodeHash(&conf, "0tdaP9H9hsk9c2CycSwOG/sa/x5JyAmSYunA/ce99Pg="),
testAccCheckSourceCodeHash(&conf, "7qn3LZOWCpWK5nm49qjw+VrbPQHfdu2ZrDjBsSUveKM="),
),
},
},
Expand Down Expand Up @@ -1945,7 +1945,7 @@ func TestAccLambdaFunction_S3Update_basic(t *testing.T) {
Config: testAccFunctionConfig_s3(key, path, rName),
Check: resource.ComposeTestCheckFunc(
testAccCheckFunctionExists(ctx, resourceName, &conf),
testAccCheckSourceCodeHash(&conf, "8DPiX+G1l2LQ8hjBkwRchQFf1TSCEvPrYGRKlM9UoyY="),
testAccCheckSourceCodeHash(&conf, "MbW0T1Pcy1QPtrFC9dT7hUfircj1NXss2uXgakqzAbk="),
),
},
{
Expand All @@ -1964,7 +1964,7 @@ func TestAccLambdaFunction_S3Update_basic(t *testing.T) {
Config: testAccFunctionConfig_s3(key, path, rName),
Check: resource.ComposeTestCheckFunc(
testAccCheckFunctionExists(ctx, resourceName, &conf),
testAccCheckSourceCodeHash(&conf, "0tdaP9H9hsk9c2CycSwOG/sa/x5JyAmSYunA/ce99Pg="),
testAccCheckSourceCodeHash(&conf, "7qn3LZOWCpWK5nm49qjw+VrbPQHfdu2ZrDjBsSUveKM="),
),
},
},
Expand Down Expand Up @@ -2001,7 +2001,7 @@ func TestAccLambdaFunction_S3Update_unversioned(t *testing.T) {
Config: testAccFunctionConfig_s3UnversionedTPL(rName, key, path),
Check: resource.ComposeTestCheckFunc(
testAccCheckFunctionExists(ctx, resourceName, &conf),
testAccCheckSourceCodeHash(&conf, "8DPiX+G1l2LQ8hjBkwRchQFf1TSCEvPrYGRKlM9UoyY="),
testAccCheckSourceCodeHash(&conf, "MbW0T1Pcy1QPtrFC9dT7hUfircj1NXss2uXgakqzAbk="),
),
},
{
Expand All @@ -2020,7 +2020,7 @@ func TestAccLambdaFunction_S3Update_unversioned(t *testing.T) {
Config: testAccFunctionConfig_s3UnversionedTPL(rName, key2, path),
Check: resource.ComposeTestCheckFunc(
testAccCheckFunctionExists(ctx, resourceName, &conf),
testAccCheckSourceCodeHash(&conf, "0tdaP9H9hsk9c2CycSwOG/sa/x5JyAmSYunA/ce99Pg="),
testAccCheckSourceCodeHash(&conf, "7qn3LZOWCpWK5nm49qjw+VrbPQHfdu2ZrDjBsSUveKM="),
),
},
},
Expand Down Expand Up @@ -2161,6 +2161,43 @@ func TestAccLambdaFunction_Zip_validation(t *testing.T) {
})
}

func TestAccLambdaFunction_ipv6AllowedForDualStack(t *testing.T) {
ctx := acctest.Context(t)
var conf lambda.GetFunctionOutput
rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)
resourceName := "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: testAccCheckFunctionDestroy(ctx),
Steps: []resource.TestStep{
{
Config: testAccFunctionConfig_ipv6AllowedForDualStackDisabled(rName),
Check: resource.ComposeTestCheckFunc(
testAccCheckFunctionExists(ctx, resourceName, &conf),
resource.TestCheckResourceAttr(resourceName, "vpc_config.#", "1"),
),
},
{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{"filename", "publish"},
},
{
Config: testAccFunctionConfig_ipv6AllowedForDualStackEnabled(rName),
Check: resource.ComposeTestCheckFunc(
testAccCheckFunctionExists(ctx, resourceName, &conf),
resource.TestCheckResourceAttr(resourceName, "vpc_config.#", "1"),
resource.TestCheckResourceAttr(resourceName, "vpc_config.0.ipv6_allowed_for_dual_stack", "true"),
),
},
},
})
}

func TestAccLambdaFunction_skipDestroy(t *testing.T) {
ctx := acctest.Context(t)
var conf lambda.GetFunctionOutput
Expand Down Expand Up @@ -3869,6 +3906,45 @@ resource "aws_lambda_function" "test" {
`, rName))
}

func testAccFunctionConfig_ipv6AllowedForDualStackDisabled(rName string) string {
return acctest.ConfigCompose(
acctest.ConfigLambdaBase(rName, rName, rName),
fmt.Sprintf(`
resource "aws_lambda_function" "test" {
filename = "test-fixtures/lambdatest.zip"
function_name = %[1]q
role = aws_iam_role.iam_for_lambda.arn
handler = "exports.example"
runtime = "nodejs16.x"
vpc_config {
subnet_ids = [aws_subnet.subnet_for_lambda.id]
security_group_ids = [aws_security_group.sg_for_lambda.id]
}
}
`, rName))
}

func testAccFunctionConfig_ipv6AllowedForDualStackEnabled(rName string) string {
return acctest.ConfigCompose(
acctest.ConfigLambdaBase(rName, rName, rName),
fmt.Sprintf(`
resource "aws_lambda_function" "test" {
filename = "test-fixtures/lambdatest.zip"
function_name = %[1]q
role = aws_iam_role.iam_for_lambda.arn
handler = "exports.example"
runtime = "nodejs16.x"
vpc_config {
ipv6_allowed_for_dual_stack = true
subnet_ids = [aws_subnet.subnet_for_lambda.id]
security_group_ids = [aws_security_group.sg_for_lambda.id]
}
}
`, rName))
}

func testAccFunctionConfig_skipDestroy(rName string) string {
return acctest.ConfigCompose(acctest.ConfigLambdaBase(rName, rName, rName), fmt.Sprintf(`
resource "aws_lambda_function" "test" {
Expand Down
3 changes: 2 additions & 1 deletion website/docs/r/lambda_function.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -331,8 +331,9 @@ Snap start settings for low-latency startups. This feature is currently only sup

For network connectivity to AWS resources in a VPC, specify a list of security groups and subnets in the VPC. When you connect a function to a VPC, it can only access resources and the internet through that VPC. See [VPC Settings][7].

~> **NOTE:** If both `subnet_ids` and `security_group_ids` are empty then `vpc_config` is considered to be empty or unset.
~> **NOTE:** If `subnet_ids`, `security_group_ids` and `ipv6_allowed_for_dual_stack` are empty then `vpc_config` is considered to be empty or unset.

* `ipv6_allowed_for_dual_stack` - (Optional) Allows outbound IPv6 traffic on VPC functions that are connected to dual-stack subnets. Default is `false`.
* `security_group_ids` - (Required) List of security group IDs associated with the Lambda function.
* `subnet_ids` - (Required) List of subnet IDs associated with the Lambda function.

Expand Down

0 comments on commit 20b530b

Please sign in to comment.