@@ -2,17 +2,20 @@ package resources
2
2
3
3
import (
4
4
"context"
5
+ "database/sql"
5
6
"fmt"
6
7
"log"
7
8
"strings"
8
9
9
10
"github.com/MaterializeInc/terraform-provider-materialize/pkg/materialize"
10
11
"github.com/MaterializeInc/terraform-provider-materialize/pkg/utils"
12
+ "github.com/hashicorp/terraform-plugin-framework/attr"
11
13
"github.com/hashicorp/terraform-plugin-framework/resource"
12
14
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
13
15
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
14
16
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
15
17
"github.com/hashicorp/terraform-plugin-framework/types"
18
+ "github.com/hashicorp/terraform-plugin-sdk/v2/diag"
16
19
)
17
20
18
21
// Define the resource schema and methods.
@@ -25,8 +28,7 @@ func NewClusterResource() resource.Resource {
25
28
}
26
29
27
30
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"
30
32
}
31
33
32
34
type ClusterStateModelV0 struct {
@@ -79,10 +81,9 @@ func (r *clusterResource) Configure(ctx context.Context, req resource.ConfigureR
79
81
return
80
82
}
81
83
82
- // client, ok := req.ProviderData.(*provider.ProviderData)
83
84
client , ok := req .ProviderData .(* utils.ProviderData )
84
85
85
- // Verbously log the reg.ProviderData
86
+ // Verbously log the reg.ProviderData for debugging purposes.
86
87
log .Printf ("[DEBUG] ProviderData contents: %+v\n " , fmt .Sprintf ("%+v" , req .ProviderData ))
87
88
88
89
if ! ok {
@@ -197,22 +198,287 @@ func (r *clusterResource) Create(ctx context.Context, req resource.CreateRequest
197
198
// After all operations are successful and you have the cluster ID:
198
199
clusterID := utils .TransformIdWithRegion (string (region ), i )
199
200
200
- // Update the ID in the state and set the entire state in the response
201
+ // Update the ID in the state
201
202
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 )
202
212
resp .Diagnostics .Append (diags ... )
203
213
if resp .Diagnostics .HasError () {
204
214
return
205
215
}
206
216
}
207
217
208
218
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 )
210
278
}
211
279
212
280
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
+ }
214
380
}
215
381
216
382
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 ""
218
484
}
0 commit comments