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_networkmanager_core_network - base policy multi region support #29623

7 changes: 7 additions & 0 deletions .changelog/29623.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
```release-note:enhancement
resource/aws_networkmanager_core_network: Add `base_policy_regions` argument
```

```release-note:note
resource/aws_networkmanager_core_network: The `base_policy_region` argument is being deprecated in favor of the new `base_policy_regions` argument.
```
78 changes: 64 additions & 14 deletions internal/service/networkmanager/core_network.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package networkmanager

import (
"context"
"encoding/json"
"fmt"
"log"
"time"
Expand Down Expand Up @@ -57,9 +58,21 @@ func ResourceCoreNetwork() *schema.Resource {
Computed: true,
},
"base_policy_region": {
Type: schema.TypeString,
Optional: true,
ValidateFunc: verify.ValidRegionName,
Deprecated: "Use the base_policy_regions argument instead. " +
"This argument will be removed in the next major version of the provider.",
Type: schema.TypeString,
Optional: true,
ValidateFunc: verify.ValidRegionName,
ConflictsWith: []string{"base_policy_regions"},
},
"base_policy_regions": {
Type: schema.TypeSet,
Optional: true,
Elem: &schema.Schema{
Type: schema.TypeString,
ValidateFunc: verify.ValidRegionName,
},
ConflictsWith: []string{"base_policy_region"},
},
"create_base_policy": {
Type: schema.TypeBool,
Expand Down Expand Up @@ -177,13 +190,18 @@ func resourceCoreNetworkCreate(ctx context.Context, d *schema.ResourceData, meta
// this is required for the first terraform apply if there attachments to the core network
// and the core network is created without the policy_document argument set
if _, ok := d.GetOk("create_base_policy"); ok {
// if user supplies a region use it in the base policy, otherwise use current region
region := meta.(*conns.AWSClient).Region
// if user supplies a region or multiple regions use it in the base policy, otherwise use current region
regions := []interface{}{meta.(*conns.AWSClient).Region}
if v, ok := d.GetOk("base_policy_region"); ok {
region = v.(string)
regions = []interface{}{v.(string)}
} else if v, ok := d.GetOk("base_policy_regions"); ok && v.(*schema.Set).Len() > 0 {
regions = v.(*schema.Set).List()
}

policyDocumentTarget := buildCoreNetworkBasePolicyDocument(region)
policyDocumentTarget, err := buildCoreNetworkBasePolicyDocument(regions)
if err != nil {
return diag.Errorf("Formatting Core Network Base Policy: %s", err)
}
input.PolicyDocument = aws.String(policyDocumentTarget)
}

Expand Down Expand Up @@ -303,14 +321,21 @@ func resourceCoreNetworkUpdate(ctx context.Context, d *schema.ResourceData, meta

if d.HasChange("create_base_policy") {
if _, ok := d.GetOk("create_base_policy"); ok {
// if user supplies a region use it in the base policy, otherwise use current region
region := meta.(*conns.AWSClient).Region
// if user supplies a region or multiple regions use it in the base policy, otherwise use current region
regions := []interface{}{meta.(*conns.AWSClient).Region}
if v, ok := d.GetOk("base_policy_region"); ok {
region = v.(string)
regions = []interface{}{v.(string)}
} else if v, ok := d.GetOk("base_policy_regions"); ok && v.(*schema.Set).Len() > 0 {
regions = v.(*schema.Set).List()
}

policyDocumentTarget, err := buildCoreNetworkBasePolicyDocument(regions)

if err != nil {
return diag.Errorf("Formatting Core Network Base Policy: %s", err)
}

policyDocumentTarget := buildCoreNetworkBasePolicyDocument(region)
err := PutAndExecuteCoreNetworkPolicy(ctx, conn, d.Id(), policyDocumentTarget)
err = PutAndExecuteCoreNetworkPolicy(ctx, conn, d.Id(), policyDocumentTarget)

if err != nil {
return diag.FromErr(err)
Expand Down Expand Up @@ -608,6 +633,31 @@ func PutAndExecuteCoreNetworkPolicy(ctx context.Context, conn *networkmanager.Ne
}

// buildCoreNetworkBasePolicyDocument returns a base policy document
func buildCoreNetworkBasePolicyDocument(region string) string {
return fmt.Sprintf("{\"core-network-configuration\":{\"asn-ranges\":[\"64512-65534\"],\"edge-locations\":[{\"location\":\"%s\"}]},\"segments\":[{\"name\":\"segment\",\"description\":\"base-policy\"}],\"version\":\"2021.12\"}", region)
func buildCoreNetworkBasePolicyDocument(regions []interface{}) (string, error) {
edgeLocations := make([]*CoreNetworkEdgeLocation, len(regions))
for i, location := range regions {
edgeLocations[i] = &CoreNetworkEdgeLocation{Location: location.(string)}
}

basePolicy := &CoreNetworkPolicyDoc{
Version: "2021.12",
CoreNetworkConfiguration: &CoreNetworkPolicyCoreNetworkConfiguration{
AsnRanges: CoreNetworkPolicyDecodeConfigStringList([]interface{}{"64512-65534"}),
EdgeLocations: edgeLocations,
},
Segments: []*CoreNetworkPolicySegment{
{
Name: "segment",
Description: "base-policy",
},
},
}

b, err := json.MarshalIndent(basePolicy, "", " ")
if err != nil {
// should never happen if the above code is correct
return "", fmt.Errorf("building base policy document: %s", err)
}

return string(b), nil
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (

"github.com/aws/aws-sdk-go/service/networkmanager"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
"github.com/hashicorp/terraform-provider-aws/internal/acctest"
"github.com/hashicorp/terraform-provider-aws/internal/conns"
Expand Down Expand Up @@ -97,6 +98,42 @@ func TestAccNetworkManagerCoreNetworkPolicyAttachment_vpcAttachment(t *testing.T
})
}

func TestAccNetworkManagerCoreNetworkPolicyAttachment_vpcAttachmentMultiRegion(t *testing.T) {
ctx := acctest.Context(t)
var providers []*schema.Provider
resourceName := "aws_networkmanager_core_network_policy_attachment.test"

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() {
acctest.PreCheck(t)
acctest.PreCheckMultipleRegion(t, 2)
},
ErrorCheck: acctest.ErrorCheck(t, networkmanager.EndpointsID),
ProtoV5ProviderFactories: acctest.ProtoV5FactoriesPlusProvidersAlternate(ctx, t, &providers),
CheckDestroy: testAccCheckCoreNetworkPolicyAttachmentDestroy(ctx),
Steps: []resource.TestStep{
{
Config: testAccCoreNetworkPolicyAttachmentConfig_vpcAttachmentMultiRegionCreate(),
Check: resource.ComposeTestCheckFunc(
testAccCheckCoreNetworkPolicyAttachmentExists(ctx, resourceName),
resource.TestMatchResourceAttr(resourceName, "policy_document", regexp.MustCompile(fmt.Sprintf(`{"core-network-configuration":{"asn-ranges":\["65022-65534"\],"edge-locations":\[{"location":"%s"},{"location":"%s"}\],"vpn-ecmp-support":true},"segment-actions":\[{"action":"create-route","destination-cidr-blocks":\["10.0.0.0/16"\],"destinations":\["attachment-.+"\],"segment":"segment"},{"action":"create-route","destination-cidr-blocks":\["10.1.0.0/16"\],"destinations":\["attachment-.+"\],"segment":"segment2"}\],"segments":\[{"isolate-attachments":false,"name":"segment","require-attachment-acceptance":true},{"isolate-attachments":false,"name":"segment2","require-attachment-acceptance":true}\],"version":"2021.12"}`, acctest.Region(), acctest.AlternateRegion()))),
// use test below if the order of locations is unordered
// resource.TestCheckResourceAttr(resourceName, "policy_document", fmt.Sprintf("{\"core-network-configuration\":{\"asn-ranges\":[\"65022-65534\"],\"edge-locations\":[{\"location\":\"%s\"},{\"location\":\"%s\"}],\"vpn-ecmp-support\":true},\"segments\":[{\"description\":\"base-policy\",\"isolate-attachments\":false,\"name\":\"segment\",\"require-attachment-acceptance\":false}],\"version\":\"2021.12\"}", acctest.AlternateRegion(), acctest.Region())),
resource.TestCheckResourceAttrPair(resourceName, "core_network_id", "aws_networkmanager_core_network.test", "id"),
resource.TestCheckResourceAttrPair(resourceName, "id", "aws_networkmanager_core_network.test", "id"),
resource.TestCheckResourceAttr(resourceName, "state", networkmanager.CoreNetworkStateAvailable),
),
},
{
Config: testAccCoreNetworkPolicyAttachmentConfig_vpcAttachmentMultiRegionCreate(),
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
},
},
})
}

func testAccCheckCoreNetworkPolicyAttachmentDestroy(ctx context.Context) resource.TestCheckFunc {
// policy document will not be reverted to empty if the attachment is deleted
return nil
Expand Down Expand Up @@ -216,3 +253,137 @@ resource "aws_networkmanager_vpc_attachment" "test" {
}
`, acctest.Region()))
}

func testAccCoreNetworkPolicyAttachmentConfig_vpcAttachmentMultiRegionCreate() string {
return acctest.ConfigCompose(
acctest.ConfigMultipleRegionProvider(2),
acctest.ConfigAvailableAZsNoOptIn(),
fmt.Sprintf(`
resource "aws_vpc" "test" {
cidr_block = "10.0.0.0/16"

tags = {
Name = "tf-acc-test-networkmanager-core-network-policy-attachment"
}
}

resource "aws_subnet" "test" {
count = 2

availability_zone = data.aws_availability_zones.available.names[count.index]
cidr_block = cidrsubnet(aws_vpc.test.cidr_block, 8, count.index)
vpc_id = aws_vpc.test.id

tags = {
Name = "tf-acc-test-networkmanager-core-network-policy-attachment"
}
}

resource "aws_networkmanager_global_network" "test" {}

data "aws_networkmanager_core_network_policy_document" "test" {
core_network_configuration {
asn_ranges = ["65022-65534"]

edge_locations {
location = %[1]q
}

edge_locations {
location = %[2]q
}
}

segments {
name = "segment"
}

segments {
name = "segment2"
}

segment_actions {
action = "create-route"
segment = "segment"
destination_cidr_blocks = [
"10.0.0.0/16"
]
destinations = [
aws_networkmanager_vpc_attachment.test.id,
]
}

segment_actions {
action = "create-route"
segment = "segment2"
destination_cidr_blocks = [
"10.1.0.0/16"
]
destinations = [
aws_networkmanager_vpc_attachment.alternate_region.id,
]
}
}

resource "aws_networkmanager_core_network" "test" {
global_network_id = aws_networkmanager_global_network.test.id
base_policy_regions = [%[1]q, %[2]q]
create_base_policy = true
}

resource "aws_networkmanager_core_network_policy_attachment" "test" {
core_network_id = aws_networkmanager_core_network.test.id
policy_document = data.aws_networkmanager_core_network_policy_document.test.json
}

resource "aws_networkmanager_vpc_attachment" "test" {
core_network_id = aws_networkmanager_core_network.test.id
subnet_arns = aws_subnet.test[*].arn
vpc_arn = aws_vpc.test.arn
}

# Alternate region
data "aws_availability_zones" "alternate_region_available" {
provider = "awsalternate"

state = "available"

filter {
name = "opt-in-status"
values = ["opt-in-not-required"]
}
}

resource "aws_vpc" "alternate_region" {
provider = "awsalternate"

cidr_block = "10.1.0.0/16"

tags = {
Name = "tf-acc-test-networkmanager-core-network-policy-attachment"
}
}

resource "aws_subnet" "alternate_region" {
provider = "awsalternate"

count = 2

availability_zone = data.aws_availability_zones.alternate_region_available.names[count.index]
cidr_block = cidrsubnet(aws_vpc.alternate_region.cidr_block, 8, count.index)
vpc_id = aws_vpc.alternate_region.id

tags = {
Name = "tf-acc-test-networkmanager-core-network-policy-attachment"
}
}

resource "aws_networkmanager_vpc_attachment" "alternate_region" {
provider = "awsalternate"

core_network_id = aws_networkmanager_core_network.test.id
subnet_arns = aws_subnet.alternate_region[*].arn
vpc_arn = aws_vpc.alternate_region.arn
}
`, acctest.Region(), acctest.AlternateRegion()))
}
Loading