Skip to content

Commit 006df6b

Browse files
jremy42dependabot[bot]remyleone
authored
feat(rdb): creating instance from snapshot (#2872)
* feat(rdb): add resource for snapshot * feat: add test * fix lint doc * fix: lint doc * fix: lint doc * fix: lint doc * fix: lint doc * fix: lint doc * chore(deps): bump github.com/aws/smithy-go from 1.22.1 to 1.22.2 (#2892) Bumps [github.com/aws/smithy-go](https://github.com/aws/smithy-go) from 1.22.1 to 1.22.2. - [Release notes](https://github.com/aws/smithy-go/releases) - [Changelog](https://github.com/aws/smithy-go/blob/main/CHANGELOG.md) - [Commits](aws/smithy-go@v1.22.1...v1.22.2) --- updated-dependencies: - dependency-name: github.com/aws/smithy-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <[email protected]> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Rémy Léone <[email protected]> * feat: add test * fix: name test * fix: lint * updated cassette * fix error 400 * delete old cassette * delete unused cassette * fix delete log * doc updated * fix: update node_type description * Fix a lint --------- Signed-off-by: dependabot[bot] <[email protected]> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Rémy Léone <[email protected]>
1 parent b213214 commit 006df6b

File tree

46 files changed

+25283
-17591
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+25283
-17591
lines changed

docs/resources/rdb_snapshot.md

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
---
2+
subcategory: "Databases"
3+
page_title: "Scaleway: scaleway_rdb_snapshot"
4+
---
5+
6+
# Resource: scaleway_rdb_snapshot
7+
8+
Creates and manages Scaleway RDB (Relational Database) Snapshots.
9+
Snapshots are point-in-time backups of a database instance that can be used for recovery or duplication.
10+
For more information, refer to [the API documentation](https://www.scaleway.com/en/developers/api/managed-database-postgre-mysql/).
11+
12+
## Example Usage
13+
14+
### Example Basic Snapshot
15+
16+
```terraform
17+
resource "scaleway_rdb_instance" "main" {
18+
name = "test-rdb-instance"
19+
node_type = "db-dev-s"
20+
engine = "PostgreSQL-15"
21+
is_ha_cluster = false
22+
disable_backup = true
23+
user_name = "my_initial_user"
24+
password = "thiZ_is_v&ry_s3cret"
25+
tags = ["terraform-test", "scaleway_rdb_instance", "minimal"]
26+
volume_type = "bssd"
27+
volume_size_in_gb = 10
28+
}
29+
30+
resource "scaleway_rdb_snapshot" "test" {
31+
name = "initial-snapshot"
32+
instance_id = scaleway_rdb_instance.main.id
33+
depends_on = [scaleway_rdb_instance.main]
34+
}
35+
```
36+
37+
### Example with Expiration
38+
39+
```terraform
40+
resource "scaleway_rdb_snapshot" "snapshot_with_expiration" {
41+
name = "snapshot-with-expiration"
42+
instance_id = scaleway_rdb_instance.main.id
43+
expires_at = "2025-01-31T00:00:00Z"
44+
}
45+
```
46+
47+
### Example with Multiple Snapshots
48+
49+
```terraform
50+
resource "scaleway_rdb_snapshot" "snapshot_a" {
51+
name = "snapshot_a"
52+
instance_id = scaleway_rdb_instance.main.id
53+
depends_on = [scaleway_rdb_instance.main]
54+
}
55+
56+
resource "scaleway_rdb_snapshot" "snapshot_b" {
57+
name = "snapshot_b"
58+
instance_id = scaleway_rdb_instance.main.id
59+
expires_at = "2025-02-07T00:00:00Z"
60+
depends_on = [scaleway_rdb_instance.main]
61+
}
62+
```
63+
64+
## Argument Reference
65+
66+
The following arguments are supported:
67+
68+
- `name` - (Required) The name of the snapshot.
69+
- `instance_id` - (Required) The UUID of the database instance for which the snapshot is created.
70+
- `snapshot_id` - (Optional, ForceNew) The ID of an existing snapshot. This allows creating an instance from a specific snapshot ID. Conflicts with `engine`.
71+
- `expires_at` - (Optional) Expiration date of the snapshot in ISO 8601 format (e.g., `2025-01-31T00:00:00Z`). If not set, the snapshot will not expire automatically.
72+
73+
## Attributes Reference
74+
75+
In addition to all arguments above, the following attributes are exported:
76+
77+
- `id` - The unique ID of the snapshot.
78+
- `created_at` - The timestamp when the snapshot was created, in ISO 8601 format.
79+
- `updated_at` - The timestamp when the snapshot was last updated, in ISO 8601 format.
80+
- `status` - The current status of the snapshot (e.g., `ready`, `creating`, `error`).
81+
- `size` - The size of the snapshot in bytes.
82+
- `node_type` - The type of the database instance for which the snapshot was created.
83+
- `volume_type` - The type of volume used by the snapshot.
84+
85+
## Attributes Reference
86+
87+
- `region` - The region where the snapshot is stored. Defaults to the region set in the provider configuration.
88+
89+
## Import
90+
91+
RDB Snapshots can be imported using the `{region}/{snapshot_id}` format.
92+
93+
## Limitations
94+
95+
- Snapshots are tied to the database instance and region where they are created.
96+
- Expired snapshots are automatically deleted and cannot be restored.
97+
98+
## Notes
99+
100+
- Ensure the `instance_id` corresponds to an existing database instance.
101+
- Use the `depends_on` argument when creating snapshots right after creating an instance to ensure proper dependency management.

internal/provider/provider.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,7 @@ func Provider(config *Config) plugin.ProviderFunc {
207207
"scaleway_rdb_privilege": rdb.ResourcePrivilege(),
208208
"scaleway_rdb_read_replica": rdb.ResourceReadReplica(),
209209
"scaleway_rdb_user": rdb.ResourceUser(),
210+
"scaleway_rdb_snapshot": rdb.ResourceSnapshot(),
210211
"scaleway_redis_cluster": redis.ResourceCluster(),
211212
"scaleway_registry_namespace": registry.ResourceNamespace(),
212213
"scaleway_sdb_sql_database": sdb.ResourceDatabase(),

internal/services/rdb/instance.go

Lines changed: 132 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -54,10 +54,23 @@ func ResourceInstance() *schema.Resource {
5454
},
5555
"engine": {
5656
Type: schema.TypeString,
57-
Required: true,
57+
Optional: true,
58+
Computed: true,
5859
ForceNew: true,
5960
Description: "Database's engine version id",
6061
DiffSuppressFunc: dsf.IgnoreCase,
62+
ConflictsWith: []string{
63+
"snapshot_id",
64+
},
65+
},
66+
"snapshot_id": {
67+
Type: schema.TypeString,
68+
Optional: true,
69+
ForceNew: true,
70+
Description: "ID of an existing snapshot to create a new instance from. This allows restoring a database instance to the state captured in the specified snapshot. Conflicts with the `engine` attribute.",
71+
ConflictsWith: []string{
72+
"engine",
73+
},
6174
},
6275
"is_ha_cluster": {
6376
Type: schema.TypeBool,
@@ -318,76 +331,145 @@ func ResourceInstance() *schema.Resource {
318331
}
319332
}
320333

334+
//gocyclo:ignore
321335
func ResourceRdbInstanceCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
322336
rdbAPI, region, err := newAPIWithRegion(d, m)
323337
if err != nil {
324338
return diag.FromErr(err)
325339
}
326340

327-
createReq := &rdb.CreateInstanceRequest{
328-
Region: region,
329-
ProjectID: types.ExpandStringPtr(d.Get("project_id")),
330-
Name: types.ExpandOrGenerateString(d.Get("name"), "rdb"),
331-
NodeType: d.Get("node_type").(string),
332-
Engine: d.Get("engine").(string),
333-
IsHaCluster: d.Get("is_ha_cluster").(bool),
334-
DisableBackup: d.Get("disable_backup").(bool),
335-
UserName: d.Get("user_name").(string),
336-
Password: d.Get("password").(string),
337-
VolumeType: rdb.VolumeType(d.Get("volume_type").(string)),
338-
Encryption: &rdb.EncryptionAtRest{
339-
Enabled: d.Get("encryption_at_rest").(bool),
340-
},
341-
}
341+
var id string
342342

343-
if initSettings, ok := d.GetOk("init_settings"); ok {
344-
createReq.InitSettings = expandInstanceSettings(initSettings)
345-
}
343+
if regionalSnapshotID, ok := d.GetOk("snapshot_id"); ok {
344+
haCluster := d.Get("is_ha_cluster").(bool)
345+
nodeType := d.Get("node_type").(string)
346346

347-
rawTag, tagExist := d.GetOk("tags")
348-
if tagExist {
349-
createReq.Tags = types.ExpandStrings(rawTag)
350-
}
347+
_, snapshotID, err := regional.ParseID(regionalSnapshotID.(string))
348+
if err != nil {
349+
return diag.FromErr(err)
350+
}
351351

352-
// Init Endpoints
353-
if pn, pnExist := d.GetOk("private_network"); pnExist {
354-
ipamConfig, staticConfig := getIPConfigCreate(d, "ip_net")
352+
createReqFromSnapshot := &rdb.CreateInstanceFromSnapshotRequest{
353+
SnapshotID: snapshotID,
354+
Region: region,
355+
InstanceName: types.ExpandOrGenerateString(d.Get("name"), "rdb"),
356+
IsHaCluster: &haCluster,
357+
NodeType: &nodeType,
358+
}
355359

356-
var diags diag.Diagnostics
360+
res, err := rdbAPI.CreateInstanceFromSnapshot(createReqFromSnapshot, scw.WithContext(ctx))
361+
if err != nil {
362+
return diag.FromErr(err)
363+
}
357364

358-
createReq.InitEndpoints, diags = expandPrivateNetwork(pn, pnExist, ipamConfig, staticConfig)
359-
if diags.HasError() {
360-
return diags
365+
_, err = waitForRDBInstance(ctx, rdbAPI, region, res.ID, d.Timeout(schema.TimeoutCreate))
366+
if err != nil {
367+
return diag.FromErr(err)
361368
}
362369

363-
for _, warning := range diags {
364-
tflog.Warn(ctx, warning.Detail)
370+
rawTag, tagExist := d.GetOk("tags")
371+
if tagExist {
372+
updateReq := &rdb.UpdateInstanceRequest{
373+
Region: region,
374+
InstanceID: res.ID,
375+
}
376+
tags := types.ExpandStrings(rawTag)
377+
updateReq.Tags = &tags
378+
379+
_, err = rdbAPI.UpdateInstance(updateReq, scw.WithContext(ctx))
380+
if err != nil {
381+
return diag.FromErr(err)
382+
}
365383
}
366-
}
367384

368-
if _, lbExists := d.GetOk("load_balancer"); lbExists {
369-
createReq.InitEndpoints = append(createReq.InitEndpoints, expandLoadBalancer())
370-
}
385+
d.SetId(regional.NewIDString(region, res.ID))
386+
id = res.ID
387+
} else {
388+
createReq := &rdb.CreateInstanceRequest{
389+
Region: region,
390+
ProjectID: types.ExpandStringPtr(d.Get("project_id")),
391+
Name: types.ExpandOrGenerateString(d.Get("name"), "rdb"),
392+
NodeType: d.Get("node_type").(string),
393+
Engine: d.Get("engine").(string),
394+
IsHaCluster: d.Get("is_ha_cluster").(bool),
395+
DisableBackup: d.Get("disable_backup").(bool),
396+
UserName: d.Get("user_name").(string),
397+
Password: d.Get("password").(string),
398+
VolumeType: rdb.VolumeType(d.Get("volume_type").(string)),
399+
Encryption: &rdb.EncryptionAtRest{
400+
Enabled: d.Get("encryption_at_rest").(bool),
401+
},
402+
}
371403

372-
if size, ok := d.GetOk("volume_size_in_gb"); ok {
373-
if createReq.VolumeType == rdb.VolumeTypeLssd {
374-
return diag.FromErr(fmt.Errorf("volume_size_in_gb should not be used with volume_type %s", rdb.VolumeTypeLssd.String()))
404+
if initSettings, ok := d.GetOk("init_settings"); ok {
405+
createReq.InitSettings = expandInstanceSettings(initSettings)
375406
}
376407

377-
createReq.VolumeSize = scw.Size(uint64(size.(int)) * uint64(scw.GB))
378-
}
408+
rawTag, tagExist := d.GetOk("tags")
409+
if tagExist {
410+
createReq.Tags = types.ExpandStrings(rawTag)
411+
}
379412

380-
res, err := rdbAPI.CreateInstance(createReq, scw.WithContext(ctx))
381-
if err != nil {
382-
return diag.FromErr(err)
383-
}
413+
// Init Endpoints
414+
if pn, pnExist := d.GetOk("private_network"); pnExist {
415+
ipamConfig, staticConfig := getIPConfigCreate(d, "ip_net")
416+
417+
var diags diag.Diagnostics
418+
419+
createReq.InitEndpoints, diags = expandPrivateNetwork(pn, pnExist, ipamConfig, staticConfig)
420+
if diags.HasError() {
421+
return diags
422+
}
423+
424+
for _, warning := range diags {
425+
tflog.Warn(ctx, warning.Detail)
426+
}
427+
}
428+
429+
if _, lbExists := d.GetOk("load_balancer"); lbExists {
430+
createReq.InitEndpoints = append(createReq.InitEndpoints, expandLoadBalancer())
431+
}
432+
// Init Endpoints
433+
if pn, pnExist := d.GetOk("private_network"); pnExist {
434+
ipamConfig, staticConfig := getIPConfigCreate(d, "ip_net")
435+
436+
var diags diag.Diagnostics
437+
438+
createReq.InitEndpoints, diags = expandPrivateNetwork(pn, pnExist, ipamConfig, staticConfig)
439+
if diags.HasError() {
440+
return diags
441+
}
384442

385-
d.SetId(regional.NewIDString(region, res.ID))
443+
for _, warning := range diags {
444+
tflog.Warn(ctx, warning.Detail)
445+
}
446+
}
447+
448+
if _, lbExists := d.GetOk("load_balancer"); lbExists {
449+
createReq.InitEndpoints = append(createReq.InitEndpoints, expandLoadBalancer())
450+
}
451+
452+
if size, ok := d.GetOk("volume_size_in_gb"); ok {
453+
if createReq.VolumeType == rdb.VolumeTypeLssd {
454+
return diag.FromErr(fmt.Errorf("volume_size_in_gb should not be used with volume_type %s", rdb.VolumeTypeLssd.String()))
455+
}
456+
457+
createReq.VolumeSize = scw.Size(uint64(size.(int)) * uint64(scw.GB))
458+
}
459+
460+
res, err := rdbAPI.CreateInstance(createReq, scw.WithContext(ctx))
461+
if err != nil {
462+
return diag.FromErr(err)
463+
}
464+
465+
d.SetId(regional.NewIDString(region, res.ID))
466+
id = res.ID
467+
}
386468

387469
mustUpdate := false
388470
updateReq := &rdb.UpdateInstanceRequest{
389471
Region: region,
390-
InstanceID: res.ID,
472+
InstanceID: id,
391473
}
392474
// Configure Schedule Backup
393475
// BackupScheduleFrequency and BackupScheduleRetention can only configure after instance creation
@@ -413,7 +495,7 @@ func ResourceRdbInstanceCreate(ctx context.Context, d *schema.ResourceData, m in
413495
}
414496

415497
if mustUpdate {
416-
_, err = waitForRDBInstance(ctx, rdbAPI, region, res.ID, d.Timeout(schema.TimeoutCreate))
498+
_, err = waitForRDBInstance(ctx, rdbAPI, region, id, d.Timeout(schema.TimeoutCreate))
417499
if err != nil {
418500
return diag.FromErr(err)
419501
}
@@ -425,12 +507,12 @@ func ResourceRdbInstanceCreate(ctx context.Context, d *schema.ResourceData, m in
425507
}
426508
// Configure Instance settings
427509
if settings, ok := d.GetOk("settings"); ok {
428-
res, err = waitForRDBInstance(ctx, rdbAPI, region, res.ID, d.Timeout(schema.TimeoutCreate))
510+
res, err := waitForRDBInstance(ctx, rdbAPI, region, id, d.Timeout(schema.TimeoutCreate))
429511
if err != nil {
430512
return diag.FromErr(err)
431513
}
432514

433-
_, err := rdbAPI.SetInstanceSettings(&rdb.SetInstanceSettingsRequest{
515+
_, err = rdbAPI.SetInstanceSettings(&rdb.SetInstanceSettingsRequest{
434516
InstanceID: res.ID,
435517
Region: region,
436518
Settings: expandInstanceSettings(settings),

0 commit comments

Comments
 (0)