From 2dc85c81844309d2e99d34658859444aff5312fb Mon Sep 17 00:00:00 2001 From: Artem Lifshits Date: Thu, 9 Jan 2025 15:01:24 +0100 Subject: [PATCH] fixes --- docs/resources/rds_backup_v3.md | 50 +++--- ...rce_opentelekomcloud_rds_backup_v3_test.go | 74 +++++++-- ...resource_opentelekomcloud_rds_backup_v3.go | 155 ++++++++---------- .../rds_backup_fix-a98dc92d7b662442.yaml | 4 + 4 files changed, 169 insertions(+), 114 deletions(-) create mode 100644 releasenotes/notes/rds_backup_fix-a98dc92d7b662442.yaml diff --git a/docs/resources/rds_backup_v3.md b/docs/resources/rds_backup_v3.md index d11727ae4..8ba5fa744 100644 --- a/docs/resources/rds_backup_v3.md +++ b/docs/resources/rds_backup_v3.md @@ -16,49 +16,61 @@ Manages a manual RDS backup. ## Example Usage +### Create a basic RDS backup + ```hcl -resource "opentelekomcloud_rds_instance_v3" "instance" { - name = "test-instance" - engine = "mysql" - datastore = "percona" - flavor_ref = "rds.mysql.s1.large" +resource "opentelekomcloud_rds_backup_v3" "test" { + instance_id = opentelekomcloud_rds_instance_v3.instance.id + name = "rds-backup-test-01" } +``` +### Create a specific RDS databases backup for Microsoft SQL Server + +```hcl resource "opentelekomcloud_rds_backup_v3" "test" { instance_id = opentelekomcloud_rds_instance_v3.instance.id name = "rds-backup-test-01" - description = "manual" + databases = ["test", "test2"] } ``` ## Argument Reference + The following arguments are supported: -* `instance_id` - (Required) The ID of the RDS instance to which the backup belongs. -* `name` - (Required) The name of the backup. -* `description` - (Optional) Specifies the backup description. - It contains a maximum of 256 characters and cannot contain the following special characters: >!<"&'= -* `databases` - (Optional) Specifies a list of self-built Microsoft SQL Server databases that are partially backed up. +* `instance_id` - (Required, String, ForceNew) The ID of the RDS instance to which the backup belongs. + +* `name` - (Required, String, ForceNew) The name of the backup. + +* `databases` - (Optional, List, ForceNew) Specifies a list of self-built Microsoft SQL Server databases that are partially backed up. (Only Microsoft SQL Server support partial backups.) ## Attributes Reference -The following attributes are exported: + +In addition to the arguments listed above, the following computed attributes are exported: * `id` - The ID of the backup. -* `instance_id` - The ID of the RDS instance to which the backup belongs. -* `name` - The name of the backup. -* `description` - The description of the backup. -* `databases` - The list of self-built Microsoft SQL Server databases that are partially backed up. - (Only Microsoft SQL Server support partial backups.) + * `begin_time` - Indicates the backup start time in the "yyyy-mm-ddThh:mm:ssZ" format, where "T" indicates the start time of the time field, and "Z" indicates the time zone offset. -* `status` - Indicates the backup status. Value: + +* `status` - Indicates the backup status. Values: - BUILDING: Backup in progress - COMPLETED: Backup completed - FAILED: Backup failed - DELETING: Backup being deleted -* `type` - Indicates the backup type. Value: + +* `type` - Indicates the backup type. Values: - auto: automated full backup - manual: manual full backup - fragment: differential full backup - incremental: automated incremental backup + +## Import + +RDS backup can be imported using related RDS `instance_id` and their `backup_id`, separated by the slashes, e.g. + +```bash +$ terraform import opentelekomcloud_rds_backup_v3.backup / +``` diff --git a/opentelekomcloud/acceptance/rds/resource_opentelekomcloud_rds_backup_v3_test.go b/opentelekomcloud/acceptance/rds/resource_opentelekomcloud_rds_backup_v3_test.go index e15493679..6b3f0654f 100644 --- a/opentelekomcloud/acceptance/rds/resource_opentelekomcloud_rds_backup_v3_test.go +++ b/opentelekomcloud/acceptance/rds/resource_opentelekomcloud_rds_backup_v3_test.go @@ -16,9 +16,38 @@ import ( "github.com/opentelekomcloud/terraform-provider-opentelekomcloud/opentelekomcloud/common/cfg" ) +func getBackupResourceFunc(conf *cfg.Config, state *terraform.ResourceState) (interface{}, error) { + client, err := conf.RdsV3Client(env.OS_REGION_NAME) + if err != nil { + return nil, fmt.Errorf("error creating RDS v3 client: %s", err) + } + + opts := backups.ListOpts{ + InstanceID: state.Primary.Attributes["instance_id"], + BackupID: state.Primary.ID, + } + + backupList, err := backups.List(client, opts) + + if err != nil { + return nil, fmt.Errorf("error listing backups: %s", err) + } + + return backupList[0], nil +} + func TestAccResourceRDSV3Backup_basic(t *testing.T) { - resourceName := "opentelekomcloud_rds_backup_v3.test" - postfix := acctest.RandString(3) + var ( + obj backups.Backup + rName = "opentelekomcloud_rds_backup_v3.test" + postfix = acctest.RandString(3) + ) + + rc := common.InitResourceCheck( + rName, + &obj, + getBackupResourceFunc, + ) resource.Test(t, resource.TestCase{ PreCheck: func() { common.TestAccPreCheck(t) }, @@ -28,17 +57,42 @@ func TestAccResourceRDSV3Backup_basic(t *testing.T) { { Config: testAccResourceRDSV3BackupBasic(postfix), Check: resource.ComposeTestCheckFunc( - testAccCheckRdsBackupV3Exists(resourceName), - resource.TestCheckResourceAttr(resourceName, "name", "tf_rds_backup_"+postfix), - resource.TestCheckResourceAttr(resourceName, "type", "manual"), - resource.TestCheckResourceAttr(resourceName, "description", ""), - resource.TestCheckResourceAttrSet(resourceName, "id"), + rc.CheckResourceExists(), + testAccCheckRdsBackupV3Exists(rName), + resource.TestCheckResourceAttr(rName, "name", "tf_rds_backup_"+postfix), + resource.TestCheckResourceAttr(rName, "type", "manual"), + resource.TestCheckResourceAttr(rName, "status", "COMPLETED"), + resource.TestCheckResourceAttrSet(rName, "id"), ), }, + { + ResourceName: rName, + ImportState: true, + ImportStateVerify: true, + ImportStateIdFunc: testAccBackupImportStateFunc(rName), + }, }, }) } +func testAccBackupImportStateFunc(rsName string) resource.ImportStateIdFunc { + return func(s *terraform.State) (string, error) { + var instanceId, backupId string + rs, ok := s.RootModule().Resources[rsName] + if !ok { + return "", fmt.Errorf("the resource (%s) of RDF backup is not found in the tfstate", rsName) + } + instanceId = rs.Primary.Attributes["instance_id"] + backupId = rs.Primary.ID + if instanceId == "" || backupId == "" { + return "", fmt.Errorf("some import IDs are missing: "+ + "'/', but got '%s/%s'", + instanceId, backupId) + } + return fmt.Sprintf("%s/%s", instanceId, backupId), nil + } +} + func testAccCheckRdsBackupV3Destroy(s *terraform.State) error { const backupDeleteRetryTimeout = 5 * time.Minute @@ -123,7 +177,7 @@ resource "opentelekomcloud_rds_instance_v3" "instance" { db { password = "Postgres!120521" type = "PostgreSQL" - version = "10" + version = "16" port = "8635" } security_group_id = data.opentelekomcloud_networking_secgroup_v2.default_secgroup.id @@ -134,10 +188,6 @@ resource "opentelekomcloud_rds_instance_v3" "instance" { size = 40 } flavor = "rds.pg.c2.large" - backup_strategy { - start_time = "08:00-09:00" - keep_days = 1 - } } resource "opentelekomcloud_rds_backup_v3" "test" { diff --git a/opentelekomcloud/services/rds/resource_opentelekomcloud_rds_backup_v3.go b/opentelekomcloud/services/rds/resource_opentelekomcloud_rds_backup_v3.go index e7b334727..83dc486a7 100644 --- a/opentelekomcloud/services/rds/resource_opentelekomcloud_rds_backup_v3.go +++ b/opentelekomcloud/services/rds/resource_opentelekomcloud_rds_backup_v3.go @@ -4,13 +4,13 @@ import ( "context" "fmt" "log" + "strings" "time" "github.com/hashicorp/go-multierror" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" golangsdk "github.com/opentelekomcloud/gophertelekomcloud" backups "github.com/opentelekomcloud/gophertelekomcloud/openstack/rds/v3/backups" "github.com/opentelekomcloud/gophertelekomcloud/openstack/rds/v3/instances" @@ -26,7 +26,7 @@ func ResourceRdsBackupV3() *schema.Resource { DeleteContext: resourceRDSv3BackupDelete, Importer: &schema.ResourceImporter{ - StateContext: schema.ImportStatePassthroughContext, + StateContext: resourceBackupImportState, }, Timeouts: &schema.ResourceTimeout{ @@ -39,23 +39,22 @@ func ResourceRdsBackupV3() *schema.Resource { Required: true, ForceNew: true, }, - "backup_id": { + "name": { Type: schema.TypeString, - Computed: true, + Required: true, + ForceNew: true, }, - "type": { - Type: schema.TypeString, + "databases": { + Type: schema.TypeList, Optional: true, - ValidateFunc: validation.StringInSlice( - []string{"auto", "manual", "fragment", "incremental"}, - false, - ), - Computed: true, + ForceNew: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, }, - "name": { + "type": { Type: schema.TypeString, - Required: true, - ForceNew: true, + Computed: true, }, "size": { Type: schema.TypeInt, @@ -69,25 +68,9 @@ func ResourceRdsBackupV3() *schema.Resource { Type: schema.TypeString, Computed: true, }, - "description": { + "backup_id": { Type: schema.TypeString, - Optional: true, - ForceNew: true, - }, - "databases": { - Type: schema.TypeList, - Optional: true, Computed: true, - ForceNew: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "name": { - Type: schema.TypeString, - Required: true, - ForceNew: true, - }, - }, - }, }, }, } @@ -95,16 +78,17 @@ func ResourceRdsBackupV3() *schema.Resource { func resourceRDSv3BackupCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { config := meta.(*cfg.Config) - client, err := config.RdsV3Client(config.GetRegion(d)) + client, err := common.ClientFromCtx(ctx, keyClientV3, func() (*golangsdk.ServiceClient, error) { + return config.RdsV3Client(config.GetRegion(d)) + }) if err != nil { return fmterr.Errorf(errCreateClient, err) } opts := backups.CreateOpts{ - InstanceID: d.Get("instance_id").(string), - Name: d.Get("name").(string), - Description: d.Get("description").(string), - Databases: resourceDatabaseExpand(d), + InstanceID: d.Get("instance_id").(string), + Name: d.Get("name").(string), + Databases: resourceDatabaseExpand(d), } // check if rds instance exists @@ -115,24 +99,22 @@ func resourceRDSv3BackupCreate(ctx context.Context, d *schema.ResourceData, meta return fmterr.Errorf("error getting RDSv3 instance: %w", err) } + if len(rds.Instances) == 0 { + return fmterr.Errorf("RDSv3 instance not found") + } + // wait until rds instance is active err = instances.WaitForStateAvailable(client, 120, opts.InstanceID) if err != nil { return diag.FromErr(err) } - if len(rds.Instances) == 0 { - return fmterr.Errorf("RDSv3 instance not found") - } - backup, err := backups.Create(client, opts) if err != nil { fmterr.Errorf("error creating new RDSv3 backup: %w", err) } - if backup == nil { - return diag.Errorf("backup not created") - } + d.SetId(backup.ID) stateConf := &resource.StateChangeConf{ Pending: []string{"BUILDING"}, @@ -150,46 +132,17 @@ func resourceRDSv3BackupCreate(ctx context.Context, d *schema.ResourceData, meta return diag.FromErr(err) } - if backup == nil { - return diag.Errorf("backup not created") - } - // Check if backup.InstanceID and backup.ID are not empty - if backup.InstanceID == "" || backup.ID == "" { - return diag.Errorf("backup instance ID or backup ID is empty") - } - log.Printf("[DEBUG] RDSv3 backup created: %#v", backup) - d.SetId(backup.ID) - return resourceRDSv3BackupRead(ctx, d, meta) + clientCtx := common.CtxWithClient(ctx, client, keyClientV3) + return resourceRDSv3BackupRead(clientCtx, d, meta) } -func resourceRDSv3BackupDelete(_ context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { +func resourceRDSv3BackupRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { config := meta.(*cfg.Config) - client, err := config.RdsV3Client(config.GetRegion(d)) - if err != nil { - return fmterr.Errorf(errCreateClient, err) - } - - instanceID := d.Get("instance_id").(string) - - err = backups.Delete(client, d.Id()) - if err != nil { - return fmterr.Errorf("error deleting OpenTelekomCloud RDSv3 backup: %s", err) - } - - err = waitForRDSBackupDeletion(client, instanceID, d.Id(), d.Timeout(schema.TimeoutDelete)) - if err != nil { - return diag.FromErr(err) - } - - d.SetId("") - return nil -} - -func resourceRDSv3BackupRead(_ context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - config := meta.(*cfg.Config) - client, err := config.RdsV3Client(config.GetRegion(d)) + client, err := common.ClientFromCtx(ctx, keyClientV3, func() (*golangsdk.ServiceClient, error) { + return config.RdsV3Client(config.GetRegion(d)) + }) if err != nil { return fmterr.Errorf(errCreateClient, err) } @@ -212,7 +165,6 @@ func resourceRDSv3BackupRead(_ context.Context, d *schema.ResourceData, meta int mErr := multierror.Append( d.Set("instance_id", backup.InstanceID), d.Set("name", backup.Name), - d.Set("description", backup.Description), d.Set("databases", expandDatabases(backup.Databases)), d.Set("status", backup.Status), d.Set("type", backup.Type), @@ -224,14 +176,38 @@ func resourceRDSv3BackupRead(_ context.Context, d *schema.ResourceData, meta int return nil } +func resourceRDSv3BackupDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + config := meta.(*cfg.Config) + client, err := common.ClientFromCtx(ctx, keyClientV3, func() (*golangsdk.ServiceClient, error) { + return config.RdsV3Client(config.GetRegion(d)) + }) + if err != nil { + return fmterr.Errorf(errCreateClient, err) + } + + instanceID := d.Get("instance_id").(string) + + err = backups.Delete(client, d.Id()) + if err != nil { + return fmterr.Errorf("error deleting OpenTelekomCloud RDSv3 backup: %s", err) + } + + err = waitForRDSBackupDeletion(client, instanceID, d.Id(), d.Timeout(schema.TimeoutDelete)) + if err != nil { + return diag.FromErr(err) + } + + d.SetId("") + return nil +} + func resourceDatabaseExpand(d *schema.ResourceData) []backups.BackupDatabase { - var backupsDatabases []backups.BackupDatabase + backupsDatabases := make([]backups.BackupDatabase, 0) dbRaw := d.Get("databases").([]interface{}) log.Printf("[DEBUG] dbRaw: %+v", dbRaw) - for i := range dbRaw { - db := dbRaw[i].(map[string]interface{}) + for _, v := range dbRaw { dbReq := backups.BackupDatabase{ - Name: db["name"].(string), + Name: v.(string), } backupsDatabases = append(backupsDatabases, dbReq) } @@ -287,3 +263,16 @@ func waitForRDSBackupDeletion(client *golangsdk.ServiceClient, instanceID, backu } return nil } + +func resourceBackupImportState(_ context.Context, d *schema.ResourceData, _ interface{}) ([]*schema.ResourceData, + error) { + parts := strings.SplitN(d.Id(), "/", 2) + if len(parts) != 2 { + return nil, fmt.Errorf("invalid format for import ID, want '/', but got '%s'", d.Id()) + } + + d.SetId(parts[1]) + err := d.Set("instance_id", parts[0]) + + return []*schema.ResourceData{d}, err +} diff --git a/releasenotes/notes/rds_backup_fix-a98dc92d7b662442.yaml b/releasenotes/notes/rds_backup_fix-a98dc92d7b662442.yaml new file mode 100644 index 000000000..908e7a9e4 --- /dev/null +++ b/releasenotes/notes/rds_backup_fix-a98dc92d7b662442.yaml @@ -0,0 +1,4 @@ +--- +fixes: + - | + **[RDS]** Fix backup schema for ``resource/opentelekomcloud_rds_backup_v3`` (`# `_)