diff --git a/.changelog/27387.txt b/.changelog/27387.txt new file mode 100644 index 000000000000..9fdebbadc19e --- /dev/null +++ b/.changelog/27387.txt @@ -0,0 +1,3 @@ +```release-note:new-resource +aws_networkmanager_site_to_site_vpn_attachment +``` diff --git a/internal/provider/provider.go b/internal/provider/provider.go index e53baa71fe2c..21fc311884a1 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -1813,6 +1813,7 @@ func New(_ context.Context) (*schema.Provider, error) { "aws_networkmanager_transit_gateway_registration": networkmanager.ResourceTransitGatewayRegistration(), "aws_networkmanager_transit_gateway_route_table_attachment": networkmanager.ResourceTransitGatewayRouteTableAttachment(), "aws_networkmanager_vpc_attachment": networkmanager.ResourceVPCAttachment(), + "aws_networkmanager_site_to_site_vpn_attachment": networkmanager.ResourceSiteToSiteVPNAttachment(), "aws_opensearch_domain": opensearch.ResourceDomain(), "aws_opensearch_domain_policy": opensearch.ResourceDomainPolicy(), diff --git a/internal/service/networkmanager/attachment_accepter.go b/internal/service/networkmanager/attachment_accepter.go index aa463fe896ff..8ee07c39baec 100644 --- a/internal/service/networkmanager/attachment_accepter.go +++ b/internal/service/networkmanager/attachment_accepter.go @@ -2,6 +2,7 @@ package networkmanager import ( "context" + "log" "time" "github.com/aws/aws-sdk-go/aws" @@ -10,6 +11,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) // AttachmentAccepter does not require AttachmentType. However, querying attachments for status updates requires knowing tyupe @@ -43,9 +45,8 @@ func ResourceAttachmentAccepter() *schema.Resource { ForceNew: true, ValidateFunc: validation.StringInSlice([]string{ networkmanager.AttachmentTypeVpc, + networkmanager.AttachmentTypeSiteToSiteVpn, }, false), - // Implement Values() function for validation as more types are onboarded to provider - // networkmanager.AttachmentType_Values(), false), }, "core_network_arn": { Type: schema.TypeString, @@ -82,18 +83,38 @@ func ResourceAttachmentAccepter() *schema.Resource { func resourceAttachmentAccepterCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { conn := meta.(*conns.AWSClient).NetworkManagerConn - if attachmentType := d.Get("attachment_type").(string); attachmentType != networkmanager.AttachmentTypeVpc { - return diag.Errorf("unsupported Network Manager Attachment type: %s", attachmentType) - } - + var state string attachmentID := d.Get("attachment_id").(string) - vpcAttachment, err := FindVPCAttachmentByID(ctx, conn, attachmentID) + attachmentType := d.Get("attachment_type").(string) + + switch attachmentType { + case networkmanager.AttachmentTypeVpc: + vpcAttachment, err := FindVPCAttachmentByID(ctx, conn, attachmentID) + + if err != nil { + return diag.Errorf("reading Network Manager VPC Attachment (%s): %s", attachmentID, err) + } + + state = aws.StringValue(vpcAttachment.Attachment.State) + + d.SetId(attachmentID) + + case networkmanager.AttachmentTypeSiteToSiteVpn: + vpnAttachment, err := FindSiteToSiteVPNAttachmentByID(ctx, conn, attachmentID) + + if err != nil { + return diag.Errorf("reading Network Manager Site To Site VPN Attachment (%s): %s", attachmentID, err) + } + + state = aws.StringValue(vpnAttachment.Attachment.State) + + d.SetId(attachmentID) - if err != nil { - return diag.Errorf("reading Network Manager VPC Attachment (%s): %s", attachmentID, err) + default: + return diag.Errorf("unsupported Network Manager Attachment type: %s", attachmentType) } - if state := aws.StringValue(vpcAttachment.Attachment.State); state == networkmanager.AttachmentStatePendingAttachmentAcceptance || state == networkmanager.AttachmentStatePendingTagAcceptance { + if state == networkmanager.AttachmentStatePendingAttachmentAcceptance || state == networkmanager.AttachmentStatePendingTagAcceptance { input := &networkmanager.AcceptAttachmentInput{ AttachmentId: aws.String(attachmentID), } @@ -104,34 +125,72 @@ func resourceAttachmentAccepterCreate(ctx context.Context, d *schema.ResourceDat return diag.Errorf("accepting Network Manager Attachment (%s): %s", attachmentID, err) } - if _, err := waitVPCAttachmentCreated(ctx, conn, attachmentID, d.Timeout(schema.TimeoutCreate)); err != nil { - return diag.Errorf("waiting for Network Manager VPC Attachment (%s) create: %s", attachmentID, err) + switch attachmentType { + case networkmanager.AttachmentTypeVpc: + if _, err := waitVPCAttachmentCreated(ctx, conn, attachmentID, d.Timeout(schema.TimeoutCreate)); err != nil { + return diag.Errorf("waiting for Network Manager VPC Attachment (%s) create: %s", attachmentID, err) + } + + case networkmanager.AttachmentTypeSiteToSiteVpn: + if _, err := waitSiteToSiteVPNAttachmentAvailable(ctx, conn, attachmentID, d.Timeout(schema.TimeoutCreate)); err != nil { + return diag.Errorf("waiting for Network Manager VPN Attachment (%s) create: %s", attachmentID, err) + } } } - d.SetId(attachmentID) - return resourceAttachmentAccepterRead(ctx, d, meta) } func resourceAttachmentAccepterRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { conn := meta.(*conns.AWSClient).NetworkManagerConn - vpcAttachment, err := FindVPCAttachmentByID(ctx, conn, d.Id()) + switch aType := d.Get("attachment_type"); aType { + case networkmanager.AttachmentTypeVpc: + vpcAttachment, err := FindVPCAttachmentByID(ctx, conn, d.Id()) - if err != nil { - return diag.Errorf("reading Network Manager VPC Attachment (%s): %s", d.Id(), err) - } + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] Network Manager VPC Attachment %s not found, removing from state", d.Id()) + d.SetId("") + return nil + } + + if err != nil { + return diag.Errorf("reading Network Manager VPC Attachment (%s): %s", d.Id(), err) + } + + a := vpcAttachment.Attachment + d.Set("attachment_policy_rule_number", a.AttachmentPolicyRuleNumber) + d.Set("core_network_arn", a.CoreNetworkArn) + d.Set("core_network_id", a.CoreNetworkId) + d.Set("edge_location", a.EdgeLocation) + d.Set("owner_account_id", a.OwnerAccountId) + d.Set("resource_arn", a.ResourceArn) + d.Set("segment_name", a.SegmentName) + d.Set("state", a.State) + + case networkmanager.AttachmentTypeSiteToSiteVpn: + vpnAttachment, err := FindSiteToSiteVPNAttachmentByID(ctx, conn, d.Id()) + + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] Network Manager Site To Site VPN Attachment %s not found, removing from state", d.Id()) + d.SetId("") + return nil + } - a := vpcAttachment.Attachment - d.Set("attachment_policy_rule_number", a.AttachmentPolicyRuleNumber) - d.Set("core_network_arn", a.CoreNetworkArn) - d.Set("core_network_id", a.CoreNetworkId) - d.Set("edge_location", a.EdgeLocation) - d.Set("owner_account_id", a.OwnerAccountId) - d.Set("resource_arn", a.ResourceArn) - d.Set("segment_name", a.SegmentName) - d.Set("state", a.State) + if err != nil { + return diag.Errorf("reading Network Manager Site To Site VPN Attachment (%s): %s", d.Id(), err) + } + + a := vpnAttachment.Attachment + d.Set("attachment_policy_rule_number", a.AttachmentPolicyRuleNumber) + d.Set("core_network_arn", a.CoreNetworkArn) + d.Set("core_network_id", a.CoreNetworkId) + d.Set("edge_location", a.EdgeLocation) + d.Set("owner_account_id", a.OwnerAccountId) + d.Set("resource_arn", a.ResourceArn) + d.Set("segment_name", a.SegmentName) + d.Set("state", a.State) + } return nil } diff --git a/internal/service/networkmanager/site_to_site_vpn_attachment.go b/internal/service/networkmanager/site_to_site_vpn_attachment.go new file mode 100644 index 000000000000..20bd869f6587 --- /dev/null +++ b/internal/service/networkmanager/site_to_site_vpn_attachment.go @@ -0,0 +1,321 @@ +package networkmanager + +import ( + "context" + "fmt" + "log" + "regexp" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/arn" + "github.com/aws/aws-sdk-go/service/networkmanager" + "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "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/helper/validation" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" + "github.com/hashicorp/terraform-provider-aws/internal/verify" +) + +func ResourceSiteToSiteVPNAttachment() *schema.Resource { + return &schema.Resource{ + CreateWithoutTimeout: resourceSiteToSiteVPNAttachmentCreate, + ReadWithoutTimeout: resourceSiteToSiteVPNAttachmentRead, + UpdateWithoutTimeout: resourceSiteToSiteVPNAttachmentUpdate, + DeleteWithoutTimeout: resourceSiteToSiteVPNAttachmentDelete, + + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + CustomizeDiff: verify.SetTagsDiff, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(10 * time.Minute), + Update: schema.DefaultTimeout(10 * time.Minute), + Delete: schema.DefaultTimeout(10 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "arn": { + Type: schema.TypeString, + Computed: true, + }, + "attachment_policy_rule_number": { + Type: schema.TypeInt, + Computed: true, + }, + "attachment_type": { + Type: schema.TypeString, + Computed: true, + }, + "core_network_arn": { + Type: schema.TypeString, + Computed: true, + }, + "core_network_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "edge_location": { + Type: schema.TypeString, + Computed: true, + }, + "owner_account_id": { + Type: schema.TypeString, + Computed: true, + }, + "resource_arn": { + Type: schema.TypeString, + Computed: true, + }, + "segment_name": { + Type: schema.TypeString, + Computed: true, + }, + "state": { + Type: schema.TypeString, + Computed: true, + }, + "tags": tftags.TagsSchema(), + "tags_all": tftags.TagsSchemaComputed(), + "vpn_connection_arn": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringMatch(regexp.MustCompile(`^arn:[^:]{1,63}:ec2:[^:]{0,63}:[^:]{0,63}:vpn-connection\/vpn-[0-9a-f]{8,17}$`), "Must be valid VPN ARN"), + }, + }, + } +} + +func resourceSiteToSiteVPNAttachmentCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).NetworkManagerConn + defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig + tags := defaultTagsConfig.MergeTags(tftags.New(d.Get("tags").(map[string]interface{}))) + + coreNetworkID := d.Get("core_network_id").(string) + vpnConnectionARN := d.Get("vpn_connection_arn").(string) + input := &networkmanager.CreateSiteToSiteVpnAttachmentInput{ + CoreNetworkId: aws.String(coreNetworkID), + VpnConnectionArn: aws.String(vpnConnectionARN), + } + + if len(tags) > 0 { + input.Tags = Tags(tags.IgnoreAWS()) + } + + output, err := conn.CreateSiteToSiteVpnAttachmentWithContext(ctx, input) + + if err != nil { + return diag.Errorf("creating Network Manager Site To Site VPN (%s) Attachment (%s): %s", vpnConnectionARN, coreNetworkID, err) + } + + d.SetId(aws.StringValue(output.SiteToSiteVpnAttachment.Attachment.AttachmentId)) + + if _, err := waitSiteToSiteVPNAttachmentCreated(ctx, conn, d.Id(), d.Timeout(schema.TimeoutCreate)); err != nil { + return diag.Errorf("waiting for Network Manager Site To Site VPN Attachment (%s) create: %s", d.Id(), err) + } + + return resourceSiteToSiteVPNAttachmentRead(ctx, d, meta) +} + +func resourceSiteToSiteVPNAttachmentRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).NetworkManagerConn + defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig + ignoreTagsConfig := meta.(*conns.AWSClient).IgnoreTagsConfig + + vpnAttachment, err := FindSiteToSiteVPNAttachmentByID(ctx, conn, d.Id()) + + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] Network Manager Site To Site VPN Attachment (%s) not found, removing from state", d.Id()) + d.SetId("") + return nil + } + + if err != nil { + return diag.Errorf("reading Network Manager Site To Site VPN Attachment (%s): %s", d.Id(), err) + } + + a := vpnAttachment.Attachment + arn := arn.ARN{ + Partition: meta.(*conns.AWSClient).Partition, + Service: "networkmanager", + AccountID: meta.(*conns.AWSClient).AccountID, + Resource: fmt.Sprintf("attachment/%s", d.Id()), + }.String() + d.Set("arn", arn) + d.Set("attachment_policy_rule_number", a.AttachmentPolicyRuleNumber) + d.Set("attachment_type", a.AttachmentType) + d.Set("core_network_arn", a.CoreNetworkArn) + d.Set("core_network_id", a.CoreNetworkId) + d.Set("edge_location", a.EdgeLocation) + d.Set("owner_account_id", a.OwnerAccountId) + d.Set("resource_arn", a.ResourceArn) + d.Set("segment_name", a.SegmentName) + d.Set("state", a.State) + d.Set("vpn_connection_arn", a.ResourceArn) + + tags := KeyValueTags(a.Tags).IgnoreAWS().IgnoreConfig(ignoreTagsConfig) + + if err := d.Set("tags", tags.RemoveDefaultConfig(defaultTagsConfig).Map()); err != nil { + return diag.Errorf("setting tags: %s", err) + } + + if err := d.Set("tags_all", tags.Map()); err != nil { + return diag.Errorf("setting tags_all: %s", err) + } + + return nil +} + +func resourceSiteToSiteVPNAttachmentUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).NetworkManagerConn + + if d.HasChange("tags_all") { + o, n := d.GetChange("tags_all") + + if err := UpdateTags(conn, d.Get("arn").(string), o, n); err != nil { + return diag.FromErr(fmt.Errorf("updating Network Manager Site To Site VPN Attachment (%s) tags: %s", d.Get("arn").(string), err)) + } + } + + return resourceSiteToSiteVPNAttachmentRead(ctx, d, meta) +} + +func resourceSiteToSiteVPNAttachmentDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).NetworkManagerConn + + // If ResourceAttachmentAccepter is used, then VPN Attachment state + // is never updated from StatePendingAttachmentAcceptance and the delete fails + output, err := FindSiteToSiteVPNAttachmentByID(ctx, conn, d.Id()) + + if tfawserr.ErrCodeEquals(err, networkmanager.ErrCodeResourceNotFoundException) { + return nil + } + + if err != nil { + return diag.Errorf("reading Network Manager Site To Site VPN Attachment (%s): %s", d.Id(), err) + } + + if state := aws.StringValue(output.Attachment.State); state == networkmanager.AttachmentStatePendingAttachmentAcceptance || state == networkmanager.AttachmentStatePendingTagAcceptance { + return diag.Errorf("cannot delete Network Manager Site To Site VPN Attachment (%s) in state: %s", d.Id(), state) + } + + log.Printf("[DEBUG] Deleting Network Manager Site To Site VPN Attachment: %s", d.Id()) + _, err = conn.DeleteAttachmentWithContext(ctx, &networkmanager.DeleteAttachmentInput{ + AttachmentId: aws.String(d.Id()), + }) + + if tfawserr.ErrCodeEquals(err, networkmanager.ErrCodeResourceNotFoundException) { + return nil + } + + if err != nil { + return diag.Errorf("deleting Network Manager Site To Site VPN Attachment (%s): %s", d.Id(), err) + } + + if _, err := waitSiteToSiteVPNAttachmentDeleted(ctx, conn, d.Id(), d.Timeout(schema.TimeoutDelete)); err != nil { + return diag.Errorf("waiting for Network Manager Site To Site VPN Attachment (%s) delete: %s", d.Id(), err) + } + + return nil +} + +func FindSiteToSiteVPNAttachmentByID(ctx context.Context, conn *networkmanager.NetworkManager, id string) (*networkmanager.SiteToSiteVpnAttachment, error) { + input := &networkmanager.GetSiteToSiteVpnAttachmentInput{ + AttachmentId: aws.String(id), + } + + output, err := conn.GetSiteToSiteVpnAttachmentWithContext(ctx, input) + + if tfawserr.ErrCodeEquals(err, networkmanager.ErrCodeResourceNotFoundException) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + if output == nil || output.SiteToSiteVpnAttachment == nil || output.SiteToSiteVpnAttachment.Attachment == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + return output.SiteToSiteVpnAttachment, nil +} + +func statusSiteToSiteVPNAttachmentState(ctx context.Context, conn *networkmanager.NetworkManager, id string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + output, err := FindSiteToSiteVPNAttachmentByID(ctx, conn, id) + + if tfresource.NotFound(err) { + return nil, "", nil + } + + if err != nil { + return nil, "", err + } + + return output, aws.StringValue(output.Attachment.State), nil + } +} + +func waitSiteToSiteVPNAttachmentCreated(ctx context.Context, conn *networkmanager.NetworkManager, id string, timeout time.Duration) (*networkmanager.SiteToSiteVpnAttachment, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{networkmanager.AttachmentStateCreating, networkmanager.AttachmentStatePendingNetworkUpdate}, + Target: []string{networkmanager.AttachmentStateAvailable, networkmanager.AttachmentStatePendingAttachmentAcceptance}, + Timeout: timeout, + Refresh: statusSiteToSiteVPNAttachmentState(ctx, conn, id), + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if output, ok := outputRaw.(*networkmanager.SiteToSiteVpnAttachment); ok { + return output, err + } + + return nil, err +} + +func waitSiteToSiteVPNAttachmentDeleted(ctx context.Context, conn *networkmanager.NetworkManager, id string, timeout time.Duration) (*networkmanager.SiteToSiteVpnAttachment, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{networkmanager.AttachmentStateDeleting}, + Target: []string{}, + Timeout: timeout, + Refresh: statusSiteToSiteVPNAttachmentState(ctx, conn, id), + NotFoundChecks: 1, + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if output, ok := outputRaw.(*networkmanager.SiteToSiteVpnAttachment); ok { + return output, err + } + + return nil, err +} + +func waitSiteToSiteVPNAttachmentAvailable(ctx context.Context, conn *networkmanager.NetworkManager, id string, timeout time.Duration) (*networkmanager.SiteToSiteVpnAttachment, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{networkmanager.AttachmentStateCreating, networkmanager.AttachmentStatePendingAttachmentAcceptance, networkmanager.AttachmentStatePendingNetworkUpdate}, + Target: []string{networkmanager.AttachmentStateAvailable}, + Timeout: timeout, + Refresh: statusSiteToSiteVPNAttachmentState(ctx, conn, id), + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if output, ok := outputRaw.(*networkmanager.SiteToSiteVpnAttachment); ok { + return output, err + } + + return nil, err +} diff --git a/internal/service/networkmanager/site_to_site_vpn_attachment_test.go b/internal/service/networkmanager/site_to_site_vpn_attachment_test.go new file mode 100644 index 000000000000..36e7e1da64f5 --- /dev/null +++ b/internal/service/networkmanager/site_to_site_vpn_attachment_test.go @@ -0,0 +1,329 @@ +package networkmanager_test + +import ( + "context" + "fmt" + "regexp" + "testing" + + "github.com/aws/aws-sdk-go/service/networkmanager" + sdkacctest "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/acctest" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + tfnetworkmanager "github.com/hashicorp/terraform-provider-aws/internal/service/networkmanager" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" +) + +func TestAccNetworkManagerSiteToSiteVPNAttachment_basic(t *testing.T) { + var v networkmanager.SiteToSiteVpnAttachment + resourceName := "aws_networkmanager_site_to_site_vpn_attachment.test" + vpnResourceName := "aws_vpn_connection.test" + coreNetwork := "awscc_networkmanager_core_network.test" + testExternalProviders := map[string]resource.ExternalProvider{ + "awscc": { + Source: "hashicorp/awscc", + VersionConstraint: "0.29.0", + }, + } + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + bgpASN := 65000 + vpnIP := "172.0.0.1" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, networkmanager.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + ExternalProviders: testExternalProviders, + CheckDestroy: testAccCheckSiteToSiteVPNAttachmentDestroy, + Steps: []resource.TestStep{ + { + Config: testAccSiteToSiteVPNAttachmentConfig_basic(rName, bgpASN, vpnIP), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckSiteToSiteVPNAttachmentExists(resourceName, &v), + acctest.MatchResourceAttrGlobalARN(resourceName, "arn", "networkmanager", regexp.MustCompile(`attachment/.+`)), + resource.TestCheckResourceAttr(resourceName, "attachment_policy_rule_number", "1"), + resource.TestCheckResourceAttr(resourceName, "attachment_type", "SITE_TO_SITE_VPN"), + resource.TestCheckResourceAttrPair(resourceName, "core_network_arn", coreNetwork, "core_network_arn"), + resource.TestCheckResourceAttrSet(resourceName, "core_network_id"), + resource.TestCheckResourceAttr(resourceName, "edge_location", acctest.Region()), + acctest.CheckResourceAttrAccountID(resourceName, "owner_account_id"), + resource.TestCheckResourceAttrPair(resourceName, "resource_arn", vpnResourceName, "arn"), + resource.TestCheckResourceAttr(resourceName, "segment_name", "shared"), + resource.TestCheckResourceAttrSet(resourceName, "state"), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttrPair(resourceName, "vpn_connection_arn", vpnResourceName, "arn"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccNetworkManagerSiteToSiteVPNAttachment_disappears(t *testing.T) { + var v networkmanager.SiteToSiteVpnAttachment + resourceName := "aws_networkmanager_site_to_site_vpn_attachment.test" + testExternalProviders := map[string]resource.ExternalProvider{ + "awscc": { + Source: "hashicorp/awscc", + VersionConstraint: "0.29.0", + }, + } + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + bgpASN := 65001 + vpnIP := "172.0.0.1" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, networkmanager.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + ExternalProviders: testExternalProviders, + CheckDestroy: testAccCheckSiteToSiteVPNAttachmentDestroy, + Steps: []resource.TestStep{ + { + Config: testAccSiteToSiteVPNAttachmentConfig_basic(rName, bgpASN, vpnIP), + Check: resource.ComposeTestCheckFunc( + testAccCheckSiteToSiteVPNAttachmentExists(resourceName, &v), + acctest.CheckResourceDisappears(acctest.Provider, tfnetworkmanager.ResourceSiteToSiteVPNAttachment(), resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func TestAccNetworkManagerSiteToSiteVPNAttachment_tags(t *testing.T) { + var v networkmanager.SiteToSiteVpnAttachment + resourceName := "aws_networkmanager_site_to_site_vpn_attachment.test" + testExternalProviders := map[string]resource.ExternalProvider{ + "awscc": { + Source: "hashicorp/awscc", + VersionConstraint: "0.29.0", + }, + } + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + bgpASN := 65002 + vpnIP := "172.0.0.1" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, networkmanager.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + ExternalProviders: testExternalProviders, + CheckDestroy: testAccCheckSiteToSiteVPNAttachmentDestroy, + Steps: []resource.TestStep{ + { + Config: testAccSiteToSiteVPNAttachmentConfig_tags1(rName, vpnIP, "segment", "shared", bgpASN), + Check: resource.ComposeTestCheckFunc( + testAccCheckSiteToSiteVPNAttachmentExists(resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.segment", "shared"), + ), + }, + { + Config: testAccSiteToSiteVPNAttachmentConfig_tags2(rName, vpnIP, "segment", "shared", "Name", "test", bgpASN), + Check: resource.ComposeTestCheckFunc( + testAccCheckSiteToSiteVPNAttachmentExists(resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), + resource.TestCheckResourceAttr(resourceName, "tags.segment", "shared"), + resource.TestCheckResourceAttr(resourceName, "tags.Name", "test"), + ), + }, + { + Config: testAccSiteToSiteVPNAttachmentConfig_tags1(rName, vpnIP, "segment", "shared", bgpASN), + Check: resource.ComposeTestCheckFunc( + testAccCheckSiteToSiteVPNAttachmentExists(resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.segment", "shared"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccCheckSiteToSiteVPNAttachmentExists(n string, v *networkmanager.SiteToSiteVpnAttachment) 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 Network Manager Site To Site VPN Attachment ID is set") + } + + conn := acctest.Provider.Meta().(*conns.AWSClient).NetworkManagerConn + + output, err := tfnetworkmanager.FindSiteToSiteVPNAttachmentByID(context.Background(), conn, rs.Primary.ID) + + if err != nil { + return err + } + + *v = *output + + return nil + } +} + +func testAccCheckSiteToSiteVPNAttachmentDestroy(s *terraform.State) error { + conn := acctest.Provider.Meta().(*conns.AWSClient).NetworkManagerConn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_networkmanager_site_to_site_vpn_attachment" { + continue + } + + _, err := tfnetworkmanager.FindSiteToSiteVPNAttachmentByID(context.Background(), conn, rs.Primary.ID) + + if tfresource.NotFound(err) { + continue + } + + if err != nil { + return err + } + + return fmt.Errorf("Network Manager Site To Site VPN Attachment %s still exists", rs.Primary.ID) + } + + return nil +} + +func testAccSiteToSiteVPNAttachmentConfig_base(rName string, bgpASN int, vpnIP string) string { + return acctest.ConfigCompose(acctest.ConfigAvailableAZsNoOptIn(), fmt.Sprintf(` +data "aws_region" "current" {} + +resource "aws_customer_gateway" "test" { + bgp_asn = %[2]d + ip_address = %[3]q + type = "ipsec.1" +} + +resource "aws_vpn_connection" "test" { + customer_gateway_id = aws_customer_gateway.test.id + type = "ipsec.1" + + tags = { + Name = %[1]q + } +} + +resource "aws_networkmanager_global_network" "test" { + tags = { + Name = %[1]q + } +} + +resource "awscc_networkmanager_core_network" "test" { + global_network_id = aws_networkmanager_global_network.test.id + policy_document = jsonencode(jsondecode(data.aws_networkmanager_core_network_policy_document.test.json)) +} + +data "aws_networkmanager_core_network_policy_document" "test" { + core_network_configuration { + vpn_ecmp_support = false + asn_ranges = ["64512-64555"] + edge_locations { + location = data.aws_region.current.name + asn = 64512 + } + } + + segments { + name = "shared" + description = "SegmentForSharedServices" + require_attachment_acceptance = true + } + + segment_actions { + action = "share" + mode = "attachment-route" + segment = "shared" + share_with = ["*"] + } + + attachment_policies { + rule_number = 1 + condition_logic = "or" + + conditions { + type = "tag-value" + operator = "equals" + key = "segment" + value = "shared" + } + + action { + association_method = "constant" + segment = "shared" + } + } +} +`, rName, bgpASN, vpnIP)) +} + +func testAccSiteToSiteVPNAttachmentConfig_basic(rName string, bgpASN int, vpnIP string) string { + return acctest.ConfigCompose(testAccSiteToSiteVPNAttachmentConfig_base(rName, bgpASN, vpnIP), ` +resource "aws_networkmanager_site_to_site_vpn_attachment" "test" { + core_network_id = awscc_networkmanager_core_network.test.id + vpn_connection_arn = aws_vpn_connection.test.arn + + tags = { + segment = "shared" + } +} + +resource "aws_networkmanager_attachment_accepter" "test" { + attachment_id = aws_networkmanager_site_to_site_vpn_attachment.test.id + attachment_type = aws_networkmanager_site_to_site_vpn_attachment.test.attachment_type +} +`) +} + +func testAccSiteToSiteVPNAttachmentConfig_tags1(rName, vpnIP, tagKey1, tagValue1 string, bgpASN int) string { + return acctest.ConfigCompose(testAccSiteToSiteVPNAttachmentConfig_base(rName, bgpASN, vpnIP), fmt.Sprintf(` +resource "aws_networkmanager_site_to_site_vpn_attachment" "test" { + core_network_id = awscc_networkmanager_core_network.test.id + vpn_connection_arn = aws_vpn_connection.test.arn + + tags = { + %[1]q = %[2]q + } +} + +resource "aws_networkmanager_attachment_accepter" "test" { + attachment_id = aws_networkmanager_site_to_site_vpn_attachment.test.id + attachment_type = aws_networkmanager_site_to_site_vpn_attachment.test.attachment_type +} +`, tagKey1, tagValue1)) +} + +func testAccSiteToSiteVPNAttachmentConfig_tags2(rName, vpnIP, tagKey1, tagValue1, tagKey2, tagValue2 string, bgpASN int) string { + return acctest.ConfigCompose(testAccSiteToSiteVPNAttachmentConfig_base(rName, bgpASN, vpnIP), fmt.Sprintf(` +resource "aws_networkmanager_site_to_site_vpn_attachment" "test" { + core_network_id = awscc_networkmanager_core_network.test.id + vpn_connection_arn = aws_vpn_connection.test.arn + + tags = { + %[1]q = %[2]q + %[3]q = %[4]q + } +} + +resource "aws_networkmanager_attachment_accepter" "test" { + attachment_id = aws_networkmanager_site_to_site_vpn_attachment.test.id + attachment_type = aws_networkmanager_site_to_site_vpn_attachment.test.attachment_type +} +`, tagKey1, tagValue1, tagKey2, tagValue2)) +} diff --git a/internal/service/networkmanager/vpc_attachment_test.go b/internal/service/networkmanager/vpc_attachment_test.go index 5639f0d5b156..45fc4e0dc048 100644 --- a/internal/service/networkmanager/vpc_attachment_test.go +++ b/internal/service/networkmanager/vpc_attachment_test.go @@ -19,6 +19,7 @@ import ( func TestAccNetworkManagerVPCAttachment_basic(t *testing.T) { var v networkmanager.VpcAttachment resourceName := "aws_networkmanager_vpc_attachment.test" + coreNetworkResourceName := "awscc_networkmanager_core_network.test" vpcResourceName := "aws_vpc.test" testExternalProviders := map[string]resource.ExternalProvider{ "awscc": { @@ -42,7 +43,7 @@ func TestAccNetworkManagerVPCAttachment_basic(t *testing.T) { acctest.MatchResourceAttrGlobalARN(resourceName, "arn", "networkmanager", regexp.MustCompile(`attachment/.+`)), resource.TestCheckResourceAttr(resourceName, "attachment_policy_rule_number", "0"), resource.TestCheckResourceAttr(resourceName, "attachment_type", "VPC"), - resource.TestCheckResourceAttr(resourceName, "core_network_arn", ""), + resource.TestCheckResourceAttrPair(resourceName, "core_network_arn", coreNetworkResourceName, "core_network_arn"), resource.TestCheckResourceAttrSet(resourceName, "core_network_id"), resource.TestCheckResourceAttr(resourceName, "edge_location", acctest.Region()), resource.TestCheckResourceAttr(resourceName, "options.#", "1"), diff --git a/website/docs/r/networkmanager_attachment_accepter.html.markdown b/website/docs/r/networkmanager_attachment_accepter.html.markdown index ec77d9efd512..2110a44f3b1b 100644 --- a/website/docs/r/networkmanager_attachment_accepter.html.markdown +++ b/website/docs/r/networkmanager_attachment_accepter.html.markdown @@ -12,7 +12,7 @@ Terraform resource for managing an AWS NetworkManager Attachment Accepter. ## Example Usage -### Basic Usage +### Example with VPC attachment ```terraform resource "aws_networkmanager_attachment_accepter" "test" { @@ -21,22 +21,31 @@ resource "aws_networkmanager_attachment_accepter" "test" { } ``` +### Example with site-to-site VPN attachment + +```terraform +resource "aws_networkmanager_attachment_accepter" "test" { + attachment_id = aws_networkmanager_site_to_site_vpn_attachment.vpn.id + attachment_type = aws_networkmanager_site_to_site_vpn_attachment.vpn.attachment_type +} +``` + ## Argument Reference The following arguments are required: -* `attachment_id` - (Required) The ID of the attachment. -* `attachment_type` - The type of attachment. Valid values can be found in the [AWS Documentation](https://docs.aws.amazon.com/networkmanager/latest/APIReference/API_ListAttachments.html#API_ListAttachments_RequestSyntax) +- `attachment_id` - (Required) The ID of the attachment. +- `attachment_type` - The type of attachment. Valid values can be found in the [AWS Documentation](https://docs.aws.amazon.com/networkmanager/latest/APIReference/API_ListAttachments.html#API_ListAttachments_RequestSyntax) ## Attributes Reference In addition to all arguments above, the following attributes are exported: -* `attachment_policy_rule_number` - The policy rule number associated with the attachment. -* `core_network_arn` - The ARN of a core network. -* `core_network_id` - The id of a core network. -* `edge_location` - The Region where the edge is located. -* `owner_account_id` - The ID of the attachment account owner. -* `resource_arn` - The attachment resource ARN. -* `segment_name` - The name of the segment attachment. -* `state` - The state of the attachment. +- `attachment_policy_rule_number` - The policy rule number associated with the attachment. +- `core_network_arn` - The ARN of a core network. +- `core_network_id` - The id of a core network. +- `edge_location` - The Region where the edge is located. +- `owner_account_id` - The ID of the attachment account owner. +- `resource_arn` - The attachment resource ARN. +- `segment_name` - The name of the segment attachment. +- `state` - The state of the attachment. diff --git a/website/docs/r/networkmanager_site_to_site_vpn_attachment.html.markdown b/website/docs/r/networkmanager_site_to_site_vpn_attachment.html.markdown new file mode 100644 index 000000000000..6efa7c0a5b84 --- /dev/null +++ b/website/docs/r/networkmanager_site_to_site_vpn_attachment.html.markdown @@ -0,0 +1,58 @@ +--- +subcategory: "Network Manager" +layout: "aws" +page_title: "AWS: aws_networkmanager_site_to_site_vpn_attachment" +description: |- + Terraform resource for managing an AWS NetworkManager SiteToSiteAttachment. +--- + +# Resource: aws_networkmanager_site_to_site_vpn_attachment + +Terraform resource for managing an AWS NetworkManager SiteToSiteAttachment. + +## Example Usage + +### Basic Usage + +```terraform +resource "aws_networkmanager_site_to_site_vpn_attachment" "example" { + core_network_id = awscc_networkmanager_core_network.example.id + vpn_connection_arn = aws_vpn_connection.example.arn +} +``` + +## Argument Reference + +The following arguments are required: + +- `core_network_id` - (Required) The ID of a core network for the VPN attachment. +- `vpn_connection_arn` - (Required) The ARN of the site-to-site VPN connection. + +The following arguments are optional: + +- `tags` - (Optional) Key-value tags for the attachment. 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. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +- `arn` - The ARN of the attachment. +- `attachment_policy_rule_number` - The policy rule number associated with the attachment. +- `attachment_type` - The type of attachment. +- `core_network_arn` - The ARN of a core network. +- `core_network_id` - The ID of a core network +- `edge_location` - The Region where the edge is located. +- `id` - The ID of the attachment. +- `owner_account_id` - The ID of the attachment account owner. +- `resource_arn` - The attachment resource ARN. +- `segment_name` - The name of the segment attachment. +- `state` - The state of the attachment. +- `tags_all` - A map of tags assigned to the resource, including those inherited from the provider [`default_tags` configuration block](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#default_tags-configuration-block). + +## Import + +`aws_networkmanager_site_to_site_vpn_attachment` can be imported using the attachment ID, e.g. + +``` +$ terraform import aws_networkmanager_site_to_site_vpn_attachment.example attachment-0f8fa60d2238d1bd8 +``` diff --git a/website/docs/r/networkmanager_vpc_attachment.html.markdown b/website/docs/r/networkmanager_vpc_attachment.html.markdown index 2645310211c2..347871003dd8 100644 --- a/website/docs/r/networkmanager_vpc_attachment.html.markdown +++ b/website/docs/r/networkmanager_vpc_attachment.html.markdown @@ -33,7 +33,7 @@ The following arguments are required: The following arguments are optional: * `options` - (Optional) Options for the VPC attachment. -* `tags` - (Optional) Key-value tags for the Global Network. 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. +* `tags` - (Optional) Key-value tags for the attachment. 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. ### options