Define a Go struct representing the Terraform resource model:
type ResourceTemplateResourceModel struct { ID types.String `tfsdk:"id"` Name types.String `tfsdk:"name"` Enabled types.Bool `tfsdk:"enabled"` Timeouts timeouts.Value `tfsdk:"timeouts"` // Always include timeouts}
Always name the model following the pattern ResourceNameResourceModel.
Implement Create, Read, Update, and Delete methods for the resource.
Create Operation
// Create handles the Create operation for the resource.// - Retrieves the plan// - Constructs the request body using a helper// - Calls the API// - Handles errors and sets statefunc (r *ResourceTemplateResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { var plan ResourceTemplateResourceModel resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) if resp.Diagnostics.HasError() { return } ctx, cancel := crud.HandleTimeout(ctx, plan.Timeouts.Create, CreateTimeout*time.Second, &resp.Diagnostics) if cancel == nil { return } defer cancel() requestBody, err := constructResource(ctx, &plan) if err != nil { resp.Diagnostics.AddError("Error constructing resource", err.Error()) return } resource, err := r.client. DeviceManagement(). ResourceTemplates(). Post(ctx, requestBody, nil) if err != nil { errors.HandleKiotaGraphError(ctx, err, resp, constants.TfOperationCreate, r.WritePermissions) return } plan.ID = types.StringValue(*resource.GetId()) resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) if resp.Diagnostics.HasError() { return } // Call Read with retry to get the initial resource state readReq := resource.ReadRequest{State: resp.State, ProviderMeta: req.ProviderMeta} stateContainer := &crud.CreateResponseContainer{CreateResponse: resp} opts := crud.DefaultReadWithRetryOptions() opts.Operation = constants.TfOperationCreate opts.ResourceTypeName = ResourceName err = crud.ReadWithRetry(ctx, r.Read, readReq, stateContainer, opts) if err != nil { resp.Diagnostics.AddError("Error reading resource state after create", err.Error()) return }}
Read Operation
// Read handles the Read operation for the resource.// - Retrieves the state// - Calls the API to get the latest data// - Maps the API response to Terraform statefunc (r *ResourceTemplateResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { var state ResourceTemplateResourceModel resp.Diagnostics.Append(req.State.Get(ctx, &state)...) if resp.Diagnostics.HasError() { return } ctx, cancel := crud.HandleTimeout(ctx, state.Timeouts.Read, ReadTimeout*time.Second, &resp.Diagnostics) if cancel == nil { return } defer cancel() resource, err := r.client. DeviceManagement(). ResourceTemplates(). ByResourceTemplateId(state.ID.ValueString()). Get(ctx, nil) if err != nil { errors.HandleKiotaGraphError(ctx, err, resp, constants.TfOperationRead, r.ReadPermissions) return } mapRemoteStateToTerraform(ctx, &state, resource) resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)}
Update Operation
// Update handles the Update operation for the resource.// - Retrieves the plan// - Constructs the request body using a helper// - Calls the API// - Handles errors and refreshes statefunc (r *ResourceTemplateResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { var plan ResourceTemplateResourceModel resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) if resp.Diagnostics.HasError() { return } ctx, cancel := crud.HandleTimeout(ctx, plan.Timeouts.Update, UpdateTimeout*time.Second, &resp.Diagnostics) if cancel == nil { return } defer cancel() requestBody, err := constructResource(ctx, &plan) if err != nil { resp.Diagnostics.AddError("Error constructing resource for update", err.Error()) return } _, err = r.client. DeviceManagement(). ResourceTemplates(). ByResourceTemplateId(plan.ID.ValueString()). Patch(ctx, requestBody, nil) if err != nil { errors.HandleKiotaGraphError(ctx, err, resp, constants.TfOperationUpdate, r.WritePermissions) return } // Call Read with retry to get the updated resource state readReq := resource.ReadRequest{State: resp.State, ProviderMeta: req.ProviderMeta} stateContainer := &crud.UpdateResponseContainer{UpdateResponse: resp} opts := crud.DefaultReadWithRetryOptions() opts.Operation = constants.TfOperationUpdate opts.ResourceTypeName = ResourceName err = crud.ReadWithRetry(ctx, r.Read, readReq, stateContainer, opts) if err != nil { resp.Diagnostics.AddError("Error reading resource state after update", err.Error()) return }}
Delete Operation
// Delete handles the Delete operation for the resource.// - Retrieves the state// - Calls the API to delete the resource// - Handles errors and removes statefunc (r *ResourceTemplateResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { var data ResourceTemplateResourceModel resp.Diagnostics.Append(req.State.Get(ctx, &data)...) if resp.Diagnostics.HasError() { return } ctx, cancel := crud.HandleTimeout(ctx, data.Timeouts.Delete, DeleteTimeout*time.Second, &resp.Diagnostics) if cancel == nil { return } defer cancel() err := r.client. DeviceManagement(). ResourceTemplates(). ByResourceTemplateId(data.ID.ValueString()). Delete(ctx, nil) if err != nil { errors.HandleKiotaGraphError(ctx, err, resp, constants.TfOperationDelete, r.WritePermissions) return } resp.State.RemoveResource(ctx)}
Do not build API request bodies directly in CRUD functions. Delegate request construction to separate functions (e.g., constructResource). CRUD functions should focus on API calls and overall logic flow.