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

r/aws_vpc_ipam_pool: set publicly_advertisable only if public_ip_source = byoip and address_family = ipv6 #39600

Merged
merged 15 commits into from
Oct 8, 2024
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
16 changes: 16 additions & 0 deletions .changelog/39600.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
```release-note:bug
resource/aws_vpc_ipam_pool: Fix `InvalidParameterCombination: The request can only contain PubliclyAdvertisable if the AddressFamily is IPv6 and PublicIpSource is byoip` errors
```

```release-note:bug
resource/aws_vpc_ipam_pool: Change `publicly_advertisable` to [ForceNew](https://developer.hashicorp.com/terraform/plugin/sdkv2/schemas/schema-behaviors#forcenew)
```

```release-note:enhancement
resource/aws_vpc_ipam: Add `enable_private_gua` argument
```


```release-note:enhancement
resource/aws_vpc_ipv6_cidr_block_association: Add `ip_source` and `ipv6_address_attribute` attributes
```
14 changes: 14 additions & 0 deletions internal/service/ec2/ipam_.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,11 @@ func resourceIPAM() *schema.Resource {
Type: schema.TypeString,
Optional: true,
},
"enable_private_gua": {
Type: schema.TypeBool,
Optional: true,
Default: false,
},
"operating_regions": {
Type: schema.TypeSet,
Required: true,
Expand Down Expand Up @@ -137,6 +142,10 @@ func resourceIPAMCreate(ctx context.Context, d *schema.ResourceData, meta interf
input.Description = aws.String(v.(string))
}

if v, ok := d.GetOk("enable_private_gua"); ok {
input.EnablePrivateGua = aws.Bool(v.(bool))
}

if v, ok := d.GetOk("tier"); ok {
input.Tier = awstypes.IpamTier(v.(string))
}
Expand Down Expand Up @@ -176,6 +185,7 @@ func resourceIPAMRead(ctx context.Context, d *schema.ResourceData, meta interfac
d.Set("default_resource_discovery_association_id", ipam.DefaultResourceDiscoveryAssociationId)
d.Set("default_resource_discovery_id", ipam.DefaultResourceDiscoveryId)
d.Set(names.AttrDescription, ipam.Description)
d.Set("enable_private_gua", ipam.EnablePrivateGua)
if err := d.Set("operating_regions", flattenIPAMOperatingRegions(ipam.OperatingRegions)); err != nil {
return sdkdiag.AppendErrorf(diags, "setting operating_regions: %s", err)
}
Expand All @@ -202,6 +212,10 @@ func resourceIPAMUpdate(ctx context.Context, d *schema.ResourceData, meta interf
input.Description = aws.String(d.Get(names.AttrDescription).(string))
}

if d.HasChange("enable_private_gua") {
input.EnablePrivateGua = aws.Bool(d.Get("enable_private_gua").(bool))
}

if d.HasChange("operating_regions") {
o, n := d.GetChange("operating_regions")
if o == nil {
Expand Down
17 changes: 11 additions & 6 deletions internal/service/ec2/ipam_pool.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ func resourceIPAMPool() *schema.Resource {
"publicly_advertisable": {
Type: schema.TypeBool,
Optional: true,
ForceNew: true,
},
"source_ipam_pool_id": {
Type: schema.TypeString,
Expand All @@ -153,11 +154,18 @@ func resourceIPAMPoolCreate(ctx context.Context, d *schema.ResourceData, meta in
var diags diag.Diagnostics
conn := meta.(*conns.AWSClient).EC2Client(ctx)

scopeID := d.Get("ipam_scope_id").(string)
scope, err := findIPAMScopeByID(ctx, conn, scopeID)

if err != nil {
return sdkdiag.AppendErrorf(diags, "reading IPAM Scope (%s): %s", scopeID, err)
}

addressFamily := awstypes.AddressFamily(d.Get("address_family").(string))
input := &ec2.CreateIpamPoolInput{
AddressFamily: addressFamily,
ClientToken: aws.String(id.UniqueId()),
IpamScopeId: aws.String(d.Get("ipam_scope_id").(string)),
IpamScopeId: aws.String(scopeID),
TagSpecifications: getTagSpecificationsIn(ctx, awstypes.ResourceTypeIpamPool),
}

Expand Down Expand Up @@ -193,15 +201,12 @@ func resourceIPAMPoolCreate(ctx context.Context, d *schema.ResourceData, meta in
input.AwsService = awstypes.IpamPoolAwsService(v.(string))
}

var publicIpSource awstypes.IpamPoolPublicIpSource
if v, ok := d.GetOk("public_ip_source"); ok {
publicIpSource = awstypes.IpamPoolPublicIpSource(v.(string))
input.PublicIpSource = publicIpSource
input.PublicIpSource = awstypes.IpamPoolPublicIpSource(v.(string))
}

// PubliclyAdvertisable must be set if if the AddressFamily is IPv6 and PublicIpSource is byoip.
// The request can only contain PubliclyAdvertisable if the AddressFamily is IPv6 and PublicIpSource is byoip.
if addressFamily == awstypes.AddressFamilyIpv6 && publicIpSource != awstypes.IpamPoolPublicIpSourceAmazon {
if addressFamily == awstypes.AddressFamilyIpv6 && scope.IpamScopeType == awstypes.IpamScopeTypePublic {
input.PubliclyAdvertisable = aws.Bool(d.Get("publicly_advertisable").(bool))
}

Expand Down
59 changes: 46 additions & 13 deletions internal/service/ec2/ipam_pool_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,27 @@ func TestAccIPAMPool_tags(t *testing.T) {
})
}

func TestAccIPAMPool_ipv6PrivateScope(t *testing.T) {
ctx := acctest.Context(t)
var pool awstypes.IpamPool
resourceName := "aws_vpc_ipam_pool.test"

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { acctest.PreCheck(ctx, t) },
ErrorCheck: acctest.ErrorCheck(t, names.EC2ServiceID),
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
CheckDestroy: testAccCheckIPAMPoolDestroy(ctx),
Steps: []resource.TestStep{
{
Config: testAccIPAMPoolConfig_ipv6PrivateScope,
Check: resource.ComposeTestCheckFunc(
testAccCheckIPAMPoolExists(ctx, resourceName, &pool),
),
},
},
})
}

func testAccCheckIPAMPoolExists(ctx context.Context, n string, v *awstypes.IpamPool) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[n]
Expand Down Expand Up @@ -285,6 +306,19 @@ func testAccCheckIPAMPoolDestroy(ctx context.Context) resource.TestCheckFunc {
}
}

func testAccCheckIPAMPoolCIDRCreate(ctx context.Context, ipampool *awstypes.IpamPool) resource.TestCheckFunc {
return func(s *terraform.State) error {
conn := acctest.Provider.Meta().(*conns.AWSClient).EC2Client(ctx)

_, err := conn.ProvisionIpamPoolCidr(ctx, &ec2.ProvisionIpamPoolCidrInput{
IpamPoolId: ipampool.IpamPoolId,
Cidr: aws.String("10.0.0.0/16"),
})

return err
}
}

const testAccIPAMPoolConfig_base = `
data "aws_region" "current" {}

Expand Down Expand Up @@ -345,19 +379,6 @@ resource "aws_vpc_ipam_pool" "test" {
}
`)

func testAccCheckIPAMPoolCIDRCreate(ctx context.Context, ipampool *awstypes.IpamPool) resource.TestCheckFunc {
return func(s *terraform.State) error {
conn := acctest.Provider.Meta().(*conns.AWSClient).EC2Client(ctx)

_, err := conn.ProvisionIpamPoolCidr(ctx, &ec2.ProvisionIpamPoolCidrInput{
IpamPoolId: ipampool.IpamPoolId,
Cidr: aws.String("10.0.0.0/16"),
})

return err
}
}

func testAccIPAMPoolConfig_tags(tagKey1, tagValue1 string) string {
return acctest.ConfigCompose(testAccIPAMPoolConfig_base, fmt.Sprintf(`
resource "aws_vpc_ipam_pool" "test" {
Expand All @@ -384,3 +405,15 @@ resource "aws_vpc_ipam_pool" "test" {
}
`, tagKey1, tagValue1, tagKey2, tagValue2))
}

var testAccIPAMPoolConfig_ipv6PrivateScope = acctest.ConfigCompose(testAccIPAMScopeConfig_base, `
resource "aws_vpc_ipam_scope" "test" {
ipam_id = aws_vpc_ipam.test.id
}

resource "aws_vpc_ipam_pool" "test" {
address_family = "ipv6"
ipam_scope_id = aws_vpc_ipam_scope.test.id
locale = data.aws_region.current.name
}
`)
53 changes: 49 additions & 4 deletions internal/service/ec2/ipam_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ func TestAccIPAM_basic(t *testing.T) {
testAccCheckIPAMExists(ctx, resourceName, &ipam),
resource.TestCheckResourceAttrSet(resourceName, names.AttrARN),
resource.TestCheckResourceAttr(resourceName, names.AttrDescription, ""),
resource.TestCheckResourceAttr(resourceName, "enable_private_gua", acctest.CtFalse),
resource.TestCheckResourceAttr(resourceName, "operating_regions.#", acctest.Ct1),
resource.TestCheckResourceAttr(resourceName, "scope_count", acctest.Ct2),
resource.TestMatchResourceAttr(resourceName, "private_default_scope_id", regexache.MustCompile(`^ipam-scope-[0-9a-f]+`)),
Expand Down Expand Up @@ -262,17 +263,47 @@ func TestAccIPAM_tags(t *testing.T) {
})
}

func TestAccIPAM_enablePrivateGUA(t *testing.T) {
ctx := acctest.Context(t)
var ipam awstypes.Ipam
resourceName := "aws_vpc_ipam.test"

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { acctest.PreCheck(ctx, t) },
ErrorCheck: acctest.ErrorCheck(t, names.EC2ServiceID),
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
CheckDestroy: testAccCheckIPAMDestroy(ctx),
Steps: []resource.TestStep{
{
Config: testAccIPAMConfig_enablePrivateGUA(true),
Check: resource.ComposeTestCheckFunc(
testAccCheckIPAMExists(ctx, resourceName, &ipam),
resource.TestCheckResourceAttr(resourceName, "enable_private_gua", acctest.CtTrue),
),
},
{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
},
{
Config: testAccIPAMConfig_enablePrivateGUA(false),
Check: resource.ComposeTestCheckFunc(
testAccCheckIPAMExists(ctx, resourceName, &ipam),
resource.TestCheckResourceAttr(resourceName, "enable_private_gua", acctest.CtFalse),
),
},
},
})
}

