Skip to content

Commit dae166a

Browse files
committed
Add CRUD funcs for the cluster resource v2
1 parent 89c78a5 commit dae166a

File tree

1 file changed

+274
-8
lines changed

1 file changed

+274
-8
lines changed

Diff for: pkg/resources/resource_cluster_new.go

+274-8
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,20 @@ package resources
22

33
import (
44
"context"
5+
"database/sql"
56
"fmt"
67
"log"
78
"strings"
89

910
"github.com/MaterializeInc/terraform-provider-materialize/pkg/materialize"
1011
"github.com/MaterializeInc/terraform-provider-materialize/pkg/utils"
12+
"github.com/hashicorp/terraform-plugin-framework/attr"
1113
"github.com/hashicorp/terraform-plugin-framework/resource"
1214
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
1315
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
1416
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
1517
"github.com/hashicorp/terraform-plugin-framework/types"
18+
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
1619
)
1720

1821
// Define the resource schema and methods.
@@ -25,8 +28,7 @@ func NewClusterResource() resource.Resource {
2528
}
2629

2730
func (r *clusterResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
28-
resp.TypeName = "materialize_cluster_2"
29-
// resp.TypeName = req.ProviderTypeName + "_cluster_2"
31+
resp.TypeName = req.ProviderTypeName + "_cluster_2"
3032
}
3133

