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

Add ipv6_allowed_for_dual_stack field for lambda function #34045

Merged
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
Loading