diff --git a/.secrets.baseline b/.secrets.baseline index 6a57b58b3c..2f52ffb72e 100644 --- a/.secrets.baseline +++ b/.secrets.baseline @@ -1872,6 +1872,14 @@ } ], "ibm/service/database/resource_ibm_database_postgresql_test.go": [ + { + "hashed_secret": "e407cbe1c64cadb886be6f42907e2dd1c06ca080", + "is_secret": false, + "is_verified": false, + "line_number": 574, + "type": "Secret Keyword", + "verified_result": null + }, { "hashed_secret": "10c28f9cf0668595d45c1090a7b4a2ae98edfa58", "is_secret": false, @@ -3378,11 +3386,19 @@ "type": "Secret Keyword", "verified_result": null }, + { + "hashed_secret": "e407cbe1c64cadb886be6f42907e2dd1c06ca080", + "is_secret": false, + "is_verified": false, + "line_number": 498, + "type": "Secret Keyword", + "verified_result": null + }, { "hashed_secret": "91199272d5d6a574a51722ca6f3d1148edb1a0e7", "is_secret": false, "is_verified": false, - "line_number": 488, + "line_number": 522, "type": "Secret Keyword", "verified_result": null } diff --git a/go.mod b/go.mod index cfad949b35..29ba084d70 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/IBM/apigateway-go-sdk v0.0.0-20210714141226-a5d5d49caaca github.com/IBM/appconfiguration-go-admin-sdk v0.3.0 github.com/IBM/appid-management-go-sdk v0.0.0-20210908164609-dd0e0eaf732f - github.com/IBM/cloud-databases-go-sdk v0.2.0 + github.com/IBM/cloud-databases-go-sdk v0.3.0 github.com/IBM/cloudant-go-sdk v0.0.43 github.com/IBM/container-registry-go-sdk v0.0.15 github.com/IBM/continuous-delivery-go-sdk v0.1.5 diff --git a/go.sum b/go.sum index c8be6cbf69..e4dbd029c9 100644 --- a/go.sum +++ b/go.sum @@ -51,6 +51,8 @@ github.com/IBM/appid-management-go-sdk v0.0.0-20210908164609-dd0e0eaf732f h1:4c1 github.com/IBM/appid-management-go-sdk v0.0.0-20210908164609-dd0e0eaf732f/go.mod h1:d22kTYY7RYBWcQlZpqrSdshpB/lJ16viWS5Sbjtlc8s= github.com/IBM/cloud-databases-go-sdk v0.2.0 h1:OkyYpj1LjUnBEgAkp5LMYyla40keEg4ZzWdEXAig4nk= github.com/IBM/cloud-databases-go-sdk v0.2.0/go.mod h1:ZOujnMABgw39Mr/5sFd16eniw9mSS4/if1Vht3m+F4M= +github.com/IBM/cloud-databases-go-sdk v0.3.0 h1:yTCfBF05PjBLTuhrwBH5P1onCSls/vbJB2HawBjheOU= +github.com/IBM/cloud-databases-go-sdk v0.3.0/go.mod h1:ZOujnMABgw39Mr/5sFd16eniw9mSS4/if1Vht3m+F4M= github.com/IBM/cloudant-go-sdk v0.0.43 h1:YxTy4RpAEezX32YIWnds76hrBREmO4u6IkBz1WylNuQ= github.com/IBM/cloudant-go-sdk v0.0.43/go.mod h1:WeYrJPaHTw19943ndWnVfwMIlZ5z0XUM2uEXNBrwZ1M= github.com/IBM/container-registry-go-sdk v0.0.15 h1:sfEXm4qNj9ZCwTlFOsdjF5P/lvajU/Sc22yNlzg0F9I= diff --git a/ibm/service/database/resource_ibm_database.go b/ibm/service/database/resource_ibm_database.go index ebaad8e643..1a98695612 100644 --- a/ibm/service/database/resource_ibm_database.go +++ b/ibm/service/database/resource_ibm_database.go @@ -338,7 +338,7 @@ func ResourceIBMDatabaseInstance() *schema.Resource { Description: "User name", Type: schema.TypeString, Required: true, - ValidateFunc: validation.StringLenBetween(5, 32), + ValidateFunc: validation.StringLenBetween(4, 32), }, "password": { Description: "User password", @@ -490,6 +490,29 @@ func ResourceIBMDatabaseInstance() *schema.Resource { }, ConflictsWith: []string{"whitelist"}, }, + "logical_replication_slot": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Description: "Logical Replication Slot name", + Type: schema.TypeString, + Required: true, + }, + "database_name": { + Description: "Database Name", + Type: schema.TypeString, + Required: true, + }, + "plugin_type": { + Description: "Plugin Type", + Type: schema.TypeString, + Required: true, + }, + }, + }, + }, "group": { Type: schema.TypeSet, Optional: true, @@ -1232,6 +1255,12 @@ func resourceIBMDatabaseInstanceDiff(_ context.Context, diff *schema.ResourceDif return fmt.Errorf("[ERROR] node_count, node_memory_allocation_mb, node_disk_allocation_mb, node_cpu_allocation_count only supported for postgresql, elasticsearch and cassandra") } + _, logicalReplicationSet := diff.GetOk("logical_replication_slot") + + if service != "databases-for-postgresql" && logicalReplicationSet { + return fmt.Errorf("[ERROR] logical_replication_slot is only supported for databases-for-postgresql") + } + _, configurationSet := diff.GetOk("configuration") if (service != "databases-for-postgresql" && service != "databases-for-redis" && service != "databases-for-enterprisedb") && configurationSet { @@ -1642,42 +1671,15 @@ func resourceIBMDatabaseInstanceCreate(context context.Context, d *schema.Resour } if userList, ok := d.GetOk("users"); ok { - cloudDatabasesClient, err := meta.(conns.ClientSession).CloudDatabasesV5() if err != nil { 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{}) - createDatabaseUserRequestUserModel := &clouddatabasesv5.User{ - Username: core.StringPtr(userEl["name"].(string)), - Password: core.StringPtr(userEl["password"].(string)), - } - - // User Role only for ops_manager user type - if userEl["type"].(string) == "ops_manager" && userEl["role"].(string) != "" { - createDatabaseUserRequestUserModel.Role = core.StringPtr(userEl["role"].(string)) - } - - instanceId := d.Id() - createDatabaseUserOptions := &clouddatabasesv5.CreateDatabaseUserOptions{ - ID: &instanceId, - UserType: core.StringPtr(userEl["type"].(string)), - User: createDatabaseUserRequestUserModel, - } - - createDatabaseUserResponse, response, err := cloudDatabasesClient.CreateDatabaseUser(createDatabaseUserOptions) - - if err != nil { - return diag.FromErr(fmt.Errorf("CreateDatabaseUser (%s) failed %s\n%s", userEl["name"], err, response)) - } - - taskID := *createDatabaseUserResponse.Task.ID - - _, err = waitForDatabaseTaskComplete(taskID, d, meta, d.Timeout(schema.TimeoutCreate)) + err := userUpdateCreate(userEl, instanceID, meta, d) if err != nil { - return diag.FromErr(fmt.Errorf( - "[ERROR] Error waiting for update of database (%s) user (%s) create task to complete: %s", d.Id(), userEl["name"], err)) + return diag.FromErr(err) } } } @@ -1702,6 +1704,48 @@ func resourceIBMDatabaseInstanceCreate(context context.Context, d *schema.Resour } } + if _, ok := d.GetOk("logical_replication_slot"); ok { + service := d.Get("service").(string) + if service != "databases-for-postgresql" { + 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() + + for _, entry := range add { + newEntry := entry.(map[string]interface{}) + logicalReplicationSlot := &clouddatabasesv5.LogicalReplicationSlot{ + Name: core.StringPtr(newEntry["name"].(string)), + DatabaseName: core.StringPtr(newEntry["database_name"].(string)), + PluginType: core.StringPtr(newEntry["plugin_type"].(string)), + } + + createLogicalReplicationOptions := &clouddatabasesv5.CreateLogicalReplicationSlotOptions{ + ID: &instanceID, + LogicalReplicationSlot: logicalReplicationSlot, + } + + createLogicalRepSlotResponse, response, err := cloudDatabasesClient.CreateLogicalReplicationSlot(createLogicalReplicationOptions) + if err != nil { + return diag.FromErr(fmt.Errorf("[ERROR] CreateLogicalReplicationSlot (%s) failed %s\n%s", *createLogicalReplicationOptions.LogicalReplicationSlot.Name, err, response)) + } + + taskID := *createLogicalRepSlotResponse.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) create task to complete: %s", instanceID, *createLogicalReplicationOptions.LogicalReplicationSlot.Name, err)) + } + } + } + return resourceIBMDatabaseInstanceRead(context, d, meta) } @@ -2395,48 +2439,8 @@ func resourceIBMDatabaseInstanceUpdate(context context.Context, d *schema.Resour } for _, change := range userChanges { - // Update Database User password only - if change.Old != nil && change.New != nil { - // No change - if change.Old["password"].(string) == change.New["password"].(string) { - continue - } - - passwordSettingUser := &clouddatabasesv5.APasswordSettingUser{ - Password: core.StringPtr(change.New["password"].(string)), - } - - changeUserPasswordOptions := &clouddatabasesv5.ChangeUserPasswordOptions{ - ID: &instanceID, - UserType: core.StringPtr(change.New["type"].(string)), - Username: core.StringPtr(change.New["name"].(string)), - User: passwordSettingUser, - } - - changeUserPasswordResponse, response, err := cloudDatabasesClient.ChangeUserPassword(changeUserPasswordOptions) - - if response.StatusCode != 404 { - if err != nil { - return diag.FromErr(fmt.Errorf("[ERROR] ChangeUserPassword (%s) failed %s\n%s", *changeUserPasswordOptions.Username, err, response)) - } - - taskID := *changeUserPasswordResponse.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) user (%s) password update task to complete: %s", instanceID, *changeUserPasswordOptions.Username, err)) - } - - continue - } - - // User not found, need to reCreate user - change.Old = nil - } - // Delete Old User - if change.Old != nil { + if change.Old != nil && change.New == nil { deleteDatabaseUserOptions := &clouddatabasesv5.DeleteDatabaseUserOptions{ ID: &instanceID, UserType: core.StringPtr(change.Old["type"].(string)), @@ -2458,36 +2462,100 @@ func resourceIBMDatabaseInstanceUpdate(context context.Context, d *schema.Resour return diag.FromErr(fmt.Errorf( "[ERROR] Error waiting for database (%s) user (%s) delete task to complete: %s", icdId, *deleteDatabaseUserOptions.Username, err)) } + + continue } - // Create New User if change.New != nil { - userEntry := &clouddatabasesv5.User{ - Username: core.StringPtr(change.New["name"].(string)), - Password: core.StringPtr(change.New["password"].(string)), + // No change + if change.Old != nil && change.Old["password"].(string) == change.New["password"].(string) && change.Old["name"].(string) == change.New["name"].(string) { + continue } - // User Role only for ops_manager user type - if change.New["type"].(string) == "ops_manager" && change.New["role"].(string) != "" { - userEntry.Role = core.StringPtr(change.New["role"].(string)) + err := userUpdateCreate(change.New, instanceID, meta, d) + if err != nil { + return diag.FromErr(err) } + } + } + } - createDatabaseUserOptions := &clouddatabasesv5.CreateDatabaseUserOptions{ - ID: &instanceID, - UserType: core.StringPtr(change.New["type"].(string)), - User: userEntry, + if d.HasChange("logical_replication_slot") { + service := d.Get("service").(string) + if service != "databases-for-postgresql" { + 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)) + } + + oldList, newList := d.GetChange("logical_replication_slot") + + if oldList == nil { + oldList = new(schema.Set) + } + if newList == nil { + newList = new(schema.Set) + } + os := oldList.(*schema.Set) + ns := newList.(*schema.Set) + + remove := os.Difference(ns).List() + add := ns.Difference(os).List() + + // Create New Logical Rep Slot + if len(add) > 0 { + for _, entry := range add { + newEntry := entry.(map[string]interface{}) + logicalReplicationSlot := &clouddatabasesv5.LogicalReplicationSlot{ + Name: core.StringPtr(newEntry["name"].(string)), + DatabaseName: core.StringPtr(newEntry["database_name"].(string)), + PluginType: core.StringPtr(newEntry["plugin_type"].(string)), + } + + createLogicalReplicationOptions := &clouddatabasesv5.CreateLogicalReplicationSlotOptions{ + ID: &instanceID, + LogicalReplicationSlot: logicalReplicationSlot, + } + + createLogicalRepSlotResponse, response, err := cloudDatabasesClient.CreateLogicalReplicationSlot(createLogicalReplicationOptions) + if err != nil { + return diag.FromErr(fmt.Errorf("[ERROR] CreateLogicalReplicationSlot (%s) failed %s\n%s", *createLogicalReplicationOptions.LogicalReplicationSlot.Name, err, response)) + } + + taskID := *createLogicalRepSlotResponse.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) create task to complete: %s", instanceID, *createLogicalReplicationOptions.LogicalReplicationSlot.Name, err)) + } + } + } + + // Delete Old Logical Rep Slot + if len(remove) > 0 { + for _, entry := range remove { + newEntry := entry.(map[string]interface{}) + deleteDatabaseUserOptions := &clouddatabasesv5.DeleteLogicalReplicationSlotOptions{ + ID: &instanceID, + Name: core.StringPtr(newEntry["name"].(string)), } - createDatabaseUserResponse, response, err := cloudDatabasesClient.CreateDatabaseUser(createDatabaseUserOptions) + deleteDatabaseUserResponse, response, err := cloudDatabasesClient.DeleteLogicalReplicationSlot(deleteDatabaseUserOptions) + if err != nil { - return diag.FromErr(fmt.Errorf("[ERROR] CreateDatabaseUser (%s) failed %s\n%s", *userEntry.Username, err, response)) + return diag.FromErr(fmt.Errorf( + "[ERROR] DeleteDatabaseUser (%s) failed %s\n%s", *deleteDatabaseUserOptions.Name, err, response)) } - taskID := *createDatabaseUserResponse.Task.ID + taskID := *deleteDatabaseUserResponse.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) user (%s) create task to complete: %s", instanceID, *userEntry.Username, err)) + "[ERROR] Error waiting for database (%s) logical replication slot (%s) delete task to complete: %s", icdId, *deleteDatabaseUserOptions.Name, err)) } } } @@ -3165,3 +3233,67 @@ 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() + // Attempt to update user password + passwordSettingUser := &clouddatabasesv5.APasswordSettingUser{ + Password: core.StringPtr(userData["password"].(string)), + } + + changeUserPasswordOptions := &clouddatabasesv5.ChangeUserPasswordOptions{ + ID: &instanceID, + UserType: core.StringPtr(userData["type"].(string)), + Username: core.StringPtr(userData["name"].(string)), + 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) + } + + taskID := *changeUserPasswordResponse.Task.ID + updatePass, err := waitForDatabaseTaskComplete(taskID, d, meta, d.Timeout(schema.TimeoutUpdate)) + + if err != nil { + log.Printf("[ERROR] Error waiting for database (%s) user (%s) password update task to complete: %s", instanceID, *changeUserPasswordOptions.Username, 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)), + } + + // User Role only for ops_manager user type + if userData["type"].(string) == "ops_manager" && userData["role"].(string) != "" { + userEntry.Role = core.StringPtr(userData["role"].(string)) + } + + createDatabaseUserOptions := &clouddatabasesv5.CreateDatabaseUserOptions{ + ID: &instanceID, + UserType: core.StringPtr(userData["type"].(string)), + User: userEntry, + } + + createDatabaseUserResponse, response, err := cloudDatabasesClient.CreateDatabaseUser(createDatabaseUserOptions) + if err != nil { + return fmt.Errorf("[ERROR] CreateDatabaseUser (%s) failed %s\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: %s", instanceID, *userEntry.Username, err) + } + } + + return nil +} diff --git a/ibm/service/database/resource_ibm_database_postgresql_test.go b/ibm/service/database/resource_ibm_database_postgresql_test.go index 48423ccd76..5a55b81583 100644 --- a/ibm/service/database/resource_ibm_database_postgresql_test.go +++ b/ibm/service/database/resource_ibm_database_postgresql_test.go @@ -68,6 +68,7 @@ func TestAccIBMDatabaseInstancePostgresBasic(t *testing.T) { resource.TestMatchResourceAttr(name, "connectionstrings.1.certname", regexp.MustCompile("[-a-z0-9]*")), resource.TestMatchResourceAttr(name, "connectionstrings.1.certbase64", regexp.MustCompile("^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$")), resource.TestCheckResourceAttr(name, "tags.#", "1"), + resource.TestCheckResourceAttr(name, "logical_replication_slot.#", "1"), ), }, { @@ -82,15 +83,16 @@ func TestAccIBMDatabaseInstancePostgresBasic(t *testing.T) { resource.TestCheckResourceAttr(name, "members_disk_allocation_mb", "14336"), resource.TestCheckResourceAttr(name, "service_endpoints", "public-and-private"), resource.TestCheckResourceAttr(name, "allowlist.#", "2"), - resource.TestCheckResourceAttr(name, "users.#", "2"), - resource.TestCheckResourceAttr(name, "connectionstrings.#", "3"), - resource.TestCheckResourceAttr(name, "connectionstrings.2.name", "admin"), + resource.TestCheckResourceAttr(name, "users.#", "3"), + resource.TestCheckResourceAttr(name, "connectionstrings.#", "4"), + resource.TestCheckResourceAttr(name, "connectionstrings.3.name", "admin"), resource.TestCheckResourceAttr(name, "connectionstrings.0.hosts.#", "1"), resource.TestCheckResourceAttr(name, "connectionstrings.0.scheme", "postgres"), resource.TestMatchResourceAttr(name, "connectionstrings.0.certname", regexp.MustCompile("[-a-z0-9]*")), resource.TestMatchResourceAttr(name, "connectionstrings.0.certbase64", regexp.MustCompile("^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$")), resource.TestMatchResourceAttr(name, "connectionstrings.0.database", regexp.MustCompile("[-a-z0-9]+")), resource.TestCheckResourceAttr(name, "tags.#", "1"), + resource.TestCheckResourceAttr(name, "logical_replication_slot.#", "2"), ), }, // { @@ -596,6 +598,11 @@ func testAccCheckIBMDatabaseInstancePostgresBasic(databaseResourceGroup string, "max_wal_senders": 21 } CONFIGURATION + logical_replication_slot { + name = "wj123" + database_name = "ibmclouddb" + plugin_type = "wal2json" + } } `, databaseResourceGroup, name, acc.IcdDbRegion) } @@ -626,6 +633,17 @@ func testAccCheckIBMDatabaseInstancePostgresFullyspecified(databaseResourceGroup name = "user124" password = "password12" } + users { + name = "repl" + password = "repl123456" + } + configuration = <:backup:`. If omitted, the database is provisioned empty. - `backup_encryption_key_crn`- (Optional, Forces new resource, String) The CRN of a key protect key, that you want to use for encrypting disk that holds deployment backups. A key protect CRN is in the format `crn:v1:<...>:key:`. Backup_encryption_key_crn can be added only at the time of creation and no update support are available. - `configuration` - (Optional, Json String) Database Configuration in JSON format. Supported services `databases-for-postgresql`, `databases-for-redis` and `databases-for-enterprisedb`. For valid values please refer [API docs](https://cloud.ibm.com/apidocs/cloud-databases-api/cloud-databases-api-v4#setdatabaseconfiguration-request). +- `logical_replication_slot` - (Optional, List of Objects) A list of logical replication slots that you want to create on the database. Multiple blocks are allowed. This is only available for `databases-for-postgresql`. + + Nested scheme for `logical_replication_slot`: + - `name` - (Required, String) The name of the `logical_replication_slot`. + - `database_name` - (Required, String) The name of the database on which you want to create the `logical_replication_slot`. + - `plugin_type` - (Required, String) The plugin type that is used to create the `logical_replication_slot`. Only `wal2json` is supported. + + Prereqs to creating a logical replication slot: + - Make sure the replication user's (`repl`) password has been changed. + - Make sure that your database is configured such that logical replication can be enabled. This means thats the `wal_level` needs to be set to `logical`. Also, `max_replication_slots` and `max_wal_senders` must be greater than 20. + - For more information on enabling logical replication slots please see [Configuring Wal2json](https://cloud.ibm.com/docs/databases-for-postgresql?topic=databases-for-postgresql-wal2json) - `guid` - (Optional, String) The unique identifier of the database instance. - `key_protect_key` - (Optional, Forces new resource, String) The root key CRN of a Key Management Services like Key Protect or Hyper Protect Crypto Service (HPCS) that you want to use for disk encryption. A key CRN is in the format `crn:v1:<…>:key:`. You can specify the root key during the database creation only. After the database is created, you cannot update the root key. For more information, refer [Disk encryption](https://cloud.ibm.com/docs/cloud-databases?topic=cloud-databases-key-protect#using-the-key-protect-key) documentation. - `key_protect_instance` - (Optional, Forces new resource, String) The instance CRN of a Key Management Services like Key Protect or Hyper Protect Crypto Service (HPCS) that you want to use for disk encryption. An instance CRN is in the format `crn:v1:<…>::`.