Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

r/aws_cognitoidp_user_pool: suppress diff when schema.string_attribute_constraints is omitted #32445

Merged
merged 2 commits into from
Jul 20, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .changelog/32445.txt
Original file line number Diff line number Diff line change
@@ -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
```
125 changes: 121 additions & 4 deletions internal/service/cognitoidp/flex_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
})
}
}
25 changes: 24 additions & 1 deletion internal/service/cognitoidp/user_pool.go
Original file line number Diff line number Diff line change
@@ -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
}
55 changes: 55 additions & 0 deletions internal/service/cognitoidp/user_pool_test.go
Original file line number Diff line number Diff line change
@@ -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" {