@@ -145,6 +145,49 @@ func (r *ProfileResource) Configure(_ context.Context, req resource.ConfigureReq
145145 r .provider = provider
146146}
147147
148+ func (r * ProfileResource ) ModifyPlan (ctx context.Context , req resource.ModifyPlanRequest , resp * resource.ModifyPlanResponse ) {
149+ priorState := req .State
150+ plannedState := req .Plan
151+
152+ // Detection creation: no prior state
153+ if priorState .Raw .IsNull () {
154+ var profileName types.String
155+ diags := plannedState .GetAttribute (ctx , path .Root ("name" ), & profileName )
156+ if diags .HasError () {
157+ resp .Diagnostics .Append (diags ... )
158+ return
159+ }
160+
161+ // We only print the warning here and take care of the correct changes in the ProfileResource#Create
162+ if profileName .ValueString () == "default" {
163+ resp .Diagnostics .AddWarning (
164+ "Default Profile Creation Attempted" ,
165+ "'default' profile cannot be created in an Incus project. Instead, its config and devices will be updated accordingly." ,
166+ )
167+ }
168+ return
169+ }
170+
171+ // Detect deletion: no planned state
172+ if plannedState .Raw .IsNull () {
173+ var profileName types.String
174+ diags := priorState .GetAttribute (ctx , path .Root ("name" ), & profileName )
175+ if diags .HasError () {
176+ resp .Diagnostics .Append (diags ... )
177+ return
178+ }
179+
180+ // We only print the warning here and take care of the correct changes in the ProfileResource#Delete
181+ if profileName .ValueString () == "default" {
182+ resp .Diagnostics .AddWarning (
183+ "Default Profile Deletion Attempted" ,
184+ "'default' profile cannot be deleted in an Incus project. Instead, its config and devices will be set to the Incus default profile state." ,
185+ )
186+ }
187+ return
188+ }
189+ }
190+
148191func (r ProfileResource ) Create (ctx context.Context , req resource.CreateRequest , resp * resource.CreateResponse ) {
149192 var plan ProfileModel
150193
@@ -154,6 +197,11 @@ func (r ProfileResource) Create(ctx context.Context, req resource.CreateRequest,
154197 return
155198 }
156199
200+ if plan .Name .ValueString () == "default" {
201+ r .importAndUpdateDefaultProfile (ctx , plan , resp )
202+ return
203+ }
204+
157205 remote := plan .Remote .ValueString ()
158206 project := plan .Project .ValueString ()
159207 server , err := r .provider .InstanceServer (remote , project , "" )
@@ -276,6 +324,11 @@ func (r ProfileResource) Delete(ctx context.Context, req resource.DeleteRequest,
276324 return
277325 }
278326
327+ if state .Name .ValueString () == "default" {
328+ r .resetDefaultProfile (ctx , state , resp )
329+ return
330+ }
331+
279332 remote := state .Remote .ValueString ()
280333 project := state .Project .ValueString ()
281334 server , err := r .provider .InstanceServer (remote , project , "" )
@@ -344,3 +397,126 @@ func (r ProfileResource) SyncState(ctx context.Context, tfState *tfsdk.State, se
344397
345398 return tfState .Set (ctx , & m )
346399}
400+
401+ func (r ProfileResource ) importAndUpdateDefaultProfile (ctx context.Context , plan ProfileModel , resp * resource.CreateResponse ) {
402+ remote := plan .Remote .ValueString ()
403+ project := plan .Project .ValueString ()
404+ profileName := plan .Name .ValueString ()
405+
406+ // 1. We import the default profile.
407+ var id string
408+ if project != "" {
409+ id = fmt .Sprintf ("%s/%s" , project , profileName )
410+ } else {
411+ id = profileName
412+ }
413+
414+ meta := common.ImportMetadata {
415+ ResourceName : "profile" ,
416+ RequiredFields : []string {"name" },
417+ }
418+
419+ fields , diag := meta .ParseImportID (id )
420+ if diag != nil {
421+ resp .Diagnostics .Append (diag )
422+ return
423+ }
424+
425+ for k , v := range fields {
426+ resp .Diagnostics .Append (resp .State .SetAttribute (ctx , path .Root (k ), v )... )
427+ }
428+
429+ // 2. After we have imported the default profile, we can now update it.
430+ server , err := r .provider .InstanceServer (remote , project , "" )
431+ if err != nil {
432+ resp .Diagnostics .Append (errors .NewInstanceServerError (err ))
433+ return
434+ }
435+
436+ _ , etag , err := server .GetProfile (profileName )
437+ if err != nil {
438+ resp .Diagnostics .AddError (fmt .Sprintf ("Failed to retrieve existing profile %q" , profileName ), err .Error ())
439+ return
440+ }
441+
442+ config , diags := common .ToConfigMap (ctx , plan .Config )
443+ resp .Diagnostics .Append (diags ... )
444+
445+ devices , diags := common .ToDeviceMap (ctx , plan .Devices )
446+ resp .Diagnostics .Append (diags ... )
447+
448+ if resp .Diagnostics .HasError () {
449+ return
450+ }
451+
452+ profile := api.ProfilePut {
453+ Description : plan .Description .ValueString (),
454+ Config : config ,
455+ Devices : devices ,
456+ }
457+
458+ err = server .UpdateProfile (profileName , profile , etag )
459+ if err != nil {
460+ resp .Diagnostics .AddError (fmt .Sprintf ("Failed to update profile %q" , profileName ), err .Error ())
461+ return
462+ }
463+
464+ // 3. Sync Terraform state.
465+ diags = r .SyncState (ctx , & resp .State , server , plan )
466+ resp .Diagnostics .Append (diags ... )
467+ }
468+
469+ func (r ProfileResource ) resetDefaultProfile (ctx context.Context , plan ProfileModel , resp * resource.DeleteResponse ) {
470+ remote := plan .Remote .ValueString ()
471+ projectName := plan .Project .ValueString ()
472+ profileName := plan .Name .ValueString ()
473+
474+ server , err := r .provider .InstanceServer (remote , projectName , "" )
475+ if err != nil {
476+ resp .Diagnostics .Append (errors .NewInstanceServerError (err ))
477+ return
478+ }
479+
480+ _ , etag , err := server .GetProfile (profileName )
481+ if err != nil {
482+ resp .Diagnostics .AddError (fmt .Sprintf ("Failed to retrieve existing profile %q" , profileName ), err .Error ())
483+ return
484+ }
485+
486+ devices , diags := common .ToDeviceMap (ctx , plan .Devices )
487+ resp .Diagnostics .Append (diags ... )
488+ if resp .Diagnostics .HasError () {
489+ return
490+ }
491+
492+ defaultDevices := make (map [string ]map [string ]string )
493+ defaultConfig := make (map [string ]string )
494+
495+ // We must keep the root device if features.profiles is set to false.
496+ project , _ , err := server .GetProject (projectName )
497+ if err != nil {
498+ resp .Diagnostics .AddError (fmt .Sprintf ("Failed to retrieve project %q" , projectName ), err .Error ())
499+ return
500+ }
501+
502+ if project .Config ["features.profiles" ] == "false" {
503+ if value , exists := devices ["root" ]; exists {
504+ defaultDevices ["root" ] = value
505+ }
506+ }
507+
508+ profile := api.ProfilePut {
509+ Description : plan .Description .ValueString (),
510+ Config : defaultConfig ,
511+ Devices : defaultDevices ,
512+ }
513+
514+ err = server .UpdateProfile (profileName , profile , etag )
515+ if err != nil {
516+ resp .Diagnostics .AddError (fmt .Sprintf ("Failed to update profile %q" , profileName ), err .Error ())
517+ return
518+ }
519+
520+ diags = r .SyncState (ctx , & resp .State , server , plan )
521+ resp .Diagnostics .Append (diags ... )
522+ }
0 commit comments