Skip to content

Commit 0bc2393

Browse files
committed
feat(block): support export to s3
1 parent 3c70114 commit 0bc2393

File tree

4 files changed

+162
-76
lines changed

4 files changed

+162
-76
lines changed

internal/services/block/snapshot.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,33 @@ func ResourceSnapshot() *schema.Resource {
8484
Description: "Import snapshot from a qcow",
8585
ConflictsWith: []string{"volume_id"},
8686
},
87+
"export": {
88+
Type: schema.TypeList,
89+
ForceNew: true,
90+
MaxItems: 1,
91+
Elem: &schema.Resource{
92+
Schema: map[string]*schema.Schema{
93+
"bucket": {
94+
Type: schema.TypeString,
95+
Required: true,
96+
ForceNew: true,
97+
Description: "Bucket containing qcow",
98+
DiffSuppressFunc: dsf.Locality,
99+
StateFunc: func(i any) string {
100+
return regional.ExpandID(i.(string)).ID
101+
},
102+
},
103+
"key": {
104+
Type: schema.TypeString,
105+
Required: true,
106+
ForceNew: true,
107+
Description: "Key of the qcow file in the specified bucket",
108+
},
109+
},
110+
},
111+
Optional: true,
112+
Description: "Export snapshot to a qcow",
113+
},
87114
"zone": zonal.Schema(),
88115
"project_id": account.ProjectIDSchema(),
89116
},
@@ -132,6 +159,17 @@ func ResourceBlockSnapshotCreate(ctx context.Context, d *schema.ResourceData, m
132159
return diag.FromErr(err)
133160
}
134161

162+
if _, shouldExport := d.GetOk("export"); shouldExport {
163+
req := block.ExportSnapshotToObjectStorageRequest{
164+
Zone: zone,
165+
SnapshotID: snapshot.ID,
166+
Bucket: regional.ExpandID(d.Get("export.0.bucket")).ID,
167+
Key: d.Get("export.0.key").(string),
168+
}
169+
170+
_, err = api.ExportSnapshotToObjectStorage(&req, scw.WithContext(ctx))
171+
}
172+
135173
return ResourceBlockSnapshotRead(ctx, d, m)
136174
}
137175

@@ -201,6 +239,17 @@ func ResourceBlockSnapshotUpdate(ctx context.Context, d *schema.ResourceData, m
201239
return diag.FromErr(err)
202240
}
203241

242+
if _, shouldExport := d.GetOk("export"); shouldExport {
243+
req := block.ExportSnapshotToObjectStorageRequest{
244+
Zone: zone,
245+
SnapshotID: snapshot.ID,
246+
Bucket: regional.ExpandID(d.Get("export.0.bucket")).ID,
247+
Key: d.Get("export.0.key").(string),
248+
}
249+
250+
_, err = api.ExportSnapshotToObjectStorage(&req, scw.WithContext(ctx))
251+
}
252+
204253
return ResourceBlockSnapshotRead(ctx, d, m)
205254
}
206255

internal/services/block/snapshot_test.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,3 +85,47 @@ func TestAccSnapshot_FromS3(t *testing.T) {
8585
},
8686
})
8787
}
88+
89+
func TestAccSnapshot_ToS3(t *testing.T) {
90+
tt := acctest.NewTestTools(t)
91+
defer tt.Cleanup()
92+
93+
bucketName := sdkacctest.RandomWithPrefix("test-acc-scaleway-export-block-snapshot")
94+
resource.ParallelTest(t, resource.TestCase{
95+
PreCheck: func() { acctest.PreCheck(t) },
96+
ProviderFactories: tt.ProviderFactories,
97+
CheckDestroy: resource.ComposeTestCheckFunc(
98+
blocktestfuncs.IsSnapshotDestroyed(tt),
99+
objectchecks.IsObjectDestroyed(tt),
100+
objectchecks.IsBucketDestroyed(tt),
101+
),
102+
Steps: []resource.TestStep{
103+
{
104+
Config: fmt.Sprintf(`
105+
resource "scaleway_object_bucket" "snapshot-bucket" {
106+
name = "%s"
107+
}
108+
109+
resource "scaleway_object" "qcow-object" {
110+
bucket = scaleway_object_bucket.snapshot-bucket.name
111+
key = "test-acc-export-block-snapshot-qcow2"
112+
}
113+
114+
resource "scaleway_block_snapshot" "qcow-block-snapshot" {
115+
name = "test-acc-block-snapshot-qcow2"
116+
export {
117+
bucket = scaleway_object.qcow-object.bucket
118+
key = scaleway_object.qcow-object.key
119+
}
120+
}
121+
`, bucketName),
122+
Check: resource.ComposeTestCheckFunc(
123+
blocktestfuncs.IsSnapshotPresent(tt, "scaleway_block_snapshot.qcow-block-snapshot"),
124+
acctest.CheckResourceAttrUUID("scaleway_block_snapshot.qcow-block-snapshot", "id"),
125+
resource.TestCheckResourceAttr("scaleway_block_snapshot.qcow-block-snapshot", "name", "test-acc-export-block-snapshot-qcow2"),
126+
objectchecks.TestAccCheckObjectExists(tt, "scaleway_object.qcow-object"),
127+
),
128+
},
129+
},
130+
})
131+
}

