From d7e9a294069640c0a9e081ce6e5db565fef20f41 Mon Sep 17 00:00:00 2001 From: Dmitry Shevchuk Date: Tue, 16 Nov 2021 13:10:14 +0200 Subject: [PATCH 1/7] Add aws_account_alternate_contact resource --- internal/conns/conns.go | 5 + internal/provider/provider.go | 3 + internal/service/account/alternate_contact.go | 146 ++++++++++++++++++ .../service/account/alternate_contact_test.go | 106 +++++++++++++ 4 files changed, 260 insertions(+) create mode 100644 internal/service/account/alternate_contact.go create mode 100644 internal/service/account/alternate_contact_test.go diff --git a/internal/conns/conns.go b/internal/conns/conns.go index 020fa86d89e..290ac209ff1 100644 --- a/internal/conns/conns.go +++ b/internal/conns/conns.go @@ -10,6 +10,7 @@ import ( "github.com/aws/aws-sdk-go/aws/request" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/accessanalyzer" + "github.com/aws/aws-sdk-go/service/account" "github.com/aws/aws-sdk-go/service/acm" "github.com/aws/aws-sdk-go/service/acmpca" "github.com/aws/aws-sdk-go/service/alexaforbusiness" @@ -286,6 +287,7 @@ import ( const ( AccessAnalyzer = "accessanalyzer" + Account = "account" ACM = "acm" ACMPCA = "acmpca" AlexaForBusiness = "alexaforbusiness" @@ -573,6 +575,7 @@ func init() { serviceData = make(map[string]*ServiceDatum) serviceData[AccessAnalyzer] = &ServiceDatum{AWSClientName: "AccessAnalyzer", AWSServiceName: accessanalyzer.ServiceName, AWSEndpointsID: accessanalyzer.EndpointsID, AWSServiceID: accessanalyzer.ServiceID, ProviderNameUpper: "AccessAnalyzer", HCLKeys: []string{"accessanalyzer"}} + serviceData[Account] = &ServiceDatum{AWSClientName: "Account", AWSServiceName: account.ServiceName, AWSEndpointsID: account.EndpointsID, AWSServiceID: account.ServiceID, ProviderNameUpper: "Account", HCLKeys: []string{"account"}} serviceData[ACM] = &ServiceDatum{AWSClientName: "ACM", AWSServiceName: acm.ServiceName, AWSEndpointsID: acm.EndpointsID, AWSServiceID: acm.ServiceID, ProviderNameUpper: "ACM", HCLKeys: []string{"acm"}} serviceData[ACMPCA] = &ServiceDatum{AWSClientName: "ACMPCA", AWSServiceName: acmpca.ServiceName, AWSEndpointsID: acmpca.EndpointsID, AWSServiceID: acmpca.ServiceID, ProviderNameUpper: "ACMPCA", HCLKeys: []string{"acmpca"}} serviceData[AlexaForBusiness] = &ServiceDatum{AWSClientName: "AlexaForBusiness", AWSServiceName: alexaforbusiness.ServiceName, AWSEndpointsID: alexaforbusiness.EndpointsID, AWSServiceID: alexaforbusiness.ServiceID, ProviderNameUpper: "AlexaForBusiness", HCLKeys: []string{"alexaforbusiness"}} @@ -881,6 +884,7 @@ type Config struct { type AWSClient struct { AccessAnalyzerConn *accessanalyzer.AccessAnalyzer + AccountConn *account.Account AccountID string ACMConn *acm.ACM ACMPCAConn *acmpca.ACMPCA @@ -1234,6 +1238,7 @@ func (c *Config) Client() (interface{}, error) { client := &AWSClient{ AccessAnalyzerConn: accessanalyzer.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[AccessAnalyzer])})), + AccountConn: account.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[Account])})), AccountID: accountID, ACMConn: acm.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[ACM])})), ACMPCAConn: acmpca.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[ACMPCA])})), diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 3999b1ea157..f53a67423cd 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -8,6 +8,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "github.com/hashicorp/terraform-provider-aws/internal/conns" "github.com/hashicorp/terraform-provider-aws/internal/service/accessanalyzer" + "github.com/hashicorp/terraform-provider-aws/internal/service/account" "github.com/hashicorp/terraform-provider-aws/internal/service/acm" "github.com/hashicorp/terraform-provider-aws/internal/service/acmpca" "github.com/hashicorp/terraform-provider-aws/internal/service/amp" @@ -712,6 +713,8 @@ func Provider() *schema.Provider { ResourcesMap: map[string]*schema.Resource{ "aws_accessanalyzer_analyzer": accessanalyzer.ResourceAnalyzer(), + "aws_account_alternate_contact": account.ResourceAlternateContact(), + "aws_acm_certificate": acm.ResourceCertificate(), "aws_acm_certificate_validation": acm.ResourceCertificateValidation(), diff --git a/internal/service/account/alternate_contact.go b/internal/service/account/alternate_contact.go new file mode 100644 index 00000000000..2ce07da5f88 --- /dev/null +++ b/internal/service/account/alternate_contact.go @@ -0,0 +1,146 @@ +package account + +import ( + "context" + "log" + "regexp" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/account" + "github.com/hashicorp/aws-sdk-go-base/tfawserr" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "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" +) + +func ResourceAlternateContact() *schema.Resource { + return &schema.Resource{ + CreateContext: resourceAlternateContactCreate, + ReadContext: resourceAlternateContactRead, + UpdateContext: resourceAlternateContactUpdate, + DeleteContext: resourceAlternateContactDelete, + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + + Schema: map[string]*schema.Schema{ + "type": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringInSlice(account.AlternateContactType_Values(), false), + }, + "name": { + Type: schema.TypeString, + Required: true, + }, + "title": { + Type: schema.TypeString, + Required: true, + }, + "email_address": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringMatch(regexp.MustCompile(`[\w+=,.-]+@[\w.-]+\.[\w]+`), "must be a valid email address"), + }, + "phone_number": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringMatch(regexp.MustCompile(`^[\s0-9()+-]+$`), "must be a valid phone number"), + }, + }, + } +} + +func resourceAlternateContactCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).AccountConn + + contactType := d.Get("type").(string) + + input := &account.PutAlternateContactInput{ + AlternateContactType: aws.String(contactType), + Name: aws.String(d.Get("name").(string)), + EmailAddress: aws.String(d.Get("email_address").(string)), + PhoneNumber: aws.String(d.Get("phone_number").(string)), + Title: aws.String(d.Get("title").(string)), + } + + log.Printf("[DEBUG] Creating %s account alternate contact: %s", contactType, input) + _, err := conn.PutAlternateContactWithContext(ctx, input) + if err != nil { + return diag.Errorf("error creating %s account alternate contact: %s", contactType, err) + } + + d.SetId(contactType) + return resourceAlternateContactRead(ctx, d, meta) +} + +func resourceAlternateContactRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).AccountConn + + alcon, err := conn.GetAlternateContactWithContext(ctx, &account.GetAlternateContactInput{ + AlternateContactType: aws.String(d.Id()), + }) + + if !d.IsNewResource() && tfawserr.ErrCodeEquals(err, account.ErrCodeResourceNotFoundException) { + log.Printf("[WARN] %s account alternate contact not found, removing from state", d.Id()) + d.SetId("") + return nil + } + + if err != nil { + return diag.Errorf("error reading %s account alternate contact: %s", d.Id(), err) + } + + d.Set("type", d.Id()) + d.Set("name", aws.StringValue(alcon.AlternateContact.Name)) + d.Set("title", aws.StringValue(alcon.AlternateContact.Title)) + d.Set("email_address", aws.StringValue(alcon.AlternateContact.EmailAddress)) + d.Set("phone_number", aws.StringValue(alcon.AlternateContact.PhoneNumber)) + + return nil +} + +func resourceAlternateContactUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).AccountConn + + contactType := d.Get("type").(string) + + input := &account.PutAlternateContactInput{ + AlternateContactType: aws.String(contactType), + Name: aws.String(d.Get("name").(string)), + EmailAddress: aws.String(d.Get("email_address").(string)), + PhoneNumber: aws.String(d.Get("phone_number").(string)), + Title: aws.String(d.Get("title").(string)), + } + + log.Printf("[DEBUG] Updating %s account alternate contact: %s", contactType, input) + _, err := conn.PutAlternateContactWithContext(ctx, input) + if err != nil { + return diag.Errorf("error updating %s account alternate contact: %s", contactType, err) + } + + return resourceAlternateContactRead(ctx, d, meta) +} + +func resourceAlternateContactDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).AccountConn + + input := &account.DeleteAlternateContactInput{ + AlternateContactType: aws.String(d.Id()), + } + + log.Printf("[DEBUG] Deleting %s account alternate contact: %s", d.Id(), input) + _, err := conn.DeleteAlternateContactWithContext(ctx, input) + + if tfawserr.ErrCodeEquals(err, account.ErrCodeResourceNotFoundException) { + return nil + } + + if err != nil { + return diag.Errorf("error deleting %s account alternate contact: %s", d.Id(), err) + } + + return nil +} diff --git a/internal/service/account/alternate_contact_test.go b/internal/service/account/alternate_contact_test.go new file mode 100644 index 00000000000..098af4123ea --- /dev/null +++ b/internal/service/account/alternate_contact_test.go @@ -0,0 +1,106 @@ +package account + +import ( + "fmt" + "testing" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/account" + "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" +) + +func TestAccAccountAlternateContact_basic(t *testing.T) { + resourceName := "aws_account_alternate_contact.test" + + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, account.EndpointsID), + ProviderFactories: acctest.ProviderFactories, + CheckDestroy: testAccountAlternateContactDestroy, + Steps: []resource.TestStep{ + { + Config: testAccountAlternateContactConfig(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckAccountAlternateContactExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "cloudfront_zone_id", "Z2FDTNDATAQYW2"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccountAlternateContactDestroy(s *terraform.State) error { + conn := acctest.Provider.Meta().(*conns.AWSClient).AccountConn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_account_alternate_contact" { + continue + } + + contactType := rs.Primary.Attributes["type"] + + input := &account.GetAlternateContactInput{AlternateContactType: aws.String(contactType)} + + resp, err := conn.GetAlternateContact(input) + + if tfawserr.ErrCodeEquals(err, account.ErrCodeResourceNotFoundException) { + return nil + } + + if err != nil { + return fmt.Errorf("error reading Account Alternate Contact (%s): %w", rs.Primary.Attributes["type"], err) + } + + if resp == nil { + return fmt.Errorf("error reading Account Alternate Contact (%s): empty response", rs.Primary.Attributes["type"]) + } + } + + return nil + +} + +func testAccCheckAccountAlternateContactExists(n string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("not found: %s", n) + } + + conn := acctest.Provider.Meta().(*conns.AWSClient).AccountConn + contactType := rs.Primary.Attributes["type"] + + input := &account.GetAlternateContactInput{AlternateContactType: aws.String(contactType)} + + _, err := conn.GetAlternateContact(input) + if err != nil { + return fmt.Errorf("error reading Account Alternate Contact (%s): %w", rs.Primary.Attributes["type"], err) + } + + return nil + } +} + +func testAccountAlternateContactConfig(rName string) string { + return fmt.Sprintf(` +resource "aws_account_alternate_contact" "test" { + type = "SECURITY" + name = %[1]q + title = %[1]q + email_address = "test@test.test" + phone_number = "+1234567890" +} +`, rName) +} From 125762b9a179b716234c133d26fa29638c9f0505 Mon Sep 17 00:00:00 2001 From: Dmitry Shevchuk Date: Tue, 16 Nov 2021 13:13:07 +0200 Subject: [PATCH 2/7] Add changelog --- .changelog/21789.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .changelog/21789.txt diff --git a/.changelog/21789.txt b/.changelog/21789.txt new file mode 100644 index 00000000000..72f4a8e1830 --- /dev/null +++ b/.changelog/21789.txt @@ -0,0 +1,3 @@ +```release-note:new-resource +aws_account_alternate_contact +``` From 7a440e2455bff622dfa88597356954d7eaa10221 Mon Sep 17 00:00:00 2001 From: Dmitry Shevchuk Date: Tue, 16 Nov 2021 15:18:50 +0200 Subject: [PATCH 3/7] Add docs --- ...ws_account_alternate_contact.html.markdown | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 website/docs/r/aws_account_alternate_contact.html.markdown diff --git a/website/docs/r/aws_account_alternate_contact.html.markdown b/website/docs/r/aws_account_alternate_contact.html.markdown new file mode 100644 index 00000000000..f5755f66ca1 --- /dev/null +++ b/website/docs/r/aws_account_alternate_contact.html.markdown @@ -0,0 +1,45 @@ +--- +subcategory: "Account" +layout: "aws" +page_title: "AWS: aws_account_alternate_contact" +description: |- + Manages the specified alternate contact attached to an AWS Account. +--- + +# Resource: aws_account_alternate_contact + +Manages the specified alternate contact attached to an AWS Account. + +## Example Usage + +```terraform +resource "aws_account_alternate_contact" "operations" { + type = "OPERATIONS" + name = "Example" + title = "Example" + email_address = "test@example.com" + phone_number = "+1234567890" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `type` - (Required) The type of the alternate contact. Allowed values are: `BILLING`, `OPERATIONS`, `SECURITY` +* `name` - (Required) The name of the alternate contact. +* `title` - (Required) A title for the alternate contact. +* `email_address` - (Required) An email address for the alternate contact. +* `phone_number` - (Required) A phone number for the alternate contact. + +## Attributes Reference + +No additional attributes are exported. + +## Import + +The current Alternate Contact can be imported using the `type`, e.g., + +``` +$ terraform import aws_account_alternate_contact.operations OPERATIONS +``` From e9a8c5fbebe2e5df1cfb702965eed0a6a5264c6d Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 19 Nov 2021 11:18:16 -0500 Subject: [PATCH 4/7] Additions for a new AWS service. --- .github/labeler-issue-triage.yml | 2 ++ .github/labeler-pr-triage.yml | 3 +++ infrastructure/repository/labels-service.tf | 1 + website/allowed-subcategories.txt | 1 + website/docs/guides/custom-service-endpoints.html.md | 1 + 5 files changed, 8 insertions(+) diff --git a/.github/labeler-issue-triage.yml b/.github/labeler-issue-triage.yml index 35753a544ab..7529b4e5dad 100644 --- a/.github/labeler-issue-triage.yml +++ b/.github/labeler-issue-triage.yml @@ -32,6 +32,8 @@ sweeper: # resource "aws_XXX" service/accessanalyzer: - '((\*|-) ?`?|(data|resource) "?)aws_accessanalyzer_' +service/account: + - '((\*|-) ?`?|(data|resource) "?)aws_account_' service/acm: - '((\*|-) ?`?|(data|resource) "?)aws_acm_' service/acmpca: diff --git a/.github/labeler-pr-triage.yml b/.github/labeler-pr-triage.yml index 2e789cfc1c4..8c14075f993 100644 --- a/.github/labeler-pr-triage.yml +++ b/.github/labeler-pr-triage.yml @@ -48,6 +48,9 @@ repository: service/accessanalyzer: - 'internal/service/accessanalyzer/**/*' - 'website/**/accessanalyzer_*' +service/account: + - 'internal/service/account/**/*' + - 'website/**/account_*' service/acm: - 'internal/service/acm/**/*' - 'website/**/acm_*' diff --git a/infrastructure/repository/labels-service.tf b/infrastructure/repository/labels-service.tf index 71282a3a360..93e58b910e5 100644 --- a/infrastructure/repository/labels-service.tf +++ b/infrastructure/repository/labels-service.tf @@ -6,6 +6,7 @@ variable "service_labels" { default = [ "accessanalyzer", + "account", "acm", "acmpca", "alexaforbusiness", diff --git a/website/allowed-subcategories.txt b/website/allowed-subcategories.txt index 676c8024b04..6a45e185fde 100644 --- a/website/allowed-subcategories.txt +++ b/website/allowed-subcategories.txt @@ -4,6 +4,7 @@ ACM API Gateway (REST APIs) API Gateway v2 (WebSocket and HTTP APIs) Access Analyzer +Account Amplify Console AppConfig AppMesh diff --git a/website/docs/guides/custom-service-endpoints.html.md b/website/docs/guides/custom-service-endpoints.html.md index af2bc7b4148..493043ca2ee 100644 --- a/website/docs/guides/custom-service-endpoints.html.md +++ b/website/docs/guides/custom-service-endpoints.html.md @@ -68,6 +68,7 @@ provider "aws" {
  • accessanalyzer
  • +
  • account
  • acm
  • acmpca
  • alexaforbusiness
  • From be5b701636364221ef34251b54b556186825f1c5 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 19 Nov 2021 11:26:25 -0500 Subject: [PATCH 5/7] Package is 'account_test'. --- internal/service/account/alternate_contact_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/service/account/alternate_contact_test.go b/internal/service/account/alternate_contact_test.go index 098af4123ea..39e1eab51ef 100644 --- a/internal/service/account/alternate_contact_test.go +++ b/internal/service/account/alternate_contact_test.go @@ -1,4 +1,4 @@ -package account +package account_test import ( "fmt" From 68730fba930303a7d0827850833fe0ad13ee3208 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 19 Nov 2021 11:31:30 -0500 Subject: [PATCH 6/7] Correct name for documentation page. --- ...tact.html.markdown => account_alternate_contact.html.markdown} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename website/docs/r/{aws_account_alternate_contact.html.markdown => account_alternate_contact.html.markdown} (100%) diff --git a/website/docs/r/aws_account_alternate_contact.html.markdown b/website/docs/r/account_alternate_contact.html.markdown similarity index 100% rename from website/docs/r/aws_account_alternate_contact.html.markdown rename to website/docs/r/account_alternate_contact.html.markdown From d95f20f7221101962ccdf04388b279662480b96a Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 19 Nov 2021 13:49:23 -0500 Subject: [PATCH 7/7] r/aws_account_alternate_contact: 'type' -> 'alternate_contact_type'. Acceptance test output: % make testacc PKG_NAME=internal/service/account TESTARGS='-run=TestAccAccountAlternateContact_' ==> Checking that code complies with gofmt requirements... TF_ACC=1 go test ./internal/service/account/... -v -count 1 -parallel 20 -run=TestAccAccountAlternateContact_ -timeout 180m === RUN TestAccAccountAlternateContact_basic --- PASS: TestAccAccountAlternateContact_basic (27.04s) === RUN TestAccAccountAlternateContact_disappears --- PASS: TestAccAccountAlternateContact_disappears (11.69s) PASS ok github.com/hashicorp/terraform-provider-aws/internal/service/account 42.243s --- internal/service/account/alternate_contact.go | 99 +++++++++------ .../service/account/alternate_contact_test.go | 113 +++++++++++++----- .../r/account_alternate_contact.html.markdown | 11 +- 3 files changed, 153 insertions(+), 70 deletions(-) diff --git a/internal/service/account/alternate_contact.go b/internal/service/account/alternate_contact.go index 2ce07da5f88..c005c882e78 100644 --- a/internal/service/account/alternate_contact.go +++ b/internal/service/account/alternate_contact.go @@ -9,9 +9,11 @@ import ( "github.com/aws/aws-sdk-go/service/account" "github.com/hashicorp/aws-sdk-go-base/tfawserr" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) func ResourceAlternateContact() *schema.Resource { @@ -25,30 +27,32 @@ func ResourceAlternateContact() *schema.Resource { }, Schema: map[string]*schema.Schema{ - "type": { + "alternate_contact_type": { Type: schema.TypeString, Required: true, ForceNew: true, ValidateFunc: validation.StringInSlice(account.AlternateContactType_Values(), false), }, - "name": { - Type: schema.TypeString, - Required: true, - }, - "title": { - Type: schema.TypeString, - Required: true, - }, "email_address": { Type: schema.TypeString, Required: true, ValidateFunc: validation.StringMatch(regexp.MustCompile(`[\w+=,.-]+@[\w.-]+\.[\w]+`), "must be a valid email address"), }, + "name": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringLenBetween(1, 64), + }, "phone_number": { Type: schema.TypeString, Required: true, ValidateFunc: validation.StringMatch(regexp.MustCompile(`^[\s0-9()+-]+$`), "must be a valid phone number"), }, + "title": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringLenBetween(1, 50), + }, }, } } @@ -56,48 +60,47 @@ func ResourceAlternateContact() *schema.Resource { func resourceAlternateContactCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { conn := meta.(*conns.AWSClient).AccountConn - contactType := d.Get("type").(string) - + contactType := d.Get("alternate_contact_type").(string) input := &account.PutAlternateContactInput{ AlternateContactType: aws.String(contactType), - Name: aws.String(d.Get("name").(string)), EmailAddress: aws.String(d.Get("email_address").(string)), + Name: aws.String(d.Get("name").(string)), PhoneNumber: aws.String(d.Get("phone_number").(string)), Title: aws.String(d.Get("title").(string)), } - log.Printf("[DEBUG] Creating %s account alternate contact: %s", contactType, input) + log.Printf("[DEBUG] Creating Account Alternate Contact: %s", input) _, err := conn.PutAlternateContactWithContext(ctx, input) + if err != nil { - return diag.Errorf("error creating %s account alternate contact: %s", contactType, err) + return diag.Errorf("error creating Account Alternate Contact (%s): %s", contactType, err) } d.SetId(contactType) + return resourceAlternateContactRead(ctx, d, meta) } func resourceAlternateContactRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { conn := meta.(*conns.AWSClient).AccountConn - alcon, err := conn.GetAlternateContactWithContext(ctx, &account.GetAlternateContactInput{ - AlternateContactType: aws.String(d.Id()), - }) + output, err := FindAlternateContactByContactType(ctx, conn, d.Id()) - if !d.IsNewResource() && tfawserr.ErrCodeEquals(err, account.ErrCodeResourceNotFoundException) { - log.Printf("[WARN] %s account alternate contact not found, removing from state", d.Id()) + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] Account Alternate Contact (%s) not found, removing from state", d.Id()) d.SetId("") return nil } if err != nil { - return diag.Errorf("error reading %s account alternate contact: %s", d.Id(), err) + return diag.Errorf("error reading Account Alternate Contact (%s): %s", d.Id(), err) } - d.Set("type", d.Id()) - d.Set("name", aws.StringValue(alcon.AlternateContact.Name)) - d.Set("title", aws.StringValue(alcon.AlternateContact.Title)) - d.Set("email_address", aws.StringValue(alcon.AlternateContact.EmailAddress)) - d.Set("phone_number", aws.StringValue(alcon.AlternateContact.PhoneNumber)) + d.Set("alternate_contact_type", output.AlternateContactType) + d.Set("email_address", output.EmailAddress) + d.Set("name", output.Name) + d.Set("phone_number", output.PhoneNumber) + d.Set("title", output.Title) return nil } @@ -105,20 +108,19 @@ func resourceAlternateContactRead(ctx context.Context, d *schema.ResourceData, m func resourceAlternateContactUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { conn := meta.(*conns.AWSClient).AccountConn - contactType := d.Get("type").(string) - input := &account.PutAlternateContactInput{ - AlternateContactType: aws.String(contactType), - Name: aws.String(d.Get("name").(string)), + AlternateContactType: aws.String(d.Id()), EmailAddress: aws.String(d.Get("email_address").(string)), + Name: aws.String(d.Get("name").(string)), PhoneNumber: aws.String(d.Get("phone_number").(string)), Title: aws.String(d.Get("title").(string)), } - log.Printf("[DEBUG] Updating %s account alternate contact: %s", contactType, input) + log.Printf("[DEBUG] Updating Account Alternate Contact: %s", input) _, err := conn.PutAlternateContactWithContext(ctx, input) + if err != nil { - return diag.Errorf("error updating %s account alternate contact: %s", contactType, err) + return diag.Errorf("error updating Account Alternate Contact (%s): %s", d.Id(), err) } return resourceAlternateContactRead(ctx, d, meta) @@ -127,20 +129,43 @@ func resourceAlternateContactUpdate(ctx context.Context, d *schema.ResourceData, func resourceAlternateContactDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { conn := meta.(*conns.AWSClient).AccountConn - input := &account.DeleteAlternateContactInput{ + log.Printf("[DEBUG] Deleting Account Alternate Contact: %s", d.Id()) + _, err := conn.DeleteAlternateContactWithContext(ctx, &account.DeleteAlternateContactInput{ AlternateContactType: aws.String(d.Id()), - } - - log.Printf("[DEBUG] Deleting %s account alternate contact: %s", d.Id(), input) - _, err := conn.DeleteAlternateContactWithContext(ctx, input) + }) if tfawserr.ErrCodeEquals(err, account.ErrCodeResourceNotFoundException) { return nil } if err != nil { - return diag.Errorf("error deleting %s account alternate contact: %s", d.Id(), err) + return diag.Errorf("error deleting Account Alternate Contact (%s): %s", d.Id(), err) } return nil } + +func FindAlternateContactByContactType(ctx context.Context, conn *account.Account, contactType string) (*account.AlternateContact, error) { + input := &account.GetAlternateContactInput{ + AlternateContactType: aws.String(contactType), + } + + output, err := conn.GetAlternateContactWithContext(ctx, input) + + if tfawserr.ErrCodeEquals(err, account.ErrCodeResourceNotFoundException) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + if output == nil || output.AlternateContact == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + return output.AlternateContact, nil +} diff --git a/internal/service/account/alternate_contact_test.go b/internal/service/account/alternate_contact_test.go index 39e1eab51ef..3b4724df73d 100644 --- a/internal/service/account/alternate_contact_test.go +++ b/internal/service/account/alternate_contact_test.go @@ -1,35 +1,43 @@ package account_test import ( + "context" "fmt" "testing" - "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/account" - "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" + tfaccount "github.com/hashicorp/terraform-provider-aws/internal/service/account" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) func TestAccAccountAlternateContact_basic(t *testing.T) { resourceName := "aws_account_alternate_contact.test" - - rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + domain := acctest.RandomDomainName() + emailAddress1 := acctest.RandomEmailAddress(domain) + emailAddress2 := acctest.RandomEmailAddress(domain) + rName1 := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + rName2 := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resource.Test(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(t) }, + PreCheck: func() { acctest.PreCheck(t); testAccPreCheck(t) }, ErrorCheck: acctest.ErrorCheck(t, account.EndpointsID), ProviderFactories: acctest.ProviderFactories, CheckDestroy: testAccountAlternateContactDestroy, Steps: []resource.TestStep{ { - Config: testAccountAlternateContactConfig(rName), + Config: testAccountAlternateContactConfig(rName1, emailAddress1), Check: resource.ComposeAggregateTestCheckFunc( testAccCheckAccountAlternateContactExists(resourceName), - resource.TestCheckResourceAttr(resourceName, "cloudfront_zone_id", "Z2FDTNDATAQYW2"), + resource.TestCheckResourceAttr(resourceName, "alternate_contact_type", "OPERATIONS"), + resource.TestCheckResourceAttr(resourceName, "email_address", emailAddress1), + resource.TestCheckResourceAttr(resourceName, "name", rName1), + resource.TestCheckResourceAttr(resourceName, "phone_number", "+17031235555"), + resource.TestCheckResourceAttr(resourceName, "title", rName1), ), }, { @@ -37,11 +45,47 @@ func TestAccAccountAlternateContact_basic(t *testing.T) { ImportState: true, ImportStateVerify: true, }, + { + Config: testAccountAlternateContactConfig(rName2, emailAddress2), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckAccountAlternateContactExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "alternate_contact_type", "OPERATIONS"), + resource.TestCheckResourceAttr(resourceName, "email_address", emailAddress2), + resource.TestCheckResourceAttr(resourceName, "name", rName2), + resource.TestCheckResourceAttr(resourceName, "phone_number", "+17031235555"), + resource.TestCheckResourceAttr(resourceName, "title", rName2), + ), + }, + }, + }) +} + +func TestAccAccountAlternateContact_disappears(t *testing.T) { + resourceName := "aws_account_alternate_contact.test" + domain := acctest.RandomDomainName() + emailAddress := acctest.RandomEmailAddress(domain) + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t); testAccPreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, account.EndpointsID), + ProviderFactories: acctest.ProviderFactories, + CheckDestroy: testAccountAlternateContactDestroy, + Steps: []resource.TestStep{ + { + Config: testAccountAlternateContactConfig(rName, emailAddress), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckAccountAlternateContactExists(resourceName), + acctest.CheckResourceDisappears(acctest.Provider, tfaccount.ResourceAlternateContact(), resourceName), + ), + ExpectNonEmptyPlan: true, + }, }, }) } func testAccountAlternateContactDestroy(s *terraform.State) error { + ctx := context.TODO() conn := acctest.Provider.Meta().(*conns.AWSClient).AccountConn for _, rs := range s.RootModule().Resources { @@ -49,23 +93,17 @@ func testAccountAlternateContactDestroy(s *terraform.State) error { continue } - contactType := rs.Primary.Attributes["type"] - - input := &account.GetAlternateContactInput{AlternateContactType: aws.String(contactType)} + _, err := tfaccount.FindAlternateContactByContactType(ctx, conn, rs.Primary.ID) - resp, err := conn.GetAlternateContact(input) - - if tfawserr.ErrCodeEquals(err, account.ErrCodeResourceNotFoundException) { - return nil + if tfresource.NotFound(err) { + continue } if err != nil { - return fmt.Errorf("error reading Account Alternate Contact (%s): %w", rs.Primary.Attributes["type"], err) + return err } - if resp == nil { - return fmt.Errorf("error reading Account Alternate Contact (%s): empty response", rs.Primary.Attributes["type"]) - } + return fmt.Errorf("Account Alternate Contact %s still exists", rs.Primary.ID) } return nil @@ -76,31 +114,50 @@ func testAccCheckAccountAlternateContactExists(n string) resource.TestCheckFunc return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] if !ok { - return fmt.Errorf("not found: %s", n) + return fmt.Errorf("Not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No Account Alternate Contact ID is set") } + ctx := context.TODO() conn := acctest.Provider.Meta().(*conns.AWSClient).AccountConn - contactType := rs.Primary.Attributes["type"] - input := &account.GetAlternateContactInput{AlternateContactType: aws.String(contactType)} + _, err := tfaccount.FindAlternateContactByContactType(ctx, conn, rs.Primary.ID) - _, err := conn.GetAlternateContact(input) if err != nil { - return fmt.Errorf("error reading Account Alternate Contact (%s): %w", rs.Primary.Attributes["type"], err) + return err } return nil } } -func testAccountAlternateContactConfig(rName string) string { +func testAccountAlternateContactConfig(rName, emailAddress string) string { return fmt.Sprintf(` resource "aws_account_alternate_contact" "test" { - type = "SECURITY" + alternate_contact_type = "OPERATIONS" + + email_address = %[2]q name = %[1]q + phone_number = "+17031235555" title = %[1]q - email_address = "test@test.test" - phone_number = "+1234567890" } -`, rName) +`, rName, emailAddress) +} + +func testAccPreCheck(t *testing.T) { + ctx := context.TODO() + conn := acctest.Provider.Meta().(*conns.AWSClient).AccountConn + + _, err := tfaccount.FindAlternateContactByContactType(ctx, conn, account.AlternateContactTypeOperations) + + if acctest.PreCheckSkipError(err) { + t.Skipf("skipping acceptance testing: %s", err) + } + + if err != nil && !tfresource.NotFound(err) { + t.Fatalf("unexpected PreCheck error: %s", err) + } } diff --git a/website/docs/r/account_alternate_contact.html.markdown b/website/docs/r/account_alternate_contact.html.markdown index f5755f66ca1..9ab5dce8b95 100644 --- a/website/docs/r/account_alternate_contact.html.markdown +++ b/website/docs/r/account_alternate_contact.html.markdown @@ -14,7 +14,8 @@ Manages the specified alternate contact attached to an AWS Account. ```terraform resource "aws_account_alternate_contact" "operations" { - type = "OPERATIONS" + alternate_contact_type = "OPERATIONS" + name = "Example" title = "Example" email_address = "test@example.com" @@ -26,11 +27,11 @@ resource "aws_account_alternate_contact" "operations" { The following arguments are supported: -* `type` - (Required) The type of the alternate contact. Allowed values are: `BILLING`, `OPERATIONS`, `SECURITY` -* `name` - (Required) The name of the alternate contact. -* `title` - (Required) A title for the alternate contact. +* `alternate_contact_type` - (Required) The type of the alternate contact. Allowed values are: `BILLING`, `OPERATIONS`, `SECURITY`. * `email_address` - (Required) An email address for the alternate contact. +* `name` - (Required) The name of the alternate contact. * `phone_number` - (Required) A phone number for the alternate contact. +* `title` - (Required) A title for the alternate contact. ## Attributes Reference @@ -38,7 +39,7 @@ No additional attributes are exported. ## Import -The current Alternate Contact can be imported using the `type`, e.g., +The current Alternate Contact can be imported using the `alternate_contact_type`, e.g., ``` $ terraform import aws_account_alternate_contact.operations OPERATIONS