Skip to content

Commit

Permalink
Merge pull request #21998 from drewmullen/f-ipam-service
Browse files Browse the repository at this point in the history
Add VPC IPAM Service and Update VPC Resources to support IPAM Parameters
  • Loading branch information
gdavison committed Dec 2, 2021
2 parents 410b851 + b4fbca5 commit 2827e28
Show file tree
Hide file tree
Showing 30 changed files with 4,476 additions and 94 deletions.
27 changes: 27 additions & 0 deletions .changelog/21998.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
```release-note:new-resource
aws_vpc_ipam
```
```release-note:new-resource
aws_vpc_ipam_pool
```
```release-note:new-resource
aws_vpc_ipam_scope
```
```release-note:new-resource
aws_vpc_ipam_pool_cidr
```
```release-note:new-resource
aws_vpc_ipam_pool_cidr_allocation
```
```release-note:new-resource
vpc_ipv6_cidr_block_association
```
```release-note:new-data-source
aws_vpc_pool_data_source
```
```release-note:enhancement
resource/aws_vpc: `cidr_block` value can now either be set explicitly or computed computed via AWS IPAM. When set explicitly, the request proceeds as before. To be computed, config uses IPAM to generate by passing `ipv{4,6}_ipam_pool_id` if the `aws_vpc_ipam_pool` has `allocation_default_netmask_length` set or by specifying both `ipv{4,6}_ipam_pool_id` & `ipv{4,6}_netmask_length` which the vpc backend calls will use to request a cidr that matches the netmask length from the specified ipam pool.
```
```release-note:enhancement
resource/vpc_ipv4_cidr_block_association: `cidr_block` value can now either be set explicitly or computed via AWS IPAM. When set explicitly, the request proceeds as before. To be computed, config uses IPAM to generate by passing `ipv{4,6}_ipam_pool_id` if the `aws_vpc_ipam_pool` has `allocation_default_netmask_length` set or by specifying both `ipv{4,6}_ipam_pool_id` & `ipv{4,6}_netmask_length` which the vpc backend calls will use to request a cidr that matches the netmask length from the specified ipam pool.
```
7 changes: 7 additions & 0 deletions internal/provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -472,6 +472,7 @@ func Provider() *schema.Provider {
"aws_vpc_dhcp_options": ec2.DataSourceVPCDHCPOptions(),
"aws_vpc_endpoint_service": ec2.DataSourceVPCEndpointService(),
"aws_vpc_endpoint": ec2.DataSourceVPCEndpoint(),
"aws_vpc_ipam_pool": ec2.DataSourceVPCIpamPool(),
"aws_vpc_peering_connection": ec2.DataSourceVPCPeeringConnection(),
"aws_vpc_peering_connections": ec2.DataSourceVPCPeeringConnections(),
"aws_vpc": ec2.DataSourceVPC(),
Expand Down Expand Up @@ -1098,7 +1099,13 @@ func Provider() *schema.Provider {
"aws_vpc_endpoint_service": ec2.ResourceVPCEndpointService(),
"aws_vpc_endpoint_service_allowed_principal": ec2.ResourceVPCEndpointServiceAllowedPrincipal(),
"aws_vpc_endpoint_subnet_association": ec2.ResourceVPCEndpointSubnetAssociation(),
"aws_vpc_ipam": ec2.ResourceVPCIpam(),
"aws_vpc_ipam_pool": ec2.ResourceVPCIpamPool(),
"aws_vpc_ipam_pool_cidr_allocation": ec2.ResourceVPCIpamPoolCidrAllocation(),
"aws_vpc_ipam_pool_cidr": ec2.ResourceVPCIpamPoolCidr(),
"aws_vpc_ipam_scope": ec2.ResourceVPCIpamScope(),
"aws_vpc_ipv4_cidr_block_association": ec2.ResourceVPCIPv4CIDRBlockAssociation(),
"aws_vpc_ipv6_cidr_block_association": ec2.ResourceVPCIPv6CIDRBlockAssociation(),
"aws_vpc_peering_connection": ec2.ResourceVPCPeeringConnection(),
"aws_vpc_peering_connection_accepter": ec2.ResourceVPCPeeringConnectionAccepter(),
"aws_vpc_peering_connection_options": ec2.ResourceVPCPeeringConnectionOptions(),
Expand Down
227 changes: 169 additions & 58 deletions internal/service/ec2/vpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,13 @@ import (
"github.com/hashicorp/terraform-provider-aws/internal/verify"
)

const (
VPCCIDRMaxIPv4 = 28
VPCCIDRMinIPv4 = 16
VPCCIDRMaxIPv6 = 56
)

// acceptance tests for byoip related tests are in vpc_byoip_test.go
func ResourceVPC() *schema.Resource {
//lintignore:R011
return &schema.Resource{
Expand All @@ -35,104 +42,134 @@ func ResourceVPC() *schema.Resource {
CustomizeDiff: customdiff.All(
resourceVPCCustomizeDiff,
verify.SetTagsDiff,
func(_ context.Context, diff *schema.ResourceDiff, v interface{}) error {
// cidr_block can be set by a value returned from IPAM or explicitly in config
if diff.Id() != "" && diff.HasChange("cidr_block") {
// if netmask is set then cidr_block is derived from ipam, ignore changes
if diff.Get("ipv4_netmask_length") != 0 {
return diff.Clear("cidr_block")
}
return diff.ForceNew("cidr_block")
}
return nil
},
),

SchemaVersion: 1,
MigrateState: VPCMigrateState,

Schema: map[string]*schema.Schema{
"arn": {
Type: schema.TypeString,
Computed: true,
},
"assign_generated_ipv6_cidr_block": {
Type: schema.TypeBool,
Optional: true,
ConflictsWith: []string{"ipv6_ipam_pool_id"},
},
"cidr_block": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validation.IsCIDRNetwork(16, 28),
Type: schema.TypeString,
Optional: true,
Computed: true,
ForceNew: true,
ValidateFunc: validation.IsCIDRNetwork(VPCCIDRMinIPv4, VPCCIDRMaxIPv4),
ConflictsWith: []string{"ipv4_netmask_length"},
},

"instance_tenancy": {
Type: schema.TypeString,
Optional: true,
Default: ec2.TenancyDefault,
ValidateFunc: validation.StringInSlice([]string{ec2.TenancyDefault, ec2.TenancyDedicated}, false),
"default_network_acl_id": {
Type: schema.TypeString,
Computed: true,
},
"dhcp_options_id": {
Type: schema.TypeString,
Computed: true,
},
"default_security_group_id": {
Type: schema.TypeString,
Computed: true,
},
"default_route_table_id": {
Type: schema.TypeString,
Computed: true,
},

"enable_dns_hostnames": {
Type: schema.TypeBool,
Optional: true,
Computed: true,
},

"enable_dns_support": {
Type: schema.TypeBool,
Optional: true,
Default: true,
},

"enable_classiclink": {
Type: schema.TypeBool,
Optional: true,
Computed: true,
},

"enable_classiclink_dns_support": {
Type: schema.TypeBool,
Optional: true,
Computed: true,
},

"assign_generated_ipv6_cidr_block": {
Type: schema.TypeBool,
Optional: true,
Default: false,
},

"main_route_table_id": {
Type: schema.TypeString,
Computed: true,
},

"default_network_acl_id": {
Type: schema.TypeString,
Computed: true,
},

"dhcp_options_id": {
Type: schema.TypeString,
Computed: true,
"instance_tenancy": {
Type: schema.TypeString,
Optional: true,
Default: ec2.TenancyDefault,
ValidateFunc: validation.StringInSlice([]string{ec2.TenancyDefault, ec2.TenancyDedicated}, false),
},

"default_security_group_id": {
"ipv4_ipam_pool_id": {
Type: schema.TypeString,
Computed: true,
Optional: true,
ForceNew: true,
},

"default_route_table_id": {
Type: schema.TypeString,
Computed: true,
"ipv4_netmask_length": {
Type: schema.TypeInt,
Optional: true,
ForceNew: true,
ValidateFunc: validation.IntBetween(VPCCIDRMinIPv4, VPCCIDRMaxIPv4),
ConflictsWith: []string{"cidr_block"},
RequiredWith: []string{"ipv4_ipam_pool_id"},
},

"ipv6_association_id": {
Type: schema.TypeString,
Computed: true,
},

"ipv6_cidr_block": {
Type: schema.TypeString,
Computed: true,
Type: schema.TypeString,
Optional: true,
Computed: true,
ConflictsWith: []string{"ipv6_netmask_length", "assign_generated_ipv6_cidr_block"},
RequiredWith: []string{"ipv6_ipam_pool_id"},
ValidateFunc: validation.Any(
validation.StringIsEmpty,
validation.All(
verify.ValidIPv6CIDRNetworkAddress,
validation.IsCIDRNetwork(VPCCIDRMaxIPv6, VPCCIDRMaxIPv6)),
),
},

"arn": {
"ipv6_ipam_pool_id": {
Type: schema.TypeString,
Optional: true,
ConflictsWith: []string{"assign_generated_ipv6_cidr_block"},
},
"ipv6_netmask_length": {
Type: schema.TypeInt,
Optional: true,
ValidateFunc: validation.IntInSlice([]int{VPCCIDRMaxIPv6}),
ConflictsWith: []string{"ipv6_cidr_block"},
RequiredWith: []string{"ipv6_ipam_pool_id"},
},
"main_route_table_id": {
Type: schema.TypeString,
Computed: true,
},

"tags": tftags.TagsSchema(),

"tags_all": tftags.TagsSchemaComputed(),

"owner_id": {
Type: schema.TypeString,
Computed: true,
},
"tags": tftags.TagsSchema(),
"tags_all": tftags.TagsSchemaComputed(),
},
}
}
Expand All @@ -144,12 +181,35 @@ func resourceVPCCreate(d *schema.ResourceData, meta interface{}) error {

// Create the VPC
createOpts := &ec2.CreateVpcInput{
CidrBlock: aws.String(d.Get("cidr_block").(string)),
InstanceTenancy: aws.String(d.Get("instance_tenancy").(string)),
AmazonProvidedIpv6CidrBlock: aws.Bool(d.Get("assign_generated_ipv6_cidr_block").(bool)),
TagSpecifications: ec2TagSpecificationsFromKeyValueTags(tags, ec2.ResourceTypeVpc),
}

if v, ok := d.GetOk("cidr_block"); ok {
createOpts.CidrBlock = aws.String(v.(string))
}

if v, ok := d.GetOk("ipv4_ipam_pool_id"); ok {
createOpts.Ipv4IpamPoolId = aws.String(v.(string))
}

if v, ok := d.GetOk("ipv4_netmask_length"); ok {
createOpts.Ipv4NetmaskLength = aws.Int64(int64(v.(int)))
}

if v, ok := d.GetOk("ipv6_ipam_pool_id"); ok {
createOpts.Ipv6IpamPoolId = aws.String(v.(string))
}

if v, ok := d.GetOk("ipv6_cidr_block"); ok {
createOpts.Ipv6CidrBlock = aws.String(v.(string))
}

if v, ok := d.GetOk("ipv6_netmask_length"); ok {
createOpts.Ipv6NetmaskLength = aws.Int64(int64(v.(int)))
}

log.Printf("[DEBUG] VPC create config: %#v", *createOpts)
vpcResp, err := conn.CreateVpc(createOpts)
if err != nil {
Expand Down Expand Up @@ -329,13 +389,15 @@ func resourceVPCRead(d *schema.ResourceData, meta interface{}) error {
d.Set("owner_id", vpc.OwnerId)

// Make sure those values are set, if an IPv6 block exists it'll be set in the loop
d.Set("assign_generated_ipv6_cidr_block", false)
d.Set("ipv6_association_id", "")
d.Set("ipv6_cidr_block", "")

// assign_generated_ipv6_cidr_block is not returned by the API
// leave unassigned if not referenced
if v := d.Get("assign_generated_ipv6_cidr_block"); v != "" {
d.Set("assign_generated_ipv6_cidr_block", aws.Bool(v.(bool)))
}
for _, a := range vpc.Ipv6CidrBlockAssociationSet {
if aws.StringValue(a.Ipv6CidrBlockState.State) == ec2.VpcCidrBlockStateCodeAssociated { //we can only ever have 1 IPv6 block associated at once
d.Set("assign_generated_ipv6_cidr_block", true)
d.Set("ipv6_association_id", a.AssociationId)
d.Set("ipv6_cidr_block", a.Ipv6CidrBlock)
}
Expand Down Expand Up @@ -557,6 +619,39 @@ func resourceVPCUpdate(d *schema.ResourceData, meta interface{}) error {
}
}

if d.HasChanges("ipv6_cidr_block", "ipv6_ipam_pool_id") {
log.Printf("[INFO] Modifying ipam IPv6 CIDR")

// if assoc id exists it needs to be disassociated
if v, ok := d.GetOk("ipv6_association_id"); ok {
if err := ipv6DisassociateCidrBlock(conn, d.Id(), v.(string)); err != nil {
return err
}
}
if v := d.Get("ipv6_ipam_pool_id"); v != "" {
modifyOpts := &ec2.AssociateVpcCidrBlockInput{
VpcId: &vpcid,
Ipv6IpamPoolId: aws.String(v.(string)),
}

if v := d.Get("ipv6_netmask_length"); v != 0 {
modifyOpts.Ipv6NetmaskLength = aws.Int64(int64(v.(int)))
}

if v := d.Get("ipv6_cidr_block"); v != "" {
modifyOpts.Ipv6CidrBlock = aws.String(v.(string))
}

resp, err := conn.AssociateVpcCidrBlock(modifyOpts)
if err != nil {
return err
}
if err := waitForEc2VpcIpv6CidrBlockAssociationCreate(conn, d.Id(), aws.StringValue(resp.Ipv6CidrBlockAssociation.AssociationId)); err != nil {
return fmt.Errorf("error waiting for EC2 VPC (%s) IPv6 CIDR to become associated: %w", d.Id(), err)
}
}
}

if d.HasChange("instance_tenancy") {
modifyOpts := &ec2.ModifyVpcTenancyInput{
VpcId: aws.String(vpcid),
Expand Down Expand Up @@ -616,6 +711,22 @@ func resourceVPCDelete(d *schema.ResourceData, meta interface{}) error {
return nil
}

func ipv6DisassociateCidrBlock(conn *ec2.EC2, id, allocationId string) error {
log.Printf("[INFO] Disassociating IPv6 CIDR association id: %s", allocationId)
modifyOpts := &ec2.DisassociateVpcCidrBlockInput{
AssociationId: aws.String(allocationId),
}
if _, err := conn.DisassociateVpcCidrBlock(modifyOpts); err != nil {
return err
}
log.Printf("[DEBUG] Waiting for EC2 VPC (%s) IPv6 CIDR to become disassociated", id)
if err := waitForEc2VpcIpv6CidrBlockAssociationDelete(conn, id, allocationId); err != nil {
return fmt.Errorf("error waiting for EC2 VPC (%s) IPv6 CIDR to become disassociated: %w", id, err)
}

return nil
}

func resourceVPCCustomizeDiff(_ context.Context, diff *schema.ResourceDiff, v interface{}) error {
if diff.HasChange("assign_generated_ipv6_cidr_block") {
if err := diff.SetNewComputed("ipv6_association_id"); err != nil {
Expand Down Expand Up @@ -842,7 +953,7 @@ func waitForEc2VpcIpv6CidrBlockAssociationCreate(conn *ec2.EC2, vpcID, associati
},
Target: []string{ec2.VpcCidrBlockStateCodeAssociated},
Refresh: Ipv6CidrStateRefreshFunc(conn, vpcID, associationID),
Timeout: 1 * time.Minute,
Timeout: 10 * time.Minute,
}
_, err := stateConf.WaitForState()

Expand All @@ -857,7 +968,7 @@ func waitForEc2VpcIpv6CidrBlockAssociationDelete(conn *ec2.EC2, vpcID, associati
},
Target: []string{ec2.VpcCidrBlockStateCodeDisassociated},
Refresh: Ipv6CidrStateRefreshFunc(conn, vpcID, associationID),
Timeout: 1 * time.Minute,
Timeout: 5 * time.Minute,
NotFoundChecks: 1,
}
_, err := stateConf.WaitForState()
Expand Down
Loading

0 comments on commit 2827e28

Please sign in to comment.