From 3028da9641306a18baf261a8f512c32aecc8f8bd Mon Sep 17 00:00:00 2001 From: Jared Baker Date: Mon, 10 Jul 2023 17:08:00 -0400 Subject: [PATCH 1/2] r/aws_cognitoidp_user_pool: suppress diff when schema.string_attribute_constraints is omitted --- internal/service/cognitoidp/flex_test.go | 125 +++++++++++++++++- internal/service/cognitoidp/user_pool.go | 25 +++- internal/service/cognitoidp/user_pool_test.go | 55 ++++++++ 3 files changed, 200 insertions(+), 5 deletions(-) diff --git a/internal/service/cognitoidp/flex_test.go b/internal/service/cognitoidp/flex_test.go index 042f13da0bbe..2d502cac4fa2 100644 --- a/internal/service/cognitoidp/flex_test.go +++ b/internal/service/cognitoidp/flex_test.go @@ -14,10 +14,12 @@ func TestUserPoolSchemaAttributeMatchesStandardAttribute(t *testing.T) { t.Parallel() cases := []struct { + Name string Input *cognitoidentityprovider.SchemaAttributeType Expected bool }{ { + Name: "birthday standard", Input: &cognitoidentityprovider.SchemaAttributeType{ AttributeDataType: aws.String(cognitoidentityprovider.AttributeDataTypeString), DeveloperOnlyAttribute: aws.Bool(false), @@ -32,6 +34,7 @@ func TestUserPoolSchemaAttributeMatchesStandardAttribute(t *testing.T) { Expected: true, }, { + Name: "birthday non-standard DeveloperOnlyAttribute", Input: &cognitoidentityprovider.SchemaAttributeType{ AttributeDataType: aws.String(cognitoidentityprovider.AttributeDataTypeString), DeveloperOnlyAttribute: aws.Bool(true), @@ -46,6 +49,7 @@ func TestUserPoolSchemaAttributeMatchesStandardAttribute(t *testing.T) { Expected: false, }, { + Name: "birthday non-standard Mutable", Input: &cognitoidentityprovider.SchemaAttributeType{ AttributeDataType: aws.String(cognitoidentityprovider.AttributeDataTypeString), DeveloperOnlyAttribute: aws.Bool(false), @@ -60,6 +64,7 @@ func TestUserPoolSchemaAttributeMatchesStandardAttribute(t *testing.T) { Expected: false, }, { + Name: "non-standard Name", Input: &cognitoidentityprovider.SchemaAttributeType{ AttributeDataType: aws.String(cognitoidentityprovider.AttributeDataTypeString), DeveloperOnlyAttribute: aws.Bool(false), @@ -74,6 +79,7 @@ func TestUserPoolSchemaAttributeMatchesStandardAttribute(t *testing.T) { Expected: false, }, { + Name: "birthday non-standard Required", Input: &cognitoidentityprovider.SchemaAttributeType{ AttributeDataType: aws.String(cognitoidentityprovider.AttributeDataTypeString), DeveloperOnlyAttribute: aws.Bool(false), @@ -88,6 +94,7 @@ func TestUserPoolSchemaAttributeMatchesStandardAttribute(t *testing.T) { Expected: false, }, { + Name: "birthday non-standard StringAttributeConstraints.Max", Input: &cognitoidentityprovider.SchemaAttributeType{ AttributeDataType: aws.String(cognitoidentityprovider.AttributeDataTypeString), DeveloperOnlyAttribute: aws.Bool(false), @@ -102,6 +109,7 @@ func TestUserPoolSchemaAttributeMatchesStandardAttribute(t *testing.T) { Expected: false, }, { + Name: "birthday non-standard StringAttributeConstraints.Min", Input: &cognitoidentityprovider.SchemaAttributeType{ AttributeDataType: aws.String(cognitoidentityprovider.AttributeDataTypeString), DeveloperOnlyAttribute: aws.Bool(false), @@ -116,6 +124,7 @@ func TestUserPoolSchemaAttributeMatchesStandardAttribute(t *testing.T) { Expected: false, }, { + Name: "email_verified standard", Input: &cognitoidentityprovider.SchemaAttributeType{ AttributeDataType: aws.String(cognitoidentityprovider.AttributeDataTypeBoolean), DeveloperOnlyAttribute: aws.Bool(false), @@ -126,6 +135,7 @@ func TestUserPoolSchemaAttributeMatchesStandardAttribute(t *testing.T) { Expected: true, }, { + Name: "updated_at standard", Input: &cognitoidentityprovider.SchemaAttributeType{ AttributeDataType: aws.String(cognitoidentityprovider.AttributeDataTypeNumber), DeveloperOnlyAttribute: aws.Bool(false), @@ -141,9 +151,116 @@ func TestUserPoolSchemaAttributeMatchesStandardAttribute(t *testing.T) { } for _, tc := range cases { - output := UserPoolSchemaAttributeMatchesStandardAttribute(tc.Input) - if output != tc.Expected { - t.Fatalf("Expected %t match with standard attribute on input: \n\n%#v\n\n", tc.Expected, tc.Input) - } + tc := tc + t.Run(tc.Name, func(t *testing.T) { + t.Parallel() + output := UserPoolSchemaAttributeMatchesStandardAttribute(tc.Input) + if output != tc.Expected { + t.Fatalf("Expected %t match with standard attribute on input: \n\n%#v\n\n", tc.Expected, tc.Input) + } + }) + } +} + +func TestSkipFlatteningStringAttributeContraints(t *testing.T) { + t.Parallel() + + cases := []struct { + name string + configured []*cognitoidentityprovider.SchemaAttributeType + input *cognitoidentityprovider.SchemaAttributeType + want bool + }{ + { + name: "config omitted", + configured: []*cognitoidentityprovider.SchemaAttributeType{ + { + AttributeDataType: aws.String(cognitoidentityprovider.AttributeDataTypeString), + DeveloperOnlyAttribute: aws.Bool(false), + Mutable: aws.Bool(false), + Name: aws.String("email"), + Required: aws.Bool(true), + }, + }, + input: &cognitoidentityprovider.SchemaAttributeType{ + AttributeDataType: aws.String(cognitoidentityprovider.AttributeDataTypeString), + DeveloperOnlyAttribute: aws.Bool(false), + Mutable: aws.Bool(false), + Name: aws.String("email"), + Required: aws.Bool(true), + StringAttributeConstraints: &cognitoidentityprovider.StringAttributeConstraintsType{ + MaxLength: aws.String("2048"), + MinLength: aws.String("0"), + }, + }, + want: true, + }, + { + name: "config set", + configured: []*cognitoidentityprovider.SchemaAttributeType{ + { + AttributeDataType: aws.String(cognitoidentityprovider.AttributeDataTypeString), + DeveloperOnlyAttribute: aws.Bool(false), + Mutable: aws.Bool(false), + Name: aws.String("email"), + Required: aws.Bool(true), + StringAttributeConstraints: &cognitoidentityprovider.StringAttributeConstraintsType{ + MaxLength: aws.String("2048"), + MinLength: aws.String("0"), + }, + }, + }, + input: &cognitoidentityprovider.SchemaAttributeType{ + AttributeDataType: aws.String(cognitoidentityprovider.AttributeDataTypeString), + DeveloperOnlyAttribute: aws.Bool(false), + Mutable: aws.Bool(false), + Name: aws.String("email"), + Required: aws.Bool(true), + StringAttributeConstraints: &cognitoidentityprovider.StringAttributeConstraintsType{ + MaxLength: aws.String("2048"), + MinLength: aws.String("0"), + }, + }, + want: false, + }, + { + name: "config set with diff", + configured: []*cognitoidentityprovider.SchemaAttributeType{ + { + AttributeDataType: aws.String(cognitoidentityprovider.AttributeDataTypeString), + DeveloperOnlyAttribute: aws.Bool(false), + Mutable: aws.Bool(false), + Name: aws.String("email"), + Required: aws.Bool(true), + StringAttributeConstraints: &cognitoidentityprovider.StringAttributeConstraintsType{ + MaxLength: aws.String("1024"), + MinLength: aws.String("5"), + }, + }, + }, + input: &cognitoidentityprovider.SchemaAttributeType{ + AttributeDataType: aws.String(cognitoidentityprovider.AttributeDataTypeString), + DeveloperOnlyAttribute: aws.Bool(false), + Mutable: aws.Bool(false), + Name: aws.String("email"), + Required: aws.Bool(true), + StringAttributeConstraints: &cognitoidentityprovider.StringAttributeConstraintsType{ + MaxLength: aws.String("2048"), + MinLength: aws.String("0"), + }, + }, + want: false, + }, + } + + for _, tc := range cases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + got := skipFlatteningStringAttributeContraints(tc.configured, tc.input) + if got != tc.want { + t.Fatalf("skipFlatteningStringAttributeContraints() got %t, want %t\n\n%#v\n\n", got, tc.want, tc.input) + } + }) } } diff --git a/internal/service/cognitoidp/user_pool.go b/internal/service/cognitoidp/user_pool.go index 52653e57ba74..d16ada0cac26 100644 --- a/internal/service/cognitoidp/user_pool.go +++ b/internal/service/cognitoidp/user_pool.go @@ -1813,7 +1813,7 @@ func flattenUserPoolSchema(configuredAttributes, inputs []*cognitoidentityprovid value["number_attribute_constraints"] = []map[string]interface{}{subvalue} } - if input.StringAttributeConstraints != nil { + if input.StringAttributeConstraints != nil && !skipFlatteningStringAttributeContraints(configuredAttributes, input) { subvalue := make(map[string]interface{}) if input.StringAttributeConstraints.MinLength != nil { @@ -2300,3 +2300,26 @@ func flattenUserPoolUserAttributeUpdateSettings(u *cognitoidentityprovider.UserA return []map[string]interface{}{m} } + +// skipFlatteningStringAttributeContraints returns true when all of the schema arguments +// match an existing configured attribute, except an empty "string_attribute_constraints" block. +// In this situation the Describe API returns default constraint values, and a persistent diff +// would be present if written to state. +func skipFlatteningStringAttributeContraints(configuredAttributes []*cognitoidentityprovider.SchemaAttributeType, input *cognitoidentityprovider.SchemaAttributeType) bool { + skip := false + for _, configuredAttribute := range configuredAttributes { + // Root elements are all equal + if reflect.DeepEqual(input.AttributeDataType, configuredAttribute.AttributeDataType) && + reflect.DeepEqual(input.DeveloperOnlyAttribute, configuredAttribute.DeveloperOnlyAttribute) && + reflect.DeepEqual(input.Mutable, configuredAttribute.Mutable) && + reflect.DeepEqual(input.Name, configuredAttribute.Name) && + reflect.DeepEqual(input.Required, configuredAttribute.Required) && + // The configured "string_attribute_constraints" object is empty, but the returned value is not + (aws.StringValue(configuredAttribute.AttributeDataType) == cognitoidentityprovider.AttributeDataTypeString && + configuredAttribute.StringAttributeConstraints == nil && + input.StringAttributeConstraints != nil) { + skip = true + } + } + return skip +} diff --git a/internal/service/cognitoidp/user_pool_test.go b/internal/service/cognitoidp/user_pool_test.go index 849e2d41ee2c..5332980b5383 100644 --- a/internal/service/cognitoidp/user_pool_test.go +++ b/internal/service/cognitoidp/user_pool_test.go @@ -1383,6 +1383,37 @@ func TestAccCognitoIDPUserPool_schemaAttributesModified(t *testing.T) { }) } +// Ref: https://github.com/hashicorp/terraform-provider-aws/issues/21654 +func TestAccCognitoIDPUserPool_schemaAttributesStringAttributeConstraints(t *testing.T) { + ctx := acctest.Context(t) + var pool cognitoidentityprovider.DescribeUserPoolOutput + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_cognito_user_pool.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheckIdentityProvider(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, cognitoidentityprovider.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckUserPoolDestroy(ctx), + Steps: []resource.TestStep{ + { + // Omit optional "string_attribute_constraints" schema argument to verify a persistent + // diff is not present when AWS returns default values in the nested object. + Config: testAccUserPoolConfig_schemaAttributesStringAttributeConstraints(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckUserPoolExists(ctx, resourceName, &pool), + ), + }, + { + // Attempting to explicitly set constraints to non-default values after creation + // should trigger an error + Config: testAccUserPoolConfig_schemaAttributes(rName), + ExpectError: regexp.MustCompile("cannot modify or remove schema items"), + }, + }, + }) +} + func TestAccCognitoIDPUserPool_withVerificationMessageTemplate(t *testing.T) { ctx := acctest.Context(t) rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -2458,6 +2489,30 @@ resource "aws_cognito_user_pool" "test" { `, name, boolname) } +func testAccUserPoolConfig_schemaAttributesStringAttributeConstraints(name string) string { + return fmt.Sprintf(` +resource "aws_cognito_user_pool" "test" { + name = "%[1]s" + + schema { + attribute_data_type = "String" + developer_only_attribute = false + mutable = false + name = "email" + required = true + } + + schema { + attribute_data_type = "Boolean" + developer_only_attribute = true + mutable = false + name = "mybool" + required = false + } +} +`, name) +} + func testAccUserPoolConfig_verificationMessageTemplate(name string) string { return fmt.Sprintf(` resource "aws_cognito_user_pool" "test" { From c5a040a760ca97410c06a113a90eacb7b0ff80fb Mon Sep 17 00:00:00 2001 From: Jared Baker Date: Tue, 11 Jul 2023 10:04:30 -0400 Subject: [PATCH 2/2] chore: changelog --- .changelog/32445.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .changelog/32445.txt diff --git a/.changelog/32445.txt b/.changelog/32445.txt new file mode 100644 index 000000000000..6bca1e34840f --- /dev/null +++ b/.changelog/32445.txt @@ -0,0 +1,3 @@ +```release-note:bug +resource/aws_cognito_user_pool: Suppress diff when `schema.string_attribute_constraints` is omitted for `String` attribute types +```