@@ -2,17 +2,20 @@ package resources
22
33import (
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
2730func (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
3234type 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
208218func (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
212280func (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
216382func (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