Skip to content

Commit 874f583

Browse files
committed
feat(instance): add support filesystem
feat(file): add support v1beta1 Create, Read and Update function Delete Methode add tests fix date issue and register cassettes test: add validation for filesystem size granularity fix: convertion fix(instance): update schema filesystem test(instance): add test attach filesystem feat(instance): delete and update filesystem chore: master sdk-go go mod tidy update doc and tests
1 parent 8dd5200 commit 874f583

File tree

9 files changed

+377
-2
lines changed

9 files changed

+377
-2
lines changed

docs/resources/instance_server.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,36 @@ resource "scaleway_instance_server" "web" {
4545
}
4646
```
4747

48+
### With filesystem
49+
50+
```terraform
51+
52+
resource "scaleway_block_volume" "volume" {
53+
iops = 15000
54+
size_in_gb = 15
55+
}
56+
57+
resource "scaleway_file_filesystem" "terraform_instance_filesystem"{
58+
name="filesystem-instance-terraform"
59+
size = 100000000000
60+
}
61+
62+
resource "scaleway_instance_server" "base" {
63+
type = "POP2-HM-2C-16G"
64+
state = "started"
65+
tags = [ "terraform-test", "scaleway_instance_server", "state" ]
66+
root_volume {
67+
volume_type = "sbs_volume"
68+
volume_id = scaleway_block_volume.volume.id
69+
}
70+
filesystems {
71+
filesystem_id = scaleway_file_filesystem.terraform_instance_filesystem.id
72+
}
73+
}`
74+
```
75+
76+
77+
4878
### With a reserved IP
4979