internal/services/object/object_test.go

Lines changed: 24 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,14 @@
11
package object_test
22

33
import (
4-
"context"
54
"encoding/base64"
6-
"errors"
75
"fmt"
86
"regexp"
97
"testing"
108

11-
"github.com/aws/aws-sdk-go-v2/service/s3"
129
sdkacctest "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest"
1310
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
14-
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
15-
"github.com/scaleway/scaleway-sdk-go/scw"
1611
"github.com/scaleway/terraform-provider-scaleway/v2/internal/acctest"
17-
"github.com/scaleway/terraform-provider-scaleway/v2/internal/locality/regional"
18-
"github.com/scaleway/terraform-provider-scaleway/v2/internal/services/object"
1912
objectchecks "github.com/scaleway/terraform-provider-scaleway/v2/internal/services/object/testfuncs"
2013
)
2114

@@ -58,7 +51,7 @@ func TestAccObject_Basic(t *testing.T) {
5851
`, bucketName, objectTestsMainRegion),
5952
Check: resource.ComposeTestCheckFunc(
6053
objectchecks.CheckBucketExists(tt, "scaleway_object_bucket.base-01", true),
61-
testAccCheckObjectExists(tt, "scaleway_object.file"),
54+
objectchecks.TestAccCheckObjectExists(tt, "scaleway_object.file"),
6255
),
6356
},
6457
{
@@ -79,7 +72,7 @@ func TestAccObject_Basic(t *testing.T) {
7972
`, bucketName, objectTestsMainRegion),
8073
Check: resource.ComposeTestCheckFunc(
8174
objectchecks.CheckBucketExists(tt, "scaleway_object_bucket.base-01", true),
82-
testAccCheckObjectExists(tt, "scaleway_object.file"),
75+
objectchecks.TestAccCheckObjectExists(tt, "scaleway_object.file"),
8376
),
8477
},
8578
{
@@ -100,7 +93,7 @@ func TestAccObject_Basic(t *testing.T) {
10093
`, bucketName, objectTestsMainRegion),
10194
Check: resource.ComposeTestCheckFunc(
10295
objectchecks.CheckBucketExists(tt, "scaleway_object_bucket.base-01", true),
103-
testAccCheckObjectExists(tt, "scaleway_object.file"),
96+
objectchecks.TestAccCheckObjectExists(tt, "scaleway_object.file"),
10497
),
10598
},
10699
},
@@ -138,7 +131,7 @@ func TestAccObject_ContentType(t *testing.T) {
138131
`, bucketName, objectTestsMainRegion),
139132
Check: resource.ComposeTestCheckFunc(
140133
objectchecks.CheckBucketExists(tt, "scaleway_object_bucket.main", true),
141-
testAccCheckObjectExists(tt, "scaleway_object.file"),
134+
objectchecks.TestAccCheckObjectExists(tt, "scaleway_object.file"),
142135
resource.TestCheckResourceAttr("scaleway_object.file", "content_type", "text/html"),
143136
),
144137
},
@@ -178,7 +171,7 @@ func TestAccObject_Hash(t *testing.T) {
178171
`, bucketName, objectTestsMainRegion),
179172
Check: resource.ComposeTestCheckFunc(
180173
objectchecks.CheckBucketExists(tt, "scaleway_object_bucket.base-01", true),
181-
testAccCheckObjectExists(tt, "scaleway_object.file"),
174+
objectchecks.TestAccCheckObjectExists(tt, "scaleway_object.file"),
182175
),
183176
},
184177
{
@@ -200,7 +193,7 @@ func TestAccObject_Hash(t *testing.T) {
200193
`, bucketName, objectTestsMainRegion),
201194
Check: resource.ComposeTestCheckFunc(
202195
objectchecks.CheckBucketExists(tt, "scaleway_object_bucket.base-01", true),
203-
testAccCheckObjectExists(tt, "scaleway_object.file"),
196+
objectchecks.TestAccCheckObjectExists(tt, "scaleway_object.file"),
204197
),
205198
},
206199
},
@@ -238,7 +231,7 @@ func TestAccObject_Move(t *testing.T) {
238231
`, bucketName, objectTestsMainRegion),
239232
Check: resource.ComposeTestCheckFunc(
240233
objectchecks.CheckBucketExists(tt, "scaleway_object_bucket.base-01", true),
241-
testAccCheckObjectExists(tt, "scaleway_object.file")),
234+
objectchecks.TestAccCheckObjectExists(tt, "scaleway_object.file")),
242235
},
243236
{
244237
Config: fmt.Sprintf(`
@@ -258,7 +251,7 @@ func TestAccObject_Move(t *testing.T) {
258251
`, bucketName, objectTestsMainRegion),
259252
Check: resource.ComposeTestCheckFunc(
260253
objectchecks.CheckBucketExists(tt, "scaleway_object_bucket.base-01", true),
261-
testAccCheckObjectExists(tt, "scaleway_object.file")),
254+
objectchecks.TestAccCheckObjectExists(tt, "scaleway_object.file")),
262255
},
263256
},
264257
})
@@ -297,7 +290,7 @@ func TestAccObject_StorageClass(t *testing.T) {
297290
`, bucketName, objectTestsMainRegion),
298291
Check: resource.ComposeTestCheckFunc(
299292
objectchecks.CheckBucketExists(tt, "scaleway_object_bucket.base-01", true),
300-
testAccCheckObjectExists(tt, "scaleway_object.file"),
293+
objectchecks.TestAccCheckObjectExists(tt, "scaleway_object.file"),
301294
resource.TestCheckResourceAttr("scaleway_object.file", "storage_class", "ONEZONE_IA"),
302295
),
303296
},
@@ -321,7 +314,7 @@ func TestAccObject_StorageClass(t *testing.T) {
321314
`, bucketName, objectTestsMainRegion),
322315
Check: resource.ComposeTestCheckFunc(
323316
objectchecks.CheckBucketExists(tt, "scaleway_object_bucket.base-01", true),
324-
testAccCheckObjectExists(tt, "scaleway_object.file"),
317+
objectchecks.TestAccCheckObjectExists(tt, "scaleway_object.file"),
325318
resource.TestCheckResourceAttr("scaleway_object.file", "storage_class", "STANDARD"),
326319
),
327320
},
@@ -364,7 +357,7 @@ func TestAccObject_Metadata(t *testing.T) {
364357
`, bucketName, objectTestsMainRegion),
365358
Check: resource.ComposeTestCheckFunc(
366359
objectchecks.CheckBucketExists(tt, "scaleway_object_bucket.base-01", true),
367-
testAccCheckObjectExists(tt, "scaleway_object.file"),
360+
objectchecks.TestAccCheckObjectExists(tt, "scaleway_object.file"),
368361
resource.TestCheckResourceAttr("scaleway_object.file", "metadata.key", "value"),
369362
),
370363
},
@@ -391,7 +384,7 @@ func TestAccObject_Metadata(t *testing.T) {
391384
`, bucketName, objectTestsMainRegion),
392385
Check: resource.ComposeTestCheckFunc(
393386
objectchecks.CheckBucketExists(tt, "scaleway_object_bucket.base-01", true),
394-
testAccCheckObjectExists(tt, "scaleway_object.file"),
387+
objectchecks.TestAccCheckObjectExists(tt, "scaleway_object.file"),
395388
resource.TestCheckResourceAttr("scaleway_object.file", "metadata.key", "other_value"),
396389
resource.TestCheckResourceAttr("scaleway_object.file", "metadata.other_key", "VALUE"),
397390
),
@@ -432,7 +425,7 @@ func TestAccObject_Tags(t *testing.T) {
432425
`, bucketName, objectTestsMainRegion),
433426
Check: resource.ComposeTestCheckFunc(
434427
objectchecks.CheckBucketExists(tt, "scaleway_object_bucket.base-01", true),
435-
testAccCheckObjectExists(tt, "scaleway_object.file"),
428+
objectchecks.TestAccCheckObjectExists(tt, "scaleway_object.file"),
436429
resource.TestCheckResourceAttr("scaleway_object.file", "tags.key", "value"),
437430
),
438431
},
@@ -456,7 +449,7 @@ func TestAccObject_Tags(t *testing.T) {
456449
`, bucketName, objectTestsMainRegion),
457450
Check: resource.ComposeTestCheckFunc(
458451
objectchecks.CheckBucketExists(tt, "scaleway_object_bucket.base-01", true),
459-
testAccCheckObjectExists(tt, "scaleway_object.file"),
452+
objectchecks.TestAccCheckObjectExists(tt, "scaleway_object.file"),
460453
resource.TestCheckResourceAttr("scaleway_object.file", "tags.key", "other_value"),
461454
resource.TestCheckResourceAttr("scaleway_object.file", "tags.other_key", "VALUE"),
462455
),
@@ -495,7 +488,7 @@ func TestAccObject_Visibility(t *testing.T) {
495488
`, bucketName, objectTestsMainRegion),
496489
Check: resource.ComposeTestCheckFunc(
497490
objectchecks.CheckBucketExists(tt, "scaleway_object_bucket.base-01", true),
498-
testAccCheckObjectExists(tt, "scaleway_object.file"),
491+
objectchecks.TestAccCheckObjectExists(tt, "scaleway_object.file"),
499492
resource.TestCheckResourceAttr("scaleway_object.file", "visibility", "public-read"),
500493
),
501494
},
@@ -516,7 +509,7 @@ func TestAccObject_Visibility(t *testing.T) {
516509
`, bucketName, objectTestsMainRegion),
517510
Check: resource.ComposeTestCheckFunc(
518511
objectchecks.CheckBucketExists(tt, "scaleway_object_bucket.base-01", true),
519-
testAccCheckObjectExists(tt, "scaleway_object.file"),
512+
objectchecks.TestAccCheckObjectExists(tt, "scaleway_object.file"),
520513
resource.TestCheckResourceAttr("scaleway_object.file", "visibility", "private"),
521514
),
522515
},
@@ -554,7 +547,7 @@ func TestAccObject_State(t *testing.T) {
554547
`, bucketName, objectTestsMainRegion),
555548
Check: resource.ComposeTestCheckFunc(
556549
objectchecks.CheckBucketExists(tt, "scaleway_object_bucket.base-01", true),
557-
testAccCheckObjectExists(tt, "scaleway_object.file"),
550+
objectchecks.TestAccCheckObjectExists(tt, "scaleway_object.file"),
558551
),
559552
},
560553
{
@@ -582,8 +575,8 @@ func TestAccObject_State(t *testing.T) {
582575
ImportStateId: fmt.Sprintf("%s/%s/myfile", objectTestsMainRegion, bucketName),
583576
Check: resource.ComposeTestCheckFunc(
584577
objectchecks.CheckBucketExists(tt, "scaleway_object_bucket.base-01", true),
585-
testAccCheckObjectExists(tt, "scaleway_object.file"),
586-
testAccCheckObjectExists(tt, "scaleway_object.file_imported"),
578+
objectchecks.TestAccCheckObjectExists(tt, "scaleway_object.file"),
579+
objectchecks.TestAccCheckObjectExists(tt, "scaleway_object.file_imported"),
587580
resource.TestCheckResourceAttrPair("scaleway_object.file_imported", "id", "scaleway_object.file", "id"),
588581
resource.TestCheckResourceAttrPair("scaleway_object.file_imported", "visibility", "scaleway_object.file", "visibility"),
589582
resource.TestCheckResourceAttrPair("scaleway_object.file_imported", "bucket", "scaleway_object.file", "bucket"),
@@ -626,7 +619,7 @@ func TestAccObject_ByContent(t *testing.T) {
626619
`, bucketName, objectTestsMainRegion, fileContentStep1),
627620
Check: resource.ComposeTestCheckFunc(
628621
objectchecks.CheckBucketExists(tt, "scaleway_object_bucket.base-01", true),
629-
testAccCheckObjectExists(tt, "scaleway_object.by-content"),
622+
objectchecks.TestAccCheckObjectExists(tt, "scaleway_object.by-content"),
630623
resource.TestCheckResourceAttr("scaleway_object.by-content", "content", fileContentStep1),
631624
),
632625
},
@@ -645,7 +638,7 @@ func TestAccObject_ByContent(t *testing.T) {
645638
`, bucketName, objectTestsMainRegion, fileContentStep2),
646639
Check: resource.ComposeTestCheckFunc(
647640
objectchecks.CheckBucketExists(tt, "scaleway_object_bucket.base-01", true),
648-
testAccCheckObjectExists(tt, "scaleway_object.by-content"),
641+
objectchecks.TestAccCheckObjectExists(tt, "scaleway_object.by-content"),
649642
resource.TestCheckResourceAttr("scaleway_object.by-content", "content", fileContentStep2),
650643
),
651644
},
@@ -687,7 +680,7 @@ func TestAccObject_ByContentBase64(t *testing.T) {
687680
`, bucketName, objectTestsMainRegion, fileContentStep1),
688681
Check: resource.ComposeTestCheckFunc(
689682
objectchecks.CheckBucketExists(tt, "scaleway_object_bucket.base-01", true),
690-
testAccCheckObjectExists(tt, "scaleway_object.by-content-base64"),
683+
objectchecks.TestAccCheckObjectExists(tt, "scaleway_object.by-content-base64"),
691684
resource.TestCheckResourceAttr("scaleway_object.by-content-base64", "content_base64", fileEncodedStep1),
692685
),
693686
},
@@ -706,7 +699,7 @@ func TestAccObject_ByContentBase64(t *testing.T) {
706699
`, bucketName, objectTestsMainRegion, fileContentStep2),
707700
Check: resource.ComposeTestCheckFunc(
708701
objectchecks.CheckBucketExists(tt, "scaleway_object_bucket.base-01", true),
709-
testAccCheckObjectExists(tt, "scaleway_object.by-content-base64"),
702+
objectchecks.TestAccCheckObjectExists(tt, "scaleway_object.by-content-base64"),
710703
resource.TestCheckResourceAttr("scaleway_object.by-content-base64", "content_base64", fileEncodedStep2),
711704
),
712705
},
@@ -779,7 +772,7 @@ func TestAccObject_WithBucketName(t *testing.T) {
779772
`, bucketName, objectTestsMainRegion),
780773
Check: resource.ComposeTestCheckFunc(
781774
objectchecks.CheckBucketExists(tt, "scaleway_object_bucket.base-01", true),
782-
testAccCheckObjectExists(tt, "scaleway_object.file"),
775+
objectchecks.TestAccCheckObjectExists(tt, "scaleway_object.file"),
783776
),
784777
},
785778
},
@@ -846,48 +839,3 @@ func TestAccObject_Encryption(t *testing.T) {
846839
},
847840
})
848841
}
849-
850-
func testAccCheckObjectExists(tt *acctest.TestTools, n string) resource.TestCheckFunc {
851-
return func(state *terraform.State) error {
852-
ctx := context.Background()
853-
854-
rs := state.RootModule().Resources[n]
855-
if rs == nil {
856-
return errors.New("resource not found")
857-
}
858-
859-
key := rs.Primary.Attributes["key"]
860-
861-
regionalID := regional.ExpandID(rs.Primary.Attributes["bucket"])
862-
bucketRegion := regionalID.Region.String()
863-
bucketName := regionalID.ID
864-
865-
s3Client, err := object.NewS3ClientFromMeta(ctx, tt.Meta, bucketRegion)
866-
if err != nil {
867-
return err
868-
}
869-
870-
rs, ok := state.RootModule().Resources[n]
871-
if !ok {
872-
return fmt.Errorf("not found: %s", n)
873-
}
874-
875-
if rs.Primary.ID == "" {
876-
return errors.New("no ID is set")
877-
}
878-
879-
_, err = s3Client.GetObject(ctx, &s3.GetObjectInput{
880-
Bucket: scw.StringPtr(bucketName),
881-
Key: scw.StringPtr(key),
882-
})
883-
if err != nil {
884-
if object.IsS3Err(err, object.ErrCodeNoSuchBucket, "") {
885-
return errors.New("s3 object not found")
886-
}
887-
888-
return err
889-
}
890-
891-
return nil
892-
}
893-
}

0 commit comments

Comments
 (0)