diff --git a/aws/data_source_aws_subnet.go b/aws/data_source_aws_subnet.go index 5302e7c5ed9..f5cea6cc05c 100644 --- a/aws/data_source_aws_subnet.go +++ b/aws/data_source_aws_subnet.go @@ -77,6 +77,16 @@ func dataSourceAwsSubnet() *schema.Resource { Computed: true, }, + "customer_owned_ipv4_pool": { + Type: schema.TypeString, + Computed: true, + }, + + "map_customer_owned_ip_on_launch": { + Type: schema.TypeBool, + Computed: true, + }, + "map_public_ip_on_launch": { Type: schema.TypeBool, Computed: true, @@ -180,6 +190,8 @@ func dataSourceAwsSubnetRead(d *schema.ResourceData, meta interface{}) error { } d.Set("assign_ipv6_address_on_creation", subnet.AssignIpv6AddressOnCreation) + d.Set("customer_owned_ipv4_pool", subnet.CustomerOwnedIpv4Pool) + d.Set("map_customer_owned_ip_on_launch", subnet.MapCustomerOwnedIpOnLaunch) d.Set("map_public_ip_on_launch", subnet.MapPublicIpOnLaunch) for _, a := range subnet.Ipv6CidrBlockAssociationSet { diff --git a/aws/data_source_aws_subnet_test.go b/aws/data_source_aws_subnet_test.go index becd33aaabe..053309b8503 100644 --- a/aws/data_source_aws_subnet_test.go +++ b/aws/data_source_aws_subnet_test.go @@ -38,6 +38,8 @@ func TestAccDataSourceAwsSubnet_basic(t *testing.T) { resource.TestCheckResourceAttr(ds1ResourceName, "cidr_block", cidr), resource.TestCheckResourceAttr(ds1ResourceName, "tags.Name", tag), resource.TestCheckResourceAttrPair(ds1ResourceName, "arn", snResourceName, "arn"), + resource.TestCheckResourceAttrPair(ds1ResourceName, "customer_owned_ipv4_pool", snResourceName, "customer_owned_ipv4_pool"), + resource.TestCheckResourceAttrPair(ds1ResourceName, "map_customer_owned_ip_on_launch", snResourceName, "map_customer_owned_ip_on_launch"), resource.TestCheckResourceAttrPair(ds1ResourceName, "outpost_arn", snResourceName, "outpost_arn"), resource.TestCheckResourceAttrPair(ds2ResourceName, "id", snResourceName, "id"), @@ -48,6 +50,8 @@ func TestAccDataSourceAwsSubnet_basic(t *testing.T) { resource.TestCheckResourceAttr(ds2ResourceName, "cidr_block", cidr), resource.TestCheckResourceAttr(ds2ResourceName, "tags.Name", tag), resource.TestCheckResourceAttrPair(ds2ResourceName, "arn", snResourceName, "arn"), + resource.TestCheckResourceAttrPair(ds2ResourceName, "customer_owned_ipv4_pool", snResourceName, "customer_owned_ipv4_pool"), + resource.TestCheckResourceAttrPair(ds2ResourceName, "map_customer_owned_ip_on_launch", snResourceName, "map_customer_owned_ip_on_launch"), resource.TestCheckResourceAttrPair(ds2ResourceName, "outpost_arn", snResourceName, "outpost_arn"), resource.TestCheckResourceAttrPair(ds3ResourceName, "id", snResourceName, "id"), @@ -58,6 +62,8 @@ func TestAccDataSourceAwsSubnet_basic(t *testing.T) { resource.TestCheckResourceAttr(ds3ResourceName, "cidr_block", cidr), resource.TestCheckResourceAttr(ds3ResourceName, "tags.Name", tag), resource.TestCheckResourceAttrPair(ds3ResourceName, "arn", snResourceName, "arn"), + resource.TestCheckResourceAttrPair(ds3ResourceName, "customer_owned_ipv4_pool", snResourceName, "customer_owned_ipv4_pool"), + resource.TestCheckResourceAttrPair(ds3ResourceName, "map_customer_owned_ip_on_launch", snResourceName, "map_customer_owned_ip_on_launch"), resource.TestCheckResourceAttrPair(ds3ResourceName, "outpost_arn", snResourceName, "outpost_arn"), resource.TestCheckResourceAttrPair(ds4ResourceName, "id", snResourceName, "id"), @@ -68,6 +74,8 @@ func TestAccDataSourceAwsSubnet_basic(t *testing.T) { resource.TestCheckResourceAttr(ds4ResourceName, "cidr_block", cidr), resource.TestCheckResourceAttr(ds4ResourceName, "tags.Name", tag), resource.TestCheckResourceAttrPair(ds4ResourceName, "arn", snResourceName, "arn"), + resource.TestCheckResourceAttrPair(ds4ResourceName, "customer_owned_ipv4_pool", snResourceName, "customer_owned_ipv4_pool"), + resource.TestCheckResourceAttrPair(ds4ResourceName, "map_customer_owned_ip_on_launch", snResourceName, "map_customer_owned_ip_on_launch"), resource.TestCheckResourceAttrPair(ds4ResourceName, "outpost_arn", snResourceName, "outpost_arn"), resource.TestCheckResourceAttrPair(ds5ResourceName, "id", snResourceName, "id"), @@ -78,6 +86,8 @@ func TestAccDataSourceAwsSubnet_basic(t *testing.T) { resource.TestCheckResourceAttr(ds5ResourceName, "cidr_block", cidr), resource.TestCheckResourceAttr(ds5ResourceName, "tags.Name", tag), resource.TestCheckResourceAttrPair(ds5ResourceName, "arn", snResourceName, "arn"), + resource.TestCheckResourceAttrPair(ds5ResourceName, "customer_owned_ipv4_pool", snResourceName, "customer_owned_ipv4_pool"), + resource.TestCheckResourceAttrPair(ds5ResourceName, "map_customer_owned_ip_on_launch", snResourceName, "map_customer_owned_ip_on_launch"), resource.TestCheckResourceAttrPair(ds5ResourceName, "outpost_arn", snResourceName, "outpost_arn"), resource.TestCheckResourceAttrPair(ds6ResourceName, "id", snResourceName, "id"), @@ -88,6 +98,8 @@ func TestAccDataSourceAwsSubnet_basic(t *testing.T) { resource.TestCheckResourceAttr(ds6ResourceName, "cidr_block", cidr), resource.TestCheckResourceAttr(ds6ResourceName, "tags.Name", tag), resource.TestCheckResourceAttrPair(ds6ResourceName, "arn", snResourceName, "arn"), + resource.TestCheckResourceAttrPair(ds6ResourceName, "customer_owned_ipv4_pool", snResourceName, "customer_owned_ipv4_pool"), + resource.TestCheckResourceAttrPair(ds6ResourceName, "map_customer_owned_ip_on_launch", snResourceName, "map_customer_owned_ip_on_launch"), resource.TestCheckResourceAttrPair(ds6ResourceName, "outpost_arn", snResourceName, "outpost_arn"), ), }, diff --git a/aws/internal/service/ec2/errors.go b/aws/internal/service/ec2/errors.go index 2429d2a236a..d076b55af27 100644 --- a/aws/internal/service/ec2/errors.go +++ b/aws/internal/service/ec2/errors.go @@ -16,6 +16,10 @@ const ( InvalidGroupNotFound = "InvalidGroup.NotFound" ) +const ( + ErrCodeInvalidSubnetIDNotFound = "InvalidSubnetID.NotFound" +) + const ( ErrCodeInvalidVpcPeeringConnectionIDNotFound = "InvalidVpcPeeringConnectionID.NotFound" ) diff --git a/aws/internal/service/ec2/finder/finder.go b/aws/internal/service/ec2/finder/finder.go index 6752919b3ec..f4260d2b783 100644 --- a/aws/internal/service/ec2/finder/finder.go +++ b/aws/internal/service/ec2/finder/finder.go @@ -72,6 +72,25 @@ func SecurityGroupByID(conn *ec2.EC2, id string) (*ec2.SecurityGroup, error) { return result.SecurityGroups[0], nil } +// SubnetByID looks up a Subnet by ID. When not found, returns nil and potentially an API error. +func SubnetByID(conn *ec2.EC2, id string) (*ec2.Subnet, error) { + input := &ec2.DescribeSubnetsInput{ + SubnetIds: aws.StringSlice([]string{id}), + } + + output, err := conn.DescribeSubnets(input) + + if err != nil { + return nil, err + } + + if output == nil || len(output.Subnets) == 0 || output.Subnets[0] == nil { + return nil, nil + } + + return output.Subnets[0], nil +} + // VpcPeeringConnectionByID returns the VPC peering connection corresponding to the specified identifier. // Returns nil and potentially an error if no VPC peering connection is found. func VpcPeeringConnectionByID(conn *ec2.EC2, id string) (*ec2.VpcPeeringConnection, error) { diff --git a/aws/internal/service/ec2/waiter/status.go b/aws/internal/service/ec2/waiter/status.go index 3bdd28b219f..9339c4f05a3 100644 --- a/aws/internal/service/ec2/waiter/status.go +++ b/aws/internal/service/ec2/waiter/status.go @@ -3,6 +3,7 @@ package waiter import ( "fmt" "log" + "strconv" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/ec2" @@ -207,6 +208,27 @@ func SecurityGroupStatus(conn *ec2.EC2, id string) resource.StateRefreshFunc { } } +// SubnetMapCustomerOwnedIpOnLaunch fetches the Subnet and its MapCustomerOwnedIpOnLaunch +func SubnetMapCustomerOwnedIpOnLaunch(conn *ec2.EC2, id string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + subnet, err := finder.SubnetByID(conn, id) + + if tfawserr.ErrCodeEquals(err, tfec2.ErrCodeInvalidSubnetIDNotFound) { + return nil, "false", nil + } + + if err != nil { + return nil, "false", err + } + + if subnet == nil { + return nil, "false", nil + } + + return subnet, strconv.FormatBool(aws.BoolValue(subnet.MapCustomerOwnedIpOnLaunch)), nil + } +} + const ( vpcPeeringConnectionStatusNotFound = "NotFound" vpcPeeringConnectionStatusUnknown = "Unknown" diff --git a/aws/internal/service/ec2/waiter/waiter.go b/aws/internal/service/ec2/waiter/waiter.go index cb597291ee9..50569bd8307 100644 --- a/aws/internal/service/ec2/waiter/waiter.go +++ b/aws/internal/service/ec2/waiter/waiter.go @@ -1,6 +1,7 @@ package waiter import ( + "strconv" "time" "github.com/aws/aws-sdk-go/service/ec2" @@ -205,6 +206,28 @@ func SecurityGroupCreated(conn *ec2.EC2, id string, timeout time.Duration) (*ec2 return nil, err } +const ( + SubnetAttributePropagationTimeout = 5 * time.Minute +) + +func SubnetMapCustomerOwnedIpOnLaunchUpdated(conn *ec2.EC2, subnetID string, expectedValue bool) (*ec2.Subnet, error) { + stateConf := &resource.StateChangeConf{ + Target: []string{strconv.FormatBool(expectedValue)}, + Refresh: SubnetMapCustomerOwnedIpOnLaunch(conn, subnetID), + Timeout: SubnetAttributePropagationTimeout, + Delay: 10 * time.Second, + MinTimeout: 3 * time.Second, + } + + outputRaw, err := stateConf.WaitForState() + + if output, ok := outputRaw.(*ec2.Subnet); ok { + return output, err + } + + return nil, err +} + const ( VpnGatewayVpcAttachmentAttachedTimeout = 15 * time.Minute diff --git a/aws/resource_aws_subnet.go b/aws/resource_aws_subnet.go index 5d476d33f21..d05943849cf 100644 --- a/aws/resource_aws_subnet.go +++ b/aws/resource_aws_subnet.go @@ -10,6 +10,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/terraform-providers/terraform-provider-aws/aws/internal/keyvaluetags" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/ec2/waiter" ) func resourceAwsSubnet() *schema.Resource { @@ -67,6 +68,18 @@ func resourceAwsSubnet() *schema.Resource { ConflictsWith: []string{"availability_zone"}, }, + "customer_owned_ipv4_pool": { + Type: schema.TypeString, + Optional: true, + RequiredWith: []string{"map_customer_owned_ip_on_launch", "outpost_arn"}, + }, + + "map_customer_owned_ip_on_launch": { + Type: schema.TypeBool, + Optional: true, + RequiredWith: []string{"customer_owned_ipv4_pool", "outpost_arn"}, + }, + "map_public_ip_on_launch": { Type: schema.TypeBool, Optional: true, @@ -153,7 +166,8 @@ func resourceAwsSubnetCreate(d *schema.ResourceData, meta interface{}) error { return fmt.Errorf("error waiting for subnet (%s) to become ready: %w", d.Id(), err) } - // You cannot modify multiple subnet attributes in the same request. + // You cannot modify multiple subnet attributes in the same request, + // except CustomerOwnedIpv4Pool and MapCustomerOwnedIpOnLaunch. // Reference: https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_ModifySubnetAttribute.html if d.Get("assign_ipv6_address_on_creation").(bool) { @@ -169,6 +183,24 @@ func resourceAwsSubnetCreate(d *schema.ResourceData, meta interface{}) error { } } + if v, ok := d.GetOk("customer_owned_ipv4_pool"); ok { + input := &ec2.ModifySubnetAttributeInput{ + CustomerOwnedIpv4Pool: aws.String(v.(string)), + MapCustomerOwnedIpOnLaunch: &ec2.AttributeBooleanValue{ + Value: aws.Bool(d.Get("map_customer_owned_ip_on_launch").(bool)), + }, + SubnetId: aws.String(d.Id()), + } + + if _, err := conn.ModifySubnetAttribute(input); err != nil { + return fmt.Errorf("error setting EC2 Subnet (%s) customer owned IPv4 pool and map customer owned IP on launch: %w", d.Id(), err) + } + + if _, err := waiter.SubnetMapCustomerOwnedIpOnLaunchUpdated(conn, d.Id(), d.Get("map_customer_owned_ip_on_launch").(bool)); err != nil { + return fmt.Errorf("error waiting for EC2 Subnet (%s) map customer owned IP on launch update: %w", d.Id(), err) + } + } + if d.Get("map_public_ip_on_launch").(bool) { input := &ec2.ModifySubnetAttributeInput{ MapPublicIpOnLaunch: &ec2.AttributeBooleanValue{ @@ -211,6 +243,8 @@ func resourceAwsSubnetRead(d *schema.ResourceData, meta interface{}) error { d.Set("availability_zone", subnet.AvailabilityZone) d.Set("availability_zone_id", subnet.AvailabilityZoneId) d.Set("cidr_block", subnet.CidrBlock) + d.Set("customer_owned_ipv4_pool", subnet.CustomerOwnedIpv4Pool) + d.Set("map_customer_owned_ip_on_launch", subnet.MapCustomerOwnedIpOnLaunch) d.Set("map_public_ip_on_launch", subnet.MapPublicIpOnLaunch) d.Set("assign_ipv6_address_on_creation", subnet.AssignIpv6AddressOnCreation) d.Set("outpost_arn", subnet.OutpostArn) @@ -249,6 +283,31 @@ func resourceAwsSubnetUpdate(d *schema.ResourceData, meta interface{}) error { } } + // You cannot modify multiple subnet attributes in the same request, + // except CustomerOwnedIpv4Pool and MapCustomerOwnedIpOnLaunch. + // Reference: https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_ModifySubnetAttribute.html + + if d.HasChanges("customer_owned_ipv4_pool", "map_customer_owned_ip_on_launch") { + input := &ec2.ModifySubnetAttributeInput{ + MapCustomerOwnedIpOnLaunch: &ec2.AttributeBooleanValue{ + Value: aws.Bool(d.Get("map_customer_owned_ip_on_launch").(bool)), + }, + SubnetId: aws.String(d.Id()), + } + + if v, ok := d.GetOk("customer_owned_ipv4_pool"); ok { + input.CustomerOwnedIpv4Pool = aws.String(v.(string)) + } + + if _, err := conn.ModifySubnetAttribute(input); err != nil { + return fmt.Errorf("error updating EC2 Subnet (%s) customer owned IPv4 pool and map customer owned IP on launch: %w", d.Id(), err) + } + + if _, err := waiter.SubnetMapCustomerOwnedIpOnLaunchUpdated(conn, d.Id(), d.Get("map_customer_owned_ip_on_launch").(bool)); err != nil { + return fmt.Errorf("error waiting for EC2 Subnet (%s) map customer owned IP on launch update: %w", d.Id(), err) + } + } + if d.HasChange("map_public_ip_on_launch") { modifyOpts := &ec2.ModifySubnetAttributeInput{ SubnetId: aws.String(d.Id()), diff --git a/aws/resource_aws_subnet_test.go b/aws/resource_aws_subnet_test.go index 81fd3b116ed..7da2ee09b6a 100644 --- a/aws/resource_aws_subnet_test.go +++ b/aws/resource_aws_subnet_test.go @@ -156,6 +156,8 @@ func TestAccAWSSubnet_basic(t *testing.T) { testAccCheckResourceAttrAccountID(resourceName, "owner_id"), resource.TestCheckResourceAttrSet(resourceName, "availability_zone"), resource.TestCheckResourceAttrSet(resourceName, "availability_zone_id"), + resource.TestCheckResourceAttr(resourceName, "customer_owned_ipv4_pool", ""), + resource.TestCheckResourceAttr(resourceName, "map_customer_owned_ip_on_launch", "false"), resource.TestCheckResourceAttr(resourceName, "outpost_arn", ""), resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), ), @@ -374,6 +376,57 @@ func TestAccAWSSubnet_disappears(t *testing.T) { }) } +func TestAccAWSSubnet_CustomerOwnedIpv4Pool(t *testing.T) { + var subnet ec2.Subnet + coipDataSourceName := "data.aws_ec2_coip_pool.test" + resourceName := "aws_subnet.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccPreCheckAWSOutpostsOutposts(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckSubnetDestroy, + Steps: []resource.TestStep{ + { + Config: testAccSubnetConfigCustomerOwnedIpv4Pool(), + Check: resource.ComposeTestCheckFunc( + testAccCheckSubnetExists(resourceName, &subnet), + resource.TestCheckResourceAttrPair(resourceName, "customer_owned_ipv4_pool", coipDataSourceName, "pool_id"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAWSSubnet_MapCustomerOwnedIpOnLaunch(t *testing.T) { + var subnet ec2.Subnet + resourceName := "aws_subnet.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccPreCheckAWSOutpostsOutposts(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckSubnetDestroy, + Steps: []resource.TestStep{ + { + Config: testAccSubnetConfigMapCustomerOwnedIpOnLaunch(true), + Check: resource.ComposeTestCheckFunc( + testAccCheckSubnetExists(resourceName, &subnet), + resource.TestCheckResourceAttr(resourceName, "map_customer_owned_ip_on_launch", "true"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + func TestAccAWSSubnet_outpost(t *testing.T) { var v ec2.Subnet outpostDataSourceName := "data.aws_outposts_outpost.test" @@ -675,6 +728,112 @@ resource "aws_subnet" "test" { `) } +func testAccSubnetConfigCustomerOwnedIpv4Pool() string { + return ` +data "aws_outposts_outposts" "test" {} + +data "aws_outposts_outpost" "test" { + id = tolist(data.aws_outposts_outposts.test.ids)[0] +} + +data "aws_ec2_local_gateway_route_tables" "test" { + filter { + name = "outpost-arn" + values = [data.aws_outposts_outpost.test.arn] + } +} + +data "aws_ec2_coip_pools" "test" { + # Filtering by Local Gateway Route Table ID is documented but not working in EC2 API. + # If there are multiple Outposts in the test account, this lookup can + # be misaligned and cause downstream resource errors. + # + # filter { + # name = "coip-pool.local-gateway-route-table-id" + # values = [tolist(data.aws_ec2_local_gateway_route_tables.test.ids)[0]] + # } +} + +data "aws_ec2_coip_pool" "test" { + pool_id = tolist(data.aws_ec2_coip_pools.test.pool_ids)[0] +} + +resource "aws_vpc" "test" { + cidr_block = "10.0.0.0/16" + + tags = { + Name = "terraform-testacc-subnet-outpost" + } +} + +resource "aws_subnet" "test" { + availability_zone = data.aws_outposts_outpost.test.availability_zone + cidr_block = cidrsubnet(aws_vpc.test.cidr_block, 8, 0) + customer_owned_ipv4_pool = data.aws_ec2_coip_pool.test.pool_id + map_customer_owned_ip_on_launch = true + outpost_arn = data.aws_outposts_outpost.test.arn + vpc_id = aws_vpc.test.id + + tags = { + Name = "tf-acc-subnet-outpost" + } +} +` +} + +func testAccSubnetConfigMapCustomerOwnedIpOnLaunch(mapCustomerOwnedIpOnLaunch bool) string { + return fmt.Sprintf(` +data "aws_outposts_outposts" "test" {} + +data "aws_outposts_outpost" "test" { + id = tolist(data.aws_outposts_outposts.test.ids)[0] +} + +data "aws_ec2_local_gateway_route_tables" "test" { + filter { + name = "outpost-arn" + values = [data.aws_outposts_outpost.test.arn] + } +} + +data "aws_ec2_coip_pools" "test" { + # Filtering by Local Gateway Route Table ID is documented but not working in EC2 API. + # If there are multiple Outposts in the test account, this lookup can + # be misaligned and cause downstream resource errors. + # + # filter { + # name = "coip-pool.local-gateway-route-table-id" + # values = [tolist(data.aws_ec2_local_gateway_route_tables.test.ids)[0]] + # } +} + +data "aws_ec2_coip_pool" "test" { + pool_id = tolist(data.aws_ec2_coip_pools.test.pool_ids)[0] +} + +resource "aws_vpc" "test" { + cidr_block = "10.0.0.0/16" + + tags = { + Name = "terraform-testacc-subnet-outpost" + } +} + +resource "aws_subnet" "test" { + availability_zone = data.aws_outposts_outpost.test.availability_zone + cidr_block = cidrsubnet(aws_vpc.test.cidr_block, 8, 0) + customer_owned_ipv4_pool = data.aws_ec2_coip_pool.test.pool_id + map_customer_owned_ip_on_launch = %[1]t + outpost_arn = data.aws_outposts_outpost.test.arn + vpc_id = aws_vpc.test.id + + tags = { + Name = "tf-acc-subnet-outpost" + } +} +`, mapCustomerOwnedIpOnLaunch) +} + func testAccSubnetConfigOutpost() string { return ` data "aws_outposts_outposts" "test" {} diff --git a/website/docs/d/subnet.html.markdown b/website/docs/d/subnet.html.markdown index fec6331ba3f..db1d9a4d6e5 100644 --- a/website/docs/d/subnet.html.markdown +++ b/website/docs/d/subnet.html.markdown @@ -97,5 +97,8 @@ the selected subnet. In addition the following attributes are exported: * `arn` - The ARN of the subnet. +* `customer_owned_ipv4_pool` - Identifier of customer owned IPv4 address pool. +* `map_customer_owned_ip_on_launch` - Whether customer owned IP addresses are assigned on network interface creation. +* `map_public_ip_on_launch` - Whether public IP addresses are assigned on instance launch. * `owner_id` - The ID of the AWS account that owns the subnet. * `outpost_arn` - The Amazon Resource Name (ARN) of the Outpost. diff --git a/website/docs/r/subnet.html.markdown b/website/docs/r/subnet.html.markdown index 90f55a30606..ab7ae79eb67 100644 --- a/website/docs/r/subnet.html.markdown +++ b/website/docs/r/subnet.html.markdown @@ -51,8 +51,10 @@ The following arguments are supported: * `availability_zone` - (Optional) The AZ for the subnet. * `availability_zone_id` - (Optional) The AZ ID of the subnet. * `cidr_block` - (Required) The CIDR block for the subnet. +* `customer_owned_ipv4_pool` - (Optional) The customer owned IPv4 address pool. Typically used with the `map_customer_owned_ip_on_launch` argument. The `outpost_arn` argument must be specified when configured. * `ipv6_cidr_block` - (Optional) The IPv6 network range for the subnet, in CIDR notation. The subnet size must use a /64 prefix length. +* `map_customer_owned_ip_on_launch` - (Optional) Specify `true` to indicate that network interfaces created in the subnet should be assigned a customer owned IP address. The `customer_owned_ipv4_pool` and `outpost_arn` arguments must be specified when set to `true`. Default is `false`. * `map_public_ip_on_launch` - (Optional) Specify true to indicate that instances launched into the subnet should be assigned a public IP address. Default is `false`.