5080
```terraform
@@ -227,6 +257,9 @@ attached to the server. Updates to this field will trigger a stop/start of the s
227257

228258
~> **Important:** If this field contains local volumes, you have to first detach them, in one apply, and then delete the volume in another apply.
229259

260+
- `filesystem` - (Optional) List of filesystems attached to the server.
261+
- `filesystem_id` - (Optional) The unique ID of the filesystem attached to the server.
262+
230263
- `enable_ipv6` - (Defaults to `false`) Determines if IPv6 is enabled for the server.
231264
Deprecated: Please use a scaleway_instance_ip with a `routed_ipv6` type.
232265

@@ -288,6 +321,7 @@ In addition to all arguments above, the following attributes are exported:
288321
- `placement_group_policy_respected` - (Deprecated) Always false, use [instance_placement_group ressource](instance_placement_group.md) to known when the placement group policy is respected.
289322
- `root_volume`
290323
- `volume_id` - The volume ID of the root volume of the server.
324+
- `filesystem` - (Computed) The current status of the filesystem (e.g., attached, detached).
291325
- `private_ip` - The Scaleway internal IP address of the server (Deprecated use [ipam_ip datasource](../data-sources/ipam_ip.md#instance-private-network-ip) instead).
292326
- `public_ip` - The public IP address of the server (Deprecated use `public_ips` instead).
293327
- `public_ips` - The list of public IPs of the server.

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ require (
2828
github.com/nats-io/jwt/v2 v2.7.4
2929
github.com/nats-io/nats.go v1.38.0
3030
github.com/robfig/cron/v3 v3.0.1
31-
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.33.0.20250604134054-a06406d42247
31+
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.33.0.20250610132304-0ea56270b666
3232
github.com/stretchr/testify v1.10.0
3333
golang.org/x/crypto v0.38.0
3434
gopkg.in/dnaeon/go-vcr.v3 v3.2.0

go.sum

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -449,6 +449,7 @@ github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWN
449449
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
450450
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.33.0.20250604134054-a06406d42247 h1:wlIvcSpGl3mGDpQmwrZHnYMIlB7Mwx3bhg151LG22Ws=
451451
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.33.0.20250604134054-a06406d42247/go.mod h1:qiGzapFyNPFwBBLJ+hTFykKSnU95n1zL64+o1ubmwf0=
452+
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.33.0.20250610132304-0ea56270b666/go.mod h1:zFWiHphneiey3s8HOtAEnGrRlWivNaxW5T6d5Xfco7g=
452453
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
453454
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
454455
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=

internal/services/file/filesystem_test.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,31 @@ func TestAccFileSystem_InvalidSizeGranularityFails(t *testing.T) {
106106
})
107107
}
108108

109+
func TestAccFileSystem_InvalidSizeGranularityFails(t *testing.T) {
110+
tt := acctest.NewTestTools(t)
111+
defer tt.Cleanup()
112+
113+
fileSystemName := "TestAccFileSystem_Basic"
114+
size := int64(25_000_000_000)
115+
116+
resource.ParallelTest(t, resource.TestCase{
117+
PreCheck: func() { acctest.PreCheck(t) },
118+
ProviderFactories: tt.ProviderFactories,
119+
CheckDestroy: filetestfuncs.CheckFileDestroy(tt),
120+
Steps: []resource.TestStep{
121+
{
122+
Config: fmt.Sprintf(`
123+
resource "scaleway_file_filesystem" "fs" {
124+
name = "%s"
125+
size = %d
126+
}
127+
`, fileSystemName, size),
128+
ExpectError: regexp.MustCompile("size does not respect constraint, size must be greater or equal to 100000000000"),
129+
},
130+
},
131+
})
132+
}
133+
109134
func testAccCheckFileSystemExists(tt *acctest.TestTools, n string) resource.TestCheckFunc {
110135
return func(s *terraform.State) error {
111136
rs, ok := s.RootModule().Resources[n]

internal/services/instance/helpers_instance.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,15 @@ const (
3333
// InstanceServerStateStandby transient state of the instance event waiting third action or rescue mode
3434
InstanceServerStateStandby = "standby"
3535

36-
DefaultInstanceServerWaitTimeout = 10 * time.Minute
36+
DefaultInstanceServerWaitTimeout = 15 * time.Minute
3737
defaultInstancePrivateNICWaitTimeout = 10 * time.Minute
3838
defaultInstanceVolumeDeleteTimeout = 10 * time.Minute
3939
defaultInstanceSecurityGroupTimeout = 1 * time.Minute
4040
defaultInstanceSecurityGroupRuleTimeout = 1 * time.Minute
4141
defaultInstancePlacementGroupTimeout = 1 * time.Minute
4242
defaultInstanceIPTimeout = 1 * time.Minute
4343
defaultInstanceIPReverseDNSTimeout = 10 * time.Minute
44+
defaultFileSystemWaitTimeout = 15 * time.Minute
4445

4546
defaultInstanceSnapshotWaitTimeout = 1 * time.Hour
4647

internal/services/instance/server.go

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,26 @@ func ResourceServer() *schema.Resource {
190190
Optional: true,
191191
Description: "The additional volumes attached to the server",
192192
},
193+
"filesystems": {
194+
Type: schema.TypeList,
195+
Optional: true,
196+
Computed: true,
197+
Description: "Filesystems attach to the server",
198+
Elem: &schema.Resource{
199+
Schema: map[string]*schema.Schema{
200+
"filesystem_id": {
201+
Type: schema.TypeString,
202+
Optional: true,
203+
Description: "The filesystem ID attached to the server",
204+
},
205+
"status": {
206+
Type: schema.TypeString,
207+
Computed: true,
208+
Description: "The state of the filesystem",
209+
},
210+
},
211+
},
212+
},
193213
"enable_ipv6": {
194214
Type: schema.TypeBool,
195215
Optional: true,
@@ -516,6 +536,26 @@ func ResourceInstanceServerCreate(ctx context.Context, d *schema.ResourceData, m
516536
}
517537
}
518538

539+
///
540+
// Attach Filesystem
541+
///
542+
_, err = waitForServer(ctx, api.API, zone, res.Server.ID, d.Timeout(schema.TimeoutCreate))
543+
if filesystems, ok := d.GetOk("filesystems"); ok {
544+
for _, filesystem := range filesystems.([]interface{}) {
545+
fs := filesystem.(map[string]interface{})
546+
filesystemID := fs["filesystem_id"]
547+
_, err := api.AttachServerFileSystem(&instanceSDK.AttachServerFileSystemRequest{
548+
Zone: zone,
549+
FilesystemID: regional.ExpandID(filesystemID.(string)).ID,
550+
ServerID: res.Server.ID,
551+
})
552+
553+
if err != nil {
554+
return nil
555+
}
556+
}
557+
}
558+
519559
////
520560
// Set user data
521561
////
@@ -641,6 +681,10 @@ func ResourceInstanceServerRead(ctx context.Context, d *schema.ResourceData, m a
641681
_ = d.Set("tags", server.Tags)
642682
}
643683

684+
if server.Filesystems != nil {
685+
_ = d.Set("filesystems", flattenServerFileSystem(server.Zone, server.Filesystems))
686+
}
687+
644688
_ = d.Set("security_group_id", zonal.NewID(zone, server.SecurityGroup.ID).String())
645689
// EnableIPv6 is deprecated
646690
_ = d.Set("enable_ipv6", server.EnableIPv6) //nolint:staticcheck
@@ -1069,6 +1113,38 @@ func ResourceInstanceServerUpdate(ctx context.Context, d *schema.ResourceData, m
10691113
}
10701114
}
10711115

1116+
////
1117+
// Update server filesystems
1118+
///
1119+
1120+
if d.HasChange("filesystems") {
1121+
oldRaw, newRaw := d.GetChange("filesystems")
1122+
1123+
oldList := oldRaw.([]interface{})
1124+
newList := newRaw.([]interface{})
1125+
1126+
oldIDs := make(map[string]struct{})
1127+
newIDs := make(map[string]struct{})
1128+
1129+
collectFilesystemIDs(oldList, oldIDs)
1130+
collectFilesystemIDs(newList, newIDs)
1131+
1132+
diagnostics, done := detachOlDFileSystem(oldIDs, newIDs, api, zone, server)
1133+
if done {
1134+
return diagnostics
1135+
}
1136+
1137+
_, err := waitForFilesystems(ctx, api.API, zone, id, *scw.TimeDurationPtr(defaultFileSystemWaitTimeout))
1138+
if err != nil {
1139+
return diag.FromErr(err)
1140+
}
1141+
1142+
d2, done2 := attachNewFileSystem(newIDs, oldIDs, api, zone, server)
1143+
if done2 {
1144+
return d2
1145+
}
1146+
}
1147+
10721148
////
10731149
// Update server private network
10741150
////
@@ -1163,6 +1239,48 @@ func ResourceInstanceServerUpdate(ctx context.Context, d *schema.ResourceData, m
11631239
return append(warnings, ResourceInstanceServerRead(ctx, d, m)...)
11641240
}
11651241

1242+
1243+
func attachNewFileSystem(newIDs map[string]struct{}, oldIDs map[string]struct{}, api *instancehelpers.BlockAndInstanceAPI, zone scw.Zone, server *instanceSDK.Server) (diag.Diagnostics, bool) {
1244+
for id := range newIDs {
1245+
if _, alreadyAttached := oldIDs[id]; !alreadyAttached {
1246+
_, err := api.AttachServerFileSystem(&instanceSDK.AttachServerFileSystemRequest{
1247+
Zone: zone,
1248+
ServerID: server.ID,
1249+
FilesystemID: locality.ExpandID(id),
1250+
})
1251+
if err != nil {
1252+
return diag.FromErr(fmt.Errorf("error attaching filesystem %s: %w", id, err)), true
1253+
}
1254+
}
1255+
}
1256+
return nil, false
1257+
}
1258+
1259+
func detachOlDFileSystem(oldIDs map[string]struct{}, newIDs map[string]struct{}, api *instancehelpers.BlockAndInstanceAPI, zone scw.Zone, server *instanceSDK.Server) (diag.Diagnostics, bool) {
1260+
for id := range oldIDs {
1261+
if _, stillPresent := newIDs[id]; !stillPresent {
1262+
_, err := api.DetachServerFileSystem(&instanceSDK.DetachServerFileSystemRequest{
1263+
Zone: zone,
1264+
ServerID: server.ID,
1265+
FilesystemID: locality.ExpandID(id),
1266+
})
1267+
if err != nil {
1268+
return diag.FromErr(fmt.Errorf("error detaching filesystem %s: %w", id, err)), true
1269+
}
1270+
}
1271+
}
1272+
return nil, false
1273+
}
1274+
1275+
func collectFilesystemIDs(fsList []interface{}, target map[string]struct{}) {
1276+
for _, fs := range fsList {
1277+
if fsMap, ok := fs.(map[string]interface{}); ok {
1278+
id := fsMap["filesystem_id"].(string)
1279+
target[id] = struct{}{}
1280+
}
1281+
}
1282+
}
1283+
11661284
func ResourceInstanceServerDelete(ctx context.Context, d *schema.ResourceData, m any) diag.Diagnostics {
11671285
api, zone, id, err := instancehelpers.InstanceAndBlockAPIWithZoneAndID(m, d.Id())
11681286
if err != nil {
@@ -1200,6 +1318,32 @@ func ResourceInstanceServerDelete(ctx context.Context, d *schema.ResourceData, m
12001318
return diag.FromErr(err)
12011319
}
12021320

1321+
// Detach filesystem
1322+
if filesystems, ok := d.GetOk("filesystems"); ok {
1323+
fsList := filesystems.([]interface{})
1324+
for i, fsRaw := range fsList {
1325+
fsMap := fsRaw.(map[string]interface{})
1326+
fsIDRaw, ok := fsMap["filesystem_id"]
1327+
if !ok || fsIDRaw == nil {
1328+
return diag.Errorf("filesystem_id is missing or nil for filesystem at index %d", i)
1329+
}
1330+
fsID := fsIDRaw.(string)
1331+
newFileSystemID := types.ExpandStringPtr(fsID)
1332+
if newFileSystemID == nil {
1333+
return diag.Errorf("failed to expand filesystem_id pointer at index %d", i)
1334+
}
1335+
1336+
_, err = api.DetachServerFileSystem(&instanceSDK.DetachServerFileSystemRequest{
1337+
Zone: zone,
1338+
ServerID: id,
1339+
FilesystemID: locality.ExpandID(*newFileSystemID),
1340+
})
1341+
if err != nil {
1342+
return diag.FromErr(err)
1343+
}
1344+
}
1345+
}
1346+
12031347
// Delete private-nic if managed by instance_server resource
12041348
if raw, ok := d.GetOk("private_network"); ok {
12051349
ph, err := newPrivateNICHandler(api.API, id, zone)

0 commit comments

Comments
 (0)