func testAccCheckIPAMExists(ctx context.Context, n string, v *awstypes.Ipam) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[n]
if !ok {
return fmt.Errorf("Not found: %s", n)
}

if rs.Primary.ID == "" {
return fmt.Errorf("No IPAM ID is set")
}

conn := acctest.Provider.Meta().(*conns.AWSClient).EC2Client(ctx)

output, err := tfec2.FindIPAMByID(ctx, conn, rs.Primary.ID)
Expand Down Expand Up @@ -426,3 +457,17 @@ resource "aws_vpc_ipam" "test" {
}
`, tier)
}

func testAccIPAMConfig_enablePrivateGUA(enablePrivateGUA bool) string {
return fmt.Sprintf(`
data "aws_region" "current" {}

resource "aws_vpc_ipam" "test" {
enable_private_gua = %[1]t

operating_regions {
region_name = data.aws_region.current.name
}
}
`, enablePrivateGUA)
}
10 changes: 10 additions & 0 deletions internal/service/ec2/vpc_ipv6_cidr_block_association.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,14 @@ func resourceVPCIPv6CIDRBlockAssociation() *schema.Resource {
ForceNew: true,
ConflictsWith: []string{"ipv6_pool", "ipv6_ipam_pool_id", "ipv6_cidr_block", "ipv6_netmask_length"},
},
"ip_source": {
Type: schema.TypeString,
Computed: true,
},
"ipv6_address_attribute": {
Type: schema.TypeString,
Computed: true,
},
"ipv6_cidr_block": {
Type: schema.TypeString,
Optional: true,
Expand Down Expand Up @@ -154,6 +162,8 @@ func resourceVPCIPv6CIDRBlockAssociationRead(ctx context.Context, d *schema.Reso
isAmazonIPv6Pool := ipv6PoolID == amazonIPv6PoolID

d.Set("assign_generated_ipv6_cidr_block", isAmazonIPv6Pool)
d.Set("ip_source", vpcIpv6CidrBlockAssociation.IpSource)
d.Set("ipv6_address_attribute", vpcIpv6CidrBlockAssociation.Ipv6AddressAttribute)
d.Set("ipv6_cidr_block", vpcIpv6CidrBlockAssociation.Ipv6CidrBlock)
d.Set("ipv6_pool", ipv6PoolID)
d.Set(names.AttrVPCID, vpc.VpcId)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ func TestAccVPCIPv6CIDRBlockAssociation_basic(t *testing.T) {
Check: resource.ComposeAggregateTestCheckFunc(
testAccCheckVPCIPv6CIDRBlockAssociationExists(ctx, resource1Name, &associationSecondary),
testAccCheckVPCIPv6CIDRBlockAssociationExists(ctx, resource2Name, &associationTertiary),
resource.TestCheckResourceAttr(resource1Name, "ip_source", "amazon"),
resource.TestCheckResourceAttr(resource2Name, "ip_source", "amazon"),
resource.TestCheckResourceAttr(resource1Name, "ipv6_address_attribute", "public"),
resource.TestCheckResourceAttr(resource2Name, "ipv6_address_attribute", "public"),
resource.TestCheckResourceAttr(resource1Name, "ipv6_pool", "Amazon"),
resource.TestCheckResourceAttr(resource2Name, "ipv6_pool", "Amazon"),
),
Expand Down
1 change: 1 addition & 0 deletions website/docs/r/vpc_ipam.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ This resource supports the following arguments:

* `cascade` - (Optional) Enables you to quickly delete an IPAM, private scopes, pools in private scopes, and any allocations in the pools in private scopes.
* `description` - (Optional) A description for the IPAM.
* `enable_private_gua` - (Optional) Enable this option to use your own GUA ranges as private IPv6 addresses. Default: `false`.
* `operating_regions` - (Required) Determines which locales can be chosen when you create pools. Locale is the Region where you want to make an IPAM pool available for allocations. You can only create pools with locales that match the operating Regions of the IPAM. You can only create VPCs from a pool whose locale matches the VPC's Region. You specify a region using the [region_name](#operating_regions) parameter. You **must** set your provider block region as an operating_region.
* `tier` - (Optional) specifies the IPAM tier. Valid options include `free` and `advanced`. Default is `advanced`.
* `tags` - (Optional) A map of tags to assign to the resource. If configured with a provider [`default_tags` configuration block](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#default_tags-configuration-block) present, tags with matching keys will overwrite those defined at the provider-level.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,9 @@ This resource supports the following arguments:

This resource exports the following attributes in addition to the arguments above:

* `id` - The ID of the VPC CIDR association
* `id` - The ID of the VPC CIDR association.
* `ip_source` - The source that allocated the IP address space. Values: `amazon`, `byoip`, `none`.
* `ipv6_address_attribute` - Public IPv6 addresses are those advertised on the internet from AWS. Private IP addresses are not and cannot be advertised on the internet from AWS. Values: `public`, `private`.

## Import

Expand Down
Loading