Skip to content

Commit

Permalink
Merge pull request hashicorp#39600 from NamanJain8/b-aws-ipam-pool-pr…
Browse files Browse the repository at this point in the history
…ivate-scope

r/aws_vpc_ipam_pool: set publicly_advertisable only if public_ip_source = byoip and address_family = ipv6
  • Loading branch information
ewbankkit authored Oct 8, 2024
2 parents f493798 + 749e16d commit b92b36b
Show file tree
Hide file tree
Showing 9 changed files with 154 additions and 24 deletions.
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
4 changes: 4 additions & 0 deletions internal/service/ec2/vpc_ipv6_cidr_block_association_test.go
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
4 changes: 3 additions & 1 deletion website/docs/r/vpc_ipv6_cidr_block_association.html.markdown
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

0 comments on commit b92b36b

Please sign in to comment.