diff --git a/docs/resources/rdb_instance.md b/docs/resources/rdb_instance.md index 21d478a640..c3157b9c32 100644 --- a/docs/resources/rdb_instance.md +++ b/docs/resources/rdb_instance.md @@ -109,6 +109,8 @@ output "upgradable_versions" { # } ``` +~> **Warning** Provider versions prior to `2.61.0` did not support engine upgrades. Changing the `engine` value in these versions would recreate the Database Instance **empty**, resulting in **data loss**. Ensure you are using provider version `>= 2.61.0` before upgrading your Database Instance engine version. + ### Examples of endpoint configuration Database Instances can have a maximum of 1 public endpoint and 1 private endpoint. They can have both, or none. @@ -175,6 +177,8 @@ interruption. - `engine` - (Required) Database Instance's engine version name (e.g. `PostgreSQL-16`, `MySQL-8`). +~> **Warning** Provider versions prior to `2.61.0` did not support engine upgrades. Changing the `engine` value in these versions would recreate the Database Instance **empty**, resulting in **data loss**. Ensure you are using provider version `>= 2.61.0` before upgrading your Database Instance engine version. + ~> **Important** Updates to `engine` will perform a blue/green upgrade using `MajorUpgradeWorkflow`. This creates a new instance from a snapshot, migrates endpoints automatically, and updates the Terraform state with the new instance ID. The upgrade ensures minimal downtime but **any writes between the snapshot and the endpoint migration will be lost**. Use the `upgradable_versions` computed attribute to check available versions for upgrade. - `volume_type` - (Optional, default to `lssd`) Type of volume where data are stored (`lssd`, `sbs_5k` or `sbs_15k`). diff --git a/internal/services/cockpit/testfuncs/sweep.go b/internal/services/cockpit/testfuncs/sweep.go index 64c69e76c3..0587e8896d 100644 --- a/internal/services/cockpit/testfuncs/sweep.go +++ b/internal/services/cockpit/testfuncs/sweep.go @@ -51,7 +51,7 @@ func testSweepCockpitToken(_ string) error { }, scw.WithAllPages()) if err != nil { if httperrors.Is404(err) { - return nil + continue } return fmt.Errorf("failed to list tokens: %w", err) @@ -93,7 +93,7 @@ func testSweepCockpitGrafanaUser(_ string) error { }, scw.WithAllPages()) if err != nil { if httperrors.Is404(err) { - return nil + continue } return fmt.Errorf("failed to list grafana users: %w", err) @@ -117,34 +117,9 @@ func testSweepCockpitGrafanaUser(_ string) error { } func testSweepCockpit(_ string) error { - return acctest.Sweep(func(scwClient *scw.Client) error { - accountAPI := accountSDK.NewProjectAPI(scwClient) - - listProjects, err := accountAPI.ListProjects(&accountSDK.ProjectAPIListProjectsRequest{}, scw.WithAllPages()) - if err != nil { - return fmt.Errorf("failed to list projects: %w", err) - } - - for _, project := range listProjects.Projects { - if !strings.HasPrefix(project.Name, "tf_tests") { - continue - } - - if err != nil { - if !httperrors.Is404(err) { - return fmt.Errorf("failed to deactivate cockpit: %w", err) - } - } - - if err != nil { - if !httperrors.Is404(err) { - return fmt.Errorf("failed to deactivate cockpit: %w", err) - } - } - } - - return nil - }) + // Cockpit resource doesn't require explicit deactivation. + // Sources, tokens, and other resources are cleaned up by their respective sweepers. + return nil } func testSweepCockpitSource(_ string) error { @@ -162,21 +137,61 @@ func testSweepCockpitSource(_ string) error { continue } + // Collect all sources by trying without filter and with all possible origins + // Some sources may only appear when filtering by origin + allDataSources := make(map[string]*cockpit.DataSource) + + // List of possible origins to try + origins := []cockpit.DataSourceOrigin{ + cockpit.DataSourceOriginUnknownOrigin, + cockpit.DataSourceOriginCustom, + cockpit.DataSourceOriginScaleway, + cockpit.DataSourceOriginExternal, + } + + // First, try without any origin filter listDatasources, err := cockpitAPI.ListDataSources(&cockpit.RegionalAPIListDataSourcesRequest{ ProjectID: project.ID, Region: region, }, scw.WithAllPages()) if err != nil { - if httperrors.Is404(err) { - return nil + if !httperrors.Is404(err) { + return fmt.Errorf("failed to list sources: %w", err) + } + } else { + // Collect all sources from the unfiltered list + for _, datasource := range listDatasources.DataSources { + allDataSources[datasource.ID] = datasource } + } - return fmt.Errorf("failed to list sources: %w", err) + // Always try with each origin to ensure we catch all sources + // Some sources may only appear when filtering by specific origin + for _, origin := range origins { + listDatasources, err := cockpitAPI.ListDataSources(&cockpit.RegionalAPIListDataSourcesRequest{ + ProjectID: project.ID, + Region: region, + Origin: origin, + }, scw.WithAllPages()) + if err != nil { + if httperrors.Is404(err) { + continue + } + + // Don't return error here, just continue with next origin + continue + } + + // Collect all unique sources + for _, datasource := range listDatasources.DataSources { + allDataSources[datasource.ID] = datasource + } } - for _, datsource := range listDatasources.DataSources { + // Delete all collected sources + for _, datasource := range allDataSources { err = cockpitAPI.DeleteDataSource(&cockpit.RegionalAPIDeleteDataSourceRequest{ - DataSourceID: datsource.ID, + DataSourceID: datasource.ID, Region: region, }) if err != nil { diff --git a/internal/services/mnq/testfuncs/sweep.go b/internal/services/mnq/testfuncs/sweep.go index 58f738e64c..8de216338c 100644 --- a/internal/services/mnq/testfuncs/sweep.go +++ b/internal/services/mnq/testfuncs/sweep.go @@ -9,6 +9,7 @@ import ( mnqSDK "github.com/scaleway/scaleway-sdk-go/api/mnq/v1beta1" "github.com/scaleway/scaleway-sdk-go/scw" "github.com/scaleway/terraform-provider-scaleway/v2/internal/acctest" + "github.com/scaleway/terraform-provider-scaleway/v2/internal/httperrors" "github.com/scaleway/terraform-provider-scaleway/v2/internal/logging" ) @@ -46,6 +47,10 @@ func testSweepSQSCredentials(_ string) error { Region: region, }, scw.WithAllPages()) if err != nil { + if httperrors.Is404(err) { + return nil + } + return fmt.Errorf("error listing sqs credentials in (%s) in sweeper: %w", region, err) } @@ -55,6 +60,12 @@ func testSweepSQSCredentials(_ string) error { Region: region, }) if err != nil { + if httperrors.Is404(err) { + logging.L.Debugf("sweeper: ignoring error (%s)", err) + + continue + } + logging.L.Debugf("sweeper: error (%s)", err) return fmt.Errorf("error deleting sqs credentials in sweeper: %w", err) @@ -87,6 +98,12 @@ func testSweepSQS(_ string) error { ProjectID: project.ID, }) if err != nil { + if httperrors.Is404(err) { + logging.L.Debugf("sweeper: ignoring error (%s)", err) + + continue + } + logging.L.Debugf("sweeper: error (%s)", err) return err @@ -108,6 +125,10 @@ func testSweepSNSCredentials(_ string) error { Region: region, }, scw.WithAllPages()) if err != nil { + if httperrors.Is404(err) { + return nil + } + return fmt.Errorf("error listing sns credentials in (%s) in sweeper: %w", region, err) } @@ -117,6 +138,12 @@ func testSweepSNSCredentials(_ string) error { Region: region, }) if err != nil { + if httperrors.Is404(err) { + logging.L.Debugf("sweeper: ignoring error (%s)", err) + + continue + } + logging.L.Debugf("sweeper: error (%s)", err) return fmt.Errorf("error deleting sns credentials in sweeper: %w", err) @@ -149,6 +176,12 @@ func testSweepSNS(_ string) error { ProjectID: project.ID, }) if err != nil { + if httperrors.Is404(err) { + logging.L.Debugf("sweeper: ignoring error (%s)", err) + + continue + } + logging.L.Debugf("sweeper: error (%s)", err) return err @@ -170,6 +203,10 @@ func testSweepNatsAccount(_ string) error { Region: region, }, scw.WithAllPages()) if err != nil { + if httperrors.Is404(err) { + return nil + } + return fmt.Errorf("error listing nats account in (%s) in sweeper: %w", region, err) } @@ -179,6 +216,12 @@ func testSweepNatsAccount(_ string) error { Region: region, }) if err != nil { + if httperrors.Is404(err) { + logging.L.Debugf("sweeper: ignoring error (%s)", err) + + continue + } + logging.L.Debugf("sweeper: error (%s)", err) return fmt.Errorf("error deleting nats account in sweeper: %w", err) diff --git a/templates/resources/rdb_instance.md.tmpl b/templates/resources/rdb_instance.md.tmpl index 2b44ee26ad..af92e0dc70 100644 --- a/templates/resources/rdb_instance.md.tmpl +++ b/templates/resources/rdb_instance.md.tmpl @@ -110,6 +110,8 @@ output "upgradable_versions" { # } ``` +~> **Warning** Provider versions prior to `2.61.0` did not support engine upgrades. Changing the `engine` value in these versions would recreate the Database Instance **empty**, resulting in **data loss**. Ensure you are using provider version `>= 2.61.0` before upgrading your Database Instance engine version. + ### Examples of endpoint configuration Database Instances can have a maximum of 1 public endpoint and 1 private endpoint. They can have both, or none. @@ -176,6 +178,8 @@ interruption. - `engine` - (Required) Database Instance's engine version name (e.g. `PostgreSQL-16`, `MySQL-8`). +~> **Warning** Provider versions prior to `2.61.0` did not support engine upgrades. Changing the `engine` value in these versions would recreate the Database Instance **empty**, resulting in **data loss**. Ensure you are using provider version `>= 2.61.0` before upgrading your Database Instance engine version. + ~> **Important** Updates to `engine` will perform a blue/green upgrade using `MajorUpgradeWorkflow`. This creates a new instance from a snapshot, migrates endpoints automatically, and updates the Terraform state with the new instance ID. The upgrade ensures minimal downtime but **any writes between the snapshot and the endpoint migration will be lost**. Use the `upgradable_versions` computed attribute to check available versions for upgrade. - `volume_type` - (Optional, default to `lssd`) Type of volume where data are stored (`lssd`, `sbs_5k` or `sbs_15k`).