From dc397fbd838a418ebc1cb1adf3c550207bffc921 Mon Sep 17 00:00:00 2001 From: Angie Pinilla Date: Wed, 24 Feb 2021 22:33:32 -0500 Subject: [PATCH] refactor UI customization settings; add test coverage and documentation --- .changelog/8114.txt | 8 +- .../cognitoidentityprovider/finder/finder.go | 13 +- aws/provider.go | 3 +- aws/resource_aws_cognito_user_pool.go | 132 ---- aws/resource_aws_cognito_user_pool_client.go | 101 --- ...ource_aws_cognito_user_pool_client_test.go | 410 ---------- aws/resource_aws_cognito_user_pool_test.go | 242 ------ ..._aws_cognito_user_pool_ui_customization.go | 223 ++++++ ...cognito_user_pool_ui_customization_test.go | 706 ++++++++++++++++++ website/docs/r/cognito_user_pool.markdown | 32 +- .../docs/r/cognito_user_pool_client.markdown | 37 +- ...o_user_pool_ui_customization.html.markdown | 92 +++ 12 files changed, 1033 insertions(+), 966 deletions(-) create mode 100644 aws/resource_aws_cognito_user_pool_ui_customization.go create mode 100644 aws/resource_aws_cognito_user_pool_ui_customization_test.go create mode 100644 website/docs/r/cognito_user_pool_ui_customization.html.markdown diff --git a/.changelog/8114.txt b/.changelog/8114.txt index adce5755345..4538eb4af9a 100644 --- a/.changelog/8114.txt +++ b/.changelog/8114.txt @@ -1,7 +1,3 @@ -```release-note:enhancement -resource/aws_cognito_user_pool: Add `ui_customization` argument -``` - -```release-note:enhancement -resource/aws_cognito_user_pool_client: Add `ui_customization` argument +```release-note:new-resource +aws_cognito_user_pool_ui_customization ``` diff --git a/aws/internal/service/cognitoidentityprovider/finder/finder.go b/aws/internal/service/cognitoidentityprovider/finder/finder.go index 8c8f27b69f7..692a7bad474 100644 --- a/aws/internal/service/cognitoidentityprovider/finder/finder.go +++ b/aws/internal/service/cognitoidentityprovider/finder/finder.go @@ -3,18 +3,16 @@ package finder import ( "reflect" + "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/cognitoidentityprovider" ) -// CognitoUserPoolUICustomization returns the UI Customization corresponding to the UserPoolId and ClientId, if provided. +// CognitoUserPoolUICustomization returns the UI Customization corresponding to the UserPoolId and ClientId. // Returns nil if no UI Customization is found. -func CognitoUserPoolUICustomization(conn *cognitoidentityprovider.CognitoIdentityProvider, userPoolId, clientId *string) (*cognitoidentityprovider.UICustomizationType, error) { +func CognitoUserPoolUICustomization(conn *cognitoidentityprovider.CognitoIdentityProvider, userPoolId, clientId string) (*cognitoidentityprovider.UICustomizationType, error) { input := &cognitoidentityprovider.GetUICustomizationInput{ - UserPoolId: userPoolId, - } - - if clientId != nil { - input.ClientId = clientId + ClientId: aws.String(clientId), + UserPoolId: aws.String(userPoolId), } output, err := conn.GetUICustomization(input) @@ -29,7 +27,6 @@ func CognitoUserPoolUICustomization(conn *cognitoidentityprovider.CognitoIdentit // The GetUICustomization API operation will return an empty struct // if nothing is present rather than nil or an error, so we equate that with nil - // to prevent non-empty plans of an empty ui_customization block if reflect.DeepEqual(output.UICustomization, &cognitoidentityprovider.UICustomizationType{}) { return nil, nil } diff --git a/aws/provider.go b/aws/provider.go index 29273c51f04..00ba0af2e9b 100644 --- a/aws/provider.go +++ b/aws/provider.go @@ -519,13 +519,14 @@ func Provider() *schema.Provider { "aws_cognito_identity_pool": resourceAwsCognitoIdentityPool(), "aws_cognito_identity_pool_roles_attachment": resourceAwsCognitoIdentityPoolRolesAttachment(), "aws_cognito_identity_provider": resourceAwsCognitoIdentityProvider(), + "aws_cognito_resource_server": resourceAwsCognitoResourceServer(), "aws_cognito_user_group": resourceAwsCognitoUserGroup(), "aws_cognito_user_pool": resourceAwsCognitoUserPool(), "aws_cognito_user_pool_client": resourceAwsCognitoUserPoolClient(), "aws_cognito_user_pool_domain": resourceAwsCognitoUserPoolDomain(), + "aws_cognito_user_pool_ui_customization": resourceAwsCognitoUserPoolUICustomization(), "aws_cloudhsm_v2_cluster": resourceAwsCloudHsmV2Cluster(), "aws_cloudhsm_v2_hsm": resourceAwsCloudHsmV2Hsm(), - "aws_cognito_resource_server": resourceAwsCognitoResourceServer(), "aws_cloudwatch_composite_alarm": resourceAwsCloudWatchCompositeAlarm(), "aws_cloudwatch_metric_alarm": resourceAwsCloudWatchMetricAlarm(), "aws_cloudwatch_dashboard": resourceAwsCloudWatchDashboard(), diff --git a/aws/resource_aws_cognito_user_pool.go b/aws/resource_aws_cognito_user_pool.go index 8bd562a3d6f..341ee3043f1 100644 --- a/aws/resource_aws_cognito_user_pool.go +++ b/aws/resource_aws_cognito_user_pool.go @@ -1,8 +1,6 @@ package aws import ( - "context" - "encoding/base64" "fmt" "log" "regexp" @@ -10,13 +8,10 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/cognitoidentityprovider" - "github.com/hashicorp/aws-sdk-go-base/tfawserr" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff" "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/terraform-providers/terraform-provider-aws/aws/internal/keyvaluetags" - "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/cognitoidentityprovider/finder" ) func resourceAwsCognitoUserPool() *schema.Resource { @@ -567,41 +562,7 @@ func resourceAwsCognitoUserPool() *schema.Resource { }, }, }, - - "ui_customization": { - Type: schema.TypeList, - Optional: true, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "css": { - Type: schema.TypeString, - Optional: true, - }, - "css_version": { - Type: schema.TypeString, - Computed: true, - }, - "image_file": { - Type: schema.TypeString, - Optional: true, - }, - "image_url": { - Type: schema.TypeString, - Computed: true, - }, - }, - }, - }, }, - - CustomizeDiff: customdiff.Sequence( - // A "ui_customization" cannot be removed from a Cognito User Pool resource; - // thus, resource recreation is triggered on configuration block removal - customdiff.ForceNewIfChange("ui_customization", func(_ context.Context, old, new, meta interface{}) bool { - return len(old.([]interface{})) == 1 && len(new.([]interface{})) == 0 - }), - ), } } @@ -973,25 +934,6 @@ func resourceAwsCognitoUserPoolRead(d *schema.ResourceData, meta interface{}) er return fmt.Errorf("error setting software_token_mfa_configuration: %w", err) } - // Retrieve UICustomization iff the User Pool is associated with a Domain - if resp.UserPool.Domain != nil { - uiCustomization, err := finder.CognitoUserPoolUICustomization(conn, resp.UserPool.Id, nil) - - if tfawserr.ErrCodeEquals(err, cognitoidentityprovider.ErrCodeResourceNotFoundException) { - log.Printf("[WARN] Cognito User Pool (%s) not found, removing from state", d.Id()) - d.SetId("") - return nil - } - - if err != nil { - return fmt.Errorf("error getting Cognito User Pool (%s) UI customization: %w", d.Id(), err) - } - - if err := d.Set("ui_customization", flattenCognitoUserPoolUICustomization(d, uiCustomization)); err != nil { - return fmt.Errorf("error setting ui_customization: %w", err) - } - } - return nil } @@ -1252,26 +1194,6 @@ func resourceAwsCognitoUserPoolUpdate(d *schema.ResourceData, meta interface{}) } } - if d.HasChange("ui_customization") { - if v, ok := d.GetOk("ui_customization"); ok { - input, err := expandCognitoUserPoolUICustomizationInput(v.([]interface{})) - - if err != nil { - return fmt.Errorf("error updating Cognito User pool (%s) UI customization: %w", d.Id(), err) - } - - if input != nil { - input.UserPoolId = aws.String(d.Id()) - - _, err := conn.SetUICustomization(input) - - if err != nil { - return fmt.Errorf("error updating Cognito User pool (%s) UI customization: %w", d.Id(), err) - } - } - } - } - return resourceAwsCognitoUserPoolRead(d, meta) } @@ -1390,33 +1312,6 @@ func expandCognitoUserPoolAccountRecoverySettingConfig(config map[string]interfa return configs } -func expandCognitoUserPoolUICustomizationInput(l []interface{}) (*cognitoidentityprovider.SetUICustomizationInput, error) { - if len(l) == 0 || l[0] == nil { - return nil, nil - } - - tfMap, ok := l[0].(map[string]interface{}) - if !ok { - return nil, nil - } - - input := &cognitoidentityprovider.SetUICustomizationInput{} - - if v, ok := tfMap["css"].(string); ok && v != "" { - input.CSS = aws.String(v) - } - - if v, ok := tfMap["image_file"].(string); ok && v != "" { - imgFile, err := base64.StdEncoding.DecodeString(v) - if err != nil { - return nil, err - } - input.ImageFile = imgFile - } - - return input, nil -} - func flattenCognitoUserPoolAccountRecoverySettingConfig(config *cognitoidentityprovider.AccountRecoverySettingType) []interface{} { if config == nil { return nil @@ -1438,30 +1333,3 @@ func flattenCognitoUserPoolAccountRecoverySettingConfig(config *cognitoidentityp return []interface{}{settings} } - -func flattenCognitoUserPoolUICustomization(d *schema.ResourceData, ui *cognitoidentityprovider.UICustomizationType) []interface{} { - if ui == nil { - return nil - } - - m := map[string]interface{}{ - "css": aws.StringValue(ui.CSS), - "css_version": aws.StringValue(ui.CSSVersion), - "image_url": aws.StringValue(ui.ImageUrl), - } - - if ui.ImageUrl != nil { - // repopulate image_file content from state, if available, - // else, the value will be overwritten - if v, ok := d.GetOk("ui_customization"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { - tfMap, ok := v.([]interface{})[0].(map[string]interface{}) - if ok { - if imgFile, ok := tfMap["image_file"].(string); ok { - m["image_file"] = imgFile - } - } - } - } - - return []interface{}{m} -} diff --git a/aws/resource_aws_cognito_user_pool_client.go b/aws/resource_aws_cognito_user_pool_client.go index 37f0ec3edb7..e2132390e5a 100644 --- a/aws/resource_aws_cognito_user_pool_client.go +++ b/aws/resource_aws_cognito_user_pool_client.go @@ -1,18 +1,14 @@ package aws import ( - "context" "fmt" "log" "strings" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/cognitoidentityprovider" - "github.com/hashicorp/aws-sdk-go-base/tfawserr" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" - "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/cognitoidentityprovider/finder" ) func resourceAwsCognitoUserPoolClient() *schema.Resource { @@ -149,7 +145,6 @@ func resourceAwsCognitoUserPoolClient() *schema.Resource { Type: schema.TypeString, }, }, - "analytics_configuration": { Type: schema.TypeList, Optional: true, @@ -188,41 +183,7 @@ func resourceAwsCognitoUserPoolClient() *schema.Resource { }, }, }, - - "ui_customization": { - Type: schema.TypeList, - Optional: true, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "css": { - Type: schema.TypeString, - Optional: true, - }, - "css_version": { - Type: schema.TypeString, - Computed: true, - }, - "image_file": { - Type: schema.TypeString, - Optional: true, - }, - "image_url": { - Type: schema.TypeString, - Computed: true, - }, - }, - }, - }, }, - - CustomizeDiff: customdiff.Sequence( - // A "ui_customization" cannot be removed from a Cognito User Pool Client resource; - // thus, resource recreation is triggered on configuration block removal. - customdiff.ForceNewIfChange("ui_customization", func(_ context.Context, old, new, meta interface{}) bool { - return len(old.([]interface{})) == 1 && len(new.([]interface{})) == 0 - }), - ), } } @@ -300,25 +261,6 @@ func resourceAwsCognitoUserPoolClientCreate(d *schema.ResourceData, meta interfa d.SetId(aws.StringValue(resp.UserPoolClient.ClientId)) - if v, ok := d.GetOk("ui_customization"); ok { - input, err := expandCognitoUserPoolUICustomizationInput(v.([]interface{})) - - if err != nil { - return fmt.Errorf("error setting Cognito User Pool Client (%s) UI customization: %w", d.Id(), err) - } - - if input != nil { - input.ClientId = aws.String(d.Id()) - input.UserPoolId = aws.String(d.Get("user_pool_id").(string)) - - _, err := conn.SetUICustomization(input) - - if err != nil { - return fmt.Errorf("error setting Cognito User Pool Client (%s) UI customization: %w", d.Id(), err) - } - } - } - return resourceAwsCognitoUserPoolClientRead(d, meta) } @@ -364,28 +306,6 @@ func resourceAwsCognitoUserPoolClientRead(d *schema.ResourceData, meta interface return fmt.Errorf("error setting analytics_configuration: %s", err) } - // Retrieve UICustomization of the User Pool the Client belongs to; - // expect to receive an InvalidParameterException if there is no associated Domain - uiCustomization, err := finder.CognitoUserPoolUICustomization(conn, resp.UserPoolClient.UserPoolId, aws.String(d.Id())) - - if tfawserr.ErrCodeEquals(err, cognitoidentityprovider.ErrCodeResourceNotFoundException) { - log.Printf("[WARN] Cognito User Pool Client (%s) not found, removing from state", d.Id()) - d.SetId("") - return nil - } - - if tfawserr.ErrMessageContains(err, cognitoidentityprovider.ErrCodeInvalidParameterException, "There has to be an existing domain associated with this user pool") { - return nil - } - - if err != nil { - return fmt.Errorf("error getting Cognito User Pool Client (%s) UI customization: %w", d.Id(), err) - } - - if err := d.Set("ui_customization", flattenCognitoUserPoolUICustomization(d, uiCustomization)); err != nil { - return fmt.Errorf("error setting ui_customization: %w", err) - } - return nil } @@ -460,27 +380,6 @@ func resourceAwsCognitoUserPoolClientUpdate(d *schema.ResourceData, meta interfa return fmt.Errorf("Error updating Cognito User Pool Client: %s", err) } - if d.HasChange("ui_customization") { - if v, ok := d.GetOk("ui_customization"); ok { - input, err := expandCognitoUserPoolUICustomizationInput(v.([]interface{})) - - if err != nil { - return fmt.Errorf("error updating Cognito User Pool Client (%s) UI customization: %w", d.Id(), err) - } - - if input != nil { - input.ClientId = aws.String(d.Id()) - input.UserPoolId = aws.String(d.Get("user_pool_id").(string)) - - _, err := conn.SetUICustomization(input) - - if err != nil { - return fmt.Errorf("error updating Cognito User Pool Client (%s) UI customization: %w", d.Id(), err) - } - } - } - } - return resourceAwsCognitoUserPoolClientRead(d, meta) } diff --git a/aws/resource_aws_cognito_user_pool_client_test.go b/aws/resource_aws_cognito_user_pool_client_test.go index a3bd0628c1f..d1fff425c1b 100644 --- a/aws/resource_aws_cognito_user_pool_client_test.go +++ b/aws/resource_aws_cognito_user_pool_client_test.go @@ -30,7 +30,6 @@ func TestAccAWSCognitoUserPoolClient_basic(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "name", clientName), resource.TestCheckResourceAttr(resourceName, "explicit_auth_flows.#", "1"), resource.TestCheckTypeSetElemAttr(resourceName, "explicit_auth_flows.*", "ADMIN_NO_SRP_AUTH"), - resource.TestCheckResourceAttr(resourceName, "ui_customization.#", "0"), ), }, { @@ -334,225 +333,6 @@ func TestAccAWSCognitoUserPoolClient_disappears(t *testing.T) { }) } -func TestAccAWSCognitoUserPoolClient_UICustomization_CSS(t *testing.T) { - var before, after cognitoidentityprovider.UserPoolClientType - rName := acctest.RandomWithPrefix("tf-acc-test") - resourceName := "aws_cognito_user_pool_client.test" - - css := ".label-customizable {font-weight: 400;}" - cssUpdated := ".label-customizable {font-weight: 100;}" - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - Providers: testAccProviders, - CheckDestroy: testAccCheckAWSCognitoUserPoolClientDestroy, - Steps: []resource.TestStep{ - { - Config: testAccAWSCognitoUserPoolClientConfig_UICustomizationCSS(rName, css), - Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckAWSCognitoUserPoolClientExists(resourceName, &before), - resource.TestCheckResourceAttr(resourceName, "ui_customization.#", "1"), - resource.TestCheckResourceAttr(resourceName, "ui_customization.0.css", css), - ), - }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateIdFunc: testAccAWSCognitoUserPoolClientImportStateIDFunc(resourceName), - ImportStateVerify: true, - }, - { - Config: testAccAWSCognitoUserPoolClientConfig_UICustomizationCSS(rName, cssUpdated), - Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckAWSCognitoUserPoolClientExists(resourceName, &before), - resource.TestCheckResourceAttr(resourceName, "ui_customization.#", "1"), - resource.TestCheckResourceAttr(resourceName, "ui_customization.0.css", cssUpdated), - ), - }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateIdFunc: testAccAWSCognitoUserPoolClientImportStateIDFunc(resourceName), - ImportStateVerify: true, - }, - { - // Test removing the UICustomization settings, forcing new resource - Config: testAccAWSCognitoUserPoolClientConfig_UICustomizationRemoved(rName), - Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckAWSCognitoUserPoolClientExists(resourceName, &after), - testAccCheckAWSCognitoUserPoolClientRecreated(&before, &after), - resource.TestCheckResourceAttr(resourceName, "ui_customization.#", "0"), - ), - }, - }, - }) -} - -func TestAccAWSCognitoUserPoolClient_UICustomization_ImageFile(t *testing.T) { - var before, after cognitoidentityprovider.UserPoolClientType - rName := acctest.RandomWithPrefix("tf-acc-test") - resourceName := "aws_cognito_user_pool_client.test" - - filename := "testdata/service/cognitoidentityprovider/logo.png" - updatedFilename := "testdata/service/cognitoidentityprovider/logo_modified.png" - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - Providers: testAccProviders, - CheckDestroy: testAccCheckAWSCognitoUserPoolClientDestroy, - Steps: []resource.TestStep{ - { - Config: testAccAWSCognitoUserPoolClientConfig_UICustomizationImage(rName, filename), - Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckAWSCognitoUserPoolClientExists(resourceName, &before), - resource.TestCheckResourceAttr(resourceName, "ui_customization.#", "1"), - resource.TestCheckResourceAttrSet(resourceName, "ui_customization.0.image_url"), - ), - }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateIdFunc: testAccAWSCognitoUserPoolClientImportStateIDFunc(resourceName), - ImportStateVerify: true, - ImportStateVerifyIgnore: []string{"ui_customization.0.image_file"}, - }, - { - Config: testAccAWSCognitoUserPoolClientConfig_UICustomizationImage(rName, updatedFilename), - Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckAWSCognitoUserPoolClientExists(resourceName, &before), - resource.TestCheckResourceAttr(resourceName, "ui_customization.#", "1"), - resource.TestCheckResourceAttrSet(resourceName, "ui_customization.0.image_url"), - ), - }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateIdFunc: testAccAWSCognitoUserPoolClientImportStateIDFunc(resourceName), - ImportStateVerify: true, - ImportStateVerifyIgnore: []string{"ui_customization.0.image_file"}, - }, - { - // Test removing the UICustomization settings, forcing new resource - Config: testAccAWSCognitoUserPoolClientConfig_UICustomizationRemoved(rName), - Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckAWSCognitoUserPoolClientExists(resourceName, &before), - testAccCheckAWSCognitoUserPoolClientRecreated(&before, &after), - resource.TestCheckResourceAttr(resourceName, "ui_customization.#", "0"), - ), - }, - }, - }) -} - -func TestAccAWSCognitoUserPoolClient_UICustomization_CSSandImageFile(t *testing.T) { - var client cognitoidentityprovider.UserPoolClientType - rName := acctest.RandomWithPrefix("tf-acc-test") - resourceName := "aws_cognito_user_pool_client.test" - - css := ".label-customizable {font-weight: 400;}" - filename := "testdata/service/cognitoidentityprovider/logo.png" - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - Providers: testAccProviders, - CheckDestroy: testAccCheckAWSCognitoUserPoolClientDestroy, - Steps: []resource.TestStep{ - { - Config: testAccAWSCognitoUserPoolClientConfig_UICustomizationCSSAndImage(rName, css, filename), - Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckAWSCognitoUserPoolClientExists(resourceName, &client), - resource.TestCheckResourceAttr(resourceName, "ui_customization.#", "1"), - resource.TestCheckResourceAttr(resourceName, "ui_customization.0.css", css), - resource.TestCheckResourceAttrSet(resourceName, "ui_customization.0.image_url"), - ), - }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateIdFunc: testAccAWSCognitoUserPoolClientImportStateIDFunc(resourceName), - ImportStateVerify: true, - ImportStateVerifyIgnore: []string{"ui_customization.0.image_file"}, - }, - { - Config: testAccAWSCognitoUserPoolClientConfig_UICustomizationCSS(rName, css), - Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckAWSCognitoUserPoolClientExists(resourceName, &client), - resource.TestCheckResourceAttr(resourceName, "ui_customization.#", "1"), - resource.TestCheckResourceAttr(resourceName, "ui_customization.0.css", css), - ), - }, - { - Config: testAccAWSCognitoUserPoolClientConfig_UICustomizationImage(rName, filename), - Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckAWSCognitoUserPoolClientExists(resourceName, &client), - resource.TestCheckResourceAttr(resourceName, "ui_customization.#", "1"), - resource.TestCheckResourceAttrSet(resourceName, "ui_customization.0.image_url"), - ), - }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateIdFunc: testAccAWSCognitoUserPoolClientImportStateIDFunc(resourceName), - ImportStateVerify: true, - ImportStateVerifyIgnore: []string{"ui_customization.0.image_file"}, - }, - }, - }) -} - -func TestAccAWSCognitoUserPoolClient_UICustomization_InheritedFromUserPool(t *testing.T) { - var client cognitoidentityprovider.UserPoolClientType - rName := acctest.RandomWithPrefix("tf-acc-test") - resourceName := "aws_cognito_user_pool_client.test" - poolName := "aws_cognito_user_pool.test" - - css := ".label-customizable {font-weight: 400;}" - cssUpdated := ".label-customizable {font-weight: 100;}" - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - Providers: testAccProviders, - CheckDestroy: testAccCheckAWSCognitoUserPoolClientDestroy, - Steps: []resource.TestStep{ - { - Config: testAccAWSCognitoUserPoolConfig_withUserPoolDomain(rName), - Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckAWSCognitoUserPoolExists(poolName), - ), - }, - { - Config: testAccAWSCognitoUserPoolConfig_withUICustomizationCSS(rName, css), - Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckAWSCognitoUserPoolExists(poolName), - ), - }, - { - Config: testAccAWSCognitoUserPoolClientConfig_UICustomizationInherited(rName, css), - Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckAWSCognitoUserPoolExists(poolName), - testAccCheckAWSCognitoUserPoolClientExists(resourceName, &client), - resource.TestCheckResourceAttr(resourceName, "ui_customization.#", "1"), - resource.TestCheckResourceAttr(resourceName, "ui_customization.0.css", css), - ), - ExpectNonEmptyPlan: true, - }, - { - Config: testAccAWSCognitoUserPoolClientConfig_UICustomizationInheritedAndConfigured(rName, css), - PlanOnly: true, - }, - { - Config: testAccAWSCognitoUserPoolClientConfig_UICustomizationInheritedAndOverridden(rName, css, cssUpdated), - Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckAWSCognitoUserPoolExists(poolName), - testAccCheckAWSCognitoUserPoolClientExists(resourceName, &client), - resource.TestCheckResourceAttr(resourceName, "ui_customization.#", "1"), - resource.TestCheckResourceAttr(resourceName, "ui_customization.0.css", cssUpdated), - ), - }, - }, - }) -} - func testAccAWSCognitoUserPoolClientImportStateIDFunc(resourceName string) resource.ImportStateIdFunc { return func(s *terraform.State) (string, error) { rs, ok := s.RootModule().Resources[resourceName] @@ -653,16 +433,6 @@ func testAccCheckAWSCognitoUserPoolClientDisappears(client *cognitoidentityprovi } } -func testAccCheckAWSCognitoUserPoolClientRecreated(before, after *cognitoidentityprovider.UserPoolClientType) resource.TestCheckFunc { - return func(s *terraform.State) error { - if aws.StringValue(before.ClientId) == aws.StringValue(after.ClientId) { - return fmt.Errorf("Cognito User Pool Client not recreated") - } - - return nil - } -} - func testAccAWSCognitoUserPoolClientConfig_basic(userPoolName, clientName string) string { return fmt.Sprintf(` resource "aws_cognito_user_pool" "test" { @@ -835,183 +605,3 @@ resource "aws_cognito_user_pool_client" "test" { } `, clientName) } - -func testAccAWSCognitoUserPoolClientConfig_UICustomizationCSS(rName, css string) string { - return fmt.Sprintf(` -resource "aws_cognito_user_pool" "test" { - name = %[1]q -} - -resource "aws_cognito_user_pool_domain" "test" { - domain = %[1]q - user_pool_id = aws_cognito_user_pool.test.id -} - -resource "aws_cognito_user_pool_client" "test" { - name = %[1]q - - ui_customization { - css = %q - } - - # Refer to the aws_cognito_user_pool_domain resource's - # user_pool_id attribute to ensure it is in an 'Active' state - user_pool_id = aws_cognito_user_pool_domain.test.user_pool_id -} -`, rName, css) -} - -func testAccAWSCognitoUserPoolClientConfig_UICustomizationImage(rName, filename string) string { - return fmt.Sprintf(` -resource "aws_cognito_user_pool" "test" { - name = %[1]q -} - -resource "aws_cognito_user_pool_domain" "test" { - domain = %[1]q - user_pool_id = aws_cognito_user_pool.test.id -} - -resource "aws_cognito_user_pool_client" "test" { - name = %[1]q - - ui_customization { - image_file = filebase64(%q) - } - - # Refer to the aws_cognito_user_pool_domain resource's - # user_pool_id attribute to ensure it is in an 'Active' state - user_pool_id = aws_cognito_user_pool_domain.test.user_pool_id -} -`, rName, filename) -} - -func testAccAWSCognitoUserPoolClientConfig_UICustomizationCSSAndImage(rName, css, filename string) string { - return fmt.Sprintf(` -resource "aws_cognito_user_pool" "test" { - name = %[1]q -} - -resource "aws_cognito_user_pool_domain" "test" { - domain = %[1]q - user_pool_id = aws_cognito_user_pool.test.id -} - -resource "aws_cognito_user_pool_client" "test" { - name = %[1]q - - ui_customization { - css = %q - image_file = filebase64(%q) - } - - # Refer to the aws_cognito_user_pool_domain resource's - # user_pool_id attribute to ensure it is in an 'Active' state - user_pool_id = aws_cognito_user_pool_domain.test.user_pool_id -} -`, rName, css, filename) -} - -func testAccAWSCognitoUserPoolClientConfig_UICustomizationRemoved(rName string) string { - return fmt.Sprintf(` -resource "aws_cognito_user_pool" "test" { - name = %[1]q -} - -resource "aws_cognito_user_pool_domain" "test" { - domain = %[1]q - user_pool_id = aws_cognito_user_pool.test.id -} - -resource "aws_cognito_user_pool_client" "test" { - name = %[1]q - - # Refer to the aws_cognito_user_pool_domain resource's - # user_pool_id attribute to ensure it is in an 'Active' state - user_pool_id = aws_cognito_user_pool_domain.test.user_pool_id -} -`, rName) -} - -func testAccAWSCognitoUserPoolClientConfig_UICustomizationInherited(rName, css string) string { - return fmt.Sprintf(` -resource "aws_cognito_user_pool" "test" { - name = %[1]q - - ui_customization { - css = %q - } -} - -resource "aws_cognito_user_pool_domain" "test" { - domain = %[1]q - user_pool_id = aws_cognito_user_pool.test.id -} - -resource "aws_cognito_user_pool_client" "test" { - name = %[1]q - - # Refer to the aws_cognito_user_pool_domain resource's - # user_pool_id attribute to ensure it is in an 'Active' state - user_pool_id = aws_cognito_user_pool_domain.test.user_pool_id -} -`, rName, css) -} - -func testAccAWSCognitoUserPoolClientConfig_UICustomizationInheritedAndConfigured(rName, css string) string { - return fmt.Sprintf(` -resource "aws_cognito_user_pool" "test" { - name = %[1]q - - ui_customization { - css = %q - } -} - -resource "aws_cognito_user_pool_domain" "test" { - domain = %[1]q - user_pool_id = aws_cognito_user_pool.test.id -} - -resource "aws_cognito_user_pool_client" "test" { - name = %[1]q - - ui_customization { - css = aws_cognito_user_pool.test.ui_customization[0].css - } - - # Refer to the aws_cognito_user_pool_domain resource's - # user_pool_id attribute to ensure it is in an 'Active' state - user_pool_id = aws_cognito_user_pool_domain.test.user_pool_id -} -`, rName, css) -} - -func testAccAWSCognitoUserPoolClientConfig_UICustomizationInheritedAndOverridden(rName, css, cssUpdated string) string { - return fmt.Sprintf(` -resource "aws_cognito_user_pool" "test" { - name = %[1]q - - ui_customization { - css = %[2]q - } -} - -resource "aws_cognito_user_pool_domain" "test" { - domain = %[1]q - user_pool_id = aws_cognito_user_pool.test.id -} - -resource "aws_cognito_user_pool_client" "test" { - name = %[1]q - - ui_customization { - css = %[3]q - } - - # Refer to the aws_cognito_user_pool_domain resource's - # user_pool_id attribute to ensure it is in an 'Active' state - user_pool_id = aws_cognito_user_pool_domain.test.user_pool_id -} -`, rName, css, cssUpdated) -} diff --git a/aws/resource_aws_cognito_user_pool_test.go b/aws/resource_aws_cognito_user_pool_test.go index d636cac2598..4f4b941ff8b 100644 --- a/aws/resource_aws_cognito_user_pool_test.go +++ b/aws/resource_aws_cognito_user_pool_test.go @@ -89,7 +89,6 @@ func TestAccAWSCognitoUserPool_basic(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "sms_configuration.#", "0"), resource.TestCheckResourceAttr(resourceName, "software_token_mfa_configuration.#", "0"), resource.TestCheckResourceAttr(resourceName, "account_recovery_setting.#", "0"), - resource.TestCheckResourceAttr(resourceName, "ui_customization.#", "0"), ), }, { @@ -1058,182 +1057,6 @@ func TestAccAWSCognitoUserPool_withSchemaAttributes(t *testing.T) { }) } -func TestAccAWSCognitoUserPool_withUICustomization_CSS(t *testing.T) { - rName := acctest.RandomWithPrefix("tf-acc-test") - resourceName := "aws_cognito_user_pool.test" - - css := ".label-customizable {font-weight: 400;}" - cssUpdated := ".label-customizable {font-weight: 100;}" - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - Providers: testAccProviders, - CheckDestroy: testAccCheckAWSCognitoUserPoolDestroy, - Steps: []resource.TestStep{ - { - Config: testAccAWSCognitoUserPoolConfig_withUserPoolDomain(rName), - Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckAWSCognitoUserPoolExists(resourceName), - resource.TestCheckResourceAttr(resourceName, "ui_customization.#", "0"), - ), - }, - { - Config: testAccAWSCognitoUserPoolConfig_withUICustomizationCSS(rName, css), - Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckAWSCognitoUserPoolExists(resourceName), - resource.TestCheckResourceAttr(resourceName, "ui_customization.#", "1"), - resource.TestCheckResourceAttr(resourceName, "ui_customization.0.css", css), - ), - }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - }, - { - Config: testAccAWSCognitoUserPoolConfig_withUICustomizationCSS(rName, cssUpdated), - Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckAWSCognitoUserPoolExists(resourceName), - resource.TestCheckResourceAttr(resourceName, "ui_customization.#", "1"), - resource.TestCheckResourceAttr(resourceName, "ui_customization.0.css", cssUpdated), - ), - }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - }, - { - // Test removing the UICustomization settings, forcing new resource - Config: testAccAWSCognitoUserPoolConfig_withUserPoolDomain(rName), - Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckAWSCognitoUserPoolExists(resourceName), - resource.TestCheckResourceAttr(resourceName, "ui_customization.#", "0"), - ), - }, - }, - }) -} - -func TestAccAWSCognitoUserPool_withUICustomization_ImageFile(t *testing.T) { - rName := acctest.RandomWithPrefix("tf-acc-test") - resourceName := "aws_cognito_user_pool.test" - - filename := "testdata/service/cognitoidentityprovider/logo.png" - updatedFilename := "testdata/service/cognitoidentityprovider/logo_modified.png" - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - Providers: testAccProviders, - CheckDestroy: testAccCheckAWSCognitoUserPoolDestroy, - Steps: []resource.TestStep{ - { - Config: testAccAWSCognitoUserPoolConfig_withUserPoolDomain(rName), - Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckAWSCognitoUserPoolExists(resourceName), - resource.TestCheckResourceAttr(resourceName, "ui_customization.#", "0"), - ), - }, - { - Config: testAccAWSCognitoUserPoolConfig_withUICustomizationImage(rName, filename), - Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckAWSCognitoUserPoolExists(resourceName), - resource.TestCheckResourceAttr(resourceName, "ui_customization.#", "1"), - resource.TestCheckResourceAttrSet(resourceName, "ui_customization.0.image_url"), - ), - }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - ImportStateVerifyIgnore: []string{"ui_customization.0.image_file"}, - }, - { - Config: testAccAWSCognitoUserPoolConfig_withUICustomizationImage(rName, updatedFilename), - Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckAWSCognitoUserPoolExists(resourceName), - resource.TestCheckResourceAttr(resourceName, "ui_customization.#", "1"), - resource.TestCheckResourceAttrSet(resourceName, "ui_customization.0.image_url"), - ), - }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - ImportStateVerifyIgnore: []string{"ui_customization.0.image_file"}, - }, - { - // Test removing the UICustomization settings, forcing new resource - Config: testAccAWSCognitoUserPoolConfig_withUserPoolDomain(rName), - Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckAWSCognitoUserPoolExists(resourceName), - resource.TestCheckResourceAttr(resourceName, "ui_customization.#", "0"), - ), - }, - }, - }) -} - -func TestAccAWSCognitoUserPool_withUICustomization_CSSandImageFile(t *testing.T) { - rName := acctest.RandomWithPrefix("tf-acc-test") - resourceName := "aws_cognito_user_pool.test" - - css := ".label-customizable {font-weight: 400;}" - filename := "testdata/service/cognitoidentityprovider/logo.png" - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - Providers: testAccProviders, - CheckDestroy: testAccCheckAWSCognitoUserPoolDestroy, - Steps: []resource.TestStep{ - { - Config: testAccAWSCognitoUserPoolConfig_withUserPoolDomain(rName), - Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckAWSCognitoUserPoolExists(resourceName), - resource.TestCheckResourceAttr(resourceName, "ui_customization.#", "0"), - ), - }, - { - Config: testAccAWSCognitoUserPoolConfig_withUICustomizationCSSAndImage(rName, css, filename), - Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckAWSCognitoUserPoolExists(resourceName), - resource.TestCheckResourceAttr(resourceName, "ui_customization.#", "1"), - resource.TestCheckResourceAttr(resourceName, "ui_customization.0.css", css), - resource.TestCheckResourceAttrSet(resourceName, "ui_customization.0.image_url"), - ), - }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - ImportStateVerifyIgnore: []string{"ui_customization.0.image_file"}, - }, - { - Config: testAccAWSCognitoUserPoolConfig_withUICustomizationCSS(rName, css), - Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckAWSCognitoUserPoolExists(resourceName), - resource.TestCheckResourceAttr(resourceName, "ui_customization.#", "1"), - resource.TestCheckResourceAttr(resourceName, "ui_customization.0.css", css), - ), - }, - { - Config: testAccAWSCognitoUserPoolConfig_withUICustomizationImage(rName, filename), - Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckAWSCognitoUserPoolExists(resourceName), - resource.TestCheckResourceAttr(resourceName, "ui_customization.#", "1"), - resource.TestCheckResourceAttrSet(resourceName, "ui_customization.0.image_url"), - ), - }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - ImportStateVerifyIgnore: []string{"ui_customization.0.image_file"}, - }, - }, - }) -} - func TestAccAWSCognitoUserPool_withVerificationMessageTemplate(t *testing.T) { rName := acctest.RandomWithPrefix("tf-acc-test") resourceName := "aws_cognito_user_pool.test" @@ -2194,68 +2017,3 @@ resource "aws_cognito_user_pool" "test" { } `, name, mfaconfig, smsAuthMsg) } - -func testAccAWSCognitoUserPoolConfig_withUserPoolDomain(rName string) string { - return fmt.Sprintf(` -resource "aws_cognito_user_pool_domain" "test" { - domain = %[1]q - user_pool_id = aws_cognito_user_pool.test.id -} - -resource "aws_cognito_user_pool" "test" { - name = %[1]q -} -`, rName) -} - -func testAccAWSCognitoUserPoolConfig_withUICustomizationCSS(rName, css string) string { - return fmt.Sprintf(` -resource "aws_cognito_user_pool_domain" "test" { - domain = %[1]q - user_pool_id = aws_cognito_user_pool.test.id -} - -resource "aws_cognito_user_pool" "test" { - name = %[1]q - - ui_customization { - css = %q - } -} -`, rName, css) -} - -func testAccAWSCognitoUserPoolConfig_withUICustomizationImage(rName, filename string) string { - return fmt.Sprintf(` -resource "aws_cognito_user_pool_domain" "test" { - domain = %[1]q - user_pool_id = aws_cognito_user_pool.test.id -} - -resource "aws_cognito_user_pool" "test" { - name = %[1]q - - ui_customization { - image_file = filebase64(%q) - } -} -`, rName, filename) -} - -func testAccAWSCognitoUserPoolConfig_withUICustomizationCSSAndImage(rName, css, filename string) string { - return fmt.Sprintf(` -resource "aws_cognito_user_pool_domain" "test" { - domain = %[1]q - user_pool_id = aws_cognito_user_pool.test.id -} - -resource "aws_cognito_user_pool" "test" { - name = %[1]q - - ui_customization { - css = %q - image_file = filebase64(%q) - } -} -`, rName, css, filename) -} diff --git a/aws/resource_aws_cognito_user_pool_ui_customization.go b/aws/resource_aws_cognito_user_pool_ui_customization.go new file mode 100644 index 00000000000..e536d5e3950 --- /dev/null +++ b/aws/resource_aws_cognito_user_pool_ui_customization.go @@ -0,0 +1,223 @@ +package aws + +import ( + "encoding/base64" + "fmt" + "log" + "strings" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/cognitoidentityprovider" + "github.com/hashicorp/aws-sdk-go-base/tfawserr" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/cognitoidentityprovider/finder" +) + +func resourceAwsCognitoUserPoolUICustomization() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsCognitoUserPoolUICustomizationPut, + Read: resourceAwsCognitoUserPoolUICustomizationRead, + Update: resourceAwsCognitoUserPoolUICustomizationPut, + Delete: resourceAwsCognitoUserPoolUICustomizationDelete, + + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "client_id": { + Type: schema.TypeString, + Optional: true, + Default: "ALL", + }, + + "creation_date": { + Type: schema.TypeString, + Computed: true, + }, + + "css": { + Type: schema.TypeString, + Optional: true, + AtLeastOneOf: []string{"css", "image_file"}, + }, + + "css_version": { + Type: schema.TypeString, + Computed: true, + }, + + "image_file": { + Type: schema.TypeString, + Optional: true, + AtLeastOneOf: []string{"image_file", "css"}, + }, + + "image_url": { + Type: schema.TypeString, + Computed: true, + }, + + "last_modified_date": { + Type: schema.TypeString, + Computed: true, + }, + + "user_pool_id": { + Type: schema.TypeString, + Required: true, + }, + }, + } +} + +func resourceAwsCognitoUserPoolUICustomizationPut(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).cognitoidpconn + + clientId := d.Get("client_id").(string) + userPoolId := d.Get("user_pool_id").(string) + + input := &cognitoidentityprovider.SetUICustomizationInput{ + ClientId: aws.String(clientId), + UserPoolId: aws.String(userPoolId), + } + + if v, ok := d.GetOk("css"); ok { + input.CSS = aws.String(v.(string)) + } + + if v, ok := d.GetOk("image_file"); ok { + imgFile, err := base64.StdEncoding.DecodeString(v.(string)) + if err != nil { + return fmt.Errorf("error Base64 decoding image file for Cognito User Pool UI customization (UserPoolId: %s, ClientId: %s): %w", userPoolId, clientId, err) + } + + input.ImageFile = imgFile + } + + _, err := conn.SetUICustomization(input) + + if err != nil { + return fmt.Errorf("error setting Cognito User Pool UI customization (UserPoolId: %s, ClientId: %s): %w", userPoolId, clientId, err) + } + + d.SetId(fmt.Sprintf("%s,%s", userPoolId, clientId)) + + return resourceAwsCognitoUserPoolUICustomizationRead(d, meta) +} + +func resourceAwsCognitoUserPoolUICustomizationRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).cognitoidpconn + + userPoolId, clientId, err := parseCognitoUserPoolUICustomizationID(d.Id()) + + if err != nil { + return fmt.Errorf("error parsing Cognito User Pool UI customization ID (%s): %w", d.Id(), err) + } + + uiCustomization, err := finder.CognitoUserPoolUICustomization(conn, userPoolId, clientId) + + if !d.IsNewResource() && tfawserr.ErrCodeEquals(err, cognitoidentityprovider.ErrCodeResourceNotFoundException) { + log.Printf("[WARN] Cognito User Pool UI customization (UserPoolId: %s, ClientId: %s) not found, removing from state", userPoolId, clientId) + d.SetId("") + return nil + } + + if err != nil { + return fmt.Errorf("error getting Cognito User Pool UI customization (UserPoolId: %s, ClientId: %s): %w", userPoolId, clientId, err) + } + + if uiCustomization == nil { + if d.IsNewResource() { + return fmt.Errorf("error getting Cognito User Pool UI customization (UserPoolId: %s, ClientId: %s): not found", userPoolId, clientId) + } + + log.Printf("[WARN] Cognito User Pool UI customization (UserPoolId: %s, ClientId: %s) not found, removing from state", userPoolId, clientId) + d.SetId("") + return nil + } + + d.Set("client_id", uiCustomization.ClientId) + d.Set("creation_date", aws.TimeValue(uiCustomization.CreationDate).Format(time.RFC3339)) + d.Set("css", uiCustomization.CSS) + d.Set("css_version", uiCustomization.CSSVersion) + d.Set("image_url", uiCustomization.ImageUrl) + d.Set("last_modified_date", aws.TimeValue(uiCustomization.LastModifiedDate).Format(time.RFC3339)) + d.Set("user_pool_id", uiCustomization.UserPoolId) + + return nil +} + +func resourceAwsCognitoUserPoolUICustomizationDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).cognitoidpconn + + userPoolId, clientId, err := parseCognitoUserPoolUICustomizationID(d.Id()) + + if err != nil { + return fmt.Errorf("error parsing Cognito User Pool UI customization ID (%s): %w", d.Id(), err) + } + + input := &cognitoidentityprovider.SetUICustomizationInput{ + ClientId: aws.String(clientId), + UserPoolId: aws.String(userPoolId), + } + + output, err := conn.SetUICustomization(input) + + if tfawserr.ErrCodeEquals(err, cognitoidentityprovider.ErrCodeResourceNotFoundException) { + return nil + } + + if err != nil { + return fmt.Errorf("error deleting Cognito User Pool UI customization (UserPoolId: %s, ClientId: %s): %w", userPoolId, clientId, err) + } + + if output == nil || output.UICustomization == nil { + return nil + } + + if cognitoUserPoolUICustomizationExists(output.UICustomization) { + return fmt.Errorf("error deleting Cognito User Pool UI customization (UserPoolId: %s, ClientId: %s): still exists", userPoolId, clientId) + } + + return nil +} + +// cognitoUserPoolUICustomizationExists validates the API object such that +// we define resource existence when the object is non-nil and +// at least one of the object's fields are non-nil with the exception of CSSVersion +// which remains as an artifact even after UI customization removal +func cognitoUserPoolUICustomizationExists(ui *cognitoidentityprovider.UICustomizationType) bool { + if ui == nil { + return false + } + + if ui.CSS != nil { + return true + } + + if ui.CreationDate != nil { + return true + } + + if ui.ImageUrl != nil { + return true + } + + if ui.LastModifiedDate != nil { + return true + } + + return false +} + +func parseCognitoUserPoolUICustomizationID(id string) (string, string, error) { + idParts := strings.SplitN(id, ",", 2) + + if len(idParts) != 2 || idParts[0] == "" || idParts[1] == "" { + return "", "", fmt.Errorf("please make sure ID is in format USER_POOL_ID,CLIENT_ID") + } + + return idParts[0], idParts[1], nil +} diff --git a/aws/resource_aws_cognito_user_pool_ui_customization_test.go b/aws/resource_aws_cognito_user_pool_ui_customization_test.go new file mode 100644 index 00000000000..de5b7ec41d6 --- /dev/null +++ b/aws/resource_aws_cognito_user_pool_ui_customization_test.go @@ -0,0 +1,706 @@ +package aws + +import ( + "errors" + "fmt" + "testing" + + "github.com/aws/aws-sdk-go/service/cognitoidentityprovider" + "github.com/hashicorp/aws-sdk-go-base/tfawserr" + "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/terraform-providers/terraform-provider-aws/aws/internal/service/cognitoidentityprovider/finder" +) + +func TestAccAWSCognitoUserPoolUICustomization_AllClients_CSS(t *testing.T) { + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_cognito_user_pool_ui_customization.test" + userPoolResourceName := "aws_cognito_user_pool.test" + + css := ".label-customizable {font-weight: 400;}" + cssUpdated := ".label-customizable {font-weight: 100;}" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSCognitoUserPoolUICustomizationDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSCognitoUserPoolUICustomizationConfig_AllClients_CSS(rName, css), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckAWSCognitoUserPoolUICustomizationExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "css", css), + resource.TestCheckResourceAttrSet(resourceName, "creation_date"), + resource.TestCheckResourceAttrSet(resourceName, "css_version"), + resource.TestCheckResourceAttr(resourceName, "client_id", "ALL"), + resource.TestCheckResourceAttrSet(resourceName, "last_modified_date"), + resource.TestCheckResourceAttrPair(resourceName, "user_pool_id", userPoolResourceName, "id"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccAWSCognitoUserPoolUICustomizationConfig_AllClients_CSS(rName, cssUpdated), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckAWSCognitoUserPoolUICustomizationExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "css", cssUpdated), + resource.TestCheckResourceAttrSet(resourceName, "creation_date"), + resource.TestCheckResourceAttrSet(resourceName, "css_version"), + resource.TestCheckResourceAttr(resourceName, "client_id", "ALL"), + resource.TestCheckResourceAttrSet(resourceName, "last_modified_date"), + resource.TestCheckResourceAttrPair(resourceName, "user_pool_id", userPoolResourceName, "id"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAWSCognitoUserPoolUICustomization_AllClients_Disappears(t *testing.T) { + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_cognito_user_pool_ui_customization.test" + + css := ".label-customizable {font-weight: 400;}" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccPreCheckAWSCognitoIdentityProvider(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSCognitoUserPoolUICustomizationDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSCognitoUserPoolUICustomizationConfig_AllClients_CSS(rName, css), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckAWSCognitoUserPoolUICustomizationExists(resourceName), + testAccCheckResourceDisappears(testAccProvider, resourceAwsCognitoUserPoolUICustomization(), resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func TestAccAWSCognitoUserPoolUICustomization_AllClients_ImageFile(t *testing.T) { + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_cognito_user_pool_ui_customization.test" + userPoolResourceName := "aws_cognito_user_pool.test" + + filename := "testdata/service/cognitoidentityprovider/logo.png" + updatedFilename := "testdata/service/cognitoidentityprovider/logo_modified.png" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSCognitoUserPoolUICustomizationDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSCognitoUserPoolUICustomizationConfig_AllClients_Image(rName, filename), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckAWSCognitoUserPoolUICustomizationExists(resourceName), + resource.TestCheckResourceAttrSet(resourceName, "creation_date"), + resource.TestCheckResourceAttr(resourceName, "client_id", "ALL"), + resource.TestCheckResourceAttrSet(resourceName, "image_url"), + resource.TestCheckResourceAttrSet(resourceName, "last_modified_date"), + resource.TestCheckResourceAttrPair(resourceName, "user_pool_id", userPoolResourceName, "id"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"image_file"}, + }, + { + Config: testAccAWSCognitoUserPoolUICustomizationConfig_AllClients_Image(rName, updatedFilename), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckAWSCognitoUserPoolUICustomizationExists(resourceName), + resource.TestCheckResourceAttrSet(resourceName, "creation_date"), + resource.TestCheckResourceAttr(resourceName, "client_id", "ALL"), + resource.TestCheckResourceAttrSet(resourceName, "image_url"), + resource.TestCheckResourceAttrSet(resourceName, "last_modified_date"), + resource.TestCheckResourceAttrPair(resourceName, "user_pool_id", userPoolResourceName, "id"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"image_file"}, + }, + }, + }) +} + +func TestAccAWSCognitoUserPoolUICustomization_AllClients_CSSAndImageFile(t *testing.T) { + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_cognito_user_pool_ui_customization.test" + userPoolResourceName := "aws_cognito_user_pool.test" + + css := ".label-customizable {font-weight: 400;}" + filename := "testdata/service/cognitoidentityprovider/logo.png" + updatedFilename := "testdata/service/cognitoidentityprovider/logo_modified.png" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSCognitoUserPoolUICustomizationDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSCognitoUserPoolUICustomizationConfig_AllClients_CSSAndImage(rName, css, filename), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckAWSCognitoUserPoolUICustomizationExists(resourceName), + resource.TestCheckResourceAttrSet(resourceName, "creation_date"), + resource.TestCheckResourceAttr(resourceName, "client_id", "ALL"), + resource.TestCheckResourceAttr(resourceName, "css", css), + resource.TestCheckResourceAttrSet(resourceName, "css_version"), + resource.TestCheckResourceAttrSet(resourceName, "image_url"), + resource.TestCheckResourceAttrSet(resourceName, "last_modified_date"), + resource.TestCheckResourceAttrPair(resourceName, "user_pool_id", userPoolResourceName, "id"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"image_file"}, + }, + { + Config: testAccAWSCognitoUserPoolUICustomizationConfig_AllClients_CSS(rName, css), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckAWSCognitoUserPoolUICustomizationExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "css", css), + resource.TestCheckResourceAttrSet(resourceName, "creation_date"), + resource.TestCheckResourceAttrSet(resourceName, "css_version"), + resource.TestCheckResourceAttr(resourceName, "client_id", "ALL"), + resource.TestCheckResourceAttrSet(resourceName, "last_modified_date"), + resource.TestCheckResourceAttrPair(resourceName, "user_pool_id", userPoolResourceName, "id"), + ), + }, + { + Config: testAccAWSCognitoUserPoolUICustomizationConfig_AllClients_Image(rName, updatedFilename), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckAWSCognitoUserPoolUICustomizationExists(resourceName), + resource.TestCheckResourceAttrSet(resourceName, "creation_date"), + resource.TestCheckResourceAttr(resourceName, "client_id", "ALL"), + resource.TestCheckResourceAttrSet(resourceName, "image_url"), + resource.TestCheckResourceAttrSet(resourceName, "last_modified_date"), + resource.TestCheckResourceAttrPair(resourceName, "user_pool_id", userPoolResourceName, "id"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"image_file"}, + }, + }, + }) +} + +func TestAccAWSCognitoUserPoolUICustomization_Client_CSS(t *testing.T) { + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_cognito_user_pool_ui_customization.test" + clientResourceName := "aws_cognito_user_pool_client.test" + userPoolResourceName := "aws_cognito_user_pool.test" + + css := ".label-customizable {font-weight: 400;}" + cssUpdated := ".label-customizable {font-weight: 100;}" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSCognitoUserPoolUICustomizationDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSCognitoUserPoolUICustomizationConfig_Client_CSS(rName, css), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckAWSCognitoUserPoolUICustomizationExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "css", css), + resource.TestCheckResourceAttrSet(resourceName, "creation_date"), + resource.TestCheckResourceAttrSet(resourceName, "css_version"), + resource.TestCheckResourceAttrPair(resourceName, "client_id", clientResourceName, "id"), + resource.TestCheckResourceAttrSet(resourceName, "last_modified_date"), + resource.TestCheckResourceAttrPair(resourceName, "user_pool_id", userPoolResourceName, "id"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccAWSCognitoUserPoolUICustomizationConfig_Client_CSS(rName, cssUpdated), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckAWSCognitoUserPoolUICustomizationExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "css", cssUpdated), + resource.TestCheckResourceAttrSet(resourceName, "creation_date"), + resource.TestCheckResourceAttrSet(resourceName, "css_version"), + resource.TestCheckResourceAttrPair(resourceName, "client_id", clientResourceName, "id"), + resource.TestCheckResourceAttrSet(resourceName, "last_modified_date"), + resource.TestCheckResourceAttrPair(resourceName, "user_pool_id", userPoolResourceName, "id"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAWSCognitoUserPoolUICustomization_Client_Disappears(t *testing.T) { + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_cognito_user_pool_ui_customization.test" + + css := ".label-customizable {font-weight: 400;}" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccPreCheckAWSCognitoIdentityProvider(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSCognitoUserPoolUICustomizationDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSCognitoUserPoolUICustomizationConfig_Client_CSS(rName, css), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckAWSCognitoUserPoolUICustomizationExists(resourceName), + testAccCheckResourceDisappears(testAccProvider, resourceAwsCognitoUserPoolUICustomization(), resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func TestAccAWSCognitoUserPoolUICustomization_Client_Image(t *testing.T) { + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_cognito_user_pool_ui_customization.test" + clientResourceName := "aws_cognito_user_pool_client.test" + userPoolResourceName := "aws_cognito_user_pool.test" + + filename := "testdata/service/cognitoidentityprovider/logo.png" + updatedFilename := "testdata/service/cognitoidentityprovider/logo_modified.png" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSCognitoUserPoolUICustomizationDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSCognitoUserPoolUICustomizationConfig_Client_Image(rName, filename), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckAWSCognitoUserPoolUICustomizationExists(resourceName), + resource.TestCheckResourceAttrSet(resourceName, "creation_date"), + resource.TestCheckResourceAttrPair(resourceName, "client_id", clientResourceName, "id"), + resource.TestCheckResourceAttrSet(resourceName, "image_url"), + resource.TestCheckResourceAttrSet(resourceName, "last_modified_date"), + resource.TestCheckResourceAttrPair(resourceName, "user_pool_id", userPoolResourceName, "id"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"image_file"}, + }, + { + Config: testAccAWSCognitoUserPoolUICustomizationConfig_Client_Image(rName, updatedFilename), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckAWSCognitoUserPoolUICustomizationExists(resourceName), + resource.TestCheckResourceAttrSet(resourceName, "creation_date"), + resource.TestCheckResourceAttrPair(resourceName, "client_id", clientResourceName, "id"), + resource.TestCheckResourceAttrSet(resourceName, "image_url"), + resource.TestCheckResourceAttrSet(resourceName, "last_modified_date"), + resource.TestCheckResourceAttrPair(resourceName, "user_pool_id", userPoolResourceName, "id"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"image_file"}, + }, + }, + }) +} + +func TestAccAWSCognitoUserPoolUICustomization_ClientAndAll_CSS(t *testing.T) { + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_cognito_user_pool_ui_customization.ui_all" + clientUIResourceName := "aws_cognito_user_pool_ui_customization.ui_client" + + clientResourceName := "aws_cognito_user_pool_client.test" + userPoolResourceName := "aws_cognito_user_pool.test" + + allCSS := ".label-customizable {font-weight: 400;}" + clientCSS := ".label-customizable {font-weight: 100;}" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSCognitoUserPoolUICustomizationDestroy, + Steps: []resource.TestStep{ + { + // Test UI Customization settings shared by ALL and a specific client + Config: testAccAWSCognitoUserPoolUICustomizationConfig_ClientAndAllCustomizations_CSS(rName, allCSS, allCSS), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckAWSCognitoUserPoolUICustomizationExists(resourceName), + testAccCheckAWSCognitoUserPoolUICustomizationExists(clientUIResourceName), + resource.TestCheckResourceAttrSet(resourceName, "creation_date"), + resource.TestCheckResourceAttr(resourceName, "css", allCSS), + resource.TestCheckResourceAttrSet(resourceName, "last_modified_date"), + resource.TestCheckResourceAttrPair(resourceName, "user_pool_id", userPoolResourceName, "id"), + resource.TestCheckResourceAttrPair(clientUIResourceName, "client_id", clientResourceName, "id"), + resource.TestCheckResourceAttrSet(clientUIResourceName, "creation_date"), + resource.TestCheckResourceAttr(clientUIResourceName, "css", allCSS), + resource.TestCheckResourceAttrSet(clientUIResourceName, "last_modified_date"), + resource.TestCheckResourceAttrPair(clientUIResourceName, "user_pool_id", userPoolResourceName, "id"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + ResourceName: clientUIResourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + // Test UI Customization settings overridden for the client + Config: testAccAWSCognitoUserPoolUICustomizationConfig_ClientAndAllCustomizations_CSS(rName, allCSS, clientCSS), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckAWSCognitoUserPoolUICustomizationExists(resourceName), + testAccCheckAWSCognitoUserPoolUICustomizationExists(clientUIResourceName), + resource.TestCheckResourceAttrSet(resourceName, "creation_date"), + resource.TestCheckResourceAttr(resourceName, "css", allCSS), + resource.TestCheckResourceAttrSet(resourceName, "last_modified_date"), + resource.TestCheckResourceAttrPair(resourceName, "user_pool_id", userPoolResourceName, "id"), + resource.TestCheckResourceAttrPair(clientUIResourceName, "client_id", clientResourceName, "id"), + resource.TestCheckResourceAttrSet(clientUIResourceName, "creation_date"), + resource.TestCheckResourceAttr(clientUIResourceName, "css", clientCSS), + resource.TestCheckResourceAttrSet(clientUIResourceName, "last_modified_date"), + resource.TestCheckResourceAttrPair(clientUIResourceName, "user_pool_id", userPoolResourceName, "id"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + ResourceName: clientUIResourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAWSCognitoUserPoolUICustomization_UpdateClientToAll_CSS(t *testing.T) { + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_cognito_user_pool_ui_customization.test" + clientResourceName := "aws_cognito_user_pool_client.test" + + css := ".label-customizable {font-weight: 100;}" + cssUpdated := ".label-customizable {font-weight: 400;}" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSCognitoUserPoolUICustomizationDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSCognitoUserPoolUICustomizationConfig_Client_CSS(rName, css), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckAWSCognitoUserPoolUICustomizationExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "css", css), + resource.TestCheckResourceAttrPair(resourceName, "client_id", clientResourceName, "id"), + ), + }, + { + Config: testAccAWSCognitoUserPoolUICustomizationConfig_AllClients_CSS(rName, cssUpdated), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckAWSCognitoUserPoolUICustomizationExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "css", cssUpdated), + resource.TestCheckResourceAttr(resourceName, "client_id", "ALL"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAWSCognitoUserPoolUICustomization_UpdateAllToClient_CSS(t *testing.T) { + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_cognito_user_pool_ui_customization.test" + clientResourceName := "aws_cognito_user_pool_client.test" + + css := ".label-customizable {font-weight: 100;}" + cssUpdated := ".label-customizable {font-weight: 400;}" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSCognitoUserPoolUICustomizationDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSCognitoUserPoolUICustomizationConfig_AllClients_CSS(rName, css), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckAWSCognitoUserPoolUICustomizationExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "css", css), + resource.TestCheckResourceAttr(resourceName, "client_id", "ALL"), + ), + }, + { + Config: testAccAWSCognitoUserPoolUICustomizationConfig_Client_CSS(rName, cssUpdated), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckAWSCognitoUserPoolUICustomizationExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "css", cssUpdated), + resource.TestCheckResourceAttrPair(resourceName, "client_id", clientResourceName, "id"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccCheckAWSCognitoUserPoolUICustomizationDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).cognitoidpconn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_cognito_user_pool_ui_customization" { + continue + } + + userPoolId, clientId, err := parseCognitoUserPoolUICustomizationID(rs.Primary.ID) + + if err != nil { + return fmt.Errorf("error parsing Cognito User Pool UI customization ID (%s): %w", rs.Primary.ID, err) + } + + output, err := finder.CognitoUserPoolUICustomization(conn, userPoolId, clientId) + + if tfawserr.ErrCodeEquals(err, cognitoidentityprovider.ErrCodeResourceNotFoundException) { + continue + } + + // Catch cases where the User Pool Domain has been destroyed, effectively eliminating + // a UI customization; calls to GetUICustomization will fail + if tfawserr.ErrMessageContains(err, cognitoidentityprovider.ErrCodeInvalidParameterException, "There has to be an existing domain associated with this user pool") { + continue + } + + if err != nil { + return err + } + + if cognitoUserPoolUICustomizationExists(output) { + return fmt.Errorf("Cognito User Pool UI Customization (UserPoolId: %s, ClientId: %s) still exists", userPoolId, clientId) + } + } + + return nil +} + +func testAccCheckAWSCognitoUserPoolUICustomizationExists(name string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[name] + if !ok { + return fmt.Errorf("Not found: %s", name) + } + + if rs.Primary.ID == "" { + return errors.New("No Cognito User Pool Client ID set") + } + + userPoolId, clientId, err := parseCognitoUserPoolUICustomizationID(rs.Primary.ID) + + if err != nil { + return fmt.Errorf("error parsing Cognito User Pool UI customization ID (%s): %w", rs.Primary.ID, err) + } + + conn := testAccProvider.Meta().(*AWSClient).cognitoidpconn + + output, err := finder.CognitoUserPoolUICustomization(conn, userPoolId, clientId) + + if err != nil { + return err + } + + if output == nil { + return fmt.Errorf("Cognito User Pool UI customization (%s) not found", rs.Primary.ID) + } + + return nil + } +} + +func testAccAWSCognitoUserPoolUICustomizationConfig_AllClients_CSS(rName, css string) string { + return fmt.Sprintf(` +resource "aws_cognito_user_pool" "test" { + name = %[1]q +} + +resource "aws_cognito_user_pool_domain" "test" { + domain = %[1]q + user_pool_id = aws_cognito_user_pool.test.id +} + +resource "aws_cognito_user_pool_ui_customization" "test" { + css = %q + + # Refer to the aws_cognito_user_pool_domain resource's + # user_pool_id attribute to ensure it is in an 'Active' state + user_pool_id = aws_cognito_user_pool_domain.test.user_pool_id +} +`, rName, css) +} + +func testAccAWSCognitoUserPoolUICustomizationConfig_AllClients_Image(rName, filename string) string { + return fmt.Sprintf(` +resource "aws_cognito_user_pool" "test" { + name = %[1]q +} + +resource "aws_cognito_user_pool_domain" "test" { + domain = %[1]q + user_pool_id = aws_cognito_user_pool.test.id +} + +resource "aws_cognito_user_pool_ui_customization" "test" { + image_file = filebase64(%q) + + # Refer to the aws_cognito_user_pool_domain resource's + # user_pool_id attribute to ensure it is in an 'Active' state + user_pool_id = aws_cognito_user_pool_domain.test.user_pool_id +} +`, rName, filename) +} + +func testAccAWSCognitoUserPoolUICustomizationConfig_AllClients_CSSAndImage(rName, css, filename string) string { + return fmt.Sprintf(` +resource "aws_cognito_user_pool" "test" { + name = %[1]q +} + +resource "aws_cognito_user_pool_domain" "test" { + domain = %[1]q + user_pool_id = aws_cognito_user_pool.test.id +} + +resource "aws_cognito_user_pool_ui_customization" "test" { + css = %q + image_file = filebase64(%q) + + # Refer to the aws_cognito_user_pool_domain resource's + # user_pool_id attribute to ensure it is in an 'Active' state + user_pool_id = aws_cognito_user_pool_domain.test.user_pool_id +} +`, rName, css, filename) +} + +func testAccAWSCognitoUserPoolUICustomizationConfig_Client_CSS(rName, css string) string { + return fmt.Sprintf(` +resource "aws_cognito_user_pool" "test" { + name = %[1]q +} + +resource "aws_cognito_user_pool_domain" "test" { + domain = %[1]q + user_pool_id = aws_cognito_user_pool.test.id +} + +resource "aws_cognito_user_pool_client" "test" { + name = %[1]q + user_pool_id = aws_cognito_user_pool.test.id +} + +resource "aws_cognito_user_pool_ui_customization" "test" { + client_id = aws_cognito_user_pool_client.test.id + css = %q + + # Refer to the aws_cognito_user_pool_domain resource's + # user_pool_id attribute to ensure it is in an 'Active' state + user_pool_id = aws_cognito_user_pool_domain.test.user_pool_id +} +`, rName, css) +} + +func testAccAWSCognitoUserPoolUICustomizationConfig_Client_Image(rName, filename string) string { + return fmt.Sprintf(` +resource "aws_cognito_user_pool" "test" { + name = %[1]q +} + +resource "aws_cognito_user_pool_domain" "test" { + domain = %[1]q + user_pool_id = aws_cognito_user_pool.test.id +} + +resource "aws_cognito_user_pool_client" "test" { + name = %[1]q + user_pool_id = aws_cognito_user_pool.test.id +} + +resource "aws_cognito_user_pool_ui_customization" "test" { + client_id = aws_cognito_user_pool_client.test.id + image_file = filebase64(%q) + + # Refer to the aws_cognito_user_pool_domain resource's + # user_pool_id attribute to ensure it is in an 'Active' state + user_pool_id = aws_cognito_user_pool_domain.test.user_pool_id +} +`, rName, filename) +} + +func testAccAWSCognitoUserPoolUICustomizationConfig_ClientAndAllCustomizations_CSS(rName, allCSS, clientCSS string) string { + return fmt.Sprintf(` +resource "aws_cognito_user_pool" "test" { + name = %[1]q +} + +resource "aws_cognito_user_pool_domain" "test" { + domain = %[1]q + user_pool_id = aws_cognito_user_pool.test.id +} + +resource "aws_cognito_user_pool_client" "test" { + name = %[1]q + user_pool_id = aws_cognito_user_pool.test.id +} + +resource "aws_cognito_user_pool_ui_customization" "ui_all" { + css = %q + + # Refer to the aws_cognito_user_pool_domain resource's + # user_pool_id attribute to ensure it is in an 'Active' state + user_pool_id = aws_cognito_user_pool_domain.test.user_pool_id +} + +resource "aws_cognito_user_pool_ui_customization" "ui_client" { + client_id = aws_cognito_user_pool_client.test.id + css = %q + + # Refer to the aws_cognito_user_pool_domain resource's + # user_pool_id attribute to ensure it is in an 'Active' state + user_pool_id = aws_cognito_user_pool_domain.test.user_pool_id +} +`, rName, allCSS, clientCSS) +} diff --git a/website/docs/r/cognito_user_pool.markdown b/website/docs/r/cognito_user_pool.markdown index 08bb7a32094..db80695b8ec 100644 --- a/website/docs/r/cognito_user_pool.markdown +++ b/website/docs/r/cognito_user_pool.markdown @@ -43,7 +43,7 @@ resource "aws_cognito_user_pool" "example" { ### Using Account Recovery Setting ```hcl -resource "aws_cognito_user_pool" "example" { +resource "aws_cognito_user_pool" "test" { name = "mypool" account_recovery_setting { @@ -60,25 +60,6 @@ resource "aws_cognito_user_pool" "example" { } ``` -### Specifying UI Customization Settings for all clients (only applicable on resource updates) - -```hcl -resource "aws_cognito_user_pool" "example" { - name = "mypool" - - # ... other configuration ... - ui_customization { - css = ".label-customizable {font-weight: 400;}" - image_file = filebase64("logo.png") - } -} - -resource "aws_cognito_user_pool_domain" "example" { - domain = "example" - user_pool_id = aws_cognito_user_pool.example.id -} -``` - ## Argument Reference The following arguments are supported: @@ -108,7 +89,6 @@ The following arguments are supported: * `user_pool_add_ons` - (Optional) Configuration block for [user pool add-ons](#user-pool-add-ons) to enable user pool advanced security mode features. * `verification_message_template` (Optional) - The [verification message templates](#verification-message-template) configuration. * `account_recovery_setting` (Optional) - The [account_recovery_setting](#account-recovery-setting) configuration. -* `ui_customization` (Optional) - Configuration block for [UI customization](#ui-customization) information for a user pool's built-in app UI. To use this feature, the user pool must have a domain associated with it. Once configured, the UI customization will be used for every client that has no UI customization set previously. Removing this configuration forces new resource. #### Admin Create User Config @@ -218,11 +198,6 @@ The following arguments are required in the `software_token_mfa_configuration` c * `advanced_security_mode` (Required) - The mode for advanced security, must be one of `OFF`, `AUDIT` or `ENFORCED`. -#### UI Customization - -* `css` (Optional) - The CSS values in the UI customization, provided as a String. -* `image_file` (Optional) - The uploaded logo image in the UI customization, provided as a base64-encoded String. Drift detection is not possible for this argument. - #### Verification Message Template * `default_email_option` (Optional) - The default email option. Must be either `CONFIRM_WITH_CODE` or `CONFIRM_WITH_LINK`. Defaults to `CONFIRM_WITH_CODE`. @@ -247,10 +222,7 @@ In addition to all arguments above, the following attributes are exported: * `endpoint` - The endpoint name of the user pool. Example format: cognito-idp.REGION.amazonaws.com/xxxx_yyyyy * `creation_date` - The date the user pool was created. * `last_modified_date` - The date the user pool was last modified. -* `ui_customization` - The UI customization information for a user pool's built-in app UI. - * `css_version` - The CSS version number. - * `image_url` - The logo image URL for the UI customization. - + ## Import Cognito User Pools can be imported using the `id`, e.g. diff --git a/website/docs/r/cognito_user_pool_client.markdown b/website/docs/r/cognito_user_pool_client.markdown index e8ef33f516a..5bf5f3347c9 100644 --- a/website/docs/r/cognito_user_pool_client.markdown +++ b/website/docs/r/cognito_user_pool_client.markdown @@ -110,32 +110,6 @@ resource "aws_cognito_user_pool_client" "test" { } ``` -### Create a user pool client with UI customization settings - -```hcl -resource "aws_cognito_user_pool" "pool" { - name = "pool" -} - -resource "aws_cognito_user_pool_domain" "domain" { - domain = "example" - user_pool_id = aws_cognito_user_pool.pool.id -} - -resource "aws_cognito_user_pool_client" "client" { - name = "client" - - ui_customization { - css = ".label-customizable {font-weight: 400;}" - image_file = filebase64("logo.png") - } - - # Refer to the aws_cognito_user_pool_domain resource's - # user_pool_id attribute to ensure the domain is 'Active' - user_pool_id = aws_cognito_user_pool_domain.domain.user_pool_id -} -``` - ## Argument Reference The following arguments are supported: @@ -156,7 +130,6 @@ The following arguments are supported: * `user_pool_id` - (Required) The user pool the client belongs to. * `write_attributes` - (Optional) List of user pool attributes the application client can write to. * `analytics_configuration` - (Optional) The Amazon Pinpoint analytics configuration for collecting metrics for this user pool. -* `ui_customization` (Optional) - Configuration block for [UI customization](#ui-customization) information for the user pool's built-in app UI. To use this feature, the user pool the client belongs to must have a domain associated with it. If UI customization settings are configured within the [`aws_cognito_user pool` resource](cognito_user_pool.markdown), this must be configured to prevent plan-time differences. Removing this configuration forces new resource. ### Analytics Configuration @@ -168,21 +141,13 @@ Either `application_arn` or `application_id` is required. * `role_arn` - (Optional) The ARN of an IAM role that authorizes Amazon Cognito to publish events to Amazon Pinpoint analytics. Conflicts with `application_arn`. * `user_data_shared` (Optional) If set to `true`, Amazon Cognito will include user data in the events it publishes to Amazon Pinpoint analytics. -#### UI Customization - -* `css` (Optional) - The CSS values in the UI customization, provided as a String. -* `image_file` (Optional) - The uploaded logo image in the UI customization, provided as a base64-encoded String. Drift detection is not possible for this argument. - ## Attributes Reference In addition to all arguments above, the following attributes are exported: * `id` - The id of the user pool client. * `client_secret` - The client secret of the user pool client. -* `ui_customization` - The UI customization information for the user pool's built-in app UI. - * `css_version` - The CSS version number. - * `image_url` - The logo image URL for the UI customization. - + ## Import Cognito User Pool Clients can be imported using the `id` of the Cognito User Pool, and the `id` of the Cognito User Pool Client, e.g. diff --git a/website/docs/r/cognito_user_pool_ui_customization.html.markdown b/website/docs/r/cognito_user_pool_ui_customization.html.markdown new file mode 100644 index 00000000000..cca7ca7da7d --- /dev/null +++ b/website/docs/r/cognito_user_pool_ui_customization.html.markdown @@ -0,0 +1,92 @@ +--- +subcategory: "Cognito" +layout: "aws" +page_title: "AWS: aws_cognito_user_pool_ui_customization" +description: |- + Provides a Cognito User Pool UI Customization resource. +--- + +# Resource: aws_cognito_user_pool_ui_customization + +Provides a Cognito User Pool UI Customization resource. + +~> **Note:** To use this resource, the user pool must have a domain associated with it. For more information, see the Amazon Cognito Developer Guide on [Customizing the Built-in Sign-In and Sign-up Webpages](https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-app-ui-customization.html). + +### Example Usage + +### UI customization settings for a single client + +```hcl +resource "aws_cognito_user_pool" "example" { + name = "example" +} + +resource "aws_cognito_user_pool_domain" "example" { + domain = "example" + user_pool_id = aws_cognito_user_pool.example.id +} + +resource "aws_cognito_user_pool_client" "example" { + name = "example" + user_pool_id = aws_cognito_user_pool.example.id +} + +resource "aws_cognito_user_pool_ui_customization" "example" { + client_id = aws_cognito_user_pool_client.example.id + + css = ".label-customizable {font-weight: 400;}" + image_file = filebase64("logo.png") + + # Refer to the aws_cognito_user_pool_domain resource's + # user_pool_id attribute to ensure it is in an 'Active' state + user_pool_id = aws_cognito_user_pool_domain.example.user_pool_id +} +``` + +### UI customization settings for all clients + +```hcl +resource "aws_cognito_user_pool" "example" { + name = "example" +} + +resource "aws_cognito_user_pool_domain" "example" { + domain = "example" + user_pool_id = aws_cognito_user_pool.example.id +} + +resource "aws_cognito_user_pool_ui_customization" "example" { + css = ".label-customizable {font-weight: 400;}" + image_file = filebase64("logo.png") + + # Refer to the aws_cognito_user_pool_domain resource's + # user_pool_id attribute to ensure it is in an 'Active' state + user_pool_id = aws_cognito_user_pool_domain.example.user_pool_id +} +``` + +## Argument Reference + +The following arguments are supported: + +* `client_id` (Optional) The client ID for the client app. Defaults to `ALL`. If `ALL` is specified, the `css` and/or `image_file` settings will be used for every client that has no UI customization set previously. +* `css` (Optional) - The CSS values in the UI customization, provided as a String. At least one of `css` or `image_file` is required. +* `image_file` (Optional) - The uploaded logo image for the UI customization, provided as a base64-encoded String. Drift detection is not possible for this argument. At least one of `css` or `image_file` is required. +* `user_pool_id` (Required) - The user pool ID for the user pool. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `creation_date` - The creation date in [RFC3339 format](https://tools.ietf.org/html/rfc3339#section-5.8) for the UI customization. +* `css_version` - The CSS version number. +* `image_url` - The logo image URL for the UI customization. +* `last_modified_date` - The last-modified date in [RFC3339 format](https://tools.ietf.org/html/rfc3339#section-5.8) for the UI customization. + +## Import + +Cognito User Pool UI Customizations can be imported using the `user_pool_id` and `client_id` separated by `,`, e.g. + +``` +$ terraform import aws_cognito_user_pool_ui_customization.example us-west-2_ZCTarbt5C,12bu4fuk3mlgqa2rtrujgp6egq +```