Skip to content

Commit 6ce9fbf

Browse files
authored
feat(block): add block volumes and snapshots (#1998)
* feat(block): add block_volume resource * feat(block): add block_snapshot resource * test volumes from snapshot and add tags * add volume datasource * add snapshot datasource * update new api changes with iops parameter * bump api and tests * api merging * wait for volume and references * restore public sdk and record cassettes * remove dead code * lint * enable Block acceptance tests * add 410 Gone to cassette validation valid status * add datasource cassettes * add doc * set iops to required
1 parent 600a912 commit 6ce9fbf

26 files changed

+7111
-69
lines changed

.github/workflows/acceptance-tests.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ jobs:
1414
- AppleSilicon
1515
- Baremetal
1616
- Billing
17+
- Block
1718
- Cockpit
1819
- Container
1920
- Domain

docs/resources/block_snapshot.md

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
---
2+
subcategory: "Block"
3+
page_title: "Scaleway: scaleway_block_snapshot"
4+
---
5+
6+
# scaleway_block_snapshot
7+
8+
Creates and manages Scaleway Block Snapshots.
9+
For more information, see [the documentation](https://www.scaleway.com/en/developers/api/block/).
10+
11+
## Example
12+
13+
```hcl
14+
resource "scaleway_block_snapshot" "block_snapshot" {
15+
name = "some-snapshot-name"
16+
volume_id = "11111111-1111-1111-1111-111111111111"
17+
}
18+
```
19+
20+
## Arguments Reference
21+
22+
The following arguments are supported:
23+
24+
- `volume_id` - (Optional) The ID of the volume to take a snapshot from.
25+
- `name` - (Optional) The name of the snapshot. If not provided it will be randomly generated.
26+
- `zone` - (Defaults to [provider](../index.md#zone) `zone`) The [zone](../guides/regions_and_zones.md#zones) in which the snapshot should be created.
27+
- `project_id` - (Defaults to [provider](../index.md#project_id) `project_id`) The ID of the project the snapshot is associated with.
28+
- `tags` - (Optional) A list of tags to apply to the snapshot.
29+
30+
## Attributes Reference
31+
32+
In addition to all above arguments, the following attributes are exported:
33+
34+
- `id` - The ID of the snapshot.
35+
36+
~> **Important:** Block snapshots' IDs are [zoned](../guides/regions_and_zones.md#resource-ids), which means they are of the form `{zone}/{id}`, e.g. `fr-par-1/11111111-1111-1111-1111-111111111111`
37+
38+
## Import
39+
40+
Block Snapshots can be imported using the `{zone}/{id}`, e.g.
41+
42+
```bash
43+
$ terraform import scaleway_block_snapshot.main fr-par-1/11111111-1111-1111-1111-111111111111
44+
```

docs/resources/block_volume.md

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
---
2+
subcategory: "Block"
3+
page_title: "Scaleway: scaleway_block_volume"
4+
---
5+
6+
# scaleway_block_volume
7+
8+
Creates and manages Scaleway Block Volumes.
9+
For more information, see [the documentation](https://www.scaleway.com/en/developers/api/block/).
10+
11+
## Example
12+
13+
```hcl
14+
resource "scaleway_block_volume" "block_volume" {
15+
iops = 5000
16+
name = "some-volume-name"
17+
size_in_gb = 20
18+
}
19+
```
20+
21+
## Arguments Reference
22+
23+
The following arguments are supported:
24+
25+
- `iops` - (Required) The maximum IO/s expected, must match available options.
26+
- `name` - (Optional) The name of the volume. If not provided it will be randomly generated.
27+
- `size_in_gb` - (Optional) The size of the volume. Only one of `size_in_gb`, and `snapshot_id` should be specified.
28+
- `snapshot_id` - (Optional) If set, the new volume will be created from this snapshot. Only one of `size_in_gb`, `snapshot_id` should be specified.
29+
- `tags` - (Optional) A list of tags to apply to the volume.
30+
- `zone` - (Defaults to [provider](../index.md#zone) `zone`) The [zone](../guides/regions_and_zones.md#zones) in which the volume should be created.
31+
- `project_id` - (Defaults to [provider](../index.md#project_id) `project_id`) The ID of the project the volume is associated with.
32+
33+
## Attributes Reference
34+
35+
In addition to all above arguments, the following attributes are exported:
36+
37+
- `id` - The ID of the volume.
38+
39+
~> **Important:** Block volumes' IDs are [zoned](../guides/regions_and_zones.md#resource-ids), which means they are of the form `{zone}/{id}`, e.g. `fr-par-1/11111111-1111-1111-1111-111111111111`
40+
41+
- `organization_id` - The organization ID the volume is associated with.
42+
43+
## Import
44+
45+
Block Volumes can be imported using the `{zone}/{id}`, e.g.
46+
47+
```bash
48+
$ terraform import scaleway_block_volume.block_volume fr-par-1/11111111-1111-1111-1111-111111111111
49+
```

docs/resources/instance_volume.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ resource "scaleway_instance_volume" "server_volume" {
2323
The following arguments are supported:
2424

2525
- `type` - (Required) The type of the volume. The possible values are: `b_ssd` (Block SSD), `l_ssd` (Local SSD), `scratch` (Local Scratch SSD).
26-
- `size_in_gb` - (Optional) The size of the volume. Only one of `size_in_gb`, `from_volume_id` and `from_snapshot_id` should be specified.
27-
- `from_snapshot_id` - (Optional) If set, the new volume will be created from this snapshot. Only one of `size_in_gb`, `from_volume_id` and `from_snapshot_id` should be specified.
26+
- `size_in_gb` - (Optional) The size of the volume. Only one of `size_in_gb` and `from_snapshot_id` should be specified.
27+
- `from_snapshot_id` - (Optional) If set, the new volume will be created from this snapshot. Only one of `size_in_gb` and `from_snapshot_id` should be specified.
2828
- `name` - (Optional) The name of the volume. If not provided it will be randomly generated.
2929
- `zone` - (Defaults to [provider](../index.md#zone) `zone`) The [zone](../guides/regions_and_zones.md#zones) in which the volume should be created.
3030
- `project_id` - (Defaults to [provider](../index.md#project_id) `project_id`) The ID of the project the volume is associated with.
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package scaleway
2+
3+
import (
4+
"context"
5+
6+
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
7+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
8+
block "github.com/scaleway/scaleway-sdk-go/api/block/v1alpha1"
9+
)
10+
11+
func dataSourceScalewayBlockSnapshot() *schema.Resource {
12+
// Generate datasource schema from resource
13+
dsSchema := datasourceSchemaFromResourceSchema(resourceScalewayBlockSnapshot().Schema)
14+
15+
addOptionalFieldsToSchema(dsSchema, "name", "zone", "volume_id")
16+
17+
dsSchema["snapshot_id"] = &schema.Schema{
18+
Type: schema.TypeString,
19+
Optional: true,
20+
Description: "The ID of the snapshot",
21+
ConflictsWith: []string{"name"},
22+
ValidateFunc: validationUUIDorUUIDWithLocality(),
23+
}
24+
25+
return &schema.Resource{
26+
ReadContext: dataSourceScalewayBlockSnapshotRead,
27+
Schema: dsSchema,
28+
}
29+
}
30+
31+
func dataSourceScalewayBlockSnapshotRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
32+
api, zone, err := blockAPIWithZone(d, meta)
33+
if err != nil {
34+
return diag.FromErr(err)
35+
}
36+
37+
snapshotID, snapshotIDExists := d.GetOk("snapshot_id")
38+
if !snapshotIDExists {
39+
res, err := api.ListSnapshots(&block.ListSnapshotsRequest{
40+
Zone: zone,
41+
Name: expandStringPtr(d.Get("name")),
42+
ProjectID: expandStringPtr(d.Get("project_id")),
43+
VolumeID: expandStringPtr(d.Get("volume_id")),
44+
})
45+
if err != nil {
46+
return diag.FromErr(err)
47+
}
48+
for _, snapshot := range res.Snapshots {
49+
if snapshot.Name == d.Get("name").(string) {
50+
if snapshotID != "" {
51+
return diag.Errorf("more than 1 snapshot found with the same name %s", d.Get("name"))
52+
}
53+
snapshotID = snapshot.ID
54+
}
55+
}
56+
if snapshotID == "" {
57+
return diag.Errorf("no snapshot found with the name %s", d.Get("name"))
58+
}
59+
}
60+
61+
zoneID := datasourceNewZonedID(snapshotID, zone)
62+
d.SetId(zoneID)
63+
err = d.Set("snapshot_id", zoneID)
64+
if err != nil {
65+
return diag.FromErr(err)
66+
}
67+
68+
diags := resourceScalewayBlockSnapshotRead(ctx, d, meta)
69+
if diags != nil {
70+
return append(diags, diag.Errorf("failed to read snapshot state")...)
71+
}
72+
73+
if d.Id() == "" {
74+
return diag.Errorf("snapshot (%s) not found", zoneID)
75+
}
76+
77+
return nil
78+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package scaleway
2+
3+
import (
4+
"testing"
5+
6+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
7+
)
8+
9+
func TestAccScalewayDataSourceBlockSnapshot_Basic(t *testing.T) {
10+
tt := NewTestTools(t)
11+
defer tt.Cleanup()
12+
resource.ParallelTest(t, resource.TestCase{
13+
PreCheck: func() { testAccPreCheck(t) },
14+
ProviderFactories: tt.ProviderFactories,
15+
CheckDestroy: resource.ComposeTestCheckFunc(
16+
testAccCheckScalewayBlockSnapshotDestroy(tt),
17+
),
18+
Steps: []resource.TestStep{
19+
{
20+
Config: `
21+
resource scaleway_block_volume main {
22+
iops = 5000
23+
size_in_gb = 10
24+
}
25+
26+
resource scaleway_block_snapshot main {
27+
name = "test-ds-block-snapshot-basic"
28+
volume_id = scaleway_block_volume.main.id
29+
}
30+
31+
data scaleway_block_snapshot find_by_name {
32+
name = scaleway_block_snapshot.main.name
33+
}
34+
35+
data scaleway_block_snapshot find_by_id {
36+
snapshot_id = scaleway_block_snapshot.main.id
37+
}
38+
`,
39+
Check: resource.ComposeTestCheckFunc(
40+
testAccCheckScalewayBlockSnapshotExists(tt, "scaleway_block_snapshot.main"),
41+
42+
resource.TestCheckResourceAttrPair("scaleway_block_snapshot.main", "name", "data.scaleway_block_snapshot.find_by_name", "name"),
43+
resource.TestCheckResourceAttrPair("scaleway_block_snapshot.main", "name", "data.scaleway_block_snapshot.find_by_id", "name"),
44+
resource.TestCheckResourceAttrPair("scaleway_block_snapshot.main", "id", "data.scaleway_block_snapshot.find_by_name", "id"),
45+
resource.TestCheckResourceAttrPair("scaleway_block_snapshot.main", "id", "data.scaleway_block_snapshot.find_by_id", "id"),
46+
),
47+
},
48+
},
49+
})
50+
}

scaleway/data_source_block_volume.go

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package scaleway
2+
3+
import (
4+
"context"
5+
6+
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
7+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
8+
block "github.com/scaleway/scaleway-sdk-go/api/block/v1alpha1"
9+
)
10+
11+
func dataSourceScalewayBlockVolume() *schema.Resource {
12+
// Generate datasource schema from resource
13+
dsSchema := datasourceSchemaFromResourceSchema(resourceScalewayBlockVolume().Schema)
14+
15+
addOptionalFieldsToSchema(dsSchema, "name", "zone")
16+
17+
dsSchema["volume_id"] = &schema.Schema{
18+
Type: schema.TypeString,
19+
Optional: true,
20+
Description: "The ID of the volume",
21+
ConflictsWith: []string{"name"},
22+
ValidateFunc: validationUUIDorUUIDWithLocality(),
23+
}
24+
25+
return &schema.Resource{
26+
ReadContext: dataSourceScalewayBlockVolumeRead,
27+
Schema: dsSchema,
28+
}
29+
}
30+
31+
func dataSourceScalewayBlockVolumeRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
32+
api, zone, err := blockAPIWithZone(d, meta)
33+
if err != nil {
34+
return diag.FromErr(err)
35+
}
36+
37+
volumeID, volumeIDExists := d.GetOk("volume_id")
38+
if !volumeIDExists {
39+
res, err := api.ListVolumes(&block.ListVolumesRequest{
40+
Zone: zone,
41+
Name: expandStringPtr(d.Get("name")),
42+
ProjectID: expandStringPtr(d.Get("project_id")),
43+
})
44+
if err != nil {
45+
return diag.FromErr(err)
46+
}
47+
for _, volume := range res.Volumes {
48+
if volume.Name == d.Get("name").(string) {
49+
if volumeID != "" {
50+
return diag.Errorf("more than 1 volume found with the same name %s", d.Get("name"))
51+
}
52+
volumeID = volume.ID
53+
}
54+
}
55+
if volumeID == "" {
56+
return diag.Errorf("no volume found with the name %s", d.Get("name"))
57+
}
58+
}
59+
60+
zoneID := datasourceNewZonedID(volumeID, zone)
61+
d.SetId(zoneID)
62+
err = d.Set("volume_id", zoneID)
63+
if err != nil {
64+
return diag.FromErr(err)
65+
}
66+
67+
diags := resourceScalewayBlockVolumeRead(ctx, d, meta)
68+
if diags != nil {
69+
return append(diags, diag.Errorf("failed to read volume state")...)
70+
}
71+
72+
if d.Id() == "" {
73+
return diag.Errorf("volume (%s) not found", zoneID)
74+
}
75+
76+
return nil
77+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package scaleway
2+
3+
import (
4+
"testing"
5+
6+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
7+
)
8+
9+
func TestAccScalewayDataSourceBlockVolume_Basic(t *testing.T) {
10+
tt := NewTestTools(t)
11+
defer tt.Cleanup()
12+
resource.ParallelTest(t, resource.TestCase{
13+
PreCheck: func() { testAccPreCheck(t) },
14+
ProviderFactories: tt.ProviderFactories,
15+
CheckDestroy: resource.ComposeTestCheckFunc(
16+
testAccCheckScalewayBlockVolumeDestroy(tt),
17+
),
18+
Steps: []resource.TestStep{
19+
{
20+
Config: `
21+
resource scaleway_block_volume main {
22+
iops = 5000
23+
size_in_gb = 10
24+
name = "test-ds-block-volume-basic"
25+
}
26+
27+
data scaleway_block_volume find_by_name {
28+
name = scaleway_block_volume.main.name
29+
}
30+
31+
data scaleway_block_volume find_by_id {
32+
volume_id = scaleway_block_volume.main.id
33+
}
34+
`,
35+
Check: resource.ComposeTestCheckFunc(
36+
testAccCheckScalewayBlockVolumeExists(tt, "scaleway_block_volume.main"),
37+
38+
resource.TestCheckResourceAttrPair("scaleway_block_volume.main", "name", "data.scaleway_block_volume.find_by_name", "name"),
39+
resource.TestCheckResourceAttrPair("scaleway_block_volume.main", "name", "data.scaleway_block_volume.find_by_id", "name"),
40+
resource.TestCheckResourceAttrPair("scaleway_block_volume.main", "id", "data.scaleway_block_volume.find_by_name", "id"),
41+
resource.TestCheckResourceAttrPair("scaleway_block_volume.main", "id", "data.scaleway_block_volume.find_by_id", "id"),
42+
),
43+
},
44+
},
45+
})
46+
}

scaleway/helpers.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,11 @@ func is409Error(err error) bool {
329329
return isHTTPCodeError(err, http.StatusConflict) || errors.As(err, &transientStateError)
330330
}
331331

332+
// is404Error returns true if err is an HTTP 410 error
333+
func is410Error(err error) bool {
334+
return isHTTPCodeError(err, http.StatusGone)
335+
}
336+
332337
// organizationIDSchema returns a standard schema for a organization_id
333338
func organizationIDSchema() *schema.Schema {
334339
return &schema.Schema{

0 commit comments

Comments
 (0)