Skip to content

Commit 5345858

Browse files
committed
feat: add storage settings support
1 parent eb42f5e commit 5345858

File tree

4 files changed

+234
-3
lines changed

4 files changed

+234
-3
lines changed

docs/resources/settings.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,18 @@ resource "supabase_settings" "production" {
3939
mfa_phone_otp_length = 6
4040
sms_otp_length = 6
4141
})
42+
43+
storage = jsonencode({
44+
fileSizeLimit = 52428800
45+
features = {
46+
imageTransformation = {
47+
enabled = true
48+
}
49+
s3Protocol = {
50+
enabled = false
51+
}
52+
}
53+
})
4254
}
4355
```
4456

examples/resources/supabase_settings/resource.tf

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,16 @@ resource "supabase_settings" "production" {
2424
mfa_phone_otp_length = 6
2525
sms_otp_length = 6
2626
})
27+
28+
storage = jsonencode({
29+
fileSizeLimit = 52428800
30+
features = {
31+
imageTransformation = {
32+
enabled = true
33+
}
34+
s3Protocol = {
35+
enabled = false
36+
}
37+
}
38+
})
2739
}

internal/provider/settings_resource.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,9 @@ func (r *SettingsResource) Create(ctx context.Context, req resource.CreateReques
145145
if !data.Auth.IsNull() {
146146
resp.Diagnostics.Append(updateAuthConfig(ctx, &data, r.client)...)
147147
}
148+
if !data.Storage.IsNull() {
149+
resp.Diagnostics.Append(updateStorageConfig(ctx, &data, r.client)...)
150+
}
148151
// TODO: update all settings above concurrently
149152
if resp.Diagnostics.HasError() {
150153
return
@@ -183,6 +186,9 @@ func (r *SettingsResource) Read(ctx context.Context, req resource.ReadRequest, r
183186
if !data.Auth.IsNull() {
184187
resp.Diagnostics.Append(readAuthConfig(ctx, &data, r.client)...)
185188
}
189+
if !data.Storage.IsNull() {
190+
resp.Diagnostics.Append(readStorageConfig(ctx, &data, r.client)...)
191+
}
186192
// TODO: read all settings above concurrently
187193
if resp.Diagnostics.HasError() {
188194
return
@@ -216,6 +222,9 @@ func (r *SettingsResource) Update(ctx context.Context, req resource.UpdateReques
216222
if !data.Auth.IsNull() {
217223
resp.Diagnostics.Append(updateAuthConfig(ctx, &data, r.client)...)
218224
}
225+
if !data.Storage.IsNull() {
226+
resp.Diagnostics.Append(updateStorageConfig(ctx, &data, r.client)...)
227+
}
219228
// TODO: update all settings above concurrently
220229
if resp.Diagnostics.HasError() {
221230
return
@@ -246,6 +255,7 @@ func (r *SettingsResource) ImportState(ctx context.Context, req resource.ImportS
246255
resp.Diagnostics.Append(readNetworkConfig(ctx, &data, r.client)...)
247256
resp.Diagnostics.Append(readApiConfig(ctx, &data, r.client)...)
248257
resp.Diagnostics.Append(readAuthConfig(ctx, &data, r.client)...)
258+
resp.Diagnostics.Append(readStorageConfig(ctx, &data, r.client)...)
249259

250260
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
251261
}
@@ -578,3 +588,52 @@ func updateNetworkConfig(ctx context.Context, plan *SettingsResourceModel, clien
578588
}
579589
return nil
580590
}
591+
592+
func readStorageConfig(ctx context.Context, state *SettingsResourceModel, client *api.ClientWithResponses) diag.Diagnostics {
593+
// Use ProjectRef if Id is not set (during Create), otherwise use Id (during Read/Import)
594+
projectRef := state.Id.ValueString()
595+
if projectRef == "" {
596+
projectRef = state.ProjectRef.ValueString()
597+
}
598+
599+
httpResp, err := client.V1GetStorageConfigWithResponse(ctx, projectRef)
600+
if err != nil {
601+
msg := fmt.Sprintf("Unable to read storage settings, got error: %s", err)
602+
return diag.Diagnostics{diag.NewErrorDiagnostic("Client Error", msg)}
603+
}
604+
// Deleted project is an orphan resource, not returning error so it can be destroyed.
605+
switch httpResp.StatusCode() {
606+
case http.StatusNotFound, http.StatusNotAcceptable:
607+
return nil
608+
}
609+
if httpResp.JSON200 == nil {
610+
msg := fmt.Sprintf("Unable to read storage settings, got status %d: %s", httpResp.StatusCode(), httpResp.Body)
611+
return diag.Diagnostics{diag.NewErrorDiagnostic("Client Error", msg)}
612+
}
613+
614+
if state.Storage, err = parseConfig(state.Storage, *httpResp.JSON200); err != nil {
615+
msg := fmt.Sprintf("Unable to read storage settings, got error: %s", err)
616+
return diag.Diagnostics{diag.NewErrorDiagnostic("Client Error", msg)}
617+
}
618+
return nil
619+
}
620+
621+
func updateStorageConfig(ctx context.Context, plan *SettingsResourceModel, client *api.ClientWithResponses) diag.Diagnostics {
622+
var body api.UpdateStorageConfigBody
623+
if diags := plan.Storage.Unmarshal(&body); diags.HasError() {
624+
return diags
625+
}
626+
627+
httpResp, err := client.V1UpdateStorageConfigWithResponse(ctx, plan.ProjectRef.ValueString(), body)
628+
if err != nil {
629+
msg := fmt.Sprintf("Unable to update storage settings, got error: %s", err)
630+
return diag.Diagnostics{diag.NewErrorDiagnostic("Client Error", msg)}
631+
}
632+
if httpResp.StatusCode() != http.StatusOK {
633+
msg := fmt.Sprintf("Unable to update storage settings, got status %d: %s", httpResp.StatusCode(), httpResp.Body)
634+
return diag.Diagnostics{diag.NewErrorDiagnostic("Client Error", msg)}
635+
}
636+
637+
// Read back the updated config to get the actual state with correct field names
638+
return readStorageConfig(ctx, plan, client)
639+
}

internal/provider/settings_resource_test.go

Lines changed: 151 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,43 @@ func TestAccSettingsResource(t *testing.T) {
8989
SmsOtpLength: 6,
9090
SmtpAdminEmail: nullable.NewNullNullable[openapi_types.Email](),
9191
})
92+
gock.New("https://api.supabase.com").
93+
Get("/v1/projects/mayuaycdtijbctgqbycg/config/storage").
94+
Reply(http.StatusOK).
95+
JSON(map[string]any{
96+
"fileSizeLimit": 52428800,
97+
"features": map[string]any{
98+
"imageTransformation": map[string]any{"enabled": true},
99+
"s3Protocol": map[string]any{"enabled": false},
100+
},
101+
"capabilities": map[string]any{
102+
"iceberg_catalog": false,
103+
"list_v2": true,
104+
},
105+
"external": map[string]any{
106+
"upstreamTarget": "main",
107+
},
108+
})
109+
gock.New("https://api.supabase.com").
110+
Patch("/v1/projects/mayuaycdtijbctgqbycg/config/storage").
111+
Reply(http.StatusOK)
112+
gock.New("https://api.supabase.com").
113+
Get("/v1/projects/mayuaycdtijbctgqbycg/config/storage").
114+
Reply(http.StatusOK).
115+
JSON(map[string]any{
116+
"fileSizeLimit": 52428800,
117+
"features": map[string]any{
118+
"imageTransformation": map[string]any{"enabled": true},
119+
"s3Protocol": map[string]any{"enabled": false},
120+
},
121+
"capabilities": map[string]any{
122+
"iceberg_catalog": false,
123+
"list_v2": true,
124+
},
125+
"external": map[string]any{
126+
"upstreamTarget": "main",
127+
},
128+
})
92129
// Step 2: read
93130
gock.New("https://api.supabase.com").
94131
Get("/v1/projects/mayuaycdtijbctgqbycg/config/database/postgres").
@@ -156,6 +193,40 @@ func TestAccSettingsResource(t *testing.T) {
156193
SmsOtpLength: 6,
157194
SmtpAdminEmail: nullable.NewNullNullable[openapi_types.Email](),
158195
})
196+
gock.New("https://api.supabase.com").
197+
Get("/v1/projects/mayuaycdtijbctgqbycg/config/storage").
198+
Reply(http.StatusOK).
199+
JSON(map[string]any{
200+
"fileSizeLimit": 52428800,
201+
"features": map[string]any{
202+
"imageTransformation": map[string]any{"enabled": true},
203+
"s3Protocol": map[string]any{"enabled": false},
204+
},
205+
"capabilities": map[string]any{
206+
"iceberg_catalog": false,
207+
"list_v2": true,
208+
},
209+
"external": map[string]any{
210+
"upstreamTarget": "main",
211+
},
212+
})
213+
gock.New("https://api.supabase.com").
214+
Get("/v1/projects/mayuaycdtijbctgqbycg/config/storage").
215+
Reply(http.StatusOK).
216+
JSON(map[string]any{
217+
"fileSizeLimit": 52428800,
218+
"features": map[string]any{
219+
"imageTransformation": map[string]any{"enabled": true},
220+
"s3Protocol": map[string]any{"enabled": false},
221+
},
222+
"capabilities": map[string]any{
223+
"iceberg_catalog": false,
224+
"list_v2": true,
225+
},
226+
"external": map[string]any{
227+
"upstreamTarget": "main",
228+
},
229+
})
159230
// Step 3: update
160231
gock.New("https://api.supabase.com").
161232
Get("/v1/projects/mayuaycdtijbctgqbycg/config/database/postgres").
@@ -248,6 +319,60 @@ func TestAccSettingsResource(t *testing.T) {
248319
JwtExp: nullable.NewNullableWithValue(1800),
249320
SmtpAdminEmail: nullable.NewNullNullable[openapi_types.Email](),
250321
})
322+
gock.New("https://api.supabase.com").
323+
Get("/v1/projects/mayuaycdtijbctgqbycg/config/storage").
324+
Reply(http.StatusOK).
325+
JSON(map[string]any{
326+
"fileSizeLimit": 52428800,
327+
"features": map[string]any{
328+
"imageTransformation": map[string]any{"enabled": true},
329+
"s3Protocol": map[string]any{"enabled": false},
330+
},
331+
"capabilities": map[string]any{
332+
"iceberg_catalog": false,
333+
"list_v2": true,
334+
},
335+
"external": map[string]any{
336+
"upstreamTarget": "main",
337+
},
338+
})
339+
gock.New("https://api.supabase.com").
340+
Patch("/v1/projects/mayuaycdtijbctgqbycg/config/storage").
341+
Reply(http.StatusOK)
342+
gock.New("https://api.supabase.com").
343+
Get("/v1/projects/mayuaycdtijbctgqbycg/config/storage").
344+
Reply(http.StatusOK).
345+
JSON(map[string]any{
346+
"fileSizeLimit": 52428800,
347+
"features": map[string]any{
348+
"imageTransformation": map[string]any{"enabled": true},
349+
"s3Protocol": map[string]any{"enabled": false},
350+
},
351+
"capabilities": map[string]any{
352+
"iceberg_catalog": false,
353+
"list_v2": true,
354+
},
355+
"external": map[string]any{
356+
"upstreamTarget": "main",
357+
},
358+
})
359+
gock.New("https://api.supabase.com").
360+
Get("/v1/projects/mayuaycdtijbctgqbycg/config/storage").
361+
Reply(http.StatusOK).
362+
JSON(map[string]any{
363+
"fileSizeLimit": 52428800,
364+
"features": map[string]any{
365+
"imageTransformation": map[string]any{"enabled": true},
366+
"s3Protocol": map[string]any{"enabled": false},
367+
},
368+
"capabilities": map[string]any{
369+
"iceberg_catalog": false,
370+
"list_v2": true,
371+
},
372+
"external": map[string]any{
373+
"upstreamTarget": "main",
374+
},
375+
})
251376
// Run test
252377
resource.Test(t, resource.TestCase{
253378
PreCheck: func() { testAccPreCheck(t) },
@@ -305,6 +430,21 @@ func TestAccSettingsResource(t *testing.T) {
305430
return fmt.Errorf("expected auth.smtp_admin_email to be filtered out, got %v", auth["smtp_admin_email"])
306431
}
307432

433+
storage, err := unmarshalStateAttr(state, "storage")
434+
if err != nil {
435+
return err
436+
}
437+
if storage["fileSizeLimit"] != float64(52428800) {
438+
return fmt.Errorf("expected storage.fileSizeLimit to be 52428800, got %v", storage["fileSizeLimit"])
439+
}
440+
if features, ok := storage["features"].(map[string]any); ok {
441+
if imgTransform, ok := features["imageTransformation"].(map[string]any); ok {
442+
if imgTransform["enabled"] != true {
443+
return fmt.Errorf("expected storage.features.imageTransformation.enabled to be true, got %v", imgTransform["enabled"])
444+
}
445+
}
446+
}
447+
308448
if projectRef, ok := state.Attributes["project_ref"]; !ok || projectRef != "mayuaycdtijbctgqbycg" {
309449
return fmt.Errorf("expected project_ref to be mayuaycdtijbctgqbycg, got %v", projectRef)
310450
}
@@ -361,9 +501,17 @@ resource "supabase_settings" "production" {
361501
jwt_exp = 1800
362502
})
363503
364-
# storage = jsonencode({
365-
# file_size_limit = "50MB"
366-
# })
504+
storage = jsonencode({
505+
fileSizeLimit = 52428800
506+
features = {
507+
imageTransformation = {
508+
enabled = true
509+
}
510+
s3Protocol = {
511+
enabled = false
512+
}
513+
}
514+
})
367515
368516
# pooler = jsonencode({
369517
# default_pool_size = 15

0 commit comments

Comments
 (0)