Skip to content

Commit

Permalink
r/aws_cognitoidp_user_pool: suppress diff when schema.string_attribut…
Browse files Browse the repository at this point in the history
…e_constraints is omitted (#32445)

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

* chore: changelog
  • Loading branch information
jar-b committed Jul 20, 2023
1 parent d1918a3 commit 334b719
Show file tree
Hide file tree
Showing 4 changed files with 203 additions and 5 deletions.
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
Expand Up @@ -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),
Expand All @@ -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),
Expand All @@ -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),
Expand All @@ -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),
Expand All @@ -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),
Expand All @@ -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),
Expand All @@ -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),
Expand All @@ -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),
Expand All @@ -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),
Expand All @@ -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
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand Up @@ -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)
Expand Down Expand Up @@ -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" {
Expand Down

0 comments on commit 334b719

Please sign in to comment.