3234
type ClusterStateModelV0 struct {
@@ -79,10 +81,9 @@ func (r *clusterResource) Configure(ctx context.Context, req resource.ConfigureR
7981
return
8082
}
8183

82-
// client, ok := req.ProviderData.(*provider.ProviderData)
8384
client, ok := req.ProviderData.(*utils.ProviderData)
8485

85-
// Verbously log the reg.ProviderData
86+
// Verbously log the reg.ProviderData for debugging purposes.
8687
log.Printf("[DEBUG] ProviderData contents: %+v\n", fmt.Sprintf("%+v", req.ProviderData))
8788

8889
if !ok {
@@ -197,22 +198,287 @@ func (r *clusterResource) Create(ctx context.Context, req resource.CreateRequest
197198
// After all operations are successful and you have the cluster ID:
198199
clusterID := utils.TransformIdWithRegion(string(region), i)
199200

200-
// Update the ID in the state and set the entire state in the response
201+
// Update the ID in the state
201202
state.ID = types.StringValue(clusterID)
203+
204+
// After the cluster is successfully created, read its current state
205+
readState, _ := r.read(ctx, &state, false)
206+
if resp.Diagnostics.HasError() {
207+
return
208+
}
209+
210+
// Update the state with the freshly read information
211+
diags = resp.State.Set(ctx, readState)
202212
resp.Diagnostics.Append(diags...)
203213
if resp.Diagnostics.HasError() {
204214
return
205215
}
206216
}
207217

208218
func (r *clusterResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
209-
// Implementation for Read operation
219+
var state ClusterStateModelV0
220+
// Retrieve the current state
221+
diags := req.State.Get(ctx, &state)
222+
resp.Diagnostics.Append(diags...)
223+
if resp.Diagnostics.HasError() {
224+
return
225+
}
226+
227+
// Extract the ID and region from the state
228+
clusterID := state.ID.ValueString()
229+
230+
metaDb, region, err := utils.NewGetDBClientFromMeta(r.client, state.Region.ValueString())
231+
if err != nil {
232+
resp.Diagnostics.AddError("Failed to get DB client", err.Error())
233+
return
234+
}
235+
236+
s, err := materialize.ScanCluster(metaDb, utils.ExtractId(clusterID))
237+
if err != nil {
238+
if err == sql.ErrNoRows {
239+
// If no rows are returned, set the resource ID to an empty string to mark it as removed
240+
state.ID = types.String{}
241+
resp.State.Set(ctx, state)
242+
return
243+
} else {
244+
resp.Diagnostics.AddError("Failed to read the cluster", err.Error())
245+
return
246+
}
247+
}
248+
249+
// Update the state with the fetched values
250+
state.ID = types.StringValue(utils.TransformIdWithRegion(string(region), clusterID))
251+
state.Name = types.StringValue(s.ClusterName.String)
252+
state.OwnershipRole = types.StringValue(s.OwnerName.String)
253+
state.ReplicationFactor = types.Int64Value(s.ReplicationFactor.Int64)
254+
state.Size = types.StringValue(s.Size.String)
255+
state.Disk = types.BoolValue(s.Disk.Bool)
256+
257+
// Convert the availability zones to the appropriate type
258+
azs := make([]types.String, len(s.AvailabilityZones))
259+
for i, az := range s.AvailabilityZones {
260+
azs[i] = types.StringValue(az)
261+
}
262+
azValues := make([]attr.Value, len(s.AvailabilityZones))
263+
for i, az := range s.AvailabilityZones {
264+
azValues[i] = types.StringValue(az)
265+
}
266+
267+
azList, diags := types.ListValue(types.StringType, azValues)
268+
resp.Diagnostics.Append(diags...)
269+
if resp.Diagnostics.HasError() {
270+
return
271+
}
272+
273+
state.AvailabilityZones = azList
274+
state.Comment = types.StringValue(s.Comment.String)
275+
276+
// Set the updated state in the response
277+
resp.State.Set(ctx, state)
210278
}
211279

212280
func (r *clusterResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
213-
// Implementation for Update operation
281+
var plan ClusterStateModelV0
282+
var state ClusterStateModelV0
283+
diags := req.Plan.Get(ctx, &plan)
284+
resp.Diagnostics.Append(diags...)
285+
if resp.Diagnostics.HasError() {
286+
return
287+
}
288+
289+
diags = req.State.Get(ctx, &state)
290+
resp.Diagnostics.Append(diags...)
291+
if resp.Diagnostics.HasError() {
292+
return
293+
}
294+
295+
metaDb, _, err := utils.NewGetDBClientFromMeta(r.client, state.Region.ValueString())
296+
if err != nil {
297+
resp.Diagnostics.AddError("Failed to get DB client", err.Error())
298+
return
299+
}
300+
301+
o := materialize.MaterializeObject{ObjectType: "CLUSTER", Name: state.Name.ValueString()}
302+
b := materialize.NewClusterBuilder(metaDb, o)
303+
304+
// Update cluster attributes if they have changed
305+
if state.OwnershipRole.ValueString() != plan.OwnershipRole.ValueString() {
306+
ownershipBuilder := materialize.NewOwnershipBuilder(metaDb, o)
307+
if err := ownershipBuilder.Alter(plan.OwnershipRole.ValueString()); err != nil {
308+
resp.Diagnostics.AddError("Failed to update ownership role", err.Error())
309+
return
310+
}
311+
}
312+
313+
if state.Size.ValueString() != plan.Size.ValueString() {
314+
if err := b.Resize(plan.Size.ValueString()); err != nil {
315+
resp.Diagnostics.AddError("Failed to resize the cluster", err.Error())
316+
return
317+
}
318+
}
319+
320+
// Handle changes in the 'disk' attribute
321+
if state.Disk.ValueBool() != plan.Disk.ValueBool() {
322+
if err := b.SetDisk(plan.Disk.ValueBool()); err != nil {
323+
resp.Diagnostics.AddError("Failed to update disk setting", err.Error())
324+
return
325+
}
326+
}
327+
328+
// Handle changes in the 'replication_factor' attribute
329+
if state.ReplicationFactor.ValueInt64() != plan.ReplicationFactor.ValueInt64() {
330+
if err := b.SetReplicationFactor(int(plan.ReplicationFactor.ValueInt64())); err != nil {
331+
resp.Diagnostics.AddError("Failed to update replication factor", err.Error())
332+
return
333+
}
334+
}
335+
336+
// Handle changes in the 'availability_zones' attribute
337+
if !state.AvailabilityZones.Equal(plan.AvailabilityZones) {
338+
azs := make([]string, len(plan.AvailabilityZones.Elements()))
339+
for i, elem := range plan.AvailabilityZones.Elements() {
340+
azs[i] = elem.(types.String).ValueString()
341+
}
342+
if err := b.SetAvailabilityZones(azs); err != nil {
343+
resp.Diagnostics.AddError("Failed to update availability zones", err.Error())
344+
return
345+
}
346+
}
347+
348+
// Handle changes in the 'introspection_interval' attribute
349+
if state.IntrospectionInterval.ValueString() != plan.IntrospectionInterval.ValueString() {
350+
if err := b.SetIntrospectionInterval(plan.IntrospectionInterval.ValueString()); err != nil {
351+
resp.Diagnostics.AddError("Failed to update introspection interval", err.Error())
352+
return
353+
}
354+
}
355+
356+
// Handle changes in the 'introspection_debugging' attribute
357+
if state.IntrospectionDebugging.ValueBool() != plan.IntrospectionDebugging.ValueBool() {
358+
if err := b.SetIntrospectionDebugging(plan.IntrospectionDebugging.ValueBool()); err != nil {
359+
resp.Diagnostics.AddError("Failed to update introspection debugging", err.Error())
360+
return
361+
}
362+
}
363+
364+
// Handle changes in the 'idle_arrangement_merge_effort' attribute
365+
if state.IdleArrangementMergeEffort.ValueInt64() != plan.IdleArrangementMergeEffort.ValueInt64() {
366+
if err := b.SetIdleArrangementMergeEffort(int(plan.IdleArrangementMergeEffort.ValueInt64())); err != nil {
367+
resp.Diagnostics.AddError("Failed to update idle arrangement merge effort", err.Error())
368+
return
369+
}
370+
}
371+
372+
// After updating the cluster, read its current state
373+
updatedState, _ := r.read(ctx, &plan, false)
374+
// Update the state with the freshly read information
375+
diags = resp.State.Set(ctx, updatedState)
376+
resp.Diagnostics.Append(diags...)
377+
if resp.Diagnostics.HasError() {
378+
return
379+
}
214380
}
215381

216382
func (r *clusterResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
217-
// Implementation for Delete operation
383+
// Retrieve the current state
384+
var state ClusterStateModelV0
385+
diags := req.State.Get(ctx, &state)
386+
resp.Diagnostics.Append(diags...)
387+
if resp.Diagnostics.HasError() {
388+
return
389+
}
390+
391+
metaDb, _, err := utils.NewGetDBClientFromMeta(r.client, state.Region.ValueString())
392+
if err != nil {
393+
resp.Diagnostics.AddError("Failed to get DB client", err.Error())
394+
return
395+
}
396+
397+
o := materialize.MaterializeObject{ObjectType: "CLUSTER", Name: state.Name.ValueString()}
398+
b := materialize.NewClusterBuilder(metaDb, o)
399+
400+
// Drop the cluster
401+
if err := b.Drop(); err != nil {
402+
resp.Diagnostics.AddError("Failed to delete the cluster", err.Error())
403+
return
404+
}
405+
406+
// After successful deletion, clear the state by setting ID to empty
407+
state.ID = types.String{}
408+
diags = resp.State.Set(ctx, &state)
409+
resp.Diagnostics.Append(diags...)
410+
if resp.Diagnostics.HasError() {
411+
return
412+
}
413+
}
414+
415+
func (r *clusterResource) read(ctx context.Context, data *ClusterStateModelV0, dryRun bool) (*ClusterStateModelV0, diag.Diagnostics) {
416+
diags := diag.Diagnostics{}
417+
418+
metaDb, _, err := utils.NewGetDBClientFromMeta(r.client, data.Region.ValueString())
419+
if err != nil {
420+
diags = append(diags, diag.Diagnostic{
421+
Severity: diag.Error,
422+
Summary: "Failed to get DB client",
423+
Detail: err.Error(),
424+
})
425+
return data, diags
426+
}
427+
428+
clusterID := data.ID.ValueString()
429+
clusterDetails, err := materialize.ScanCluster(metaDb, utils.ExtractId(clusterID))
430+
if err != nil {
431+
if err == sql.ErrNoRows {
432+
data.ID = types.String{}
433+
data.Name = types.String{}
434+
data.Size = types.String{}
435+
data.ReplicationFactor = types.Int64{}
436+
data.Disk = types.Bool{}
437+
data.AvailabilityZones = types.List{}
438+
data.IntrospectionInterval = types.String{}
439+
data.IntrospectionDebugging = types.Bool{}
440+
data.IdleArrangementMergeEffort = types.Int64{}
441+
data.OwnershipRole = types.String{}
442+
data.Comment = types.String{}
443+
} else {
444+
diags = append(diags, diag.Diagnostic{
445+
Severity: diag.Error,
446+
Summary: "Failed to read the cluster",
447+
Detail: err.Error(),
448+
})
449+
}
450+
return data, diags
451+
}
452+
453+
// Set the values from clusterDetails to data, checking for null values.
454+
data.ID = types.StringValue(clusterID)
455+
data.Name = types.StringValue(getNullString(clusterDetails.ClusterName))
456+
data.ReplicationFactor = types.Int64Value(clusterDetails.ReplicationFactor.Int64)
457+
data.Disk = types.BoolValue(clusterDetails.Disk.Bool)
458+
data.OwnershipRole = types.StringValue(getNullString(clusterDetails.OwnerName))
459+
460+
// TODO: Fix failing error for the following fields when they are not set
461+
// data.Size = types.StringValue(getNullString(clusterDetails.Size))
462+
// data.Comment = types.StringValue(getNullString(clusterDetails.Comment))
463+
// data.Region = types.StringValue(string(region))
464+
465+
// Handle the AvailabilityZones which is a slice of strings.
466+
azValues := make([]attr.Value, len(clusterDetails.AvailabilityZones))
467+
for i, az := range clusterDetails.AvailabilityZones {
468+
azValues[i] = types.StringValue(az)
469+
}
470+
471+
azList, _ := types.ListValue(types.StringType, azValues)
472+
473+
data.AvailabilityZones = azList
474+
475+
return data, diags
476+
}
477+
478+
// getNullString checks if the sql.NullString is valid and returns the string or an empty string if not.
479+
func getNullString(ns sql.NullString) string {
480+
if ns.Valid {
481+
return ns.String
482+
}
483+
return ""
218484
}

0 commit comments

Comments
 (0)