From dc6d1a96a0b1cbf4bf8bd7a280e73838dc273369 Mon Sep 17 00:00:00 2001 From: alex hemard Date: Wed, 29 Nov 2023 09:36:30 -0600 Subject: [PATCH] feat(Cloud Databases): Database user password complexity validation (#4931) * feat(Cloud Databases): Database user password complexity validation * update tests to conform to new complexity rules * fix password validation edgecases * add test case for password beginning with special char * fix regexp * refactor of db users * delete and create ops manager users when updating * validate adminpassword, update docs --- .secrets.baseline | 118 ++-- ...rce_ibm_code_engine_domain_mapping_test.go | 2 +- ibm/service/database/resource_ibm_database.go | 505 ++++++++++++------ .../resource_ibm_database_cassandra_test.go | 40 +- .../resource_ibm_database_edb_test.go | 12 +- ...bm_database_elasticsearch_platinum_test.go | 42 +- ...esource_ibm_database_elasticsearch_test.go | 42 +- .../resource_ibm_database_etcd_test.go | 12 +- ...ce_ibm_database_mongodb_enterprise_test.go | 16 +- ...urce_ibm_database_mongodb_sharding_test.go | 10 +- .../resource_ibm_database_mongodb_test.go | 12 +- .../resource_ibm_database_mysql_test.go | 10 +- .../resource_ibm_database_postgresql_test.go | 26 +- .../resource_ibm_database_rabbitmq_test.go | 12 +- .../resource_ibm_database_redis_test.go | 8 +- .../database/resource_ibm_database_test.go | 107 ++++ website/docs/r/database.html.markdown | 36 +- 17 files changed, 678 insertions(+), 332 deletions(-) create mode 100644 ibm/service/database/resource_ibm_database_test.go diff --git a/.secrets.baseline b/.secrets.baseline index 934bc75b57..f7d1438953 100644 --- a/.secrets.baseline +++ b/.secrets.baseline @@ -3,7 +3,7 @@ "files": "go.mod|go.sum|.*.map|^.secrets.baseline$", "lines": null }, - "generated_at": "2023-11-22T09:50:41Z", + "generated_at": "2023-11-29T02:05:13Z", "plugins_used": [ { "name": "AWSKeyDetector" @@ -760,7 +760,7 @@ "hashed_secret": "731438016c5ab94431f61820f35e3ae5f8ad6004", "is_secret": false, "is_verified": false, - "line_number": 416, + "line_number": 417, "type": "Secret Keyword", "verified_result": null }, @@ -768,7 +768,7 @@ "hashed_secret": "12da2e35d6b50c902c014f1ab9e3032650368df7", "is_secret": false, "is_verified": false, - "line_number": 422, + "line_number": 423, "type": "Secret Keyword", "verified_result": null }, @@ -776,7 +776,7 @@ "hashed_secret": "813274ccae5b6b509379ab56982d862f7b5969b6", "is_secret": false, "is_verified": false, - "line_number": 1127, + "line_number": 1134, "type": "Base64 High Entropy String", "verified_result": null } @@ -864,7 +864,7 @@ "hashed_secret": "c8b6f5ef11b9223ac35a5663975a466ebe7ebba9", "is_secret": false, "is_verified": false, - "line_number": 1806, + "line_number": 1807, "type": "Secret Keyword", "verified_result": null }, @@ -872,7 +872,7 @@ "hashed_secret": "8abf4899c01104241510ba87685ad4de76b0c437", "is_secret": false, "is_verified": false, - "line_number": 1812, + "line_number": 1813, "type": "Secret Keyword", "verified_result": null } @@ -2056,7 +2056,7 @@ "hashed_secret": "deab23f996709b4e3d14e5499d1cc2de677bfaa8", "is_secret": false, "is_verified": false, - "line_number": 1357, + "line_number": 1334, "type": "Secret Keyword", "verified_result": null }, @@ -2064,7 +2064,7 @@ "hashed_secret": "20a25bac21219ffff1904bde871ded4027eca2f8", "is_secret": false, "is_verified": false, - "line_number": 1944, + "line_number": 1923, "type": "Secret Keyword", "verified_result": null }, @@ -2072,7 +2072,7 @@ "hashed_secret": "b732fb611fd46a38e8667f9972e0cde777fbe37f", "is_secret": false, "is_verified": false, - "line_number": 1963, + "line_number": 1942, "type": "Secret Keyword", "verified_result": null }, @@ -2080,14 +2080,14 @@ "hashed_secret": "1f5e25be9b575e9f5d39c82dfd1d9f4d73f1975c", "is_secret": false, "is_verified": false, - "line_number": 2203, + "line_number": 2155, "type": "Secret Keyword", "verified_result": null } ], "ibm/service/database/resource_ibm_database_cassandra_test.go": [ { - "hashed_secret": "10c28f9cf0668595d45c1090a7b4a2ae98edfa58", + "hashed_secret": "2317aa72dafa0a07f05af47baa2e388f95dcf6f3", "is_secret": false, "is_verified": false, "line_number": 731, @@ -2097,7 +2097,7 @@ ], "ibm/service/database/resource_ibm_database_edb_test.go": [ { - "hashed_secret": "10c28f9cf0668595d45c1090a7b4a2ae98edfa58", + "hashed_secret": "2317aa72dafa0a07f05af47baa2e388f95dcf6f3", "is_secret": false, "is_verified": false, "line_number": 205, @@ -2107,7 +2107,7 @@ ], "ibm/service/database/resource_ibm_database_elasticsearch_platinum_test.go": [ { - "hashed_secret": "10c28f9cf0668595d45c1090a7b4a2ae98edfa58", + "hashed_secret": "2317aa72dafa0a07f05af47baa2e388f95dcf6f3", "is_secret": false, "is_verified": false, "line_number": 823, @@ -2117,7 +2117,7 @@ ], "ibm/service/database/resource_ibm_database_elasticsearch_test.go": [ { - "hashed_secret": "10c28f9cf0668595d45c1090a7b4a2ae98edfa58", + "hashed_secret": "2317aa72dafa0a07f05af47baa2e388f95dcf6f3", "is_secret": false, "is_verified": false, "line_number": 778, @@ -2127,7 +2127,7 @@ ], "ibm/service/database/resource_ibm_database_etcd_test.go": [ { - "hashed_secret": "10c28f9cf0668595d45c1090a7b4a2ae98edfa58", + "hashed_secret": "2317aa72dafa0a07f05af47baa2e388f95dcf6f3", "is_secret": false, "is_verified": false, "line_number": 213, @@ -2137,7 +2137,7 @@ ], "ibm/service/database/resource_ibm_database_mongodb_enterprise_test.go": [ { - "hashed_secret": "68ab9ef0953865fef0558010a9f7afcef110d5b8", + "hashed_secret": "8cbbbfad0206e5953901f679b0d26d583c4f5ffe", "is_secret": false, "is_verified": false, "line_number": 271, @@ -2145,7 +2145,7 @@ "verified_result": null }, { - "hashed_secret": "10c28f9cf0668595d45c1090a7b4a2ae98edfa58", + "hashed_secret": "2317aa72dafa0a07f05af47baa2e388f95dcf6f3", "is_secret": false, "is_verified": false, "line_number": 336, @@ -2163,7 +2163,7 @@ "verified_result": null }, { - "hashed_secret": "10c28f9cf0668595d45c1090a7b4a2ae98edfa58", + "hashed_secret": "2317aa72dafa0a07f05af47baa2e388f95dcf6f3", "is_secret": false, "is_verified": false, "line_number": 189, @@ -2173,7 +2173,7 @@ ], "ibm/service/database/resource_ibm_database_mongodb_test.go": [ { - "hashed_secret": "10c28f9cf0668595d45c1090a7b4a2ae98edfa58", + "hashed_secret": "2317aa72dafa0a07f05af47baa2e388f95dcf6f3", "is_secret": false, "is_verified": false, "line_number": 214, @@ -2183,7 +2183,7 @@ ], "ibm/service/database/resource_ibm_database_mysql_test.go": [ { - "hashed_secret": "10c28f9cf0668595d45c1090a7b4a2ae98edfa58", + "hashed_secret": "2317aa72dafa0a07f05af47baa2e388f95dcf6f3", "is_secret": false, "is_verified": false, "line_number": 150, @@ -2193,7 +2193,7 @@ ], "ibm/service/database/resource_ibm_database_postgresql_test.go": [ { - "hashed_secret": "e407cbe1c64cadb886be6f42907e2dd1c06ca080", + "hashed_secret": "728e83f156932be9b1dc48a5c3f7a3bfbeeb08ce", "is_secret": false, "is_verified": false, "line_number": 490, @@ -2201,7 +2201,7 @@ "verified_result": null }, { - "hashed_secret": "10c28f9cf0668595d45c1090a7b4a2ae98edfa58", + "hashed_secret": "2317aa72dafa0a07f05af47baa2e388f95dcf6f3", "is_secret": false, "is_verified": false, "line_number": 658, @@ -2211,7 +2211,7 @@ ], "ibm/service/database/resource_ibm_database_rabbitmq_test.go": [ { - "hashed_secret": "10c28f9cf0668595d45c1090a7b4a2ae98edfa58", + "hashed_secret": "2317aa72dafa0a07f05af47baa2e388f95dcf6f3", "is_secret": false, "is_verified": false, "line_number": 223, @@ -2221,7 +2221,7 @@ ], "ibm/service/database/resource_ibm_database_redis_test.go": [ { - "hashed_secret": "10c28f9cf0668595d45c1090a7b4a2ae98edfa58", + "hashed_secret": "2317aa72dafa0a07f05af47baa2e388f95dcf6f3", "is_secret": false, "is_verified": false, "line_number": 272, @@ -2229,6 +2229,64 @@ "verified_result": null } ], + "ibm/service/database/resource_ibm_database_test.go": [ + { + "hashed_secret": "c237978e1983e0caf1c3a84f1c2e72a7fb2981f2", + "is_secret": false, + "is_verified": false, + "line_number": 19, + "type": "Secret Keyword", + "verified_result": null + }, + { + "hashed_secret": "d67007844d8f7fbc45ea3b27c4bea0bffafb53a0", + "is_secret": false, + "is_verified": false, + "line_number": 27, + "type": "Secret Keyword", + "verified_result": null + }, + { + "hashed_secret": "279fb854eb9fa001b4629518a45c921cfad6d697", + "is_secret": false, + "is_verified": false, + "line_number": 35, + "type": "Secret Keyword", + "verified_result": null + }, + { + "hashed_secret": "dad6fac3e5b6be7bb6f274970b4c50739a7e26ee", + "is_secret": false, + "is_verified": false, + "line_number": 59, + "type": "Secret Keyword", + "verified_result": null + }, + { + "hashed_secret": "8cbbbfad0206e5953901f679b0d26d583c4f5ffe", + "is_secret": false, + "is_verified": false, + "line_number": 67, + "type": "Secret Keyword", + "verified_result": null + }, + { + "hashed_secret": "f5ecb30890399c7b1d1781583478aaa9d0b0c89d", + "is_secret": false, + "is_verified": false, + "line_number": 91, + "type": "Secret Keyword", + "verified_result": null + }, + { + "hashed_secret": "6da9eab371358a331c59a76d80a0ffcd589fe3c9", + "is_secret": false, + "is_verified": false, + "line_number": 101, + "type": "Secret Keyword", + "verified_result": null + } + ], "ibm/service/directlink/resource_ibm_dl_provider_gateway_test.go": [ { "hashed_secret": "a184c8ba0974f2e1da4ca1d71f54e1cf40604335", @@ -2758,7 +2816,7 @@ "hashed_secret": "b732fb611fd46a38e8667f9972e0cde777fbe37f", "is_secret": false, "is_verified": false, - "line_number": 1315, + "line_number": 1326, "type": "Secret Keyword", "verified_result": null } @@ -3774,7 +3832,7 @@ "hashed_secret": "f855f5027fd8fdb2df3f6a6f1cf858fffcbedb0c", "is_secret": false, "is_verified": false, - "line_number": 96615, + "line_number": 96613, "type": "Secret Keyword", "verified_result": null }, @@ -3782,7 +3840,7 @@ "hashed_secret": "5fb0fa884132a8724a8d7cba55853737e442adbd", "is_secret": false, "is_verified": false, - "line_number": 119404, + "line_number": 119402, "type": "Secret Keyword", "verified_result": null }, @@ -3790,7 +3848,7 @@ "hashed_secret": "1e5c2f367f02e47a8c160cda1cd9d91decbac441", "is_secret": false, "is_verified": false, - "line_number": 151612, + "line_number": 151610, "type": "Secret Keyword", "verified_result": null } @@ -4267,7 +4325,7 @@ ], "website/docs/r/database.html.markdown": [ { - "hashed_secret": "10c28f9cf0668595d45c1090a7b4a2ae98edfa58", + "hashed_secret": "2317aa72dafa0a07f05af47baa2e388f95dcf6f3", "is_secret": false, "is_verified": false, "line_number": 494, @@ -4275,7 +4333,7 @@ "verified_result": null }, { - "hashed_secret": "e407cbe1c64cadb886be6f42907e2dd1c06ca080", + "hashed_secret": "ddf75a48487b387b1dc328ac0a942377b377c556", "is_secret": false, "is_verified": false, "line_number": 559, diff --git a/ibm/service/codeengine/resource_ibm_code_engine_domain_mapping_test.go b/ibm/service/codeengine/resource_ibm_code_engine_domain_mapping_test.go index d6178fb825..21ae9dfa77 100644 --- a/ibm/service/codeengine/resource_ibm_code_engine_domain_mapping_test.go +++ b/ibm/service/codeengine/resource_ibm_code_engine_domain_mapping_test.go @@ -214,7 +214,7 @@ func testAccCheckIbmCodeEngineDomainMappingDestroy(s *terraform.State) error { func decodeBase64EnvVar(base64Text string, envVar string) string { decodedText, err := base64.StdEncoding.DecodeString(base64Text) if err != nil { - fmt.Printf("Error decoding environment variable %s: %s", envVar, err) + // fmt.Errorf("Error decoding environment variable %s: %s", envVar, err) return "" } return string(decodedText) diff --git a/ibm/service/database/resource_ibm_database.go b/ibm/service/database/resource_ibm_database.go index ee2a9d09fa..aa2808dcbe 100644 --- a/ibm/service/database/resource_ibm_database.go +++ b/ibm/service/database/resource_ibm_database.go @@ -6,6 +6,7 @@ package database import ( "context" "encoding/json" + "errors" "fmt" "log" "net/url" @@ -50,8 +51,24 @@ const ( databaseTaskFailStatus = "failed" ) +const ( + databaseUserSpecialChars = "_-" + opsManagerUserSpecialChars = "~!@#$%^&*()=+[]{}|;:,.<>/?_-" +) + +const ( + redisRBACRoleRegexPattern = `([+-][a-z]+\s?)+` +) + +type DatabaseUser struct { + Username string + Password string + Role string + Type string +} + type userChange struct { - Old, New map[string]interface{} + Old, New *DatabaseUser } func retry(f func() error) (err error) { @@ -84,7 +101,8 @@ func ResourceIBMDatabaseInstance() *schema.Resource { CustomizeDiff: customdiff.All( resourceIBMDatabaseInstanceDiff, - checkV5Groups), + validateGroupsDiff, + validateUsersDiff), Importer: &schema.ResourceImporter{}, @@ -154,11 +172,14 @@ func ResourceIBMDatabaseInstance() *schema.Resource { Computed: true, }, "adminpassword": { - Description: "The admin user password for the instance", - Type: schema.TypeString, - Optional: true, - ValidateFunc: validation.StringLenBetween(10, 32), - Sensitive: true, + Description: "The admin user password for the instance", + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.All( + validation.StringLenBetween(15, 32), + DatabaseUserPasswordValidator("database"), + ), + Sensitive: true, // DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { // return true // }, @@ -264,7 +285,7 @@ func ResourceIBMDatabaseInstance() *schema.Resource { Type: schema.TypeString, Required: true, Sensitive: true, - ValidateFunc: validation.StringLenBetween(10, 32), + ValidateFunc: validation.StringLenBetween(15, 32), }, "type": { Description: "User type", @@ -975,50 +996,6 @@ func getGroups(instanceID string, meta interface{}) (groups []clouddatabasesv5.G return groupsResponse.Groups, nil } -// V5 Groups -func checkGroupScaling(groupId string, resourceName string, value int, resource *GroupResource, nodeCount int) error { - if nodeCount == 0 { - nodeCount = 1 - } - if resource.StepSize == 0 { - return fmt.Errorf("%s group must have members scaled > 0 before scaling %s", groupId, resourceName) - } - if value < resource.Minimum/nodeCount || value > resource.Maximum/nodeCount || value%(resource.StepSize/nodeCount) != 0 { - if !(value == 0 && resource.IsOptional) { - return fmt.Errorf("%s group %s must be >= %d and <= %d in increments of %d", groupId, resourceName, resource.Minimum/nodeCount, resource.Maximum/nodeCount, resource.StepSize/nodeCount) - } - } - if value != resource.Allocation/nodeCount && !resource.IsAdjustable { - return fmt.Errorf("%s can not change %s value after create", groupId, resourceName) - } - if value < resource.Allocation/nodeCount && !resource.CanScaleDown { - return fmt.Errorf("can not scale %s group %s below %d to %d", groupId, resourceName, resource.Allocation/nodeCount, value) - } - return nil -} - -func checkGroupValue(name string, limits GroupResource, divider int, diff *schema.ResourceDiff) error { - if diff.HasChange(name) { - oldSetting, newSetting := diff.GetChange(name) - old := oldSetting.(int) - new := newSetting.(int) - - if new < limits.Minimum/divider || new > limits.Maximum/divider || new%(limits.StepSize/divider) != 0 { - if !(new == 0 && limits.IsOptional) { - return fmt.Errorf("%s must be >= %d and <= %d in increments of %d", name, limits.Minimum/divider, limits.Maximum/divider/divider, limits.StepSize/divider) - } - } - if old != new && !limits.IsAdjustable { - return fmt.Errorf("%s can not change value after create", name) - } - if new < old && !limits.CanScaleDown { - return fmt.Errorf("%s can not scale down from %d to %d", name, old, new) - } - return nil - } - return nil -} - type CountLimit struct { Units string AllocationCount int @@ -1451,9 +1428,16 @@ func resourceIBMDatabaseInstanceCreate(context context.Context, d *schema.Resour return diag.FromErr(fmt.Errorf("[ERROR] Error getting database client settings: %s", err)) } - for _, user := range userList.(*schema.Set).List() { - userEl := user.(map[string]interface{}) - err := userUpdateCreate(userEl, instanceID, meta, d) + users := expandUsers(userList.(*schema.Set).List()) + for _, user := range users { + // Note: Some db users exist after provisioning (i.e. admin, repl) + // so we must attempt both methods + err := user.Update(instanceID, d, meta) + + if err != nil { + err = user.Create(instanceID, d, meta) + } + if err != nil { return diag.FromErr(err) } @@ -1500,11 +1484,6 @@ func resourceIBMDatabaseInstanceCreate(context context.Context, d *schema.Resour return diag.FromErr(fmt.Errorf("[ERROR] Error Logical Replication can only be set for databases-for-postgresql instances")) } - cloudDatabasesClient, err := meta.(conns.ClientSession).CloudDatabasesV5() - if err != nil { - return diag.FromErr(fmt.Errorf("[ERROR] Error getting database client settings: %s", err)) - } - _, logicalReplicationList := d.GetChange("logical_replication_slot") add := logicalReplicationList.(*schema.Set).List() @@ -2000,65 +1979,38 @@ func resourceIBMDatabaseInstanceUpdate(context context.Context, d *schema.Resour if d.HasChange("users") { oldUsers, newUsers := d.GetChange("users") - userChanges := make(map[string]*userChange) - userKey := func(raw map[string]interface{}) string { - if raw["role"].(string) != "" { - return fmt.Sprintf("%s-%s-%s", raw["type"].(string), raw["role"].(string), raw["name"].(string)) - } else { - return fmt.Sprintf("%s-%s", raw["type"].(string), raw["name"].(string)) - } - } - - for _, raw := range oldUsers.(*schema.Set).List() { - user := raw.(map[string]interface{}) - k := userKey(user) - userChanges[k] = &userChange{Old: user} - } - - for _, raw := range newUsers.(*schema.Set).List() { - user := raw.(map[string]interface{}) - k := userKey(user) - if _, ok := userChanges[k]; !ok { - userChanges[k] = &userChange{} - } - userChanges[k].New = user - } + userChanges := expandUserChanges(oldUsers.(*schema.Set).List(), newUsers.(*schema.Set).List()) for _, change := range userChanges { - // Delete Old User - if change.Old != nil && change.New == nil { - deleteDatabaseUserOptions := &clouddatabasesv5.DeleteDatabaseUserOptions{ - ID: &instanceID, - UserType: core.StringPtr(change.Old["type"].(string)), - Username: core.StringPtr(change.Old["name"].(string)), - } - - deleteDatabaseUserResponse, response, err := cloudDatabasesClient.DeleteDatabaseUser(deleteDatabaseUserOptions) + // Delete User + if change.isDelete() { + // Delete Old User + err = change.Old.Delete(instanceID, d, meta) if err != nil { - return diag.FromErr(fmt.Errorf( - "[ERROR] DeleteDatabaseUser (%s) failed %s\n%s", *deleteDatabaseUserOptions.Username, err, response)) - + return diag.FromErr(err) } + } - taskID := *deleteDatabaseUserResponse.Task.ID - _, err = waitForDatabaseTaskComplete(taskID, d, meta, d.Timeout(schema.TimeoutUpdate)) + if change.isCreate() || change.isUpdate() { - if err != nil { - return diag.FromErr(fmt.Errorf( - "[ERROR] Error waiting for database (%s) user (%s) delete task to complete: %s", icdId, *deleteDatabaseUserOptions.Username, err)) - } + // Note: User Update is not supported for ops_manager user type + // Delete (ignoring errors), then re-create + if change.isUpdate() && !change.New.isUpdatable() { + change.Old.Delete(instanceID, d, meta) - continue - } + err = change.New.Create(instanceID, d, meta) + } else { + // Note: Some db users exist after provisioning (i.e. admin, repl) + // so we must attempt both methods + err = change.New.Update(instanceID, d, meta) - if change.New != nil { - // No change - if change.Old != nil && change.Old["password"].(string) == change.New["password"].(string) && change.Old["name"].(string) == change.New["name"].(string) { - continue + // Create User if Update failed + if err != nil { + err = change.New.Create(instanceID, d, meta) + } } - err := userUpdateCreate(change.New, instanceID, meta, d) if err != nil { return diag.FromErr(err) } @@ -2124,24 +2076,24 @@ func resourceIBMDatabaseInstanceUpdate(context context.Context, d *schema.Resour if len(remove) > 0 { for _, entry := range remove { newEntry := entry.(map[string]interface{}) - deleteDatabaseUserOptions := &clouddatabasesv5.DeleteLogicalReplicationSlotOptions{ + deleteLogicalReplicationSlotOptions := &clouddatabasesv5.DeleteLogicalReplicationSlotOptions{ ID: &instanceID, Name: core.StringPtr(newEntry["name"].(string)), } - deleteDatabaseUserResponse, response, err := cloudDatabasesClient.DeleteLogicalReplicationSlot(deleteDatabaseUserOptions) + deleteLogicalReplicationSlotResponse, response, err := cloudDatabasesClient.DeleteLogicalReplicationSlot(deleteLogicalReplicationSlotOptions) if err != nil { return diag.FromErr(fmt.Errorf( - "[ERROR] DeleteDatabaseUser (%s) failed %s\n%s", *deleteDatabaseUserOptions.Name, err, response)) + "[ERROR] DeleteLogicalReplicationSlot (%s) failed %s\n%s", *deleteLogicalReplicationSlotOptions.Name, err, response)) } - taskID := *deleteDatabaseUserResponse.Task.ID + taskID := *deleteLogicalReplicationSlotResponse.Task.ID _, err = waitForDatabaseTaskComplete(taskID, d, meta, d.Timeout(schema.TimeoutUpdate)) if err != nil { return diag.FromErr(fmt.Errorf( - "[ERROR] Error waiting for database (%s) logical replication slot (%s) delete task to complete: %s", icdId, *deleteDatabaseUserOptions.Name, err)) + "[ERROR] Error waiting for database (%s) logical replication slot (%s) delete task to complete: %s", icdId, *deleteLogicalReplicationSlotOptions.Name, err)) } } } @@ -2401,7 +2353,10 @@ func waitForDatabaseTaskComplete(taskId string, d *schema.ResourceData, meta int delayDuration := 5 * time.Second timeout := time.After(t) - delay := time.Tick(delayDuration) + ticker := time.NewTicker(delayDuration) + delay := ticker.C + defer ticker.Stop() + getTaskOptions := &clouddatabasesv5.GetTaskOptions{ ID: &taskId, } @@ -2748,7 +2703,28 @@ func expandGroups(_groups []interface{}) []*Group { return groups } -func checkV5Groups(_ context.Context, diff *schema.ResourceDiff, meta interface{}) (err error) { +func validateGroupScaling(groupId string, resourceName string, value int, resource *GroupResource, nodeCount int) error { + if nodeCount == 0 { + nodeCount = 1 + } + if resource.StepSize == 0 { + return fmt.Errorf("%s group must have members scaled > 0 before scaling %s", groupId, resourceName) + } + if value < resource.Minimum/nodeCount || value > resource.Maximum/nodeCount || value%(resource.StepSize/nodeCount) != 0 { + if !(value == 0 && resource.IsOptional) { + return fmt.Errorf("%s group %s must be >= %d and <= %d in increments of %d", groupId, resourceName, resource.Minimum/nodeCount, resource.Maximum/nodeCount, resource.StepSize/nodeCount) + } + } + if value != resource.Allocation/nodeCount && !resource.IsAdjustable { + return fmt.Errorf("%s can not change %s value after create", groupId, resourceName) + } + if value < resource.Allocation/nodeCount && !resource.CanScaleDown { + return fmt.Errorf("can not scale %s group %s below %d to %d", groupId, resourceName, resource.Allocation/nodeCount, value) + } + return nil +} + +func validateGroupsDiff(_ context.Context, diff *schema.ResourceDiff, meta interface{}) (err error) { instanceID := diff.Id() service := diff.Get("service").(string) plan := diff.Get("plan").(string) @@ -2772,7 +2748,7 @@ func checkV5Groups(_ context.Context, diff *schema.ResourceDiff, meta interface{ tfGroups := expandGroups(group.(*schema.Set).List()) - // Check group_ids are unique + // validate group_ids are unique groupIds = make([]string, 0, len(tfGroups)) for _, g := range tfGroups { groupIds = append(groupIds, g.ID) @@ -2804,28 +2780,28 @@ func checkV5Groups(_ context.Context, diff *schema.ResourceDiff, meta interface{ nodeCount := groupDefaults.Members.Allocation if group.Members != nil { - err = checkGroupScaling(groupId, "members", group.Members.Allocation, groupDefaults.Members, 1) + err = validateGroupScaling(groupId, "members", group.Members.Allocation, groupDefaults.Members, 1) if err != nil { return err } } if group.Memory != nil { - err = checkGroupScaling(groupId, "memory", group.Memory.Allocation, groupDefaults.Memory, nodeCount) + err = validateGroupScaling(groupId, "memory", group.Memory.Allocation, groupDefaults.Memory, nodeCount) if err != nil { return err } } if group.Disk != nil { - err = checkGroupScaling(groupId, "disk", group.Disk.Allocation, groupDefaults.Disk, nodeCount) + err = validateGroupScaling(groupId, "disk", group.Disk.Allocation, groupDefaults.Disk, nodeCount) if err != nil { return err } } if group.CPU != nil { - err = checkGroupScaling(groupId, "cpu", group.CPU.Allocation, groupDefaults.CPU, nodeCount) + err = validateGroupScaling(groupId, "cpu", group.CPU.Allocation, groupDefaults.CPU, nodeCount) if err != nil { return err } @@ -2836,73 +2812,278 @@ func checkV5Groups(_ context.Context, diff *schema.ResourceDiff, meta interface{ return nil } -// Updates and creates users. Because we cannot get users, we first attempt to update the users, then create them -func userUpdateCreate(userData map[string]interface{}, instanceID string, meta interface{}, d *schema.ResourceData) (err error) { - cloudDatabasesClient, _ := meta.(conns.ClientSession).CloudDatabasesV5() +func validateUsersDiff(_ context.Context, diff *schema.ResourceDiff, meta interface{}) (err error) { + oldUsers, newUsers := diff.GetChange("users") + userChanges := expandUserChanges(oldUsers.(*schema.Set).List(), newUsers.(*schema.Set).List()) + + for _, change := range userChanges { + if change.isDelete() { + continue + } + + if change.isCreate() || change.isUpdate() { + err = change.New.Validate() + if err != nil { + return err + } + } + } + + return nil +} + +func expandUsers(_users []interface{}) []*DatabaseUser { + if len(_users) == 0 { + return nil + } + + users := make([]*DatabaseUser, 0, len(_users)) + + for _, userRaw := range _users { + if tfUser, ok := userRaw.(map[string]interface{}); ok { + + user := DatabaseUser{ + Username: tfUser["name"].(string), + Password: tfUser["password"].(string), + Role: tfUser["role"].(string), + Type: tfUser["type"].(string), + } + + users = append(users, &user) + } + } + + return users +} + +func expandUserChanges(_oldUsers []interface{}, _newUsers []interface{}) (userChanges []*userChange) { + oldUsers := expandUsers(_oldUsers) + newUsers := expandUsers(_newUsers) + + userChangeMap := make(map[string]*userChange) + + for _, user := range oldUsers { + userChangeMap[user.ID()] = &userChange{Old: user} + } + + for _, user := range newUsers { + if _, ok := userChangeMap[user.ID()]; !ok { + userChangeMap[user.ID()] = &userChange{} + } + userChangeMap[user.ID()].New = user + } + + userChanges = make([]*userChange, 0, len(userChangeMap)) + + for _, user := range userChangeMap { + userChanges = append(userChanges, user) + } + + return userChanges +} + +func (c *userChange) isDelete() bool { + return c.Old != nil && c.New == nil +} + +func (c *userChange) isCreate() bool { + return c.Old == nil && c.New != nil +} + +func (c *userChange) isUpdate() bool { + return c.New != nil && + c.Old != nil && + ((c.Old.Password != c.New.Password) || + (c.Old.Role != c.New.Role)) +} + +func (u *DatabaseUser) ID() (id string) { + return fmt.Sprintf("%s-%s", u.Type, u.Username) +} + +func (u *DatabaseUser) Create(instanceID string, d *schema.ResourceData, meta interface{}) (err error) { + cloudDatabasesClient, err := meta.(conns.ClientSession).CloudDatabasesV5() + if err != nil { + return fmt.Errorf("[ERROR] Error getting database client settings: %w", err) + } + + //Attempt to create user + userEntry := &clouddatabasesv5.User{ + Username: core.StringPtr(u.Username), + Password: core.StringPtr(u.Password), + } + + // User Role only for ops_manager user type + if u.Type == "ops_manager" && u.Role != "" { + userEntry.Role = core.StringPtr(u.Role) + } + + createDatabaseUserOptions := &clouddatabasesv5.CreateDatabaseUserOptions{ + ID: &instanceID, + UserType: core.StringPtr(u.Type), + User: userEntry, + } + + createDatabaseUserResponse, response, err := cloudDatabasesClient.CreateDatabaseUser(createDatabaseUserOptions) + if err != nil { + return fmt.Errorf("[ERROR] CreateDatabaseUser (%s) failed %w\n%s", *userEntry.Username, err, response) + } + + taskID := *createDatabaseUserResponse.Task.ID + _, err = waitForDatabaseTaskComplete(taskID, d, meta, d.Timeout(schema.TimeoutUpdate)) + + if err != nil { + return fmt.Errorf( + "[ERROR] Error waiting for database (%s) user (%s) create task to complete: %w", instanceID, *userEntry.Username, err) + } + + return nil +} + +func (u *DatabaseUser) Update(instanceID string, d *schema.ResourceData, meta interface{}) (err error) { + cloudDatabasesClient, err := meta.(conns.ClientSession).CloudDatabasesV5() + if err != nil { + return fmt.Errorf("[ERROR] Error getting database client settings: %s", err) + } + // Attempt to update user password passwordSettingUser := &clouddatabasesv5.APasswordSettingUser{ - Password: core.StringPtr(userData["password"].(string)), + Password: core.StringPtr(u.Password), } changeUserPasswordOptions := &clouddatabasesv5.ChangeUserPasswordOptions{ ID: &instanceID, - UserType: core.StringPtr(userData["type"].(string)), - Username: core.StringPtr(userData["name"].(string)), + UserType: core.StringPtr(u.Type), + Username: core.StringPtr(u.Username), User: passwordSettingUser, } changeUserPasswordResponse, response, err := cloudDatabasesClient.ChangeUserPassword(changeUserPasswordOptions) // user was found but an error occurs while triggering task - if response.StatusCode != 404 && err != nil { - return fmt.Errorf("[ERROR] ChangeUserPassword (%s) failed %s\n%s", *changeUserPasswordOptions.Username, err, response) + if err != nil || (response.StatusCode < 200 || response.StatusCode >= 300) { + return fmt.Errorf("[ERROR] ChangeUserPassword (%s) failed %w\n%s", *changeUserPasswordOptions.Username, err, response) } - updatePass := true // Assume that update password passed + taskID := *changeUserPasswordResponse.Task.ID + _, err = waitForDatabaseTaskComplete(taskID, d, meta, d.Timeout(schema.TimeoutUpdate)) - if userData["type"].(string) == "ops_manager" && response.StatusCode == 404 { - updatePass = false // when user_password api can't find an ops_manager user, it returns a 404 and does not get to the point of creating a task - } else { - // when user_password api can't find a database user, its task fails - taskID := *changeUserPasswordResponse.Task.ID - updatePass, err = waitForDatabaseTaskComplete(taskID, d, meta, d.Timeout(schema.TimeoutUpdate)) + if err != nil { + return fmt.Errorf( + "[ERROR] Error waiting for database (%s) user (%s) create task to complete: %w", instanceID, *changeUserPasswordOptions.Username, err) + } - if err != nil { - log.Printf("[ERROR] Error waiting for database (%s) user (%s) password update task to complete: %s", instanceID, *changeUserPasswordOptions.Username, err) - } + return nil +} + +func (u *DatabaseUser) Delete(instanceID string, d *schema.ResourceData, meta interface{}) (err error) { + cloudDatabasesClient, err := meta.(conns.ClientSession).CloudDatabasesV5() + if err != nil { + return fmt.Errorf("[ERROR] Error getting database client settings: %s", err) } - // Updating the password has failed - if !updatePass { - //Attempt to create user - userEntry := &clouddatabasesv5.User{ - Username: core.StringPtr(userData["name"].(string)), - Password: core.StringPtr(userData["password"].(string)), - } + deleteDatabaseUserOptions := &clouddatabasesv5.DeleteDatabaseUserOptions{ + ID: &instanceID, + UserType: core.StringPtr(u.Type), + Username: core.StringPtr(u.Username), + } - // User Role only for ops_manager user type - if userData["type"].(string) == "ops_manager" && userData["role"].(string) != "" { - userEntry.Role = core.StringPtr(userData["role"].(string)) - } + deleteDatabaseUserResponse, response, err := cloudDatabasesClient.DeleteDatabaseUser(deleteDatabaseUserOptions) + + if err != nil { + return fmt.Errorf( + "[ERROR] DeleteDatabaseUser (%s) failed %s\n%s", *deleteDatabaseUserOptions.Username, err, response) + + } + + taskID := *deleteDatabaseUserResponse.Task.ID + _, err = waitForDatabaseTaskComplete(taskID, d, meta, d.Timeout(schema.TimeoutUpdate)) + + if err != nil { + return fmt.Errorf( + "[ERROR] Error waiting for database (%s) user (%s) delete task to complete: %s", instanceID, *deleteDatabaseUserOptions.Username, err) + } + + return nil +} + +func (u *DatabaseUser) isUpdatable() bool { + return u.Type != "ops_manager" +} - createDatabaseUserOptions := &clouddatabasesv5.CreateDatabaseUserOptions{ - ID: &instanceID, - UserType: core.StringPtr(userData["type"].(string)), - User: userEntry, +func (u *DatabaseUser) Validate() error { + var errs []error + + var specialChars string + switch u.Type { + case "ops_manager": + specialChars = opsManagerUserSpecialChars + default: + specialChars = databaseUserSpecialChars + } + + // Format for regexp + var specialCharPattern string + var bs strings.Builder + for i, c := range strings.Split(specialChars, "") { + if i > 0 { + bs.WriteByte('|') } + bs.WriteString(regexp.QuoteMeta(c)) + } - createDatabaseUserResponse, response, err := cloudDatabasesClient.CreateDatabaseUser(createDatabaseUserOptions) - if err != nil { - return fmt.Errorf("[ERROR] CreateDatabaseUser (%s) failed %s\n%s", *userEntry.Username, err, response) + specialCharPattern = bs.String() + + var allowedCharacters = regexp.MustCompile(fmt.Sprintf("^(?:[a-zA-Z0-9]|%s)+$", specialCharPattern)) + var beginWithSpecialChar = regexp.MustCompile(fmt.Sprintf("^(?:%s)", specialCharPattern)) + var containsLetter = regexp.MustCompile("[a-zA-Z]") + var containsNumber = regexp.MustCompile("[0-9]") + var containsSpecialChar = regexp.MustCompile(fmt.Sprintf("(?:%s)", specialCharPattern)) + + if u.Type == "ops_manager" && !containsSpecialChar.MatchString(u.Password) { + errs = append(errs, fmt.Errorf( + "password must contain at least one special character (%s)", specialChars)) + } + + if u.Type == "database" && beginWithSpecialChar.MatchString(u.Password) { + errs = append(errs, fmt.Errorf( + "password must not begin with a special character (%s)", specialChars)) + } + + if !containsLetter.MatchString(u.Password) { + errs = append(errs, errors.New("password must contain at least one letter")) + } + + if !containsNumber.MatchString(u.Password) { + errs = append(errs, errors.New("password must contain at least one number")) + } + + if !allowedCharacters.MatchString(u.Password) { + errs = append(errs, errors.New("password must not contain invalid characters")) + } + + if len(errs) == 0 { + return nil + } + + var b []byte + for i, err := range errs { + if i > 0 { + b = append(b, '\n') } + b = append(b, err.Error()...) + } - taskID := *createDatabaseUserResponse.Task.ID - _, err = waitForDatabaseTaskComplete(taskID, d, meta, d.Timeout(schema.TimeoutUpdate)) + return fmt.Errorf("database user (%s) validation error:\n%w", u.Username, errors.New(string(b))) +} + +func DatabaseUserPasswordValidator(userType string) schema.SchemaValidateFunc { + return func(i interface{}, k string) (warnings []string, errors []error) { + user := &DatabaseUser{Username: "admin", Type: userType, Password: i.(string)} + err := user.Validate() if err != nil { - return fmt.Errorf( - "[ERROR] Error waiting for database (%s) user (%s) create task to complete: %s", instanceID, *userEntry.Username, err) + errors = append(errors, err) } + return } - - return nil } diff --git a/ibm/service/database/resource_ibm_database_cassandra_test.go b/ibm/service/database/resource_ibm_database_cassandra_test.go index 831d52752b..2701ecb344 100644 --- a/ibm/service/database/resource_ibm_database_cassandra_test.go +++ b/ibm/service/database/resource_ibm_database_cassandra_test.go @@ -295,10 +295,10 @@ func testAccCheckIBMDatabaseInstanceCassandraBasic(databaseResourceGroup string, service = "databases-for-cassandra" plan = "enterprise" location = "%[3]s" - adminpassword = "password12" + adminpassword = "password12345678" users { name = "user123" - password = "password12" + password = "password12345678" } allowlist { address = "172.168.1.2/32" @@ -327,14 +327,14 @@ func testAccCheckIBMDatabaseInstanceCassandraFullyspecified(databaseResourceGrou service = "databases-for-cassandra" plan = "enterprise" location = "%[3]s" - adminpassword = "password12" + adminpassword = "password12345678" users { name = "user123" - password = "password12" + password = "password12345678" } users { name = "user124" - password = "password12" + password = "password12345678" } allowlist { address = "172.168.1.2/32" @@ -367,7 +367,7 @@ func testAccCheckIBMDatabaseInstanceCassandraReduced(databaseResourceGroup strin service = "databases-for-cassandra" plan = "enterprise" location = "%[3]s" - adminpassword = "password12" + adminpassword = "password12345678" timeouts { create = "480m" @@ -391,7 +391,7 @@ func testAccCheckIBMDatabaseInstanceCassandraNodeBasic(databaseResourceGroup str service = "databases-for-cassandra" plan = "enterprise" location = "%[3]s" - adminpassword = "password12" + adminpassword = "password12345678" group { group_id = "member" @@ -411,7 +411,7 @@ func testAccCheckIBMDatabaseInstanceCassandraNodeBasic(databaseResourceGroup str users { name = "user123" - password = "password12" + password = "password12345678" } allowlist { address = "172.168.1.2/32" @@ -441,7 +441,7 @@ func testAccCheckIBMDatabaseInstanceCassandraNodeFullyspecified(databaseResource plan = "enterprise" location = "%[3]s" version = "5.1" - adminpassword = "password12" + adminpassword = "password12345678" group { group_id = "member" @@ -462,11 +462,11 @@ func testAccCheckIBMDatabaseInstanceCassandraNodeFullyspecified(databaseResource users { name = "user123" - password = "password12" + password = "password12345678" } users { name = "user124" - password = "password12" + password = "password12345678" } allowlist { address = "172.168.1.2/32" @@ -500,7 +500,7 @@ func testAccCheckIBMDatabaseInstanceCassandraNodeReduced(databaseResourceGroup s plan = "enterprise" location = "%[3]s" version = "5.1" - adminpassword = "password12" + adminpassword = "password12345678" group { group_id = "member" @@ -540,7 +540,7 @@ func testAccCheckIBMDatabaseInstanceCassandraNodeScaleOut(databaseResourceGroup service = "databases-for-cassandra" plan = "enterprise" location = "%[3]s" - adminpassword = "password12" + adminpassword = "password12345678" group { group_id = "member" @@ -581,7 +581,7 @@ func testAccCheckIBMDatabaseInstanceCassandraGroupBasic(databaseResourceGroup st plan = "enterprise" version = "5.1" location = "%[3]s" - adminpassword = "password12" + adminpassword = "password12345678" group { group_id = "member" @@ -600,7 +600,7 @@ func testAccCheckIBMDatabaseInstanceCassandraGroupBasic(databaseResourceGroup st } users { name = "user123" - password = "password12" + password = "password12345678" } allowlist { address = "172.168.1.2/32" @@ -630,7 +630,7 @@ func testAccCheckIBMDatabaseInstanceCassandraGroupFullyspecified(databaseResourc plan = "enterprise" version = "5.1" location = "%[3]s" - adminpassword = "password12" + adminpassword = "password12345678" group { group_id = "member" @@ -649,11 +649,11 @@ func testAccCheckIBMDatabaseInstanceCassandraGroupFullyspecified(databaseResourc } users { name = "user123" - password = "password12" + password = "password12345678" } users { name = "user124" - password = "password12" + password = "password12345678" } allowlist { address = "172.168.1.2/32" @@ -688,7 +688,7 @@ func testAccCheckIBMDatabaseInstanceCassandraGroupReduced(databaseResourceGroup plan = "enterprise" version = "5.1" location = "%[3]s" - adminpassword = "password12" + adminpassword = "password12345678" group { group_id = "member" members { @@ -728,7 +728,7 @@ func testAccCheckIBMDatabaseInstanceCassandraGroupScaleOut(databaseResourceGroup plan = "enterprise" version = "5.1" location = "%[3]s" - adminpassword = "password12" + adminpassword = "password12345678" group { group_id = "member" diff --git a/ibm/service/database/resource_ibm_database_edb_test.go b/ibm/service/database/resource_ibm_database_edb_test.go index 2f7a639e25..f4ec044be2 100644 --- a/ibm/service/database/resource_ibm_database_edb_test.go +++ b/ibm/service/database/resource_ibm_database_edb_test.go @@ -110,7 +110,7 @@ func testAccCheckIBMDatabaseInstanceEDBBasic(databaseResourceGroup string, name service = "databases-for-enterprisedb" plan = "standard" location = "%[3]s" - adminpassword = "password12" + adminpassword = "password12345678" group { group_id = "member" memory { @@ -123,7 +123,7 @@ func testAccCheckIBMDatabaseInstanceEDBBasic(databaseResourceGroup string, name tags = ["one:two"] users { name = "user123" - password = "password12" + password = "password12345678" } allowlist { address = "172.168.1.2/32" @@ -150,7 +150,7 @@ func testAccCheckIBMDatabaseInstanceEDBFullyspecified(databaseResourceGroup stri service = "databases-for-enterprisedb" plan = "standard" location = "%[3]s" - adminpassword = "password12" + adminpassword = "password12345678" group { group_id = "member" memory { @@ -167,11 +167,11 @@ func testAccCheckIBMDatabaseInstanceEDBFullyspecified(databaseResourceGroup stri tags = ["one:two"] users { name = "user123" - password = "password12" + password = "password12345678" } users { name = "user124" - password = "password12" + password = "password12345678" } allowlist { address = "172.168.1.2/32" @@ -202,7 +202,7 @@ func testAccCheckIBMDatabaseInstanceEDBReduced(databaseResourceGroup string, nam service = "databases-for-enterprisedb" plan = "standard" location = "%[3]s" - adminpassword = "password12" + adminpassword = "password12345678" group { group_id = "member" memory { diff --git a/ibm/service/database/resource_ibm_database_elasticsearch_platinum_test.go b/ibm/service/database/resource_ibm_database_elasticsearch_platinum_test.go index e1cefb491d..114400145f 100644 --- a/ibm/service/database/resource_ibm_database_elasticsearch_platinum_test.go +++ b/ibm/service/database/resource_ibm_database_elasticsearch_platinum_test.go @@ -351,7 +351,7 @@ func testAccCheckIBMDatabaseInstanceElasticsearchPlatinumBasic(databaseResourceG service = "databases-for-elasticsearch" plan = "platinum" location = "%[3]s" - adminpassword = "password12" + adminpassword = "password12345678" group { group_id = "member" @@ -364,7 +364,7 @@ func testAccCheckIBMDatabaseInstanceElasticsearchPlatinumBasic(databaseResourceG } users { name = "user123" - password = "password12" + password = "password12345678" } allowlist { address = "172.168.1.2/32" @@ -393,14 +393,14 @@ func testAccCheckIBMDatabaseInstanceElasticsearchPlatinumFullyspecified(database service = "databases-for-elasticsearch" plan = "platinum" location = "%[3]s" - adminpassword = "password12" + adminpassword = "password12345678" users { name = "user123" - password = "password12" + password = "password12345678" } users { name = "user124" - password = "password12" + password = "password12345678" } allowlist { address = "172.168.1.2/32" @@ -434,7 +434,7 @@ func testAccCheckIBMDatabaseInstanceElasticsearchPlatinumReduced(databaseResourc service = "databases-for-elasticsearch" plan = "platinum" location = "%[3]s" - adminpassword = "password12" + adminpassword = "password12345678" timeouts { create = "120m" @@ -458,7 +458,7 @@ func testAccCheckIBMDatabaseInstanceElasticsearchPlatinumGroupMigration(database service = "databases-for-elasticsearch" plan = "platinum" location = "%[3]s" - adminpassword = "password12" + adminpassword = "password12345678" group { group_id = "member" @@ -493,7 +493,7 @@ func testAccCheckIBMDatabaseInstanceElasticsearchPlatinumNodeBasic(databaseResou service = "databases-for-elasticsearch" plan = "platinum" location = "%[3]s" - adminpassword = "password12" + adminpassword = "password12345678" group { group_id = "member" members { @@ -512,7 +512,7 @@ func testAccCheckIBMDatabaseInstanceElasticsearchPlatinumNodeBasic(databaseResou users { name = "user123" - password = "password12" + password = "password12345678" } allowlist { address = "172.168.1.2/32" @@ -541,7 +541,7 @@ func testAccCheckIBMDatabaseInstanceElasticsearchPlatinumNodeFullyspecified(data service = "databases-for-elasticsearch" plan = "platinum" location = "%[3]s" - adminpassword = "password12" + adminpassword = "password12345678" group { group_id = "member" members { @@ -559,11 +559,11 @@ func testAccCheckIBMDatabaseInstanceElasticsearchPlatinumNodeFullyspecified(data } users { name = "user123" - password = "password12" + password = "password12345678" } users { name = "user124" - password = "password12" + password = "password12345678" } allowlist { address = "172.168.1.2/32" @@ -596,7 +596,7 @@ func testAccCheckIBMDatabaseInstanceElasticsearchPlatinumNodeReduced(databaseRes service = "databases-for-elasticsearch" plan = "platinum" location = "%[3]s" - adminpassword = "password12" + adminpassword = "password12345678" group { group_id = "member" members { @@ -635,7 +635,7 @@ func testAccCheckIBMDatabaseInstanceElasticsearchPlatinumNodeScaleOut(databaseRe service = "databases-for-elasticsearch" plan = "platinum" location = "%[3]s" - adminpassword = "password12" + adminpassword = "password12345678" group { group_id = "member" members { @@ -674,7 +674,7 @@ func testAccCheckIBMDatabaseInstanceElasticsearchPlatinumGroupBasic(databaseReso service = "databases-for-elasticsearch" plan = "platinum" location = "%[3]s" - adminpassword = "password12" + adminpassword = "password12345678" group { group_id = "member" @@ -694,7 +694,7 @@ func testAccCheckIBMDatabaseInstanceElasticsearchPlatinumGroupBasic(databaseReso users { name = "user123" - password = "password12" + password = "password12345678" } allowlist { address = "172.168.1.2/32" @@ -723,7 +723,7 @@ func testAccCheckIBMDatabaseInstanceElasticsearchPlatinumGroupFullyspecified(dat service = "databases-for-elasticsearch" plan = "platinum" location = "%[3]s" - adminpassword = "password12" + adminpassword = "password12345678" group { group_id = "member" @@ -742,11 +742,11 @@ func testAccCheckIBMDatabaseInstanceElasticsearchPlatinumGroupFullyspecified(dat } users { name = "user123" - password = "password12" + password = "password12345678" } users { name = "user124" - password = "password12" + password = "password12345678" } allowlist { address = "172.168.1.2/32" @@ -780,7 +780,7 @@ func testAccCheckIBMDatabaseInstanceElasticsearchPlatinumGroupReduced(databaseRe service = "databases-for-elasticsearch" plan = "platinum" location = "%[3]s" - adminpassword = "password12" + adminpassword = "password12345678" group { group_id = "member" @@ -820,7 +820,7 @@ func testAccCheckIBMDatabaseInstanceElasticsearchPlatinumGroupScaleOut(databaseR service = "databases-for-elasticsearch" plan = "platinum" location = "%[3]s" - adminpassword = "password12" + adminpassword = "password12345678" group { group_id = "member" diff --git a/ibm/service/database/resource_ibm_database_elasticsearch_test.go b/ibm/service/database/resource_ibm_database_elasticsearch_test.go index 400e61b7b9..2a6059f36a 100644 --- a/ibm/service/database/resource_ibm_database_elasticsearch_test.go +++ b/ibm/service/database/resource_ibm_database_elasticsearch_test.go @@ -316,10 +316,10 @@ func testAccCheckIBMDatabaseInstanceElasticsearchBasic(databaseResourceGroup str service = "databases-for-elasticsearch" plan = "standard" location = "%[3]s" - adminpassword = "password12" + adminpassword = "password12345678" users { name = "user123" - password = "password12" + password = "password12345678" } allowlist { address = "172.168.1.2/32" @@ -348,14 +348,14 @@ func testAccCheckIBMDatabaseInstanceElasticsearchFullyspecified(databaseResource service = "databases-for-elasticsearch" plan = "standard" location = "%[3]s" - adminpassword = "password12" + adminpassword = "password12345678" users { name = "user123" - password = "password12" + password = "password12345678" } users { name = "user124" - password = "password12" + password = "password12345678" } allowlist { address = "172.168.1.2/32" @@ -389,7 +389,7 @@ func testAccCheckIBMDatabaseInstanceElasticsearchReduced(databaseResourceGroup s service = "databases-for-elasticsearch" plan = "standard" location = "%[3]s" - adminpassword = "password12" + adminpassword = "password12345678" timeouts { create = "120m" @@ -413,7 +413,7 @@ func testAccCheckIBMDatabaseInstanceElasticsearchGroupMigration(databaseResource service = "databases-for-elasticsearch" plan = "standard" location = "%[3]s" - adminpassword = "password12" + adminpassword = "password12345678" group { group_id = "member" @@ -448,7 +448,7 @@ func testAccCheckIBMDatabaseInstanceElasticsearchNodeBasic(databaseResourceGroup service = "databases-for-elasticsearch" plan = "standard" location = "%[3]s" - adminpassword = "password12" + adminpassword = "password12345678" group { group_id = "member" @@ -467,7 +467,7 @@ func testAccCheckIBMDatabaseInstanceElasticsearchNodeBasic(databaseResourceGroup } users { name = "user123" - password = "password12" + password = "password12345678" } allowlist { address = "172.168.1.2/32" @@ -496,7 +496,7 @@ func testAccCheckIBMDatabaseInstanceElasticsearchNodeFullyspecified(databaseReso service = "databases-for-elasticsearch" plan = "standard" location = "%[3]s" - adminpassword = "password12" + adminpassword = "password12345678" group { group_id = "member" members { @@ -514,11 +514,11 @@ func testAccCheckIBMDatabaseInstanceElasticsearchNodeFullyspecified(databaseReso } users { name = "user123" - password = "password12" + password = "password12345678" } users { name = "user124" - password = "password12" + password = "password12345678" } allowlist { address = "172.168.1.2/32" @@ -551,7 +551,7 @@ func testAccCheckIBMDatabaseInstanceElasticsearchNodeReduced(databaseResourceGro service = "databases-for-elasticsearch" plan = "standard" location = "%[3]s" - adminpassword = "password12" + adminpassword = "password12345678" group { group_id = "member" members { @@ -590,7 +590,7 @@ func testAccCheckIBMDatabaseInstanceElasticsearchNodeScaleOut(databaseResourceGr service = "databases-for-elasticsearch" plan = "standard" location = "%[3]s" - adminpassword = "password12" + adminpassword = "password12345678" group { group_id = "member" members { @@ -629,7 +629,7 @@ func testAccCheckIBMDatabaseInstanceElasticsearchGroupBasic(databaseResourceGrou service = "databases-for-elasticsearch" plan = "standard" location = "%[3]s" - adminpassword = "password12" + adminpassword = "password12345678" group { group_id = "member" @@ -649,7 +649,7 @@ func testAccCheckIBMDatabaseInstanceElasticsearchGroupBasic(databaseResourceGrou users { name = "user123" - password = "password12" + password = "password12345678" } allowlist { address = "172.168.1.2/32" @@ -678,7 +678,7 @@ func testAccCheckIBMDatabaseInstanceElasticsearchGroupFullyspecified(databaseRes service = "databases-for-elasticsearch" plan = "standard" location = "%[3]s" - adminpassword = "password12" + adminpassword = "password12345678" group { group_id = "member" @@ -697,11 +697,11 @@ func testAccCheckIBMDatabaseInstanceElasticsearchGroupFullyspecified(databaseRes } users { name = "user123" - password = "password12" + password = "password12345678" } users { name = "user124" - password = "password12" + password = "password12345678" } allowlist { address = "172.168.1.2/32" @@ -735,7 +735,7 @@ func testAccCheckIBMDatabaseInstanceElasticsearchGroupReduced(databaseResourceGr service = "databases-for-elasticsearch" plan = "standard" location = "%[3]s" - adminpassword = "password12" + adminpassword = "password12345678" group { group_id = "member" @@ -775,7 +775,7 @@ func testAccCheckIBMDatabaseInstanceElasticsearchGroupScaleOut(databaseResourceG service = "databases-for-elasticsearch" plan = "standard" location = "%[3]s" - adminpassword = "password12" + adminpassword = "password12345678" group { group_id = "member" diff --git a/ibm/service/database/resource_ibm_database_etcd_test.go b/ibm/service/database/resource_ibm_database_etcd_test.go index 90ae1134d9..07e2eea5e6 100644 --- a/ibm/service/database/resource_ibm_database_etcd_test.go +++ b/ibm/service/database/resource_ibm_database_etcd_test.go @@ -131,7 +131,7 @@ func testAccCheckIBMDatabaseInstanceEtcdBasic(databaseResourceGroup string, name service = "databases-for-etcd" plan = "standard" location = "%[3]s" - adminpassword = "password12" + adminpassword = "password12345678" group { group_id = "member" memory { @@ -143,7 +143,7 @@ func testAccCheckIBMDatabaseInstanceEtcdBasic(databaseResourceGroup string, name } users { name = "user123" - password = "password12" + password = "password12345678" } allowlist { address = "172.168.1.2/32" @@ -166,7 +166,7 @@ func testAccCheckIBMDatabaseInstanceEtcdFullyspecified(databaseResourceGroup str service = "databases-for-etcd" plan = "standard" location = "%[3]s" - adminpassword = "password12" + adminpassword = "password12345678" group { group_id = "member" memory { @@ -178,11 +178,11 @@ func testAccCheckIBMDatabaseInstanceEtcdFullyspecified(databaseResourceGroup str } users { name = "user123" - password = "password12" + password = "password12345678" } users { name = "user124" - password = "password12" + password = "password12345678" } allowlist { address = "172.168.1.2/32" @@ -210,7 +210,7 @@ func testAccCheckIBMDatabaseInstanceEtcdReduced(databaseResourceGroup string, na service = "databases-for-etcd" plan = "standard" location = "%[3]s" - adminpassword = "password12" + adminpassword = "password12345678" group { group_id = "member" memory { diff --git a/ibm/service/database/resource_ibm_database_mongodb_enterprise_test.go b/ibm/service/database/resource_ibm_database_mongodb_enterprise_test.go index 0993877628..d3ed077681 100644 --- a/ibm/service/database/resource_ibm_database_mongodb_enterprise_test.go +++ b/ibm/service/database/resource_ibm_database_mongodb_enterprise_test.go @@ -93,7 +93,7 @@ func TestAccIBMMongoDBEnterpriseDatabaseInstanceBasic(t *testing.T) { ImportState: true, ImportStateVerify: true, ImportStateVerifyIgnore: []string{ - "wait_time_minutes", "adminpassword", "connectionstrings.0.queryoptions"}, + "wait_time_minutes", "adminpassword", "connectionstrings.0.queryoptions", "group"}, }, }, }) @@ -206,7 +206,7 @@ func testAccCheckIBMDatabaseInstanceMongoDBEnterpriseBasic(databaseResourceGroup service = "databases-for-mongodb" plan = "enterprise" location = "%[3]s" - adminpassword = "password12" + adminpassword = "password12345678" tags = ["one:two"] group { group_id = "member" @@ -219,7 +219,7 @@ func testAccCheckIBMDatabaseInstanceMongoDBEnterpriseBasic(databaseResourceGroup } users { name = "user123" - password = "password12" + password = "password12345678" type = "database" } allowlist { @@ -247,7 +247,7 @@ func testAccCheckIBMDatabaseInstanceMongoDBEnterpriseFullyspecified(databaseReso service = "databases-for-mongodb" plan = "enterprise" location = "%[3]s" - adminpassword = "password12" + adminpassword = "password12345678" tags = ["one:two"] group { group_id = "member" @@ -263,12 +263,12 @@ func testAccCheckIBMDatabaseInstanceMongoDBEnterpriseFullyspecified(databaseReso } users { name = "user123" - password = "password12" + password = "password12345678" type = "database" } users { name = "user124" - password = "password12$password" + password = "password12345678$password" type = "ops_manager" } allowlist { @@ -300,7 +300,7 @@ func testAccCheckIBMDatabaseInstanceMongoDBEnterpriseReduced(databaseResourceGro service = "databases-for-mongodb" plan = "enterprise" location = "%[3]s" - adminpassword = "password12" + adminpassword = "password12345678" service_endpoints = "public" tags = ["one:two"] group { @@ -333,7 +333,7 @@ func testAccCheckIBMDatabaseInstanceMongoDBEnterpriseGroupBasic(databaseResource service = "databases-for-mongodb" plan = "enterprise" location = "%[3]s" - adminpassword = "password12" + adminpassword = "password12345678" tags = ["one:two"] group { diff --git a/ibm/service/database/resource_ibm_database_mongodb_sharding_test.go b/ibm/service/database/resource_ibm_database_mongodb_sharding_test.go index dad9d64ff0..1cce4bc9a4 100644 --- a/ibm/service/database/resource_ibm_database_mongodb_sharding_test.go +++ b/ibm/service/database/resource_ibm_database_mongodb_sharding_test.go @@ -94,7 +94,7 @@ func testAccCheckIBMDatabaseInstanceMongoDBShardingBasic(databaseResourceGroup s service = "databases-for-mongodb" plan = "enterprise-sharding" location = "%[3]s" - adminpassword = "password12" + adminpassword = "password12345678" group { group_id = "member" memory { @@ -106,7 +106,7 @@ func testAccCheckIBMDatabaseInstanceMongoDBShardingBasic(databaseResourceGroup s } users { name = "user123" - password = "password12" + password = "password12345678" type = "database" } allowlist { @@ -134,7 +134,7 @@ func testAccCheckIBMDatabaseInstanceMongoDBShardingFullyspecified(databaseResour service = "databases-for-mongodb" plan = "enterprise-sharding" location = "%[3]s" - adminpassword = "password12" + adminpassword = "password12345678" group { group_id = "member" memory { @@ -149,7 +149,7 @@ func testAccCheckIBMDatabaseInstanceMongoDBShardingFullyspecified(databaseResour } users { name = "user123" - password = "password12" + password = "password12345678" type = "database" } users { @@ -186,7 +186,7 @@ func testAccCheckIBMDatabaseInstanceMongoDBShardingReduced(databaseResourceGroup service = "databases-for-mongodb" plan = "enterprise-sharding" location = "%[3]s" - adminpassword = "password12" + adminpassword = "password12345678" group { group_id = "member" memory { diff --git a/ibm/service/database/resource_ibm_database_mongodb_test.go b/ibm/service/database/resource_ibm_database_mongodb_test.go index 3bbfae9e52..81fd0cd1f7 100644 --- a/ibm/service/database/resource_ibm_database_mongodb_test.go +++ b/ibm/service/database/resource_ibm_database_mongodb_test.go @@ -135,7 +135,7 @@ func testAccCheckIBMDatabaseInstanceMongodbBasic(databaseResourceGroup string, n service = "databases-for-mongodb" plan = "standard" location = "%[3]s" - adminpassword = "password12" + adminpassword = "password12345678" group { group_id = "member" memory { @@ -147,7 +147,7 @@ func testAccCheckIBMDatabaseInstanceMongodbBasic(databaseResourceGroup string, n } users { name = "user123" - password = "password12" + password = "password12345678" } allowlist { address = "172.168.1.2/32" @@ -169,7 +169,7 @@ func testAccCheckIBMDatabaseInstanceMongodbFullyspecified(databaseResourceGroup service = "databases-for-mongodb" plan = "standard" location = "%[3]s" - adminpassword = "password12" + adminpassword = "password12345678" group { group_id = "member" memory { @@ -181,11 +181,11 @@ func testAccCheckIBMDatabaseInstanceMongodbFullyspecified(databaseResourceGroup } users { name = "user123" - password = "password12" + password = "password12345678" } users { name = "user124" - password = "password12" + password = "password12345678" } allowlist { address = "172.168.1.2/32" @@ -211,7 +211,7 @@ func testAccCheckIBMDatabaseInstanceMongodbReduced(databaseResourceGroup string, service = "databases-for-mongodb" plan = "standard" location = "%[3]s" - adminpassword = "password12" + adminpassword = "password12345678" group { group_id = "member" memory { diff --git a/ibm/service/database/resource_ibm_database_mysql_test.go b/ibm/service/database/resource_ibm_database_mysql_test.go index a1291df54d..ddb33d5ffe 100644 --- a/ibm/service/database/resource_ibm_database_mysql_test.go +++ b/ibm/service/database/resource_ibm_database_mysql_test.go @@ -86,7 +86,7 @@ func testAccCheckIBMDatabaseInstanceMysqlBasic(databaseResourceGroup string, nam service = "databases-for-mysql" plan = "standard" location = "%[3]s" - adminpassword = "password12" + adminpassword = "password12345678" group { group_id = "member" memory { @@ -99,7 +99,7 @@ func testAccCheckIBMDatabaseInstanceMysqlBasic(databaseResourceGroup string, nam tags = ["one:two"] users { name = "user123" - password = "password12" + password = "password12345678" } allowlist { address = "172.168.1.2/32" @@ -126,7 +126,7 @@ func testAccCheckIBMDatabaseInstanceMysqlFullyspecified(databaseResourceGroup st service = "databases-for-mysql" plan = "standard" location = "%[3]s" - adminpassword = "password12" + adminpassword = "password12345678" group { group_id = "member" memory { @@ -143,11 +143,11 @@ func testAccCheckIBMDatabaseInstanceMysqlFullyspecified(databaseResourceGroup st tags = ["one:two"] users { name = "user123" - password = "password12" + password = "password12345678" } users { name = "user124" - password = "password12" + password = "password12345678" } allowlist { address = "172.168.1.2/32" diff --git a/ibm/service/database/resource_ibm_database_postgresql_test.go b/ibm/service/database/resource_ibm_database_postgresql_test.go index 56769cba61..dc34b287c4 100644 --- a/ibm/service/database/resource_ibm_database_postgresql_test.go +++ b/ibm/service/database/resource_ibm_database_postgresql_test.go @@ -415,7 +415,7 @@ func testAccCheckIBMDatabaseInstancePostgresBasic(databaseResourceGroup string, service = "databases-for-postgresql" plan = "standard" location = "%[3]s" - adminpassword = "password12" + adminpassword = "password12345678" group { group_id = "member" memory { @@ -428,7 +428,7 @@ func testAccCheckIBMDatabaseInstancePostgresBasic(databaseResourceGroup string, tags = ["one:two"] users { name = "user123" - password = "password12" + password = "password12345678" } allowlist { address = "172.168.1.2/32" @@ -462,7 +462,7 @@ func testAccCheckIBMDatabaseInstancePostgresFullyspecified(databaseResourceGroup service = "databases-for-postgresql" plan = "standard" location = "%[3]s" - adminpassword = "password12" + adminpassword = "password12345678" group { group_id = "member" memory { @@ -479,15 +479,15 @@ func testAccCheckIBMDatabaseInstancePostgresFullyspecified(databaseResourceGroup tags = ["one:two"] users { name = "user123" - password = "password12" + password = "password12345678" } users { name = "user124" - password = "password12" + password = "password12345678" } users { name = "repl" - password = "repl123456" + password = "repl123456password" } configuration = </?_-)", + }, + { + user: DatabaseUser{ + Username: "testy", + Password: "password12345678$password", + Type: "ops_manager", + }, + expectedError: "", + }, + { + user: DatabaseUser{ + Username: "testy", + Password: "~!@#$%^&*()=+[]{}|;:,.<>/?_-", + Type: "ops_manager", + }, + expectedError: "database user (testy) validation error:\npassword must contain at least one letter\npassword must contain at least one number", + }, + { + user: DatabaseUser{ + Username: "testy", + Password: "~!@#$%^&*()=+[]{}|;:,.<>/?_-a1", + Type: "ops_manager", + }, + expectedError: "", + }, + { + user: DatabaseUser{ + Username: "testy", + Password: "pizza1pizzapizza1", + Type: "database", + }, + expectedError: "", + }, + } + for _, tc := range testcases { + err := tc.user.Validate() + if tc.expectedError == "" { + if err != nil { + t.Errorf("TestValidateUserPassword: %q, %q unexpected error: %q", tc.user.Username, tc.user.Password, err.Error()) + } + } else { + assert.Equal(t, tc.expectedError, err.Error()) + } + } +} diff --git a/website/docs/r/database.html.markdown b/website/docs/r/database.html.markdown index 04248090bb..b27f42deab 100644 --- a/website/docs/r/database.html.markdown +++ b/website/docs/r/database.html.markdown @@ -31,7 +31,7 @@ resource "ibm_database" "" { resource_group_id = data.ibm_resource_group.group.id tags = ["tag1", "tag2"] - adminpassword = "password12" + adminpassword = "password12345678" group { group_id = "member" @@ -51,7 +51,7 @@ resource "ibm_database" "" { users { name = "user123" - password = "password12" + password = "password12345678" type = "database" } @@ -83,7 +83,7 @@ resource "ibm_database" "" { resource_group_id = data.ibm_resource_group.group.id tags = ["tag1", "tag2"] - adminpassword = "password12" + adminpassword = "password12345678" group { group_id = "member" @@ -103,7 +103,7 @@ resource "ibm_database" "" { users { name = "user123" - password = "password12" + password = "password12345678" } allowlist { @@ -187,7 +187,7 @@ resource "ibm_database" "cassandra" { service = "databases-for-cassandra" plan = "enterprise" location = "us-south" - adminpassword = "password12" + adminpassword = "password12345678" group { group_id = "member" @@ -207,7 +207,7 @@ resource "ibm_database" "cassandra" { users { name = "user123" - password = "password12" + password = "password12345678" type = "database" } @@ -239,7 +239,7 @@ resource "ibm_database" "mongodb" { service = "databases-for-mongodb" plan = "enterprise" location = "us-south" - adminpassword = "password12" + adminpassword = "password12345678" group { group_id = "member" @@ -261,7 +261,7 @@ resource "ibm_database" "mongodb" { users { name = "dbuser" - password = "password12" + password = "password12345678" type = "database" } @@ -300,7 +300,7 @@ resource "ibm_database" "mongodb_enterprise" { service = "databases-for-mongodb" plan = "enterprise" location = "us-south" - adminpassword = "password12" + adminpassword = "password12345678" tags = ["one:two"] group { @@ -375,7 +375,7 @@ resource "ibm_database" "edb" { service = "databases-for-enterprisedb" plan = "standard" location = "us-south" - adminpassword = "password12" + adminpassword = "password12345678" group { group_id = "member" @@ -397,7 +397,7 @@ resource "ibm_database" "edb" { users { name = "user123" - password = "password12" + password = "password12345678" type = "database" } @@ -427,7 +427,7 @@ resource "ibm_database" "es" { service = "databases-for-elasticsearch" plan = "enterprise" location = "eu-gb" - adminpassword = "password12" + adminpassword = "password12345678" version = "7.17" group { group_id = "member" @@ -446,7 +446,7 @@ resource "ibm_database" "es" { } users { name = "user123" - password = "password12" + password = "password12345678" } allowlist { address = "172.168.1.2/32" @@ -473,7 +473,7 @@ resource "ibm_database" "es" { service = "databases-for-elasticsearch" plan = "platinum" location = "eu-gb" - adminpassword = "password12" + adminpassword = "password12345678" group { group_id = "member" members { @@ -491,7 +491,7 @@ resource "ibm_database" "es" { } users { name = "user123" - password = "password12" + password = "password12345678" } allowlist { address = "172.168.1.2/32" @@ -556,7 +556,7 @@ resource "ibm_database" "db" { users { name = "repl" - password = "repl123456" + password = "repl12345password" } configuration = </?_-` as well as a letter and a number. Other user types may only use special characters `-_`. - `type` - (Optional, String) The type for the user. Examples: `database`, `ops_manager`, `read_only_replica`. The default value is `database`. - `role` - (Optional, String) The role for the user. Only available for `ops_manager` user type. Examples: `group_read_only`, `group_data_access_admin`.