@@ -7,13 +7,16 @@ import (
77 "context"
88 "fmt"
99 "net/http"
10+ "strings"
1011
12+ "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
1113 "github.com/hashicorp/terraform-plugin-framework/diag"
1214 "github.com/hashicorp/terraform-plugin-framework/path"
1315 "github.com/hashicorp/terraform-plugin-framework/resource"
1416 "github.com/hashicorp/terraform-plugin-framework/resource/schema"
1517 "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
1618 "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
19+ "github.com/hashicorp/terraform-plugin-framework/schema/validator"
1720 "github.com/hashicorp/terraform-plugin-framework/types"
1821 "github.com/hashicorp/terraform-plugin-log/tflog"
1922 "github.com/supabase/cli/pkg/api"
@@ -71,6 +74,34 @@ func (r *ProjectResource) Schema(ctx context.Context, req resource.SchemaRequest
7174 "instance_size" : schema.StringAttribute {
7275 MarkdownDescription : "Desired instance size of the project" ,
7376 Optional : true ,
77+ Computed : true ,
78+ PlanModifiers : []planmodifier.String {
79+ stringplanmodifier .UseStateForUnknown (),
80+ },
81+ Validators : []validator.String {
82+ stringvalidator .OneOf (
83+ string (api .V1CreateProjectBodyDesiredInstanceSizeLarge ),
84+ string (api .V1CreateProjectBodyDesiredInstanceSizeMedium ),
85+ string (api .V1CreateProjectBodyDesiredInstanceSizeMicro ),
86+ string (api .V1CreateProjectBodyDesiredInstanceSizeN12xlarge ),
87+ string (api .V1CreateProjectBodyDesiredInstanceSizeN16xlarge ),
88+ string (api .V1CreateProjectBodyDesiredInstanceSizeN24xlarge ),
89+ string (api .V1CreateProjectBodyDesiredInstanceSizeN24xlargeHighMemory ),
90+ string (api .V1CreateProjectBodyDesiredInstanceSizeN24xlargeOptimizedCpu ),
91+ string (api .V1CreateProjectBodyDesiredInstanceSizeN24xlargeOptimizedMemory ),
92+ string (api .V1CreateProjectBodyDesiredInstanceSizeN2xlarge ),
93+ string (api .V1CreateProjectBodyDesiredInstanceSizeN48xlarge ),
94+ string (api .V1CreateProjectBodyDesiredInstanceSizeN48xlargeHighMemory ),
95+ string (api .V1CreateProjectBodyDesiredInstanceSizeN48xlargeOptimizedCpu ),
96+ string (api .V1CreateProjectBodyDesiredInstanceSizeN48xlargeOptimizedMemory ),
97+ string (api .V1CreateProjectBodyDesiredInstanceSizeN4xlarge ),
98+ string (api .V1CreateProjectBodyDesiredInstanceSizeN8xlarge ),
99+ string (api .V1CreateProjectBodyDesiredInstanceSizeNano ),
100+ string (api .V1CreateProjectBodyDesiredInstanceSizePico ),
101+ string (api .V1CreateProjectBodyDesiredInstanceSizeSmall ),
102+ string (api .V1CreateProjectBodyDesiredInstanceSizeXlarge ),
103+ ),
104+ },
74105 },
75106 "id" : schema.StringAttribute {
76107 MarkdownDescription : "Project identifier" ,
@@ -110,12 +141,17 @@ func (r *ProjectResource) Create(ctx context.Context, req resource.CreateRequest
110141 return
111142 }
112143
144+ tflog .Trace (ctx , "create project" )
113145 resp .Diagnostics .Append (createProject (ctx , & data , r .client )... )
114146 if resp .Diagnostics .HasError () {
115147 return
116148 }
117149
118- tflog .Trace (ctx , "create project" )
150+ tflog .Trace (ctx , "read up to date project" )
151+ resp .Diagnostics .Append (readProject (ctx , & data , r .client )... )
152+ if resp .Diagnostics .HasError () {
153+ return
154+ }
119155
120156 // Save data into Terraform state
121157 resp .Diagnostics .Append (resp .State .Set (ctx , & data )... )
@@ -142,17 +178,41 @@ func (r *ProjectResource) Read(ctx context.Context, req resource.ReadRequest, re
142178}
143179
144180func (r * ProjectResource ) Update (ctx context.Context , req resource.UpdateRequest , resp * resource.UpdateResponse ) {
145- var data ProjectResourceModel
181+ var plan , state ProjectResourceModel
146182
147- // Read Terraform plan data into the model
148- resp .Diagnostics .Append (req .Plan .Get (ctx , & data )... )
183+ resp . Diagnostics . Append ( req . Plan . Get ( ctx , & plan ) ... )
184+ resp .Diagnostics .Append (req .State .Get (ctx , & state )... )
149185 if resp .Diagnostics .HasError () {
150186 return
151187 }
152188
153- // TODO: allow api to update project resource
154- msg := fmt .Sprintf ("Update is not supported for project resource: %s" , data .Id .ValueString ())
155- resp .Diagnostics .Append (diag .NewErrorDiagnostic ("Client Error" , msg ))
189+ // required attributes
190+ if ! plan .Name .Equal (state .Name ) {
191+ resp .Diagnostics .AddAttributeError (path .Root ("name" ), "Client Error" , "Update is not supported for this attribute" )
192+ return
193+ }
194+ if ! plan .DatabasePassword .Equal (state .DatabasePassword ) {
195+ resp .Diagnostics .AddAttributeError (path .Root ("database_password" ), "Client Error" , "Update is not supported for this attribute" )
196+ return
197+ }
198+ if ! plan .Region .Equal (state .Region ) {
199+ resp .Diagnostics .AddAttributeError (path .Root ("region" ), "Client Error" , "Update is not supported for this attribute" )
200+ return
201+ }
202+ if ! plan .OrganizationId .Equal (state .OrganizationId ) {
203+ resp .Diagnostics .AddAttributeError (path .Root ("organization_id" ), "Client Error" , "Update is not supported for this attribute" )
204+ return
205+ }
206+
207+ // optional attributes
208+ if ! plan .InstanceSize .IsNull () && ! plan .InstanceSize .Equal (state .InstanceSize ) {
209+ resp .Diagnostics .Append (updateInstanceSize (ctx , & plan , r .client )... )
210+ }
211+
212+ if resp .Diagnostics .HasError () {
213+ return
214+ }
215+ resp .Diagnostics .Append (resp .State .Set (ctx , & plan )... )
156216}
157217
158218func (r * ProjectResource ) Delete (ctx context.Context , req resource.DeleteRequest , resp * resource.DeleteResponse ) {
@@ -198,7 +258,7 @@ func createProject(ctx context.Context, data *ProjectResourceModel, client *api.
198258 DbPass : data .DatabasePassword .ValueString (),
199259 RegionSelection : & region ,
200260 }
201- if ! data .InstanceSize .IsNull () {
261+ if ! data .InstanceSize .IsUnknown () && ! data . InstanceSize . IsNull () {
202262 body .DesiredInstanceSize = Ptr (api .V1CreateProjectBodyDesiredInstanceSize (data .InstanceSize .ValueString ()))
203263 }
204264
@@ -218,28 +278,53 @@ func createProject(ctx context.Context, data *ProjectResourceModel, client *api.
218278}
219279
220280func readProject (ctx context.Context , data * ProjectResourceModel , client * api.ClientWithResponses ) diag.Diagnostics {
221- httpResp , err := client .V1ListAllProjectsWithResponse (ctx )
281+ projectResp , err := client .V1GetProjectWithResponse (ctx , data . Id . ValueString () )
222282 if err != nil {
223283 msg := fmt .Sprintf ("Unable to read project, got error: %s" , err )
224284 return diag.Diagnostics {diag .NewErrorDiagnostic ("Client Error" , msg )}
225285 }
226286
227- if httpResp .JSON200 == nil {
228- msg := fmt .Sprintf ("Unable to read project, got status %d: %s" , httpResp .StatusCode (), httpResp .Body )
287+ if projectResp .StatusCode () == http .StatusNotFound {
288+ return nil
289+ }
290+
291+ if projectResp .JSON200 == nil {
292+ msg := fmt .Sprintf ("Unable to read project, got status %d: %s" , projectResp .StatusCode (), projectResp .Body )
293+ return diag.Diagnostics {diag .NewErrorDiagnostic ("Client Error" , msg )}
294+ }
295+
296+ project := projectResp .JSON200
297+ data .OrganizationId = types .StringValue (project .OrganizationId )
298+ data .Name = types .StringValue (project .Name )
299+ data .Region = types .StringValue (project .Region )
300+ data .InstanceSize = types .StringNull ()
301+
302+ addonsResp , err := client .V1ListProjectAddonsWithResponse (ctx , project .Id )
303+ if err != nil {
304+ msg := fmt .Sprintf ("Unable to read project addons, got error: %s" , err )
229305 return diag.Diagnostics {diag .NewErrorDiagnostic ("Client Error" , msg )}
230306 }
231307
232- for _ , project := range * httpResp .JSON200 {
233- if project .Id == data .Id .ValueString () {
234- data .OrganizationId = types .StringValue (project .OrganizationId )
235- data .Name = types .StringValue (project .Name )
236- data .Region = types .StringValue (project .Region )
237- return nil
308+ if addonsResp .JSON200 == nil {
309+ msg := fmt .Sprintf ("Unable to read project addons, got error: %s" , string (addonsResp .Body ))
310+ return diag.Diagnostics {diag .NewErrorDiagnostic ("Client Error" , msg )}
311+ }
312+
313+ for _ , addon := range addonsResp .JSON200 .SelectedAddons {
314+ if addon .Type != api .ComputeInstance {
315+ continue
316+ }
317+
318+ val , err := addon .Variant .Id .AsListProjectAddonsResponseSelectedAddonsVariantId0 ()
319+ if err != nil {
320+ msg := fmt .Sprintf ("Unable to read compute instance addon, got error: %s" , err )
321+ return diag.Diagnostics {diag .NewErrorDiagnostic ("Client Error" , msg )}
238322 }
323+
324+ data .InstanceSize = types .StringValue (strings .TrimPrefix (string (val ), "ci_" ))
325+ break
239326 }
240327
241- // Not finding a project means our local state is stale. Return no error to allow TF to refresh its state.
242- tflog .Trace (ctx , fmt .Sprintf ("project not found: %s" , data .Id .ValueString ()))
243328 return nil
244329}
245330
@@ -262,3 +347,31 @@ func deleteProject(ctx context.Context, data *ProjectResourceModel, client *api.
262347
263348 return nil
264349}
350+
351+ func updateInstanceSize (ctx context.Context , plan * ProjectResourceModel , client * api.ClientWithResponses ) diag.Diagnostics {
352+ addon := api.ApplyProjectAddonBody_AddonVariant {}
353+ variant := api .ApplyProjectAddonBodyAddonVariant0 ("ci_" + plan .InstanceSize .ValueString ())
354+ if err := addon .FromApplyProjectAddonBodyAddonVariant0 (variant ); err != nil {
355+ return diag.Diagnostics {diag .NewErrorDiagnostic (
356+ "Internal Error" ,
357+ fmt .Sprintf ("Failed to configure instance size: %s" , err ),
358+ )}
359+ }
360+ body := api.V1ApplyProjectAddonJSONRequestBody {
361+ AddonType : api .ApplyProjectAddonBodyAddonTypeComputeInstance ,
362+ AddonVariant : addon ,
363+ }
364+
365+ httpResp , err := client .V1ApplyProjectAddonWithResponse (ctx , plan .Id .ValueString (), body )
366+ if err != nil {
367+ msg := fmt .Sprintf ("Unable to update project, got error: %s" , err )
368+ return diag.Diagnostics {diag .NewErrorDiagnostic ("Client Error" , msg )}
369+ }
370+
371+ if httpResp .StatusCode () != http .StatusOK {
372+ msg := fmt .Sprintf ("Unable to update project, got error: %s" , string (httpResp .Body ))
373+ return diag.Diagnostics {diag .NewErrorDiagnostic ("Client Error" , msg )}
374+ }
375+
376+ return nil
377+ }
0 commit comments