diff --git a/.changelog/22435.txt b/.changelog/22435.txt new file mode 100644 index 00000000000..9d53a99f0db --- /dev/null +++ b/.changelog/22435.txt @@ -0,0 +1,7 @@ +```release-note:enhancement +resource/aws_customer_gateway: Add `certificate_arn` argument +``` + +```release-note:enhancement +data-source/aws_customer_gateway: Add `certificate_arn` attribute +``` \ No newline at end of file diff --git a/internal/acctest/acctest.go b/internal/acctest/acctest.go index ed282b15b61..2fec60ca912 100644 --- a/internal/acctest/acctest.go +++ b/internal/acctest/acctest.go @@ -1302,22 +1302,27 @@ func ACMCertificateRandomSubDomain(rootDomain string) string { rootDomain) } -func CheckACMPCACertificateAuthorityActivateCA(certificateAuthority *acmpca.CertificateAuthority) resource.TestCheckFunc { +func CheckACMPCACertificateAuthorityActivateRootCA(certificateAuthority *acmpca.CertificateAuthority) resource.TestCheckFunc { return func(s *terraform.State) error { conn := Provider.Meta().(*conns.AWSClient).ACMPCAConn + if v := aws.StringValue(certificateAuthority.Type); v != acmpca.CertificateAuthorityTypeRoot { + return fmt.Errorf("attempting to activate ACM PCA %s Certificate Authority", v) + } + arn := aws.StringValue(certificateAuthority.Arn) - getCsrResp, err := conn.GetCertificateAuthorityCsr(&acmpca.GetCertificateAuthorityCsrInput{ + getCsrOutput, err := conn.GetCertificateAuthorityCsr(&acmpca.GetCertificateAuthorityCsrInput{ CertificateAuthorityArn: aws.String(arn), }) + if err != nil { - return fmt.Errorf("error getting ACM PCA Certificate Authority (%s) CSR: %s", arn, err) + return fmt.Errorf("error getting ACM PCA Certificate Authority (%s) CSR: %w", arn, err) } - issueCertResp, err := conn.IssueCertificate(&acmpca.IssueCertificateInput{ + issueCertOutput, err := conn.IssueCertificate(&acmpca.IssueCertificateInput{ CertificateAuthorityArn: aws.String(arn), - Csr: []byte(aws.StringValue(getCsrResp.Csr)), + Csr: []byte(aws.StringValue(getCsrOutput.Csr)), IdempotencyToken: aws.String(resource.UniqueId()), SigningAlgorithm: certificateAuthority.CertificateAuthorityConfiguration.SigningAlgorithm, TemplateArn: aws.String(fmt.Sprintf("arn:%s:acm-pca:::template/RootCACertificate/V1", Partition())), @@ -1326,33 +1331,106 @@ func CheckACMPCACertificateAuthorityActivateCA(certificateAuthority *acmpca.Cert Value: aws.Int64(10), }, }) + if err != nil { - return fmt.Errorf("error issuing ACM PCA Certificate Authority (%s) Root CA certificate from CSR: %s", arn, err) + return fmt.Errorf("error issuing ACM PCA Certificate Authority (%s) Root CA certificate from CSR: %w", arn, err) } // Wait for certificate status to become ISSUED. err = conn.WaitUntilCertificateIssued(&acmpca.GetCertificateInput{ CertificateAuthorityArn: aws.String(arn), - CertificateArn: issueCertResp.CertificateArn, + CertificateArn: issueCertOutput.CertificateArn, + }) + + if err != nil { + return fmt.Errorf("error waiting for ACM PCA Certificate Authority (%s) Root CA certificate to become ISSUED: %w", arn, err) + } + + getCertOutput, err := conn.GetCertificate(&acmpca.GetCertificateInput{ + CertificateAuthorityArn: aws.String(arn), + CertificateArn: issueCertOutput.CertificateArn, + }) + + if err != nil { + return fmt.Errorf("error getting ACM PCA Certificate Authority (%s) issued Root CA certificate: %w", arn, err) + } + + _, err = conn.ImportCertificateAuthorityCertificate(&acmpca.ImportCertificateAuthorityCertificateInput{ + CertificateAuthorityArn: aws.String(arn), + Certificate: []byte(aws.StringValue(getCertOutput.Certificate)), }) + if err != nil { - return fmt.Errorf("error waiting for ACM PCA Certificate Authority (%s) Root CA certificate to become ISSUED: %s", arn, err) + return fmt.Errorf("error importing ACM PCA Certificate Authority (%s) Root CA certificate: %w", arn, err) } - getCertResp, err := conn.GetCertificate(&acmpca.GetCertificateInput{ + return err + } +} + +func CheckACMPCACertificateAuthorityActivateSubordinateCA(rootCertificateAuthority, certificateAuthority *acmpca.CertificateAuthority) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := Provider.Meta().(*conns.AWSClient).ACMPCAConn + + if v := aws.StringValue(certificateAuthority.Type); v != acmpca.CertificateAuthorityTypeSubordinate { + return fmt.Errorf("attempting to activate ACM PCA %s Certificate Authority", v) + } + + arn := aws.StringValue(certificateAuthority.Arn) + + getCsrOutput, err := conn.GetCertificateAuthorityCsr(&acmpca.GetCertificateAuthorityCsrInput{ CertificateAuthorityArn: aws.String(arn), - CertificateArn: issueCertResp.CertificateArn, }) + + if err != nil { + return fmt.Errorf("error getting ACM PCA Certificate Authority (%s) CSR: %w", arn, err) + } + + rootCertificateAuthorityArn := aws.StringValue(rootCertificateAuthority.Arn) + + issueCertOutput, err := conn.IssueCertificate(&acmpca.IssueCertificateInput{ + CertificateAuthorityArn: aws.String(rootCertificateAuthorityArn), + Csr: []byte(aws.StringValue(getCsrOutput.Csr)), + IdempotencyToken: aws.String(resource.UniqueId()), + SigningAlgorithm: certificateAuthority.CertificateAuthorityConfiguration.SigningAlgorithm, + TemplateArn: aws.String(fmt.Sprintf("arn:%s:acm-pca:::template/SubordinateCACertificate_PathLen0/V1", Partition())), + Validity: &acmpca.Validity{ + Type: aws.String(acmpca.ValidityPeriodTypeYears), + Value: aws.Int64(3), + }, + }) + + if err != nil { + return fmt.Errorf("error issuing ACM PCA Certificate Authority (%s) Subordinate CA certificate from CSR: %w", arn, err) + } + + // Wait for certificate status to become ISSUED. + err = conn.WaitUntilCertificateIssued(&acmpca.GetCertificateInput{ + CertificateAuthorityArn: aws.String(rootCertificateAuthorityArn), + CertificateArn: issueCertOutput.CertificateArn, + }) + + if err != nil { + return fmt.Errorf("error waiting for ACM PCA Certificate Authority (%s) Subordinate CA certificate to become ISSUED: %w", arn, err) + } + + getCertOutput, err := conn.GetCertificate(&acmpca.GetCertificateInput{ + CertificateAuthorityArn: aws.String(rootCertificateAuthorityArn), + CertificateArn: issueCertOutput.CertificateArn, + }) + if err != nil { - return fmt.Errorf("error getting ACM PCA Certificate Authority (%s) issued Root CA certificate: %s", arn, err) + return fmt.Errorf("error getting ACM PCA Certificate Authority (%s) issued Subordinate CA certificate: %w", arn, err) } _, err = conn.ImportCertificateAuthorityCertificate(&acmpca.ImportCertificateAuthorityCertificateInput{ CertificateAuthorityArn: aws.String(arn), - Certificate: []byte(aws.StringValue(getCertResp.Certificate)), + Certificate: []byte(aws.StringValue(getCertOutput.Certificate)), + CertificateChain: []byte(aws.StringValue(getCertOutput.CertificateChain)), }) + if err != nil { - return fmt.Errorf("error importing ACM PCA Certificate Authority (%s) Root CA certificate: %s", arn, err) + return fmt.Errorf("error importing ACM PCA Certificate Authority (%s) Subordinate CA certificate: %w", arn, err) } return err @@ -1372,14 +1450,19 @@ func CheckACMPCACertificateAuthorityDisableCA(certificateAuthority *acmpca.Certi } } -func CheckACMPCACertificateAuthorityExists(resourceName string, certificateAuthority *acmpca.CertificateAuthority) resource.TestCheckFunc { +func CheckACMPCACertificateAuthorityExists(n string, certificateAuthority *acmpca.CertificateAuthority) resource.TestCheckFunc { return func(s *terraform.State) error { - rs, ok := s.RootModule().Resources[resourceName] + rs, ok := s.RootModule().Resources[n] if !ok { - return fmt.Errorf("Not found: %s", resourceName) + return fmt.Errorf("Not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No ACM PCA Certificate Authority ID is set") } conn := Provider.Meta().(*conns.AWSClient).ACMPCAConn + input := &acmpca.DescribeCertificateAuthorityInput{ CertificateAuthorityArn: aws.String(rs.Primary.ID), } @@ -1391,7 +1474,7 @@ func CheckACMPCACertificateAuthorityExists(resourceName string, certificateAutho } if output == nil || output.CertificateAuthority == nil { - return fmt.Errorf("ACM PCA Certificate Authority %q does not exist", rs.Primary.ID) + return fmt.Errorf("ACM PCA Certificate Authority %s does not exist", rs.Primary.ID) } *certificateAuthority = *output.CertificateAuthority diff --git a/internal/service/acmpca/certificate_authority_test.go b/internal/service/acmpca/certificate_authority_test.go index 365e4bc3372..3825ca421bf 100644 --- a/internal/service/acmpca/certificate_authority_test.go +++ b/internal/service/acmpca/certificate_authority_test.go @@ -106,7 +106,7 @@ func TestAccACMPCACertificateAuthority_enabled(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "type", acmpca.CertificateAuthorityTypeRoot), resource.TestCheckResourceAttr(resourceName, "enabled", "true"), resource.TestCheckResourceAttr(resourceName, "status", acmpca.CertificateAuthorityStatusPendingCertificate), - acctest.CheckACMPCACertificateAuthorityActivateCA(&certificateAuthority), + acctest.CheckACMPCACertificateAuthorityActivateRootCA(&certificateAuthority), ), }, { diff --git a/internal/service/appmesh/virtual_gateway_test.go b/internal/service/appmesh/virtual_gateway_test.go index 707cafc3d85..6f217fdb4c0 100644 --- a/internal/service/appmesh/virtual_gateway_test.go +++ b/internal/service/appmesh/virtual_gateway_test.go @@ -459,7 +459,7 @@ func testAccVirtualGateway_ListenerTLS(t *testing.T) { Config: testAccAppmeshVirtualGatewayConfigRootCA(domain), Check: resource.ComposeTestCheckFunc( acctest.CheckACMPCACertificateAuthorityExists(acmCAResourceName, &ca), - acctest.CheckACMPCACertificateAuthorityActivateCA(&ca), + acctest.CheckACMPCACertificateAuthorityActivateRootCA(&ca), ), }, { diff --git a/internal/service/appmesh/virtual_node_test.go b/internal/service/appmesh/virtual_node_test.go index 68220c2dc3a..b199b201a89 100644 --- a/internal/service/appmesh/virtual_node_test.go +++ b/internal/service/appmesh/virtual_node_test.go @@ -102,7 +102,7 @@ func testAccVirtualNode_backendClientPolicyACM(t *testing.T) { Config: testAccAppmeshVirtualNodeConfigRootCA(domain), Check: resource.ComposeTestCheckFunc( acctest.CheckACMPCACertificateAuthorityExists(acmCAResourceName, &ca), - acctest.CheckACMPCACertificateAuthorityActivateCA(&ca), + acctest.CheckACMPCACertificateAuthorityActivateRootCA(&ca), ), }, { @@ -940,7 +940,7 @@ func testAccVirtualNode_listenerTLS(t *testing.T) { Config: testAccAppmeshVirtualNodeConfigRootCA(domain), Check: resource.ComposeTestCheckFunc( acctest.CheckACMPCACertificateAuthorityExists(acmCAResourceName, &ca), - acctest.CheckACMPCACertificateAuthorityActivateCA(&ca), + acctest.CheckACMPCACertificateAuthorityActivateRootCA(&ca), ), }, { diff --git a/internal/service/ec2/customer_gateway.go b/internal/service/ec2/customer_gateway.go index 048770aec03..991ac1c5851 100644 --- a/internal/service/ec2/customer_gateway.go +++ b/internal/service/ec2/customer_gateway.go @@ -4,13 +4,11 @@ import ( "fmt" "log" "strconv" - "time" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/arn" "github.com/aws/aws-sdk-go/service/ec2" "github.com/hashicorp/aws-sdk-go-base/tfawserr" - "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" @@ -25,25 +23,34 @@ func ResourceCustomerGateway() *schema.Resource { Read: resourceCustomerGatewayRead, Update: resourceCustomerGatewayUpdate, Delete: resourceCustomerGatewayDelete, + Importer: &schema.ResourceImporter{ State: schema.ImportStatePassthrough, }, Schema: map[string]*schema.Schema{ + "arn": { + Type: schema.TypeString, + Computed: true, + }, "bgp_asn": { Type: schema.TypeString, Required: true, ForceNew: true, ValidateFunc: valid4ByteASN, }, - + "certificate_arn": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ValidateFunc: verify.ValidARN, + }, "device_name": { Type: schema.TypeString, Optional: true, ForceNew: true, ValidateFunc: validation.StringLenBetween(1, 255), }, - "ip_address": { Type: schema.TypeString, Required: true, @@ -53,22 +60,13 @@ func ResourceCustomerGateway() *schema.Resource { validation.IsIPv4Address, ), }, - - "type": { - Type: schema.TypeString, - Required: true, - ForceNew: true, - ValidateFunc: validation.StringInSlice([]string{ - ec2.GatewayTypeIpsec1, - }, false), - }, - "tags": tftags.TagsSchema(), "tags_all": tftags.TagsSchemaComputed(), - - "arn": { - Type: schema.TypeString, - Computed: true, + "type": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringInSlice(ec2.GatewayType_Values(), false), }, }, @@ -81,133 +79,41 @@ func resourceCustomerGatewayCreate(d *schema.ResourceData, meta interface{}) err defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig tags := defaultTagsConfig.MergeTags(tftags.New(d.Get("tags").(map[string]interface{}))) - ipAddress := d.Get("ip_address").(string) - vpnType := d.Get("type").(string) - bgpAsn := d.Get("bgp_asn").(string) - deviceName := d.Get("device_name").(string) + i64BgpAsn, err := strconv.ParseInt(d.Get("bgp_asn").(string), 10, 64) - alreadyExists, err := resourceCustomerGatewayExists(vpnType, ipAddress, bgpAsn, deviceName, conn) if err != nil { return err } - if alreadyExists { - return fmt.Errorf("An existing customer gateway for IpAddress: %s, VpnType: %s, BGP ASN: %s has been found", ipAddress, vpnType, bgpAsn) - } - - i64BgpAsn, err := strconv.ParseInt(bgpAsn, 10, 64) - if err != nil { - return err - } - - createOpts := &ec2.CreateCustomerGatewayInput{ + input := &ec2.CreateCustomerGatewayInput{ BgpAsn: aws.Int64(i64BgpAsn), - PublicIp: aws.String(ipAddress), - Type: aws.String(vpnType), + PublicIp: aws.String(d.Get("ip_address").(string)), TagSpecifications: ec2TagSpecificationsFromKeyValueTags(tags, ec2.ResourceTypeCustomerGateway), + Type: aws.String(d.Get("type").(string)), } - if len(deviceName) != 0 { - createOpts.DeviceName = aws.String(deviceName) - } - - // Create the Customer Gateway. - log.Printf("[DEBUG] Creating customer gateway") - resp, err := conn.CreateCustomerGateway(createOpts) - if err != nil { - return fmt.Errorf("Error creating customer gateway: %s", err) - } - - // Store the ID - customerGateway := resp.CustomerGateway - cgId := aws.StringValue(customerGateway.CustomerGatewayId) - d.SetId(cgId) - log.Printf("[INFO] Customer gateway ID: %s", cgId) - - // Wait for the CustomerGateway to be available. - stateConf := &resource.StateChangeConf{ - Pending: []string{"pending"}, - Target: []string{"available"}, - Refresh: customerGatewayRefreshFunc(conn, cgId), - Timeout: 10 * time.Minute, - Delay: 10 * time.Second, - MinTimeout: 3 * time.Second, + if v, ok := d.GetOk("certificate_arn"); ok { + input.CertificateArn = aws.String(v.(string)) } - _, stateErr := stateConf.WaitForState() - - if stateErr != nil { - return fmt.Errorf( - "Error waiting for customer gateway (%s) to become ready: %s", cgId, err) + if v, ok := d.GetOk("device_name"); ok { + input.DeviceName = aws.String(v.(string)) } - return resourceCustomerGatewayRead(d, meta) -} - -func customerGatewayRefreshFunc(conn *ec2.EC2, gatewayId string) resource.StateRefreshFunc { - return func() (interface{}, string, error) { - gatewayFilter := &ec2.Filter{ - Name: aws.String("customer-gateway-id"), - Values: []*string{aws.String(gatewayId)}, - } + log.Printf("[DEBUG] Creating EC2 Customer Gateway: %s", input) + output, err := conn.CreateCustomerGateway(input) - resp, err := conn.DescribeCustomerGateways(&ec2.DescribeCustomerGatewaysInput{ - Filters: []*ec2.Filter{gatewayFilter}, - }) - if err != nil { - if tfawserr.ErrMessageContains(err, "InvalidCustomerGatewayID.NotFound", "") { - resp = nil - } else { - log.Printf("Error on CustomerGatewayRefresh: %s", err) - return nil, "", err - } - } - - if resp == nil || len(resp.CustomerGateways) == 0 { - // handle consistency issues - return nil, "", nil - } - - gateway := resp.CustomerGateways[0] - return gateway, *gateway.State, nil - } -} - -func resourceCustomerGatewayExists(vpnType, ipAddress, bgpAsn, deviceName string, conn *ec2.EC2) (bool, error) { - filters := []*ec2.Filter{ - { - Name: aws.String("ip-address"), - Values: []*string{aws.String(ipAddress)}, - }, - { - Name: aws.String("type"), - Values: []*string{aws.String(vpnType)}, - }, - { - Name: aws.String("bgp-asn"), - Values: []*string{aws.String(bgpAsn)}, - }, - } - - if len(deviceName) != 0 { - filters = append(filters, &ec2.Filter{ - Name: aws.String("device-name"), - Values: []*string{aws.String(deviceName)}, - }) - } - - resp, err := conn.DescribeCustomerGateways(&ec2.DescribeCustomerGatewaysInput{ - Filters: filters, - }) if err != nil { - return false, err + return fmt.Errorf("error creating EC2 Customer Gateway: %w", err) } - if len(resp.CustomerGateways) > 0 && aws.StringValue(resp.CustomerGateways[0].State) != "deleted" { - return true, nil + d.SetId(aws.StringValue(output.CustomerGateway.CustomerGatewayId)) + + if _, err := WaitCustomerGatewayCreated(conn, d.Id()); err != nil { + return fmt.Errorf("error waiting for EC2 Customer Gateway (%s) create: %w", d.Id(), err) } - return false, nil + return resourceCustomerGatewayRead(d, meta) } func resourceCustomerGatewayRead(d *schema.ResourceData, meta interface{}) error { @@ -215,40 +121,31 @@ func resourceCustomerGatewayRead(d *schema.ResourceData, meta interface{}) error defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig ignoreTagsConfig := meta.(*conns.AWSClient).IgnoreTagsConfig - gatewayFilter := &ec2.Filter{ - Name: aws.String("customer-gateway-id"), - Values: []*string{aws.String(d.Id())}, - } - - resp, err := conn.DescribeCustomerGateways(&ec2.DescribeCustomerGatewaysInput{ - Filters: []*ec2.Filter{gatewayFilter}, - }) - if err != nil { - if tfawserr.ErrMessageContains(err, "InvalidCustomerGatewayID.NotFound", "") { - log.Printf("[WARN] Customer Gateway (%s) not found, removing from state", d.Id()) - d.SetId("") - return nil - } else { - log.Printf("[ERROR] Error finding CustomerGateway: %s", err) - return err - } - } - - if len(resp.CustomerGateways) != 1 { - return fmt.Errorf("Error finding CustomerGateway: %s", d.Id()) - } + customerGateway, err := FindCustomerGatewayByID(conn, d.Id()) - if aws.StringValue(resp.CustomerGateways[0].State) == "deleted" { - log.Printf("[INFO] Customer Gateway is in `deleted` state: %s", d.Id()) + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] EC2 Customer Gateway (%s) not found, removing from state", d.Id()) d.SetId("") return nil } - customerGateway := resp.CustomerGateways[0] + if err != nil { + return fmt.Errorf("error reading EC2 Customer Gateway (%s): %w", d.Id(), err) + } + + arn := arn.ARN{ + Partition: meta.(*conns.AWSClient).Partition, + Service: ec2.ServiceName, + Region: meta.(*conns.AWSClient).Region, + AccountID: meta.(*conns.AWSClient).AccountID, + Resource: fmt.Sprintf("customer-gateway/%s", d.Id()), + }.String() + d.Set("arn", arn) d.Set("bgp_asn", customerGateway.BgpAsn) + d.Set("certificate_arn", customerGateway.CertificateArn) + d.Set("device_name", customerGateway.DeviceName) d.Set("ip_address", customerGateway.IpAddress) d.Set("type", customerGateway.Type) - d.Set("device_name", customerGateway.DeviceName) tags := KeyValueTags(customerGateway.Tags).IgnoreAWS().IgnoreConfig(ignoreTagsConfig) @@ -261,16 +158,6 @@ func resourceCustomerGatewayRead(d *schema.ResourceData, meta interface{}) error return fmt.Errorf("error setting tags_all: %w", err) } - arn := arn.ARN{ - Partition: meta.(*conns.AWSClient).Partition, - Service: ec2.ServiceName, - Region: meta.(*conns.AWSClient).Region, - AccountID: meta.(*conns.AWSClient).AccountID, - Resource: fmt.Sprintf("customer-gateway/%s", d.Id()), - }.String() - - d.Set("arn", arn) - return nil } @@ -281,7 +168,7 @@ func resourceCustomerGatewayUpdate(d *schema.ResourceData, meta interface{}) err o, n := d.GetChange("tags_all") if err := UpdateTags(conn, d.Id(), o, n); err != nil { - return fmt.Errorf("error updating EC2 Customer Gateway (%s) tags: %s", d.Id(), err) + return fmt.Errorf("error updating EC2 Customer Gateway (%s) tags: %w", d.Id(), err) } } @@ -291,70 +178,22 @@ func resourceCustomerGatewayUpdate(d *schema.ResourceData, meta interface{}) err func resourceCustomerGatewayDelete(d *schema.ResourceData, meta interface{}) error { conn := meta.(*conns.AWSClient).EC2Conn + log.Printf("[INFO] Deleting EC2 Customer Gateway: %s", d.Id()) _, err := conn.DeleteCustomerGateway(&ec2.DeleteCustomerGatewayInput{ CustomerGatewayId: aws.String(d.Id()), }) - if err != nil { - if tfawserr.ErrMessageContains(err, "InvalidCustomerGatewayID.NotFound", "") { - return nil - } else { - return fmt.Errorf("[ERROR] Error deleting CustomerGateway: %s", err) - } - } - gatewayFilter := &ec2.Filter{ - Name: aws.String("customer-gateway-id"), - Values: []*string{aws.String(d.Id())}, - } - - input := &ec2.DescribeCustomerGatewaysInput{ - Filters: []*ec2.Filter{gatewayFilter}, - } - err = resource.Retry(5*time.Minute, func() *resource.RetryError { - resp, err := conn.DescribeCustomerGateways(input) - - if err != nil { - if tfawserr.ErrMessageContains(err, "InvalidCustomerGatewayID.NotFound", "") { - return nil - } - return resource.NonRetryableError(err) - } - - err = checkGatewayDeleteResponse(resp, d.Id()) - if err != nil { - return resource.RetryableError(err) - } + if tfawserr.ErrCodeEquals(err, ErrCodeInvalidCustomerGatewayIDNotFound) { return nil - }) - - if tfresource.TimedOut(err) { - var resp *ec2.DescribeCustomerGatewaysOutput - resp, err = conn.DescribeCustomerGateways(input) - - if err != nil { - return checkGatewayDeleteResponse(resp, d.Id()) - } } if err != nil { - return fmt.Errorf("Error deleting customer gateway: %s", err) + return fmt.Errorf("error deleting EC2 Customer Gateway (%s): %w", d.Id(), err) } - return nil - -} -func checkGatewayDeleteResponse(resp *ec2.DescribeCustomerGatewaysOutput, id string) error { - if len(resp.CustomerGateways) != 1 { - return fmt.Errorf("Error finding CustomerGateway for delete: %s", id) + if _, err := WaitCustomerGatewayDeleted(conn, d.Id()); err != nil { + return fmt.Errorf("error waiting for EC2 Customer Gateway (%s) delete: %w", d.Id(), err) } - cgState := aws.StringValue(resp.CustomerGateways[0].State) - switch cgState { - case "pending", "available", "deleting": - return fmt.Errorf("Gateway (%s) in state (%s), retrying", id, cgState) - case "deleted": - return nil - default: - return fmt.Errorf("Unrecognized state (%s) for Customer Gateway delete on (%s)", *resp.CustomerGateways[0].State, id) - } + return nil } diff --git a/internal/service/ec2/customer_gateway_data_source.go b/internal/service/ec2/customer_gateway_data_source.go index 6a043734078..7ddc26ca711 100644 --- a/internal/service/ec2/customer_gateway_data_source.go +++ b/internal/service/ec2/customer_gateway_data_source.go @@ -1,9 +1,7 @@ package ec2 import ( - "errors" "fmt" - "log" "strconv" "github.com/aws/aws-sdk-go/aws" @@ -12,36 +10,42 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "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" ) func DataSourceCustomerGateway() *schema.Resource { return &schema.Resource{ Read: dataSourceCustomerGatewayRead, + Schema: map[string]*schema.Schema{ - "filter": DataSourceFiltersSchema(), - "id": { + "arn": { Type: schema.TypeString, - Optional: true, Computed: true, }, "bgp_asn": { Type: schema.TypeInt, Computed: true, }, + "certificate_arn": { + Type: schema.TypeString, + Computed: true, + }, "device_name": { Type: schema.TypeString, Computed: true, }, - "ip_address": { + "filter": DataSourceFiltersSchema(), + "id": { Type: schema.TypeString, + Optional: true, Computed: true, }, - "tags": tftags.TagsSchemaComputed(), - "type": { + "ip_address": { Type: schema.TypeString, Computed: true, }, - "arn": { + "tags": tftags.TagsSchemaComputed(), + "type": { Type: schema.TypeString, Computed: true, }, @@ -53,7 +57,7 @@ func dataSourceCustomerGatewayRead(d *schema.ResourceData, meta interface{}) err conn := meta.(*conns.AWSClient).EC2Conn ignoreTagsConfig := meta.(*conns.AWSClient).IgnoreTagsConfig - input := ec2.DescribeCustomerGatewaysInput{} + input := &ec2.DescribeCustomerGatewaysInput{} if v, ok := d.GetOk("filter"); ok { input.Filters = BuildFiltersDataSource(v.(*schema.Set)) @@ -63,43 +67,13 @@ func dataSourceCustomerGatewayRead(d *schema.ResourceData, meta interface{}) err input.CustomerGatewayIds = []*string{aws.String(v.(string))} } - log.Printf("[DEBUG] Reading EC2 Customer Gateways: %s", input) - output, err := conn.DescribeCustomerGateways(&input) + cgw, err := FindCustomerGateway(conn, input) if err != nil { - return fmt.Errorf("error reading EC2 Customer Gateways: %w", err) + return tfresource.SingularDataSourceFindError("EC2 Customer Gateway", err) } - if output == nil || len(output.CustomerGateways) == 0 { - return errors.New("error reading EC2 Customer Gateways: no results found") - } - - if len(output.CustomerGateways) > 1 { - return errors.New("error reading EC2 Customer Gateways: multiple results found, try adjusting search criteria") - } - - cg := output.CustomerGateways[0] - if cg == nil { - return errors.New("error reading EC2 Customer Gateway: empty result") - } - - d.Set("ip_address", cg.IpAddress) - d.Set("type", cg.Type) - d.Set("device_name", cg.DeviceName) - d.SetId(aws.StringValue(cg.CustomerGatewayId)) - - if v := aws.StringValue(cg.BgpAsn); v != "" { - asn, err := strconv.ParseInt(v, 0, 0) - if err != nil { - return fmt.Errorf("error parsing BGP ASN %q: %w", v, err) - } - - d.Set("bgp_asn", int(asn)) - } - - if err := d.Set("tags", KeyValueTags(cg.Tags).IgnoreAWS().IgnoreConfig(ignoreTagsConfig).Map()); err != nil { - return fmt.Errorf("error setting tags for EC2 Customer Gateway %q: %w", aws.StringValue(cg.CustomerGatewayId), err) - } + d.SetId(aws.StringValue(cgw.CustomerGatewayId)) arn := arn.ARN{ Partition: meta.(*conns.AWSClient).Partition, @@ -108,8 +82,26 @@ func dataSourceCustomerGatewayRead(d *schema.ResourceData, meta interface{}) err AccountID: meta.(*conns.AWSClient).AccountID, Resource: fmt.Sprintf("customer-gateway/%s", d.Id()), }.String() - d.Set("arn", arn) + if v := aws.StringValue(cgw.BgpAsn); v != "" { + v, err := strconv.ParseInt(v, 0, 0) + + if err != nil { + return err + } + + d.Set("bgp_asn", v) + } else { + d.Set("bgp_asn", nil) + } + d.Set("certificate_arn", cgw.CertificateArn) + d.Set("device_name", cgw.DeviceName) + d.Set("ip_address", cgw.IpAddress) + d.Set("type", cgw.Type) + + if err := d.Set("tags", KeyValueTags(cgw.Tags).IgnoreAWS().IgnoreConfig(ignoreTagsConfig).Map()); err != nil { + return fmt.Errorf("error setting tags: %w", err) + } return nil } diff --git a/internal/service/ec2/customer_gateway_data_source_test.go b/internal/service/ec2/customer_gateway_data_source_test.go index ea98c229a3e..92a56ca63c3 100644 --- a/internal/service/ec2/customer_gateway_data_source_test.go +++ b/internal/service/ec2/customer_gateway_data_source_test.go @@ -13,7 +13,7 @@ import ( func TestAccEC2CustomerGatewayDataSource_filter(t *testing.T) { dataSourceName := "data.aws_customer_gateway.test" resourceName := "aws_customer_gateway.test" - + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) asn := sdkacctest.RandIntRange(64512, 65534) hostOctet := sdkacctest.RandIntRange(1, 254) @@ -24,13 +24,15 @@ func TestAccEC2CustomerGatewayDataSource_filter(t *testing.T) { CheckDestroy: testAccCheckCustomerGatewayDestroy, Steps: []resource.TestStep{ { - Config: testAccCustomerGatewayFilterDataSourceConfig(asn, hostOctet), + Config: testAccCustomerGatewayFilterDataSourceConfig(rName, asn, hostOctet), Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrPair(resourceName, "arn", dataSourceName, "arn"), resource.TestCheckResourceAttrPair(resourceName, "bgp_asn", dataSourceName, "bgp_asn"), + resource.TestCheckResourceAttrPair(resourceName, "certificate_arn", dataSourceName, "certificate_arn"), + resource.TestCheckResourceAttrPair(resourceName, "device_name", dataSourceName, "device_name"), resource.TestCheckResourceAttrPair(resourceName, "ip_address", dataSourceName, "ip_address"), resource.TestCheckResourceAttrPair(resourceName, "tags.%", dataSourceName, "tags.%"), resource.TestCheckResourceAttrPair(resourceName, "type", dataSourceName, "type"), - resource.TestCheckResourceAttrPair(resourceName, "arn", dataSourceName, "arn"), ), }, }, @@ -40,7 +42,7 @@ func TestAccEC2CustomerGatewayDataSource_filter(t *testing.T) { func TestAccEC2CustomerGatewayDataSource_id(t *testing.T) { dataSourceName := "data.aws_customer_gateway.test" resourceName := "aws_customer_gateway.test" - + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) asn := sdkacctest.RandIntRange(64512, 65534) hostOctet := sdkacctest.RandIntRange(1, 254) @@ -51,29 +53,30 @@ func TestAccEC2CustomerGatewayDataSource_id(t *testing.T) { CheckDestroy: testAccCheckCustomerGatewayDestroy, Steps: []resource.TestStep{ { - Config: testAccCustomerGatewayIDDataSourceConfig(asn, hostOctet), + Config: testAccCustomerGatewayIDDataSourceConfig(rName, asn, hostOctet), Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrPair(resourceName, "arn", dataSourceName, "arn"), resource.TestCheckResourceAttrPair(resourceName, "bgp_asn", dataSourceName, "bgp_asn"), + resource.TestCheckResourceAttrPair(resourceName, "certificate_arn", dataSourceName, "certificate_arn"), + resource.TestCheckResourceAttrPair(resourceName, "device_name", dataSourceName, "device_name"), resource.TestCheckResourceAttrPair(resourceName, "ip_address", dataSourceName, "ip_address"), resource.TestCheckResourceAttrPair(resourceName, "tags.%", dataSourceName, "tags.%"), resource.TestCheckResourceAttrPair(resourceName, "type", dataSourceName, "type"), - resource.TestCheckResourceAttrPair(resourceName, "device_name", dataSourceName, "device_name"), ), }, }, }) } -func testAccCustomerGatewayFilterDataSourceConfig(asn, hostOctet int) string { - name := sdkacctest.RandomWithPrefix("test-filter") +func testAccCustomerGatewayFilterDataSourceConfig(rName string, asn, hostOctet int) string { return fmt.Sprintf(` resource "aws_customer_gateway" "test" { - bgp_asn = %d - ip_address = "50.0.0.%d" + bgp_asn = %[2]d + ip_address = "50.0.0.%[3]d" type = "ipsec.1" tags = { - Name = "%s" + Name = %[1]q } } @@ -83,20 +86,24 @@ data "aws_customer_gateway" "test" { values = [aws_customer_gateway.test.tags.Name] } } -`, asn, hostOctet, name) +`, rName, asn, hostOctet) } -func testAccCustomerGatewayIDDataSourceConfig(asn, hostOctet int) string { +func testAccCustomerGatewayIDDataSourceConfig(rName string, asn, hostOctet int) string { return fmt.Sprintf(` resource "aws_customer_gateway" "test" { - bgp_asn = %d - ip_address = "50.0.0.%d" + bgp_asn = %[2]d + ip_address = "50.0.0.%[3]d" device_name = "test" type = "ipsec.1" + + tags = { + Name = %[1]q + } } data "aws_customer_gateway" "test" { id = aws_customer_gateway.test.id } -`, asn, hostOctet) +`, rName, asn, hostOctet) } diff --git a/internal/service/ec2/customer_gateway_test.go b/internal/service/ec2/customer_gateway_test.go index 9f31eba4e71..d394ffb80ba 100644 --- a/internal/service/ec2/customer_gateway_test.go +++ b/internal/service/ec2/customer_gateway_test.go @@ -6,15 +6,15 @@ import ( "strconv" "testing" - "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/acmpca" "github.com/aws/aws-sdk-go/service/ec2" - "github.com/hashicorp/aws-sdk-go-base/tfawserr" 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" tfec2 "github.com/hashicorp/terraform-provider-aws/internal/service/ec2" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) func TestAccEC2CustomerGateway_basic(t *testing.T) { @@ -31,10 +31,13 @@ func TestAccEC2CustomerGateway_basic(t *testing.T) { { Config: testAccCustomerGatewayConfig(rBgpAsn), Check: resource.ComposeTestCheckFunc( - testAccCheckCustomerGateway(resourceName, &gateway), + testAccCheckCustomerGatewayExists(resourceName, &gateway), acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "ec2", regexp.MustCompile(`customer-gateway/cgw-.+`)), resource.TestCheckResourceAttr(resourceName, "bgp_asn", strconv.Itoa(rBgpAsn)), + resource.TestCheckResourceAttr(resourceName, "certificate_arn", ""), + resource.TestCheckResourceAttr(resourceName, "device_name", ""), resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), + resource.TestCheckResourceAttr(resourceName, "type", "ipsec.1"), ), }, { @@ -42,11 +45,28 @@ func TestAccEC2CustomerGateway_basic(t *testing.T) { ImportState: true, ImportStateVerify: true, }, + }, + }) +} + +func TestAccEC2CustomerGateway_disappears(t *testing.T) { + var gateway ec2.CustomerGateway + rBgpAsn := sdkacctest.RandIntRange(64512, 65534) + resourceName := "aws_customer_gateway.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckCustomerGatewayDestroy, + Steps: []resource.TestStep{ { - Config: testAccCustomerGatewayConfigForceReplace(rBgpAsn), + Config: testAccCustomerGatewayConfig(rBgpAsn), Check: resource.ComposeTestCheckFunc( - testAccCheckCustomerGateway(resourceName, &gateway), + testAccCheckCustomerGatewayExists(resourceName, &gateway), + acctest.CheckResourceDisappears(acctest.Provider, tfec2.ResourceCustomerGateway(), resourceName), ), + ExpectNonEmptyPlan: true, }, }, }) @@ -66,7 +86,7 @@ func TestAccEC2CustomerGateway_tags(t *testing.T) { { Config: testAccCustomerGatewayConfigTags1(rBgpAsn, "key1", "value1"), Check: resource.ComposeTestCheckFunc( - testAccCheckCustomerGateway(resourceName, &gateway), + testAccCheckCustomerGatewayExists(resourceName, &gateway), resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1")), }, @@ -78,7 +98,7 @@ func TestAccEC2CustomerGateway_tags(t *testing.T) { { Config: testAccCustomerGatewayConfigTags2(rBgpAsn, "key1", "value1updated", "key2", "value2"), Check: resource.ComposeTestCheckFunc( - testAccCheckCustomerGateway(resourceName, &gateway), + testAccCheckCustomerGatewayExists(resourceName, &gateway), resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1updated"), resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2")), @@ -86,7 +106,7 @@ func TestAccEC2CustomerGateway_tags(t *testing.T) { { Config: testAccCustomerGatewayConfigTags1(rBgpAsn, "key2", "value2"), Check: resource.ComposeTestCheckFunc( - testAccCheckCustomerGateway(resourceName, &gateway), + testAccCheckCustomerGatewayExists(resourceName, &gateway), resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2")), }, @@ -94,9 +114,10 @@ func TestAccEC2CustomerGateway_tags(t *testing.T) { }) } -func TestAccEC2CustomerGateway_similarAlreadyExists(t *testing.T) { +func TestAccEC2CustomerGateway_deviceName(t *testing.T) { var gateway ec2.CustomerGateway rBgpAsn := sdkacctest.RandIntRange(64512, 65534) + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_customer_gateway.test" resource.ParallelTest(t, resource.TestCase{ @@ -106,9 +127,10 @@ func TestAccEC2CustomerGateway_similarAlreadyExists(t *testing.T) { CheckDestroy: testAccCheckCustomerGatewayDestroy, Steps: []resource.TestStep{ { - Config: testAccCustomerGatewayConfig(rBgpAsn), + Config: testAccCustomerGatewayConfigDeviceName(rName, rBgpAsn), Check: resource.ComposeTestCheckFunc( - testAccCheckCustomerGateway(resourceName, &gateway), + testAccCheckCustomerGatewayExists(resourceName, &gateway), + resource.TestCheckResourceAttr(resourceName, "device_name", "test"), ), }, { @@ -116,17 +138,14 @@ func TestAccEC2CustomerGateway_similarAlreadyExists(t *testing.T) { ImportState: true, ImportStateVerify: true, }, - { - Config: testAccCustomerGatewayConfigIdentical(rBgpAsn), - ExpectError: regexp.MustCompile("An existing customer gateway"), - }, }, }) } -func TestAccEC2CustomerGateway_deviceName(t *testing.T) { +func TestAccEC2CustomerGateway_4ByteASN(t *testing.T) { var gateway ec2.CustomerGateway - rBgpAsn := sdkacctest.RandIntRange(64512, 65534) + rBgpAsn := strconv.FormatInt(int64(sdkacctest.RandIntRange(64512, 65534))*10000, 10) + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_customer_gateway.test" resource.ParallelTest(t, resource.TestCase{ @@ -136,13 +155,10 @@ func TestAccEC2CustomerGateway_deviceName(t *testing.T) { CheckDestroy: testAccCheckCustomerGatewayDestroy, Steps: []resource.TestStep{ { - Config: testAccCustomerGatewayConfigDeviceName(rBgpAsn), + Config: testAccCustomerGatewayConfig4ByteAsn(rName, rBgpAsn), Check: resource.ComposeTestCheckFunc( - testAccCheckCustomerGateway(resourceName, &gateway), - acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "ec2", regexp.MustCompile(`customer-gateway/cgw-.+`)), - resource.TestCheckResourceAttr(resourceName, "bgp_asn", strconv.Itoa(rBgpAsn)), - resource.TestCheckResourceAttr(resourceName, "device_name", "test"), - resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), + testAccCheckCustomerGatewayExists(resourceName, &gateway), + resource.TestCheckResourceAttr(resourceName, "bgp_asn", rBgpAsn), ), }, { @@ -154,10 +170,17 @@ func TestAccEC2CustomerGateway_deviceName(t *testing.T) { }) } -func TestAccEC2CustomerGateway_disappears(t *testing.T) { - rBgpAsn := sdkacctest.RandIntRange(64512, 65534) +func TestAccEC2CustomerGateway_certificate(t *testing.T) { var gateway ec2.CustomerGateway + var caRoot acmpca.CertificateAuthority + var caSubordinate acmpca.CertificateAuthority + rBgpAsn := sdkacctest.RandIntRange(64512, 65534) + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_customer_gateway.test" + acmRootCAResourceName := "aws_acmpca_certificate_authority.root" + acmSubordinateCAResourceName := "aws_acmpca_certificate_authority.test" + acmCertificateResourceName := "aws_acm_certificate.test" + domain := acctest.RandomDomainName() resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -165,36 +188,21 @@ func TestAccEC2CustomerGateway_disappears(t *testing.T) { Providers: acctest.Providers, CheckDestroy: testAccCheckCustomerGatewayDestroy, Steps: []resource.TestStep{ + // We need to create and activate the CAs before issuing a certificate. { - Config: testAccCustomerGatewayConfig(rBgpAsn), + Config: testAccCustomerGatewayConfigCAs(domain), Check: resource.ComposeTestCheckFunc( - testAccCheckCustomerGateway(resourceName, &gateway), - acctest.CheckResourceDisappears(acctest.Provider, tfec2.ResourceCustomerGateway(), resourceName), + acctest.CheckACMPCACertificateAuthorityExists(acmRootCAResourceName, &caRoot), + acctest.CheckACMPCACertificateAuthorityExists(acmSubordinateCAResourceName, &caSubordinate), + acctest.CheckACMPCACertificateAuthorityActivateRootCA(&caRoot), + acctest.CheckACMPCACertificateAuthorityActivateSubordinateCA(&caRoot, &caSubordinate), ), - ExpectNonEmptyPlan: true, }, - }, - }) -} - -func TestAccEC2CustomerGateway_4ByteASN(t *testing.T) { - var gateway ec2.CustomerGateway - rBgpAsn := strconv.FormatInt(int64(sdkacctest.RandIntRange(64512, 65534))*10000, 10) - resourceName := "aws_customer_gateway.test" - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(t) }, - ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), - Providers: acctest.Providers, - CheckDestroy: testAccCheckCustomerGatewayDestroy, - Steps: []resource.TestStep{ { - Config: testAccCustomerGatewayConfig4ByteAsn(rBgpAsn), + Config: testAccCustomerGatewayConfigCertificate(rName, rBgpAsn, domain), Check: resource.ComposeTestCheckFunc( - testAccCheckCustomerGateway(resourceName, &gateway), - acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "ec2", regexp.MustCompile(`customer-gateway/cgw-.+`)), - resource.TestCheckResourceAttr(resourceName, "bgp_asn", rBgpAsn), - resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), + testAccCheckCustomerGatewayExists(resourceName, &gateway), + resource.TestCheckResourceAttrPair(resourceName, "certificate_arn", acmCertificateResourceName, "arn"), ), }, { @@ -202,6 +210,15 @@ func TestAccEC2CustomerGateway_4ByteASN(t *testing.T) { ImportState: true, ImportStateVerify: true, }, + { + Config: testAccCustomerGatewayConfigCertificate(rName, rBgpAsn, domain), + Check: resource.ComposeTestCheckFunc( + // CAs must be DISABLED for deletion. + acctest.CheckACMPCACertificateAuthorityDisableCA(&caSubordinate), + acctest.CheckACMPCACertificateAuthorityDisableCA(&caRoot), + ), + ExpectNonEmptyPlan: true, + }, }, }) } @@ -210,71 +227,46 @@ func testAccCheckCustomerGatewayDestroy(s *terraform.State) error { conn := acctest.Provider.Meta().(*conns.AWSClient).EC2Conn for _, rs := range s.RootModule().Resources { - if rs.Type != "aws_customer_gatewah" { + if rs.Type != "aws_customer_gateway" { continue } - gatewayFilter := &ec2.Filter{ - Name: aws.String("customer-gateway-id"), - Values: []*string{aws.String(rs.Primary.ID)}, - } - - resp, err := conn.DescribeCustomerGateways(&ec2.DescribeCustomerGatewaysInput{ - Filters: []*ec2.Filter{gatewayFilter}, - }) + _, err := tfec2.FindCustomerGatewayByID(conn, rs.Primary.ID) - if tfawserr.ErrMessageContains(err, "InvalidCustomerGatewayID.NotFound", "") { + if tfresource.NotFound(err) { continue } - if err == nil { - if len(resp.CustomerGateways) > 0 { - return fmt.Errorf("Customer gateway still exists: %v", resp.CustomerGateways) - } - - if aws.StringValue(resp.CustomerGateways[0].State) == "deleted" { - continue - } + if err != nil { + return err } - return err + return fmt.Errorf("EC2 Customer Gateway %s still exists", rs.Primary.ID) } return nil } -func testAccCheckCustomerGateway(gatewayResource string, cgw *ec2.CustomerGateway) resource.TestCheckFunc { +func testAccCheckCustomerGatewayExists(n string, v *ec2.CustomerGateway) resource.TestCheckFunc { return func(s *terraform.State) error { - rs, ok := s.RootModule().Resources[gatewayResource] + rs, ok := s.RootModule().Resources[n] if !ok { - return fmt.Errorf("Not found: %s", gatewayResource) + return fmt.Errorf("Not found: %s", n) } if rs.Primary.ID == "" { - return fmt.Errorf("No ID is set") - } - - gateway, ok := s.RootModule().Resources[gatewayResource] - if !ok { - return fmt.Errorf("Not found: %s", gatewayResource) + return fmt.Errorf("No EC2 Customer Gateway ID is set") } conn := acctest.Provider.Meta().(*conns.AWSClient).EC2Conn - gatewayFilter := &ec2.Filter{ - Name: aws.String("customer-gateway-id"), - Values: []*string{aws.String(gateway.Primary.ID)}, - } - resp, err := conn.DescribeCustomerGateways(&ec2.DescribeCustomerGatewaysInput{ - Filters: []*ec2.Filter{gatewayFilter}, - }) + output, err := tfec2.FindCustomerGatewayByID(conn, rs.Primary.ID) if err != nil { return err } - respGateway := resp.CustomerGateways[0] - *cgw = *respGateway + *v = *output return nil } @@ -283,7 +275,7 @@ func testAccCheckCustomerGateway(gatewayResource string, cgw *ec2.CustomerGatewa func testAccCustomerGatewayConfig(rBgpAsn int) string { return fmt.Sprintf(` resource "aws_customer_gateway" "test" { - bgp_asn = %d + bgp_asn = %[1]d ip_address = "172.0.0.1" type = "ipsec.1" } @@ -319,50 +311,85 @@ resource "aws_customer_gateway" "test" { `, rBgpAsn, tagKey1, tagValue1, tagKey2, tagValue2) } -func testAccCustomerGatewayConfigIdentical(rBgpAsn int) string { - return fmt.Sprintf(` -resource "aws_customer_gateway" "test" { - bgp_asn = %[1]d - ip_address = "172.0.0.1" - type = "ipsec.1" -} - -resource "aws_customer_gateway" "identical" { - bgp_asn = %[1]d - ip_address = "172.0.0.1" - type = "ipsec.1" -} -`, rBgpAsn) -} - -func testAccCustomerGatewayConfigDeviceName(rBgpAsn int) string { +func testAccCustomerGatewayConfigDeviceName(rName string, rBgpAsn int) string { return fmt.Sprintf(` resource "aws_customer_gateway" "test" { - bgp_asn = %[1]d + bgp_asn = %[2]d ip_address = "172.0.0.1" type = "ipsec.1" device_name = "test" + + tags = { + Name = %[1]q + } } -`, rBgpAsn) +`, rName, rBgpAsn) } -// Change the ip_address. -func testAccCustomerGatewayConfigForceReplace(rBgpAsn int) string { +func testAccCustomerGatewayConfig4ByteAsn(rName, rBgpAsn string) string { return fmt.Sprintf(` resource "aws_customer_gateway" "test" { - bgp_asn = %d - ip_address = "172.10.10.1" + bgp_asn = %[2]q + ip_address = "172.0.0.1" type = "ipsec.1" + + tags = { + Name = %[1]q + } } -`, rBgpAsn) +`, rName, rBgpAsn) } -func testAccCustomerGatewayConfig4ByteAsn(rBgpAsn string) string { +func testAccCustomerGatewayConfigCAs(domain string) string { return fmt.Sprintf(` +resource "aws_acmpca_certificate_authority" "root" { + permanent_deletion_time_in_days = 7 + type = "ROOT" + + certificate_authority_configuration { + key_algorithm = "RSA_4096" + signing_algorithm = "SHA512WITHRSA" + + subject { + common_name = %[1]q + } + } +} + +resource "aws_acmpca_certificate_authority" "test" { + permanent_deletion_time_in_days = 7 + type = "SUBORDINATE" + + certificate_authority_configuration { + key_algorithm = "RSA_4096" + signing_algorithm = "SHA512WITHRSA" + + subject { + common_name = "sub.%[1]s" + } + } +} +`, domain) +} + +func testAccCustomerGatewayConfigCertificate(rName string, rBgpAsn int, domain string) string { + return acctest.ConfigCompose( + testAccCustomerGatewayConfigCAs(domain), + fmt.Sprintf(` +resource "aws_acm_certificate" "test" { + domain_name = "test.sub.%[3]s" + certificate_authority_arn = aws_acmpca_certificate_authority.test.arn +} + resource "aws_customer_gateway" "test" { - bgp_asn = %[1]q - ip_address = "172.0.0.1" - type = "ipsec.1" + bgp_asn = %[2]d + ip_address = "172.0.0.1" + type = "ipsec.1" + certificate_arn = aws_acm_certificate.test.arn + + tags = { + Name = %[1]q + } } -`, rBgpAsn) +`, rName, rBgpAsn, domain)) } diff --git a/internal/service/ec2/enum.go b/internal/service/ec2/enum.go index cbb3b628776..467cc082421 100644 --- a/internal/service/ec2/enum.go +++ b/internal/service/ec2/enum.go @@ -45,3 +45,11 @@ const ( const ( InternetGatewayAttachmentStateAvailable = "available" ) + +// See https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_CustomerGateway.html#API_CustomerGateway_Contents. +const ( + CustomerGatewayStateAvailable = "available" + CustomerGatewayStateDeleted = "deleted" + CustomerGatewayStateDeleting = "deleting" + CustomerGatewayStatePending = "pending" +) diff --git a/internal/service/ec2/errors.go b/internal/service/ec2/errors.go index 1939fafd3bd..c22a8dca0f6 100644 --- a/internal/service/ec2/errors.go +++ b/internal/service/ec2/errors.go @@ -17,9 +17,11 @@ const ( ErrCodeClientVpnRouteNotFound = "InvalidClientVpnRouteNotFound" ErrCodeDependencyViolation = "DependencyViolation" ErrCodeGatewayNotAttached = "Gateway.NotAttached" + ErrCodeIncorrectState = "IncorrectState" ErrCodeInvalidAssociationIDNotFound = "InvalidAssociationID.NotFound" ErrCodeInvalidAttachmentIDNotFound = "InvalidAttachmentID.NotFound" ErrCodeInvalidCarrierGatewayIDNotFound = "InvalidCarrierGatewayID.NotFound" + ErrCodeInvalidCustomerGatewayIDNotFound = "InvalidCustomerGatewayID.NotFound" ErrCodeInvalidFlowLogIdNotFound = "InvalidFlowLogId.NotFound" ErrCodeInvalidGroupNotFound = "InvalidGroup.NotFound" ErrCodeInvalidHostIDNotFound = "InvalidHostID.NotFound" diff --git a/internal/service/ec2/find.go b/internal/service/ec2/find.go index 8743381aba7..e19450dc96a 100644 --- a/internal/service/ec2/find.go +++ b/internal/service/ec2/find.go @@ -1086,46 +1086,135 @@ func FindVPNGatewayRoutePropagationExists(conn *ec2.EC2, routeTableID, gatewayID } } -// FindVPNGatewayVPCAttachment returns the attachment between the specified VPN gateway and VPC. -// Returns nil and potentially an error if no attachment is found. func FindVPNGatewayVPCAttachment(conn *ec2.EC2, vpnGatewayID, vpcID string) (*ec2.VpcAttachment, error) { vpnGateway, err := FindVPNGatewayByID(conn, vpnGatewayID) + if err != nil { return nil, err } - if vpnGateway == nil { - return nil, nil - } - for _, vpcAttachment := range vpnGateway.VpcAttachments { if aws.StringValue(vpcAttachment.VpcId) == vpcID { + if state := aws.StringValue(vpcAttachment.State); state == ec2.AttachmentStatusDetached { + return nil, &resource.NotFoundError{ + Message: state, + LastRequest: vpcID, + } + } + return vpcAttachment, nil } } - return nil, nil + return nil, tfresource.NewEmptyResultError(vpcID) } -// FindVPNGatewayByID returns the VPN gateway corresponding to the specified identifier. -// Returns nil and potentially an error if no VPN gateway is found. func FindVPNGatewayByID(conn *ec2.EC2, id string) (*ec2.VpnGateway, error) { input := &ec2.DescribeVpnGatewaysInput{ VpnGatewayIds: aws.StringSlice([]string{id}), } + output, err := FindVPNGateway(conn, input) + + if err != nil { + return nil, err + } + + if state := aws.StringValue(output.State); state == ec2.VpnStateDeleted { + return nil, &resource.NotFoundError{ + Message: state, + LastRequest: input, + } + } + + // Eventual consistency check. + if aws.StringValue(output.VpnGatewayId) != id { + return nil, &resource.NotFoundError{ + LastRequest: input, + } + } + + return output, nil +} + +func FindVPNGateway(conn *ec2.EC2, input *ec2.DescribeVpnGatewaysInput) (*ec2.VpnGateway, error) { output, err := conn.DescribeVpnGateways(input) + + if tfawserr.ErrCodeEquals(err, ErrCodeInvalidVpnGatewayIDNotFound) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + if err != nil { return nil, err } - if output == nil || len(output.VpnGateways) == 0 { - return nil, nil + if output == nil || len(output.VpnGateways) == 0 || output.VpnGateways[0] == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + if count := len(output.VpnGateways); count > 1 { + return nil, tfresource.NewTooManyResultsError(count, input) } return output.VpnGateways[0], nil } +func FindCustomerGatewayByID(conn *ec2.EC2, id string) (*ec2.CustomerGateway, error) { + input := &ec2.DescribeCustomerGatewaysInput{ + CustomerGatewayIds: aws.StringSlice([]string{id}), + } + + output, err := FindCustomerGateway(conn, input) + + if err != nil { + return nil, err + } + + if state := aws.StringValue(output.State); state == CustomerGatewayStateDeleted { + return nil, &resource.NotFoundError{ + Message: state, + LastRequest: input, + } + } + + // Eventual consistency check. + if aws.StringValue(output.CustomerGatewayId) != id { + return nil, &resource.NotFoundError{ + LastRequest: input, + } + } + + return output, nil +} + +func FindCustomerGateway(conn *ec2.EC2, input *ec2.DescribeCustomerGatewaysInput) (*ec2.CustomerGateway, error) { + output, err := conn.DescribeCustomerGateways(input) + + if tfawserr.ErrCodeEquals(err, ErrCodeInvalidCustomerGatewayIDNotFound) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + if output == nil || len(output.CustomerGateways) == 0 || output.CustomerGateways[0] == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + if count := len(output.CustomerGateways); count > 1 { + return nil, tfresource.NewTooManyResultsError(count, input) + } + + return output.CustomerGateways[0], nil +} + func FindFlowLogByID(conn *ec2.EC2, id string) (*ec2.FlowLog, error) { input := &ec2.DescribeFlowLogsInput{ FlowLogIds: aws.StringSlice([]string{id}), diff --git a/internal/service/ec2/status.go b/internal/service/ec2/status.go index e6f7ddf74f4..5b30ccac1d3 100644 --- a/internal/service/ec2/status.go +++ b/internal/service/ec2/status.go @@ -525,27 +525,35 @@ func StatusVPCPeeringConnection(conn *ec2.EC2, vpcPeeringConnectionID string) re } } -const ( - attachmentStateNotFound = "NotFound" - attachmentStateUnknown = "Unknown" -) - -// StatusVPNGatewayVPCAttachmentState fetches the attachment between the specified VPN gateway and VPC and its state func StatusVPNGatewayVPCAttachmentState(conn *ec2.EC2, vpnGatewayID, vpcID string) resource.StateRefreshFunc { return func() (interface{}, string, error) { - vpcAttachment, err := FindVPNGatewayVPCAttachment(conn, vpnGatewayID, vpcID) - if tfawserr.ErrCodeEquals(err, ErrCodeInvalidVpnGatewayIDNotFound) { - return nil, attachmentStateNotFound, nil + output, err := FindVPNGatewayVPCAttachment(conn, vpnGatewayID, vpcID) + + if tfresource.NotFound(err) { + return nil, "", nil } + if err != nil { - return nil, attachmentStateUnknown, err + return nil, "", err } - if vpcAttachment == nil { - return nil, attachmentStateNotFound, nil + return output, aws.StringValue(output.State), nil + } +} + +func StatusCustomerGatewayState(conn *ec2.EC2, id string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + output, err := FindCustomerGatewayByID(conn, id) + + if tfresource.NotFound(err) { + return nil, "", nil } - return vpcAttachment, aws.StringValue(vpcAttachment.State), nil + if err != nil { + return nil, "", err + } + + return output, aws.StringValue(output.State), nil } } diff --git a/internal/service/ec2/sweep.go b/internal/service/ec2/sweep.go index 016f5c4c657..cbec084923c 100644 --- a/internal/service/ec2/sweep.go +++ b/internal/service/ec2/sweep.go @@ -20,6 +20,14 @@ import ( ) func init() { + resource.AddTestSweepers("aws_customer_gateway", &resource.Sweeper{ + Name: "aws_customer_gateway", + F: sweepCustomerGateways, + Dependencies: []string{ + "aws_vpn_connection", + }, + }) + resource.AddTestSweepers("aws_ec2_capacity_reservation", &resource.Sweeper{ Name: "aws_ec2_capacity_reservation", F: sweepCapacityReservations, @@ -276,6 +284,7 @@ func init() { F: sweepVPNGateways, Dependencies: []string{ "aws_dx_gateway_association", + "aws_vpn_connection", }, }) @@ -2034,59 +2043,88 @@ func sweepVPNGateways(region string) error { return fmt.Errorf("error getting client: %s", err) } conn := client.(*conns.AWSClient).EC2Conn - var sweeperErrs *multierror.Error + input := &ec2.DescribeVpnGatewaysInput{} + sweepResources := make([]*sweep.SweepResource, 0) - req := &ec2.DescribeVpnGatewaysInput{} - resp, err := conn.DescribeVpnGateways(req) - if err != nil { - if sweep.SkipSweepError(err) { - log.Printf("[WARN] Skipping EC2 VPN Gateway sweep for %s: %s", region, err) - return nil - } - return fmt.Errorf("Error describing VPN Gateways: %s", err) - } + output, err := conn.DescribeVpnGateways(input) - if len(resp.VpnGateways) == 0 { - log.Print("[DEBUG] No VPN Gateways to sweep") + if sweep.SkipSweepError(err) { + log.Printf("[WARN] Skipping EC2 VPN Gateway sweep for %s: %s", region, err) return nil } - for _, vpng := range resp.VpnGateways { - if aws.StringValue(vpng.State) == ec2.VpnStateDeleted { + if err != nil { + return fmt.Errorf("error listing EC2 VPN Gateways (%s): %w", region, err) + } + + for _, v := range output.VpnGateways { + if aws.StringValue(v.State) == ec2.VpnStateDeleted { continue } - for _, vpcAttachment := range vpng.VpcAttachments { - if aws.StringValue(vpcAttachment.State) == ec2.AttachmentStatusDetached { - continue - } + r := ResourceVPNGateway() + d := r.Data(nil) + d.SetId(aws.StringValue(v.VpnGatewayId)) - r := ResourceVPNGatewayAttachment() - d := r.Data(nil) - d.Set("vpc_id", vpcAttachment.VpcId) - d.Set("vpn_gateway_id", vpng.VpnGatewayId) - err := r.Delete(d, client) + for _, v := range v.VpcAttachments { + if aws.StringValue(v.State) != ec2.AttachmentStatusDetached { + d.Set("vpc_id", v.VpcId) - if err != nil { - log.Printf("[ERROR] %s", err) - sweeperErrs = multierror.Append(sweeperErrs, err) - continue + break } } - r := ResourceVPNGateway() - d := r.Data(nil) - d.SetId(aws.StringValue(vpng.VpnGatewayId)) - err := r.Delete(d, client) + sweepResources = append(sweepResources, sweep.NewSweepResource(r, d, client)) + } - if err != nil { - log.Printf("[ERROR] %s", err) - sweeperErrs = multierror.Append(sweeperErrs, err) + err = sweep.SweepOrchestrator(sweepResources) + + if err != nil { + return fmt.Errorf("error sweeping EC2 VPN Gateways (%s): %w", region, err) + } + + return nil +} + +func sweepCustomerGateways(region string) error { + client, err := sweep.SharedRegionalSweepClient(region) + if err != nil { + return fmt.Errorf("error getting client: %s", err) + } + conn := client.(*conns.AWSClient).EC2Conn + input := &ec2.DescribeCustomerGatewaysInput{} + sweepResources := make([]*sweep.SweepResource, 0) + + output, err := conn.DescribeCustomerGateways(input) + + if sweep.SkipSweepError(err) { + log.Printf("[WARN] Skipping EC2 Customer Gateway sweep for %s: %s", region, err) + return nil + } + + if err != nil { + return fmt.Errorf("error listing EC2 Customer Gateways (%s): %w", region, err) + } + + for _, v := range output.CustomerGateways { + if aws.StringValue(v.State) == CustomerGatewayStateDeleted { continue } + + r := ResourceCustomerGateway() + d := r.Data(nil) + d.SetId(aws.StringValue(v.CustomerGatewayId)) + + sweepResources = append(sweepResources, sweep.NewSweepResource(r, d, client)) } - return sweeperErrs.ErrorOrNil() + err = sweep.SweepOrchestrator(sweepResources) + + if err != nil { + return fmt.Errorf("error sweeping EC2 Customer Gateways (%s): %w", region, err) + } + + return nil } func sweepIPAMPoolCIDRs(region string) error { diff --git a/internal/service/ec2/vpn_gateway.go b/internal/service/ec2/vpn_gateway.go index 29b0c12fe21..1beb821e652 100644 --- a/internal/service/ec2/vpn_gateway.go +++ b/internal/service/ec2/vpn_gateway.go @@ -4,13 +4,11 @@ import ( "fmt" "log" "strconv" - "time" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/arn" "github.com/aws/aws-sdk-go/service/ec2" "github.com/hashicorp/aws-sdk-go-base/tfawserr" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-provider-aws/internal/conns" tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" @@ -24,11 +22,19 @@ func ResourceVPNGateway() *schema.Resource { Read: resourceVPNGatewayRead, Update: resourceVPNGatewayUpdate, Delete: resourceVPNGatewayDelete, + Importer: &schema.ResourceImporter{ State: schema.ImportStatePassthrough, }, Schema: map[string]*schema.Schema{ + "amazon_side_asn": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Computed: true, + ValidateFunc: validAmazonSideASN, + }, "arn": { Type: schema.TypeString, Computed: true, @@ -38,24 +44,13 @@ func ResourceVPNGateway() *schema.Resource { Optional: true, ForceNew: true, }, - - "amazon_side_asn": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, - Computed: true, - ValidateFunc: validAmazonSideASN, - }, - + "tags": tftags.TagsSchema(), + "tags_all": tftags.TagsSchemaComputed(), "vpc_id": { Type: schema.TypeString, Optional: true, Computed: true, }, - - "tags": tftags.TagsSchema(), - - "tags_all": tftags.TagsSchemaComputed(), }, CustomizeDiff: verify.SetTagsDiff, @@ -67,32 +62,34 @@ func resourceVPNGatewayCreate(d *schema.ResourceData, meta interface{}) error { defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig tags := defaultTagsConfig.MergeTags(tftags.New(d.Get("tags").(map[string]interface{}))) - createOpts := &ec2.CreateVpnGatewayInput{ + input := &ec2.CreateVpnGatewayInput{ AvailabilityZone: aws.String(d.Get("availability_zone").(string)), - Type: aws.String(ec2.GatewayTypeIpsec1), TagSpecifications: ec2TagSpecificationsFromKeyValueTags(tags, ec2.ResourceTypeVpnGateway), + Type: aws.String(ec2.GatewayTypeIpsec1), } - if asn, ok := d.GetOk("amazon_side_asn"); ok { - i, err := strconv.ParseInt(asn.(string), 10, 64) + if v, ok := d.GetOk("amazon_side_asn"); ok { + v, err := strconv.ParseInt(v.(string), 10, 64) + if err != nil { return err } - createOpts.AmazonSideAsn = aws.Int64(i) + + input.AmazonSideAsn = aws.Int64(v) } - // Create the VPN gateway - log.Printf("[DEBUG] Creating VPN gateway") - resp, err := conn.CreateVpnGateway(createOpts) + log.Printf("[DEBUG] Creating EC2 VPN Gateway: %s", input) + output, err := conn.CreateVpnGateway(input) + if err != nil { - return fmt.Errorf("Error creating VPN gateway: %s", err) + return fmt.Errorf("error creating EC2 VPN Gateway: %w", err) } - d.SetId(aws.StringValue(resp.VpnGateway.VpnGatewayId)) + d.SetId(aws.StringValue(output.VpnGateway.VpnGatewayId)) - if _, ok := d.GetOk("vpc_id"); ok { - if err := resourceVPNGatewayAttach(d, meta); err != nil { - return fmt.Errorf("error attaching EC2 VPN Gateway (%s) to VPC: %s", d.Id(), err) + if v, ok := d.GetOk("vpc_id"); ok { + if err := attachVPNGatewayToVPC(conn, d.Id(), v.(string)); err != nil { + return err } } @@ -104,39 +101,41 @@ func resourceVPNGatewayRead(d *schema.ResourceData, meta interface{}) error { defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig ignoreTagsConfig := meta.(*conns.AWSClient).IgnoreTagsConfig - resp, err := conn.DescribeVpnGateways(&ec2.DescribeVpnGatewaysInput{ - VpnGatewayIds: []*string{aws.String(d.Id())}, - }) - if err != nil { - if tfawserr.ErrMessageContains(err, "InvalidVpnGatewayID.NotFound", "") { - log.Printf("[WARN] VPC Gateway (%s) not found, removing from state", d.Id()) - d.SetId("") - return nil - } else { - log.Printf("[ERROR] Error finding VpnGateway: %s", err) - return err - } - } + outputRaw, err := tfresource.RetryWhenNewResourceNotFound(PropagationTimeout, func() (interface{}, error) { + return FindVPNGatewayByID(conn, d.Id()) + }, d.IsNewResource()) - vpnGateway := resp.VpnGateways[0] - if vpnGateway == nil || aws.StringValue(vpnGateway.State) == ec2.VpnStateDeleted { - log.Printf("[WARN] VPC Gateway (%s) not found, removing from state", d.Id()) + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] EC2 VPN Gateway (%s) not found, removing from state", d.Id()) d.SetId("") return nil } - vpnAttachment := vpnGatewayGetAttachment(vpnGateway) - if vpnAttachment == nil { - // Gateway exists but not attached to the VPC - d.Set("vpc_id", "") - } else { - d.Set("vpc_id", vpnAttachment.VpcId) + if err != nil { + return fmt.Errorf("error reading EC2 VPN Gateway (%s): %w", d.Id(), err) } - if vpnGateway.AvailabilityZone != nil && aws.StringValue(vpnGateway.AvailabilityZone) != "" { + vpnGateway := outputRaw.(*ec2.VpnGateway) + + d.Set("amazon_side_asn", strconv.FormatInt(aws.Int64Value(vpnGateway.AmazonSideAsn), 10)) + arn := arn.ARN{ + Partition: meta.(*conns.AWSClient).Partition, + Service: ec2.ServiceName, + Region: meta.(*conns.AWSClient).Region, + AccountID: meta.(*conns.AWSClient).AccountID, + Resource: fmt.Sprintf("vpn-gateway/%s", d.Id()), + }.String() + d.Set("arn", arn) + if aws.StringValue(vpnGateway.AvailabilityZone) != "" { d.Set("availability_zone", vpnGateway.AvailabilityZone) } - d.Set("amazon_side_asn", strconv.FormatInt(aws.Int64Value(vpnGateway.AmazonSideAsn), 10)) + + d.Set("vpc_id", nil) + for _, vpcAttachment := range vpnGateway.VpcAttachments { + if aws.StringValue(vpcAttachment.State) == ec2.AttachmentStatusAttached { + d.Set("vpc_id", vpcAttachment.VpcId) + } + } tags := KeyValueTags(vpnGateway.Tags).IgnoreAWS().IgnoreConfig(ignoreTagsConfig) @@ -149,39 +148,33 @@ func resourceVPNGatewayRead(d *schema.ResourceData, meta interface{}) error { return fmt.Errorf("error setting tags_all: %w", err) } - arn := arn.ARN{ - Partition: meta.(*conns.AWSClient).Partition, - Service: ec2.ServiceName, - Region: meta.(*conns.AWSClient).Region, - AccountID: meta.(*conns.AWSClient).AccountID, - Resource: fmt.Sprintf("vpn-gateway/%s", d.Id()), - }.String() - - d.Set("arn", arn) - return nil } func resourceVPNGatewayUpdate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*conns.AWSClient).EC2Conn + if d.HasChange("vpc_id") { - // If we're already attached, detach it first - if err := resourceVPNGatewayDetach(d, meta); err != nil { - return err + o, n := d.GetChange("vpc_id") + + if vpcID, ok := o.(string); ok && vpcID != "" { + if err := detachVPNGatewayFromVPC(conn, d.Id(), vpcID); err != nil { + return err + } } - // Attach the VPN gateway to the new vpc - if err := resourceVPNGatewayAttach(d, meta); err != nil { - return err + if vpcID, ok := n.(string); ok && vpcID != "" { + if err := attachVPNGatewayToVPC(conn, d.Id(), vpcID); err != nil { + return err + } } } - conn := meta.(*conns.AWSClient).EC2Conn - if d.HasChange("tags_all") { o, n := d.GetChange("tags_all") if err := UpdateTags(conn, d.Id(), o, n); err != nil { - return fmt.Errorf("error updating EC2 VPN Gateway (%s) tags: %s", d.Id(), err) + return fmt.Errorf("error updating EC2 VPN Gateway (%s) tags: %w", d.Id(), err) } } @@ -191,140 +184,76 @@ func resourceVPNGatewayUpdate(d *schema.ResourceData, meta interface{}) error { func resourceVPNGatewayDelete(d *schema.ResourceData, meta interface{}) error { conn := meta.(*conns.AWSClient).EC2Conn - // Detach if it is attached - if err := resourceVPNGatewayDetach(d, meta); err != nil { - return err - } - - log.Printf("[INFO] Deleting VPN gateway: %s", d.Id()) - input := &ec2.DeleteVpnGatewayInput{ - VpnGatewayId: aws.String(d.Id()), - } - err := resource.Retry(5*time.Minute, func() *resource.RetryError { - _, err := conn.DeleteVpnGateway(input) - if err == nil { - return nil + if v, ok := d.GetOk("vpc_id"); ok { + if err := detachVPNGatewayFromVPC(conn, d.Id(), v.(string)); err != nil { + return err } + } - if tfawserr.ErrMessageContains(err, "InvalidVpnGatewayID.NotFound", "") { - return nil - } - if tfawserr.ErrMessageContains(err, "IncorrectState", "") { - return resource.RetryableError(err) - } + log.Printf("[INFO] Deleting EC2 VPN Gateway: %s", d.Id()) + _, err := tfresource.RetryWhenAWSErrCodeEquals(VPNGatewayDeletedTimeout, func() (interface{}, error) { + return conn.DeleteVpnGateway(&ec2.DeleteVpnGatewayInput{ + VpnGatewayId: aws.String(d.Id()), + }) + }, ErrCodeIncorrectState) - return resource.NonRetryableError(err) - }) - if tfresource.TimedOut(err) { - _, err = conn.DeleteVpnGateway(input) - if tfawserr.ErrMessageContains(err, "InvalidVpnGatewayID.NotFound", "") { - return nil - } + if tfawserr.ErrCodeEquals(err, ErrCodeInvalidVpnGatewayIDNotFound) { + return nil } if err != nil { - return fmt.Errorf("Error deleting VPN gateway: %s", err) + return fmt.Errorf("error deleting EC2 VPN Gateway (%s): %w", d.Id(), err) } + return nil } -func resourceVPNGatewayAttach(d *schema.ResourceData, meta interface{}) error { - conn := meta.(*conns.AWSClient).EC2Conn - - vpcId := d.Get("vpc_id").(string) - - if vpcId == "" { - log.Printf("[DEBUG] Not attaching VPN Gateway '%s' as no VPC ID is set", d.Id()) - return nil - } - - log.Printf( - "[INFO] Attaching VPN Gateway '%s' to VPC '%s'", - d.Id(), - vpcId) - - req := &ec2.AttachVpnGatewayInput{ - VpnGatewayId: aws.String(d.Id()), - VpcId: aws.String(vpcId), +func attachVPNGatewayToVPC(conn *ec2.EC2, vpnGatewayID, vpcID string) error { + input := &ec2.AttachVpnGatewayInput{ + VpcId: aws.String(vpcID), + VpnGatewayId: aws.String(vpnGatewayID), } - err := resource.Retry(1*time.Minute, func() *resource.RetryError { - _, err := conn.AttachVpnGateway(req) - if err != nil { - if tfawserr.ErrMessageContains(err, ErrCodeInvalidVpnGatewayIDNotFound, "") { - return resource.RetryableError(err) - } - return resource.NonRetryableError(err) - } - return nil - }) - if tfresource.TimedOut(err) { - _, err = conn.AttachVpnGateway(req) - } + log.Printf("[INFO] Attaching EC2 VPN Gateway (%s) to VPC (%s)", vpnGatewayID, vpcID) + _, err := tfresource.RetryWhenAWSErrCodeEquals(PropagationTimeout, func() (interface{}, error) { + return conn.AttachVpnGateway(input) + }, ErrCodeInvalidVpnGatewayIDNotFound) if err != nil { - return fmt.Errorf("Error attaching VPN gateway: %s", err) + return fmt.Errorf("error attaching EC2 VPN Gateway (%s) to VPC (%s): %w", vpnGatewayID, vpcID, err) } - // Wait for it to be fully attached before continuing - log.Printf("[DEBUG] Waiting for VPN gateway (%s) to attach", d.Id()) - _, err = WaitVPNGatewayVPCAttachmentAttached(conn, d.Id(), vpcId) + _, err = WaitVPNGatewayVPCAttachmentAttached(conn, vpnGatewayID, vpcID) if err != nil { - return fmt.Errorf("error waiting for VPN Gateway (%s) Attachment (%s) to become attached: %w", d.Id(), vpcId, err) + return fmt.Errorf("error waiting for EC2 VPN Gateway (%s) to attach to VPC (%s): %w", vpnGatewayID, vpcID, err) } return nil } -func resourceVPNGatewayDetach(d *schema.ResourceData, meta interface{}) error { - conn := meta.(*conns.AWSClient).EC2Conn - - // Get the old VPC ID to detach from - vpcIdRaw, _ := d.GetChange("vpc_id") - vpcId := vpcIdRaw.(string) - - if vpcId == "" { - log.Printf( - "[DEBUG] Not detaching VPN Gateway '%s' as no VPC ID is set", - d.Id()) - return nil +func detachVPNGatewayFromVPC(conn *ec2.EC2, vpnGatewayID, vpcID string) error { + input := &ec2.DetachVpnGatewayInput{ + VpcId: aws.String(vpcID), + VpnGatewayId: aws.String(vpnGatewayID), } - log.Printf( - "[INFO] Detaching VPN Gateway '%s' from VPC '%s'", - d.Id(), - vpcId) - - _, err := conn.DetachVpnGateway(&ec2.DetachVpnGatewayInput{ - VpnGatewayId: aws.String(d.Id()), - VpcId: aws.String(vpcId), - }) + log.Printf("[INFO] Detaching EC2 VPN Gateway (%s) from VPC (%s)", vpnGatewayID, vpcID) + _, err := conn.DetachVpnGateway(input) - if tfawserr.ErrMessageContains(err, ErrCodeInvalidVpnGatewayAttachmentNotFound, "") || tfawserr.ErrMessageContains(err, ErrCodeInvalidVpnGatewayIDNotFound, "") { + if tfawserr.ErrCodeEquals(err, ErrCodeInvalidVpnGatewayAttachmentNotFound, ErrCodeInvalidVpnGatewayIDNotFound) { return nil } if err != nil { - return fmt.Errorf("error deleting VPN Gateway (%s) Attachment (%s): %w", d.Id(), vpcId, err) + return fmt.Errorf("error detaching EC2 VPN Gateway (%s) from VPC (%s): %w", vpnGatewayID, vpcID, err) } - // Wait for it to be fully detached before continuing - _, err = WaitVPNGatewayVPCAttachmentDetached(conn, d.Id(), vpcId) + _, err = WaitVPNGatewayVPCAttachmentDetached(conn, vpnGatewayID, vpcID) if err != nil { - return fmt.Errorf("error waiting for VPN Gateway (%s) Attachment (%s) to become detached: %w", d.Id(), vpcId, err) + return fmt.Errorf("error waiting for EC2 VPN Gateway (%s) to detach from VPC (%s): %w", vpnGatewayID, vpcID, err) } return nil } - -// vpnGatewayGetAttachment returns any VGW attachment that's in "attached" state or nil. -func vpnGatewayGetAttachment(vgw *ec2.VpnGateway) *ec2.VpcAttachment { - for _, vpcAttachment := range vgw.VpcAttachments { - if aws.StringValue(vpcAttachment.State) == ec2.AttachmentStatusAttached { - return vpcAttachment - } - } - return nil -} diff --git a/internal/service/ec2/vpn_gateway_attachment.go b/internal/service/ec2/vpn_gateway_attachment.go index 507d76c9aa6..8ea51f7d710 100644 --- a/internal/service/ec2/vpn_gateway_attachment.go +++ b/internal/service/ec2/vpn_gateway_attachment.go @@ -9,6 +9,7 @@ import ( "github.com/hashicorp/aws-sdk-go-base/tfawserr" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) func ResourceVPNGatewayAttachment() *schema.Resource { @@ -23,7 +24,6 @@ func ResourceVPNGatewayAttachment() *schema.Resource { Required: true, ForceNew: true, }, - "vpn_gateway_id": { Type: schema.TypeString, Required: true, @@ -36,27 +36,26 @@ func ResourceVPNGatewayAttachment() *schema.Resource { func resourceVPNGatewayAttachmentCreate(d *schema.ResourceData, meta interface{}) error { conn := meta.(*conns.AWSClient).EC2Conn - vpcId := d.Get("vpc_id").(string) - vgwId := d.Get("vpn_gateway_id").(string) - + vpcID := d.Get("vpc_id").(string) + vpnGatewayID := d.Get("vpn_gateway_id").(string) input := &ec2.AttachVpnGatewayInput{ - VpcId: aws.String(vpcId), - VpnGatewayId: aws.String(vgwId), + VpcId: aws.String(vpcID), + VpnGatewayId: aws.String(vpnGatewayID), } - log.Printf("[DEBUG] Creating VPN Gateway Attachment: %s", input) + log.Printf("[DEBUG] Creating EC2 VPN Gateway Attachment: %s", input) _, err := conn.AttachVpnGateway(input) if err != nil { - return fmt.Errorf("error creating VPN Gateway (%s) Attachment (%s): %w", vgwId, vpcId, err) + return fmt.Errorf("error creating EC2 VPN Gateway (%s) Attachment (%s): %w", vpnGatewayID, vpcID, err) } - d.SetId(VPNGatewayVPCAttachmentCreateID(vgwId, vpcId)) + d.SetId(VPNGatewayVPCAttachmentCreateID(vpnGatewayID, vpcID)) - _, err = WaitVPNGatewayVPCAttachmentAttached(conn, vgwId, vpcId) + _, err = WaitVPNGatewayVPCAttachmentAttached(conn, vpnGatewayID, vpcID) if err != nil { - return fmt.Errorf("error waiting for VPN Gateway (%s) Attachment (%s) to become attached: %w", vgwId, vpcId, err) + return fmt.Errorf("error waiting for EC2 VPN Gateway (%s) Attachment (%s) to become attached: %w", vpnGatewayID, vpcID, err) } return resourceVPNGatewayAttachmentRead(d, meta) @@ -65,25 +64,19 @@ func resourceVPNGatewayAttachmentCreate(d *schema.ResourceData, meta interface{} func resourceVPNGatewayAttachmentRead(d *schema.ResourceData, meta interface{}) error { conn := meta.(*conns.AWSClient).EC2Conn - vpcId := d.Get("vpc_id").(string) - vgwId := d.Get("vpn_gateway_id").(string) + vpcID := d.Get("vpc_id").(string) + vpnGatewayID := d.Get("vpn_gateway_id").(string) - vpcAttachment, err := FindVPNGatewayVPCAttachment(conn, vgwId, vpcId) + _, err := FindVPNGatewayVPCAttachment(conn, vpnGatewayID, vpcID) - if tfawserr.ErrMessageContains(err, ErrCodeInvalidVpnGatewayIDNotFound, "") { - log.Printf("[WARN] VPN Gateway (%s) Attachment (%s) not found, removing from state", vgwId, vpcId) + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] EC2 VPN Gateway (%s) Attachment (%s) not found, removing from state", vpnGatewayID, vpcID) d.SetId("") return nil } if err != nil { - return fmt.Errorf("error reading VPN Gateway (%s) Attachment (%s): %w", vgwId, vpcId, err) - } - - if vpcAttachment == nil || aws.StringValue(vpcAttachment.State) == ec2.AttachmentStatusDetached { - log.Printf("[WARN] VPN Gateway (%s) Attachment (%s) not found, removing from state", vgwId, vpcId) - d.SetId("") - return nil + return fmt.Errorf("error reading EC2 VPN Gateway (%s) Attachment (%s): %w", vpnGatewayID, vpcID, err) } return nil @@ -92,27 +85,27 @@ func resourceVPNGatewayAttachmentRead(d *schema.ResourceData, meta interface{}) func resourceVPNGatewayAttachmentDelete(d *schema.ResourceData, meta interface{}) error { conn := meta.(*conns.AWSClient).EC2Conn - vpcId := d.Get("vpc_id").(string) - vgwId := d.Get("vpn_gateway_id").(string) + vpcID := d.Get("vpc_id").(string) + vpnGatewayID := d.Get("vpn_gateway_id").(string) - log.Printf("[INFO] Deleting VPN Gateway (%s) Attachment (%s)", vgwId, vpcId) + log.Printf("[INFO] Deleting EC2 VPN Gateway (%s) Attachment (%s)", vpnGatewayID, vpcID) _, err := conn.DetachVpnGateway(&ec2.DetachVpnGatewayInput{ - VpcId: aws.String(vpcId), - VpnGatewayId: aws.String(vgwId), + VpcId: aws.String(vpcID), + VpnGatewayId: aws.String(vpnGatewayID), }) - if tfawserr.ErrMessageContains(err, ErrCodeInvalidVpnGatewayAttachmentNotFound, "") || tfawserr.ErrMessageContains(err, ErrCodeInvalidVpnGatewayIDNotFound, "") { + if tfawserr.ErrCodeEquals(err, ErrCodeInvalidVpnGatewayAttachmentNotFound, ErrCodeInvalidVpnGatewayIDNotFound) { return nil } if err != nil { - return fmt.Errorf("error deleting VPN Gateway (%s) Attachment (%s): %w", vgwId, vpcId, err) + return fmt.Errorf("error deleting EC2 VPN Gateway (%s) Attachment (%s): %w", vpnGatewayID, vpcID, err) } - _, err = WaitVPNGatewayVPCAttachmentDetached(conn, vgwId, vpcId) + _, err = WaitVPNGatewayVPCAttachmentDetached(conn, vpnGatewayID, vpcID) if err != nil { - return fmt.Errorf("error waiting for VPN Gateway (%s) Attachment (%s) to become detached: %w", vgwId, vpcId, err) + return fmt.Errorf("error waiting for EC2 VPN Gateway (%s) Attachment (%s) to become detached: %w", vpnGatewayID, vpcID, err) } return nil diff --git a/internal/service/ec2/vpn_gateway_attachment_test.go b/internal/service/ec2/vpn_gateway_attachment_test.go index fcbfc45907a..94c177f659d 100644 --- a/internal/service/ec2/vpn_gateway_attachment_test.go +++ b/internal/service/ec2/vpn_gateway_attachment_test.go @@ -4,7 +4,6 @@ import ( "fmt" "testing" - "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/ec2" sdkacctest "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" @@ -12,6 +11,7 @@ import ( "github.com/hashicorp/terraform-provider-aws/internal/acctest" "github.com/hashicorp/terraform-provider-aws/internal/conns" tfec2 "github.com/hashicorp/terraform-provider-aws/internal/service/ec2" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) func TestAccEC2VPNGatewayAttachment_basic(t *testing.T) { @@ -66,22 +66,18 @@ func testAccCheckVpnGatewayAttachmentExists(n string, v *ec2.VpcAttachment) reso } if rs.Primary.ID == "" { - return fmt.Errorf("No ID is set") + return fmt.Errorf("No EC2 VPN Gateway Attachment ID is set") } conn := acctest.Provider.Meta().(*conns.AWSClient).EC2Conn - out, err := tfec2.FindVPNGatewayVPCAttachment(conn, rs.Primary.Attributes["vpn_gateway_id"], rs.Primary.Attributes["vpc_id"]) + + output, err := tfec2.FindVPNGatewayVPCAttachment(conn, rs.Primary.Attributes["vpn_gateway_id"], rs.Primary.Attributes["vpc_id"]) + if err != nil { return err } - if out == nil { - return fmt.Errorf("VPN Gateway Attachment not found") - } - if state := aws.StringValue(out.State); state != ec2.AttachmentStatusAttached { - return fmt.Errorf("VPN Gateway Attachment in incorrect state. Expected: %s, got: %s", ec2.AttachmentStatusAttached, state) - } - *v = *out + *v = *output return nil } @@ -95,16 +91,17 @@ func testAccCheckVpnGatewayAttachmentDestroy(s *terraform.State) error { continue } - out, err := tfec2.FindVPNGatewayVPCAttachment(conn, rs.Primary.Attributes["vpn_gateway_id"], rs.Primary.Attributes["vpc_id"]) - if err != nil { - return err - } - if out == nil { + _, err := tfec2.FindVPNGatewayVPCAttachment(conn, rs.Primary.Attributes["vpn_gateway_id"], rs.Primary.Attributes["vpc_id"]) + + if tfresource.NotFound(err) { continue } - if state := aws.StringValue(out.State); state != ec2.AttachmentStatusDetached { - return fmt.Errorf("VPN Gateway Attachment in incorrect state. Expected: %s, got: %s", ec2.AttachmentStatusDetached, state) + + if err != nil { + return err } + + return fmt.Errorf("EC2 VPN Gateway Attachment %s still exists", rs.Primary.ID) } return nil diff --git a/internal/service/ec2/vpn_gateway_data_source.go b/internal/service/ec2/vpn_gateway_data_source.go index c9d19934e71..9f15a46d67d 100644 --- a/internal/service/ec2/vpn_gateway_data_source.go +++ b/internal/service/ec2/vpn_gateway_data_source.go @@ -2,7 +2,6 @@ package ec2 import ( "fmt" - "log" "strconv" "github.com/aws/aws-sdk-go/aws" @@ -11,6 +10,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "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" ) func DataSourceVPNGateway() *schema.Resource { @@ -18,37 +18,37 @@ func DataSourceVPNGateway() *schema.Resource { Read: dataSourceVPNGatewayRead, Schema: map[string]*schema.Schema{ - "arn": { + "amazon_side_asn": { Type: schema.TypeString, + Optional: true, Computed: true, }, - "id": { + "arn": { Type: schema.TypeString, - Optional: true, Computed: true, }, - "state": { + "attached_vpc_id": { Type: schema.TypeString, Optional: true, Computed: true, }, - "attached_vpc_id": { + "availability_zone": { Type: schema.TypeString, Optional: true, Computed: true, }, - "availability_zone": { + "filter": CustomFiltersSchema(), + "id": { Type: schema.TypeString, Optional: true, Computed: true, }, - "amazon_side_asn": { + "state": { Type: schema.TypeString, Optional: true, Computed: true, }, - "filter": CustomFiltersSchema(), - "tags": tftags.TagsSchemaComputed(), + "tags": tftags.TagsSchemaComputed(), }, } } @@ -57,74 +57,53 @@ func dataSourceVPNGatewayRead(d *schema.ResourceData, meta interface{}) error { conn := meta.(*conns.AWSClient).EC2Conn ignoreTagsConfig := meta.(*conns.AWSClient).IgnoreTagsConfig - req := &ec2.DescribeVpnGatewaysInput{} + input := &ec2.DescribeVpnGatewaysInput{} if id, ok := d.GetOk("id"); ok { - req.VpnGatewayIds = aws.StringSlice([]string{id.(string)}) + input.VpnGatewayIds = aws.StringSlice([]string{id.(string)}) } - req.Filters = BuildAttributeFilterList( + input.Filters = BuildAttributeFilterList( map[string]string{ "state": d.Get("state").(string), "availability-zone": d.Get("availability_zone").(string), }, ) if asn, ok := d.GetOk("amazon_side_asn"); ok { - req.Filters = append(req.Filters, BuildAttributeFilterList( + input.Filters = append(input.Filters, BuildAttributeFilterList( map[string]string{ "amazon-side-asn": asn.(string), }, )...) } if id, ok := d.GetOk("attached_vpc_id"); ok { - req.Filters = append(req.Filters, BuildAttributeFilterList( + input.Filters = append(input.Filters, BuildAttributeFilterList( map[string]string{ "attachment.state": "attached", "attachment.vpc-id": id.(string), }, )...) } - req.Filters = append(req.Filters, BuildTagFilterList( + input.Filters = append(input.Filters, BuildTagFilterList( Tags(tftags.New(d.Get("tags").(map[string]interface{}))), )...) - req.Filters = append(req.Filters, BuildCustomFilterList( + input.Filters = append(input.Filters, BuildCustomFilterList( d.Get("filter").(*schema.Set), )...) - if len(req.Filters) == 0 { + if len(input.Filters) == 0 { // Don't send an empty filters list; the EC2 API won't accept it. - req.Filters = nil + input.Filters = nil } - log.Printf("[DEBUG] Reading VPN Gateway: %s", req) - resp, err := conn.DescribeVpnGateways(req) + vgw, err := FindVPNGateway(conn, input) + if err != nil { - return err - } - if resp == nil || len(resp.VpnGateways) == 0 { - return fmt.Errorf("no matching VPN gateway found: %#v", req) + return tfresource.SingularDataSourceFindError("EC2 VPN Gateway", err) } - if len(resp.VpnGateways) > 1 { - return fmt.Errorf("multiple VPN gateways matched; use additional constraints to reduce matches to a single VPN gateway") - } - - vgw := resp.VpnGateways[0] d.SetId(aws.StringValue(vgw.VpnGatewayId)) - d.Set("state", vgw.State) - d.Set("availability_zone", vgw.AvailabilityZone) - d.Set("amazon_side_asn", strconv.FormatInt(aws.Int64Value(vgw.AmazonSideAsn), 10)) - - if err := d.Set("tags", KeyValueTags(vgw.Tags).IgnoreAWS().IgnoreConfig(ignoreTagsConfig).Map()); err != nil { - return fmt.Errorf("error setting tags: %w", err) - } - - for _, attachment := range vgw.VpcAttachments { - if aws.StringValue(attachment.State) == ec2.AttachmentStatusAttached { - d.Set("attached_vpc_id", attachment.VpcId) - break - } - } + d.Set("amazon_side_asn", strconv.FormatInt(aws.Int64Value(vgw.AmazonSideAsn), 10)) arn := arn.ARN{ Partition: meta.(*conns.AWSClient).Partition, Service: ec2.ServiceName, @@ -132,8 +111,19 @@ func dataSourceVPNGatewayRead(d *schema.ResourceData, meta interface{}) error { AccountID: meta.(*conns.AWSClient).AccountID, Resource: fmt.Sprintf("vpn-gateway/%s", d.Id()), }.String() - d.Set("arn", arn) + for _, attachment := range vgw.VpcAttachments { + if aws.StringValue(attachment.State) == ec2.AttachmentStatusAttached { + d.Set("attached_vpc_id", attachment.VpcId) + break + } + } + d.Set("availability_zone", vgw.AvailabilityZone) + d.Set("state", vgw.State) + + if err := d.Set("tags", KeyValueTags(vgw.Tags).IgnoreAWS().IgnoreConfig(ignoreTagsConfig).Map()); err != nil { + return fmt.Errorf("error setting tags: %w", err) + } return nil } diff --git a/internal/service/ec2/vpn_gateway_data_source_test.go b/internal/service/ec2/vpn_gateway_data_source_test.go index 30ae3406c40..512f2230443 100644 --- a/internal/service/ec2/vpn_gateway_data_source_test.go +++ b/internal/service/ec2/vpn_gateway_data_source_test.go @@ -12,7 +12,7 @@ import ( ) func TestAccEC2VPNGatewayDataSource_unattached(t *testing.T) { - rInt := sdkacctest.RandInt() + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) dataSourceNameById := "data.aws_vpn_gateway.test_by_id" dataSourceNameByTags := "data.aws_vpn_gateway.test_by_tags" dataSourceNameByAsn := "data.aws_vpn_gateway.test_by_amazon_side_asn" @@ -24,7 +24,7 @@ func TestAccEC2VPNGatewayDataSource_unattached(t *testing.T) { Providers: acctest.Providers, Steps: []resource.TestStep{ { - Config: testAccVPNGatewayUnattachedDataSourceConfig(rInt), + Config: testAccVPNGatewayUnattachedDataSourceConfig(rName), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttrPair(dataSourceNameById, "id", resourceName, "id"), resource.TestCheckResourceAttrPair(dataSourceNameById, "arn", resourceName, "arn"), @@ -41,7 +41,7 @@ func TestAccEC2VPNGatewayDataSource_unattached(t *testing.T) { } func TestAccEC2VPNGatewayDataSource_attached(t *testing.T) { - rInt := sdkacctest.RandInt() + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) dataSourceName := "data.aws_vpn_gateway.test" resource.ParallelTest(t, resource.TestCase{ @@ -50,7 +50,7 @@ func TestAccEC2VPNGatewayDataSource_attached(t *testing.T) { Providers: acctest.Providers, Steps: []resource.TestStep{ { - Config: testAccVPNGatewayAttachedDataSourceConfig(rInt), + Config: testAccVPNGatewayAttachedDataSourceConfig(rName), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttrPair(dataSourceName, "id", "aws_vpn_gateway.test", "id"), resource.TestCheckResourceAttrPair(dataSourceName, "attached_vpc_id", "aws_vpc.test", "id"), @@ -61,13 +61,13 @@ func TestAccEC2VPNGatewayDataSource_attached(t *testing.T) { }) } -func testAccVPNGatewayUnattachedDataSourceConfig(rInt int) string { +func testAccVPNGatewayUnattachedDataSourceConfig(rName string) string { return fmt.Sprintf(` resource "aws_vpn_gateway" "test" { tags = { - Name = "terraform-testacc-vpn-gateway-data-source-unattached-%d" - ABC = "testacc-%d" - XYZ = "testacc-%d" + Name = %[1]q + ABC = "abc" + XYZ = "xyz" } amazon_side_asn = 4294967293 @@ -85,22 +85,22 @@ data "aws_vpn_gateway" "test_by_amazon_side_asn" { amazon_side_asn = aws_vpn_gateway.test.amazon_side_asn state = "available" } -`, rInt, rInt+1, rInt-1) +`, rName) } -func testAccVPNGatewayAttachedDataSourceConfig(rInt int) string { +func testAccVPNGatewayAttachedDataSourceConfig(rName string) string { return fmt.Sprintf(` resource "aws_vpc" "test" { cidr_block = "10.1.0.0/16" tags = { - Name = "terraform-testacc-vpn-gateway-data-source-attached-%d" + Name = %[1]q } } resource "aws_vpn_gateway" "test" { tags = { - Name = "terraform-testacc-vpn-gateway-data-source-attached-%d" + Name = %[1]q } } @@ -112,5 +112,5 @@ resource "aws_vpn_gateway_attachment" "test" { data "aws_vpn_gateway" "test" { attached_vpc_id = aws_vpn_gateway_attachment.test.vpc_id } -`, rInt, rInt) +`, rName) } diff --git a/internal/service/ec2/vpn_gateway_test.go b/internal/service/ec2/vpn_gateway_test.go index be4a5e9c8f6..d365fa1d33d 100644 --- a/internal/service/ec2/vpn_gateway_test.go +++ b/internal/service/ec2/vpn_gateway_test.go @@ -6,33 +6,33 @@ import ( "testing" "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/service/ec2" + 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" tfec2 "github.com/hashicorp/terraform-provider-aws/internal/service/ec2" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) // add sweeper to delete known test VPN Gateways func TestAccEC2VPNGateway_basic(t *testing.T) { - var v, v2 ec2.VpnGateway + var v1, v2 ec2.VpnGateway resourceName := "aws_vpn_gateway.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) testNotEqual := func(*terraform.State) error { - if len(v.VpcAttachments) == 0 { + if len(v1.VpcAttachments) == 0 { return fmt.Errorf("VPN Gateway A is not attached") } if len(v2.VpcAttachments) == 0 { return fmt.Errorf("VPN Gateway B is not attached") } - id1 := v.VpcAttachments[0].VpcId - id2 := v2.VpcAttachments[0].VpcId - if id1 == id2 { - return fmt.Errorf("Both attachment IDs are the same") + if aws.StringValue(v1.VpcAttachments[0].VpcId) == aws.StringValue(v2.VpcAttachments[0].VpcId) { + return fmt.Errorf("Attachment IDs are equal") } return nil @@ -45,9 +45,9 @@ func TestAccEC2VPNGateway_basic(t *testing.T) { CheckDestroy: testAccCheckVpnGatewayDestroy, Steps: []resource.TestStep{ { - Config: testAccVpnGatewayConfig, + Config: testAccVpnGatewayConfig(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckVpnGatewayExists(resourceName, &v), + testAccCheckVpnGatewayExists(resourceName, &v1), acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "ec2", regexp.MustCompile(`vpn-gateway/vgw-.+`)), resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), ), @@ -58,7 +58,7 @@ func TestAccEC2VPNGateway_basic(t *testing.T) { ImportStateVerify: true, }, { - Config: testAccVpnGatewayConfigChangeVPC, + Config: testAccVpnGatewayConfigChangeVPC(rName), Check: resource.ComposeTestCheckFunc( testAccCheckVpnGatewayExists(resourceName, &v2), testNotEqual, @@ -72,6 +72,7 @@ func TestAccEC2VPNGateway_withAvailabilityZoneSetToState(t *testing.T) { var v ec2.VpnGateway resourceName := "aws_vpn_gateway.test" azDataSourceName := "data.aws_availability_zones.available" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -80,7 +81,7 @@ func TestAccEC2VPNGateway_withAvailabilityZoneSetToState(t *testing.T) { CheckDestroy: testAccCheckVpnGatewayDestroy, Steps: []resource.TestStep{ { - Config: testAccVpnGatewayConfigWithAZ(), + Config: testAccVpnGatewayConfigWithAZ(rName), Check: resource.ComposeTestCheckFunc( testAccCheckVpnGatewayExists(resourceName, &v), resource.TestCheckResourceAttrPair(resourceName, "availability_zone", azDataSourceName, "names.0"), @@ -96,9 +97,10 @@ func TestAccEC2VPNGateway_withAvailabilityZoneSetToState(t *testing.T) { }) } -func TestAccEC2VPNGateway_withAmazonSideASNSetToState(t *testing.T) { +func TestAccEC2VPNGateway_withAmazonSideASN(t *testing.T) { var v ec2.VpnGateway resourceName := "aws_vpn_gateway.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -107,7 +109,7 @@ func TestAccEC2VPNGateway_withAmazonSideASNSetToState(t *testing.T) { CheckDestroy: testAccCheckVpnGatewayDestroy, Steps: []resource.TestStep{ { - Config: testAccVpnGatewayConfigWithASN, + Config: testAccVpnGatewayConfigWithASN(rName), Check: resource.ComposeTestCheckFunc( testAccCheckVpnGatewayExists(resourceName, &v), resource.TestCheckResourceAttr( @@ -126,6 +128,7 @@ func TestAccEC2VPNGateway_withAmazonSideASNSetToState(t *testing.T) { func TestAccEC2VPNGateway_disappears(t *testing.T) { var v ec2.VpnGateway resourceName := "aws_vpn_gateway.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -134,7 +137,7 @@ func TestAccEC2VPNGateway_disappears(t *testing.T) { CheckDestroy: testAccCheckVpnGatewayDestroy, Steps: []resource.TestStep{ { - Config: testAccVpnGatewayConfig, + Config: testAccVpnGatewayConfig(rName), Check: resource.ComposeTestCheckFunc( testAccCheckVpnGatewayExists(resourceName, &v), acctest.CheckResourceDisappears(acctest.Provider, tfec2.ResourceVPNGateway(), resourceName), @@ -148,38 +151,39 @@ func TestAccEC2VPNGateway_disappears(t *testing.T) { func TestAccEC2VPNGateway_reattach(t *testing.T) { var vpc1, vpc2 ec2.Vpc var vgw1, vgw2 ec2.VpnGateway - resourceName := "aws_vpn_gateway.test" + vpcResourceName1 := "aws_vpc.test1" + vpcResourceName2 := "aws_vpc.test2" + resourceName1 := "aws_vpn_gateway.test1" resourceName2 := "aws_vpn_gateway.test2" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) testAttachmentFunc := func(vgw *ec2.VpnGateway, vpc *ec2.Vpc) func(*terraform.State) error { return func(*terraform.State) error { if len(vgw.VpcAttachments) == 0 { - return fmt.Errorf("VPN Gateway %q has no VPC attachments.", - *vgw.VpnGatewayId) + return fmt.Errorf("VPN Gateway %q has no VPC attachments.", aws.StringValue(vgw.VpnGatewayId)) } if len(vgw.VpcAttachments) > 1 { count := 0 for _, v := range vgw.VpcAttachments { - if *v.State == "attached" { + if aws.StringValue(v.State) == ec2.AttachmentStatusAttached { count += 1 } } if count > 1 { return fmt.Errorf( "VPN Gateway %q has an unexpected number of VPC attachments (more than 1): %#v", - *vgw.VpnGatewayId, vgw.VpcAttachments) + aws.StringValue(vgw.VpnGatewayId), vgw.VpcAttachments) } } - if *vgw.VpcAttachments[0].State != "attached" { - return fmt.Errorf("Expected VPN Gateway %q to be attached.", - *vgw.VpnGatewayId) + if aws.StringValue(vgw.VpcAttachments[0].State) != ec2.AttachmentStatusAttached { + return fmt.Errorf("Expected VPN Gateway %q to be attached.", aws.StringValue(vgw.VpnGatewayId)) } if *vgw.VpcAttachments[0].VpcId != *vpc.VpcId { return fmt.Errorf("Expected VPN Gateway %q to be attached to VPC %q, but got: %q", - *vgw.VpnGatewayId, *vpc.VpcId, *vgw.VpcAttachments[0].VpcId) + aws.StringValue(vgw.VpnGatewayId), aws.StringValue(vpc.VpcId), aws.StringValue(vgw.VpcAttachments[0].VpcId)) } return nil } @@ -192,41 +196,40 @@ func TestAccEC2VPNGateway_reattach(t *testing.T) { CheckDestroy: testAccCheckVpnGatewayDestroy, Steps: []resource.TestStep{ { - Config: testAccCheckVpnGatewayConfigReattach, + Config: testAccCheckVpnGatewayConfigReattach(rName), Check: resource.ComposeTestCheckFunc( - acctest.CheckVPCExists("aws_vpc.test", &vpc1), - acctest.CheckVPCExists("aws_vpc.test2", &vpc2), - testAccCheckVpnGatewayExists( - resourceName, &vgw1), - testAccCheckVpnGatewayExists( - resourceName2, &vgw2), + acctest.CheckVPCExists(vpcResourceName1, &vpc1), + acctest.CheckVPCExists(vpcResourceName2, &vpc2), + testAccCheckVpnGatewayExists(resourceName1, &vgw1), + testAccCheckVpnGatewayExists(resourceName2, &vgw2), testAttachmentFunc(&vgw1, &vpc1), testAttachmentFunc(&vgw2, &vpc2), ), }, { - ResourceName: resourceName, + ResourceName: resourceName1, ImportState: true, ImportStateVerify: true, }, { - Config: testAccCheckVpnGatewayConfigReattachChange, + ResourceName: resourceName2, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccCheckVpnGatewayConfigReattachChange(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckVpnGatewayExists( - resourceName, &vgw1), - testAccCheckVpnGatewayExists( - resourceName2, &vgw2), + testAccCheckVpnGatewayExists(resourceName1, &vgw1), + testAccCheckVpnGatewayExists(resourceName2, &vgw2), testAttachmentFunc(&vgw2, &vpc1), testAttachmentFunc(&vgw1, &vpc2), ), }, { - Config: testAccCheckVpnGatewayConfigReattach, + Config: testAccCheckVpnGatewayConfigReattach(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckVpnGatewayExists( - resourceName, &vgw1), - testAccCheckVpnGatewayExists( - resourceName2, &vgw2), + testAccCheckVpnGatewayExists(resourceName1, &vgw1), + testAccCheckVpnGatewayExists(resourceName2, &vgw2), testAttachmentFunc(&vgw1, &vpc1), testAttachmentFunc(&vgw2, &vpc2), ), @@ -235,47 +238,10 @@ func TestAccEC2VPNGateway_reattach(t *testing.T) { }) } -func TestAccEC2VPNGateway_delete(t *testing.T) { - var vpnGateway ec2.VpnGateway - resourceName := "aws_vpn_gateway.test" - - testDeleted := func(r string) resource.TestCheckFunc { - return func(s *terraform.State) error { - _, ok := s.RootModule().Resources[r] - if ok { - return fmt.Errorf("VPN Gateway %q should have been deleted.", r) - } - return nil - } - } - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(t) }, - ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), - Providers: acctest.Providers, - CheckDestroy: testAccCheckVpnGatewayDestroy, - Steps: []resource.TestStep{ - { - Config: testAccVpnGatewayConfig, - Check: resource.ComposeTestCheckFunc( - testAccCheckVpnGatewayExists(resourceName, &vpnGateway)), - }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - }, - { - Config: testAccNoVpnGatewayConfig, - Check: resource.ComposeTestCheckFunc(testDeleted(resourceName)), - }, - }, - }) -} - func TestAccEC2VPNGateway_tags(t *testing.T) { var v ec2.VpnGateway resourceName := "aws_vpn_gateway.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -284,7 +250,7 @@ func TestAccEC2VPNGateway_tags(t *testing.T) { CheckDestroy: testAccCheckVpnGatewayDestroy, Steps: []resource.TestStep{ { - Config: testAccCheckVpnGatewayConfigTags1("key1", "value1"), + Config: testAccCheckVpnGatewayConfigTags1(rName, "key1", "value1"), Check: resource.ComposeTestCheckFunc( testAccCheckVpnGatewayExists(resourceName, &v), resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), @@ -297,7 +263,7 @@ func TestAccEC2VPNGateway_tags(t *testing.T) { ImportStateVerify: true, }, { - Config: testAccCheckVpnGatewayConfigTags2("key1", "value1updated", "key2", "value2"), + Config: testAccCheckVpnGatewayConfigTags2(rName, "key1", "value1updated", "key2", "value2"), Check: resource.ComposeTestCheckFunc( testAccCheckVpnGatewayExists(resourceName, &v), resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), @@ -306,7 +272,7 @@ func TestAccEC2VPNGateway_tags(t *testing.T) { ), }, { - Config: testAccCheckVpnGatewayConfigTags1("key2", "value2"), + Config: testAccCheckVpnGatewayConfigTags1(rName, "key2", "value2"), Check: resource.ComposeTestCheckFunc( testAccCheckVpnGatewayExists(resourceName, &v), resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), @@ -325,43 +291,23 @@ func testAccCheckVpnGatewayDestroy(s *terraform.State) error { continue } - // Try to find the resource - resp, err := conn.DescribeVpnGateways(&ec2.DescribeVpnGatewaysInput{ - VpnGatewayIds: []*string{aws.String(rs.Primary.ID)}, - }) - if err == nil { - var v *ec2.VpnGateway - for _, g := range resp.VpnGateways { - if *g.VpnGatewayId == rs.Primary.ID { - v = g - } - } - - if v == nil { - // wasn't found - return nil - } + _, err := tfec2.FindVPNGatewayByID(conn, rs.Primary.ID) - if *v.State != "deleted" { - return fmt.Errorf("Expected VPN Gateway to be in deleted state, but was not: %s", v) - } - return nil + if tfresource.NotFound(err) { + continue } - // Verify the error is what we want - ec2err, ok := err.(awserr.Error) - if !ok { - return err - } - if ec2err.Code() != "InvalidVpnGatewayID.NotFound" { + if err != nil { return err } + + return fmt.Errorf("EC2 VPN Gateway %s still exists", rs.Primary.ID) } return nil } -func testAccCheckVpnGatewayExists(n string, ig *ec2.VpnGateway) resource.TestCheckFunc { +func testAccCheckVpnGatewayExists(n string, v *ec2.VpnGateway) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] if !ok { @@ -369,56 +315,46 @@ func testAccCheckVpnGatewayExists(n string, ig *ec2.VpnGateway) resource.TestChe } if rs.Primary.ID == "" { - return fmt.Errorf("No ID is set") + return fmt.Errorf("No EC2 VPN Gateway ID is set") } conn := acctest.Provider.Meta().(*conns.AWSClient).EC2Conn - resp, err := conn.DescribeVpnGateways(&ec2.DescribeVpnGatewaysInput{ - VpnGatewayIds: []*string{aws.String(rs.Primary.ID)}, - }) + + output, err := tfec2.FindVPNGatewayByID(conn, rs.Primary.ID) + if err != nil { return err } - if len(resp.VpnGateways) == 0 { - return fmt.Errorf("VPN Gateway not found") - } - *ig = *resp.VpnGateways[0] + *v = *output return nil } } -const testAccNoVpnGatewayConfig = ` -resource "aws_vpc" "test" { - cidr_block = "10.1.0.0/16" - - tags = { - Name = "terraform-testacc-vpn-gateway-removed" - } -} -` - -const testAccVpnGatewayConfig = ` -resource "aws_vpc" "test" { +func testAccVpnGatewayConfig(rName string) string { + return fmt.Sprintf(` +resource "aws_vpc" "test1" { cidr_block = "10.1.0.0/16" tags = { - Name = "terraform-testacc-vpn-gateway" + Name = %[1]q } } resource "aws_vpn_gateway" "test" { - vpc_id = aws_vpc.test.id + vpc_id = aws_vpc.test1.id +} +`, rName) } -` -const testAccVpnGatewayConfigChangeVPC = ` -resource "aws_vpc" "test" { +func testAccVpnGatewayConfigChangeVPC(rName string) string { + return fmt.Sprintf(` +resource "aws_vpc" "test1" { cidr_block = "10.1.0.0/16" tags = { - Name = "terraform-testacc-vpn-gateway" + Name = %[1]q } } @@ -426,26 +362,23 @@ resource "aws_vpc" "test2" { cidr_block = "10.2.0.0/16" tags = { - Name = "terraform-testacc-vpn-gateway-change-vpc" + Name = %[1]q } } resource "aws_vpn_gateway" "test" { vpc_id = aws_vpc.test2.id - - tags = { - Name = "terraform-testacc-vpn-gateway-basic" - } } -` +`, rName) +} -func testAccCheckVpnGatewayConfigTags1(tagKey1, tagValue1 string) string { +func testAccCheckVpnGatewayConfigTags1(rName, tagKey1, tagValue1 string) string { return fmt.Sprintf(` resource "aws_vpc" "test" { cidr_block = "10.1.0.0/16" tags = { - Name = "terraform-testacc-vpn-gateway-tags" + Name = %[1]q } } @@ -453,19 +386,19 @@ resource "aws_vpn_gateway" "test" { vpc_id = aws_vpc.test.id tags = { - %[1]q = %[2]q + %[2]q = %[3]q } } -`, tagKey1, tagValue1) +`, rName, tagKey1, tagValue1) } -func testAccCheckVpnGatewayConfigTags2(tagKey1, tagValue1, tagKey2, tagValue2 string) string { +func testAccCheckVpnGatewayConfigTags2(rName, tagKey1, tagValue1, tagKey2, tagValue2 string) string { return fmt.Sprintf(` resource "aws_vpc" "test" { cidr_block = "10.1.0.0/16" tags = { - Name = "terraform-testacc-vpn-gateway-tags" + Name = %[1]q } } @@ -473,19 +406,20 @@ resource "aws_vpn_gateway" "test" { vpc_id = aws_vpc.test.id tags = { - %[1]q = %[2]q - %[3]q = %[4]q + %[2]q = %[3]q + %[4]q = %[5]q } } -`, tagKey1, tagValue1, tagKey2, tagValue2) +`, rName, tagKey1, tagValue1, tagKey2, tagValue2) } -const testAccCheckVpnGatewayConfigReattach = ` -resource "aws_vpc" "test" { +func testAccCheckVpnGatewayConfigReattach(rName string) string { + return fmt.Sprintf(` +resource "aws_vpc" "test1" { cidr_block = "10.1.0.0/16" tags = { - Name = "terraform-testacc-vpn-gateway-reattach-test" + Name = %[1]q } } @@ -493,15 +427,15 @@ resource "aws_vpc" "test2" { cidr_block = "10.2.0.0/16" tags = { - Name = "terraform-testacc-vpn-gateway-reattach-test2" + Name = %[1]q } } -resource "aws_vpn_gateway" "test" { - vpc_id = aws_vpc.test.id +resource "aws_vpn_gateway" "test1" { + vpc_id = aws_vpc.test1.id tags = { - Name = "terraform-testacc-vpn-gateway-reattach" + Name = %[1]q } } @@ -509,17 +443,19 @@ resource "aws_vpn_gateway" "test2" { vpc_id = aws_vpc.test2.id tags = { - Name = "terraform-testacc-vpn-gateway-reattach" + Name = %[1]q } } -` +`, rName) +} -const testAccCheckVpnGatewayConfigReattachChange = ` -resource "aws_vpc" "test" { +func testAccCheckVpnGatewayConfigReattachChange(rName string) string { + return fmt.Sprintf(` +resource "aws_vpc" "test1" { cidr_block = "10.1.0.0/16" tags = { - Name = "terraform-testacc-vpn-gateway-reattach-test" + Name = %[1]q } } @@ -527,34 +463,35 @@ resource "aws_vpc" "test2" { cidr_block = "10.2.0.0/16" tags = { - Name = "terraform-testacc-vpn-gateway-reattach-test2" + Name = %[1]q } } -resource "aws_vpn_gateway" "test" { +resource "aws_vpn_gateway" "test1" { vpc_id = aws_vpc.test2.id tags = { - Name = "terraform-testacc-vpn-gateway-reattach" + Name = %[1]q } } resource "aws_vpn_gateway" "test2" { - vpc_id = aws_vpc.test.id + vpc_id = aws_vpc.test1.id tags = { - Name = "terraform-testacc-vpn-gateway-reattach" + Name = %[1]q } } -` +`, rName) +} -func testAccVpnGatewayConfigWithAZ() string { - return acctest.ConfigCompose(acctest.ConfigAvailableAZsNoOptIn(), ` +func testAccVpnGatewayConfigWithAZ(rName string) string { + return acctest.ConfigCompose(acctest.ConfigAvailableAZsNoOptIn(), fmt.Sprintf(` resource "aws_vpc" "test" { cidr_block = "10.1.0.0/16" tags = { - Name = "terraform-testacc-vpn-gateway-with-az" + Name = %[1]q } } @@ -563,23 +500,29 @@ resource "aws_vpn_gateway" "test" { availability_zone = data.aws_availability_zones.available.names[0] tags = { - Name = "terraform-testacc-vpn-gateway-with-az" + Name = %[1]q } } -`) +`, rName)) } -const testAccVpnGatewayConfigWithASN = ` +func testAccVpnGatewayConfigWithASN(rName string) string { + return fmt.Sprintf(` resource "aws_vpc" "test" { cidr_block = "10.1.0.0/16" tags = { - Name = "terraform-testacc-vpn-gateway-with-asn" + Name = %[1]q } } resource "aws_vpn_gateway" "test" { vpc_id = aws_vpc.test.id amazon_side_asn = 4294967294 + + tags = { + Name = %[1]q + } +} +`, rName) } -` diff --git a/internal/service/ec2/wait.go b/internal/service/ec2/wait.go index acd91eae06e..535b43612a8 100644 --- a/internal/service/ec2/wait.go +++ b/internal/service/ec2/wait.go @@ -710,14 +710,15 @@ func WaitVPCAttributeUpdated(conn *ec2.EC2, vpcID string, attribute string, expe } const ( - VPNGatewayVPCAttachmentAttachedTimeout = 15 * time.Minute + VPNGatewayDeletedTimeout = 5 * time.Minute + VPNGatewayVPCAttachmentAttachedTimeout = 15 * time.Minute VPNGatewayVPCAttachmentDetachedTimeout = 30 * time.Minute ) func WaitVPNGatewayVPCAttachmentAttached(conn *ec2.EC2, vpnGatewayID, vpcID string) (*ec2.VpcAttachment, error) { stateConf := &resource.StateChangeConf{ - Pending: []string{ec2.AttachmentStatusDetached, ec2.AttachmentStatusAttaching}, + Pending: []string{ec2.AttachmentStatusAttaching}, Target: []string{ec2.AttachmentStatusAttached}, Refresh: StatusVPNGatewayVPCAttachmentState(conn, vpnGatewayID, vpcID), Timeout: VPNGatewayVPCAttachmentAttachedTimeout, @@ -735,7 +736,7 @@ func WaitVPNGatewayVPCAttachmentAttached(conn *ec2.EC2, vpnGatewayID, vpcID stri func WaitVPNGatewayVPCAttachmentDetached(conn *ec2.EC2, vpnGatewayID, vpcID string) (*ec2.VpcAttachment, error) { stateConf := &resource.StateChangeConf{ Pending: []string{ec2.AttachmentStatusAttached, ec2.AttachmentStatusDetaching}, - Target: []string{ec2.AttachmentStatusDetached}, + Target: []string{}, Refresh: StatusVPNGatewayVPCAttachmentState(conn, vpnGatewayID, vpcID), Timeout: VPNGatewayVPCAttachmentDetachedTimeout, } @@ -749,6 +750,47 @@ func WaitVPNGatewayVPCAttachmentDetached(conn *ec2.EC2, vpnGatewayID, vpcID stri return nil, err } +const ( + customerGatewayCreatedTimeout = 10 * time.Minute + customerGatewayDeletedTimeout = 5 * time.Minute +) + +func WaitCustomerGatewayCreated(conn *ec2.EC2, id string) (*ec2.CustomerGateway, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{CustomerGatewayStatePending}, + Target: []string{CustomerGatewayStateAvailable}, + Refresh: StatusCustomerGatewayState(conn, id), + Timeout: customerGatewayCreatedTimeout, + Delay: 10 * time.Second, + MinTimeout: 3 * time.Second, + } + + outputRaw, err := stateConf.WaitForState() + + if output, ok := outputRaw.(*ec2.CustomerGateway); ok { + return output, err + } + + return nil, err +} + +func WaitCustomerGatewayDeleted(conn *ec2.EC2, id string) (*ec2.CustomerGateway, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{CustomerGatewayStateAvailable, CustomerGatewayStateDeleting}, + Target: []string{}, + Refresh: StatusCustomerGatewayState(conn, id), + Timeout: customerGatewayDeletedTimeout, + } + + outputRaw, err := stateConf.WaitForState() + + if output, ok := outputRaw.(*ec2.CustomerGateway); ok { + return output, err + } + + return nil, err +} + const ( HostCreatedTimeout = 10 * time.Minute HostUpdatedTimeout = 10 * time.Minute diff --git a/internal/service/kafka/cluster_test.go b/internal/service/kafka/cluster_test.go index 8a3f04e2ccd..a34218a49ed 100644 --- a/internal/service/kafka/cluster_test.go +++ b/internal/service/kafka/cluster_test.go @@ -318,7 +318,7 @@ func TestAccKafkaCluster_ClientAuthenticationTLS_certificateAuthorityARNs(t *tes Config: testAccClusterConfigRootCA(commonName), Check: resource.ComposeTestCheckFunc( acctest.CheckACMPCACertificateAuthorityExists(acmCAResourceName, &ca), - acctest.CheckACMPCACertificateAuthorityActivateCA(&ca), + acctest.CheckACMPCACertificateAuthorityActivateRootCA(&ca), ), }, { diff --git a/internal/service/transfer/server_test.go b/internal/service/transfer/server_test.go index b68b5a33a79..8cdae5c377a 100644 --- a/internal/service/transfer/server_test.go +++ b/internal/service/transfer/server_test.go @@ -688,7 +688,7 @@ func testAccServer_protocols(t *testing.T) { Config: testAccServerRootCAConfig(rName), Check: resource.ComposeTestCheckFunc( acctest.CheckACMPCACertificateAuthorityExists(acmCAResourceName, &ca), - acctest.CheckACMPCACertificateAuthorityActivateCA(&ca), + acctest.CheckACMPCACertificateAuthorityActivateRootCA(&ca), ), }, { diff --git a/website/docs/d/customer_gateway.html.markdown b/website/docs/d/customer_gateway.html.markdown index 66dd18c0570..9542a3b5b68 100644 --- a/website/docs/d/customer_gateway.html.markdown +++ b/website/docs/d/customer_gateway.html.markdown @@ -47,8 +47,9 @@ The following arguments are supported: In addition to the arguments above, the following attributes are exported: * `arn` - The ARN of the customer gateway. -* `bgp_asn` - (Optional) The gateway's Border Gateway Protocol (BGP) Autonomous System Number (ASN). -* `device_name` - (Optional) A name for the customer gateway device. -* `ip_address` - (Optional) The IP address of the gateway's Internet-routable external interface. +* `bgp_asn` - The gateway's Border Gateway Protocol (BGP) Autonomous System Number (ASN). +* `certificate_arn` - The Amazon Resource Name (ARN) for the customer gateway certificate. +* `device_name` - A name for the customer gateway device. +* `ip_address` - The IP address of the gateway's Internet-routable external interface. * `tags` - Map of key-value pairs assigned to the gateway. -* `type` - (Optional) The type of customer gateway. The only type AWS supports at this time is "ipsec.1". +* `type` - The type of customer gateway. The only type AWS supports at this time is "ipsec.1". diff --git a/website/docs/r/customer_gateway.html.markdown b/website/docs/r/customer_gateway.html.markdown index 7836a7c6526..a79141dd62d 100644 --- a/website/docs/r/customer_gateway.html.markdown +++ b/website/docs/r/customer_gateway.html.markdown @@ -31,6 +31,7 @@ resource "aws_customer_gateway" "main" { The following arguments are supported: * `bgp_asn` - (Required) The gateway's Border Gateway Protocol (BGP) Autonomous System Number (ASN). +* `certificate_arn` - (Optional) The Amazon Resource Name (ARN) for the customer gateway certificate. * `device_name` - (Optional) A name for the customer gateway device. * `ip_address` - (Required) The IP address of the gateway's Internet-routable external interface. * `type` - (Required) The type of customer gateway. The only type AWS