From 521dba0552f253ba2b1473429e1a56188b0a9d7a Mon Sep 17 00:00:00 2001 From: Marcin Maciaszczyk Date: Wed, 15 Nov 2023 16:08:35 +0100 Subject: [PATCH] add cluster data source --- example/byok/main.tf | 4 ++ internal/datasource/cluster.go | 111 ++++++++++++++++++++++++++++++--- internal/model/model.go | 14 +++++ internal/resource/cluster.go | 45 ++++++------- 4 files changed, 139 insertions(+), 35 deletions(-) create mode 100644 internal/model/model.go diff --git a/example/byok/main.tf b/example/byok/main.tf index 315a1ec..b9c90f6 100644 --- a/example/byok/main.tf +++ b/example/byok/main.tf @@ -20,3 +20,7 @@ resource "plural_cluster" "byok_workload_cluster" { "managed-by" = "terraform-provider-plural" } } + +data "plural_cluster" "byok_workload_cluster" { + handle = "wctf" +} diff --git a/internal/datasource/cluster.go b/internal/datasource/cluster.go index 7919d76..b4148d2 100644 --- a/internal/datasource/cluster.go +++ b/internal/datasource/cluster.go @@ -2,10 +2,14 @@ package datasource import ( "context" + "fmt" + + "terraform-provider-plural/internal/model" "github.com/hashicorp/terraform-plugin-framework/datasource" "github.com/hashicorp/terraform-plugin-framework/datasource/schema" "github.com/hashicorp/terraform-plugin-framework/types" + console "github.com/pluralsh/console-client-go" ) func NewClusterDataSource() datasource.DataSource { @@ -13,11 +17,7 @@ func NewClusterDataSource() datasource.DataSource { } type clusterDataSource struct { - Id types.String `tfsdk:"id"` - Name types.String `tfsdk:"name"` - Handle types.String `tfsdk:"handle"` - Cloud types.String `tfsdk:"cloud"` - Tags types.Map `tfsdk:"tags"` + client *console.Client } func (d *clusterDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { @@ -25,9 +25,104 @@ func (d *clusterDataSource) Metadata(_ context.Context, req datasource.MetadataR } func (d *clusterDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { - resp.Schema = schema.Schema{} + resp.Schema = schema.Schema{ + MarkdownDescription: "A representation of a cluster you can deploy to.", + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Optional: true, + Computed: true, + MarkdownDescription: "Internal identifier of this cluster.", + }, + "inserted_at": schema.StringAttribute{ + MarkdownDescription: "Creation date of this cluster.", + Computed: true, + }, + "name": schema.StringAttribute{ + MarkdownDescription: "Human-readable name of this cluster, that also translates to cloud resource name.", + Computed: true, + }, + "handle": schema.StringAttribute{ + MarkdownDescription: "A short, unique human-readable name used to identify this cluster. Does not necessarily map to the cloud resource name.", + Optional: true, + Computed: true, + }, + "cloud": schema.StringAttribute{ + MarkdownDescription: "The cloud provider used to create this cluster.", + Computed: true, + }, + "protect": schema.BoolAttribute{ + MarkdownDescription: "If set to `true` then this cluster cannot be deleted.", + Computed: true, + }, + "tags": schema.MapAttribute{ + MarkdownDescription: "Key-value tags used to filter clusters.", + Computed: true, + ElementType: types.StringType, + }, + }, + } +} + +func (d *clusterDataSource) Configure(_ context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + + client, ok := req.ProviderData.(*console.Client) + if !ok { + resp.Diagnostics.AddError( + "Unexpected Cluster Resource Configure Type", + fmt.Sprintf("Expected *console.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + return + } + + d.client = client } -// TODO: Support read by handle and ID. -func (d *clusterDataSource) Read(_ context.Context, _ datasource.ReadRequest, _ *datasource.ReadResponse) { +func (d *clusterDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + var data model.Cluster + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + if data.Id.IsNull() && data.Handle.IsNull() { + resp.Diagnostics.AddError( + "Missing Cluster ID and Handle", + "The provider could not read cluster data. ID or handle needs to be specified.", + ) + } + + // First try to fetch cluster by ID if it was provided. + var cluster *console.ClusterFragment + if !data.Id.IsNull() { + if c, err := d.client.GetCluster(ctx, data.Id.ValueStringPointer()); err != nil { + resp.Diagnostics.AddWarning("Client Error", fmt.Sprintf("Unable to read cluster by ID, got error: %s", err)) + } else { + cluster = c.Cluster + } + } + + // If cluster was not fetched yet and handle was provided then try to use it to fetch cluster. + if cluster == nil && !data.Handle.IsNull() { + if c, err := d.client.GetClusterByHandle(ctx, data.Handle.ValueStringPointer()); err != nil { + resp.Diagnostics.AddWarning("Client Error", fmt.Sprintf("Unable to read cluster by handle, got error: %s", err)) + } else { + cluster = c.Cluster + } + } + + if cluster == nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to read cluster, see warnings for more information")) + return + } + + data.Id = types.StringValue(cluster.ID) + data.InseredAt = types.StringPointerValue(cluster.InsertedAt) + data.Name = types.StringValue(cluster.Name) + data.Handle = types.StringPointerValue(cluster.Handle) + data.Protect = types.BoolPointerValue(cluster.Protect) + + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) } diff --git a/internal/model/model.go b/internal/model/model.go new file mode 100644 index 0000000..1221d61 --- /dev/null +++ b/internal/model/model.go @@ -0,0 +1,14 @@ +package model + +import "github.com/hashicorp/terraform-plugin-framework/types" + +// Cluster describes the cluster resource and data source model. +type Cluster struct { + Id types.String `tfsdk:"id"` + InseredAt types.String `tfsdk:"inserted_at"` + Name types.String `tfsdk:"name"` + Handle types.String `tfsdk:"handle"` + Cloud types.String `tfsdk:"cloud"` + Protect types.Bool `tfsdk:"protect"` + Tags types.Map `tfsdk:"tags"` +} diff --git a/internal/resource/cluster.go b/internal/resource/cluster.go index d829948..65c5f1b 100644 --- a/internal/resource/cluster.go +++ b/internal/resource/cluster.go @@ -4,6 +4,8 @@ import ( "context" "fmt" + "terraform-provider-plural/internal/model" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/resource" @@ -16,34 +18,23 @@ import ( console "github.com/pluralsh/console-client-go" ) -var _ resource.Resource = &ClusterResource{} -var _ resource.ResourceWithImportState = &ClusterResource{} +var _ resource.Resource = &clusterResource{} +var _ resource.ResourceWithImportState = &clusterResource{} func NewClusterResource() resource.Resource { - return &ClusterResource{} + return &clusterResource{} } // ClusterResource defines the cluster resource implementation. -type ClusterResource struct { +type clusterResource struct { client *console.Client } -// ClusterResourceModel describes the cluster resource data model. -type ClusterResourceModel struct { - Id types.String `tfsdk:"id"` - InseredAt types.String `tfsdk:"inserted_at"` - Name types.String `tfsdk:"name"` - Handle types.String `tfsdk:"handle"` - Cloud types.String `tfsdk:"cloud"` - Protect types.Bool `tfsdk:"protect"` - Tags types.Map `tfsdk:"tags"` -} - -func (r *ClusterResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { +func (r *clusterResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { resp.TypeName = req.ProviderTypeName + "_cluster" } -func (r *ClusterResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { +func (r *clusterResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { resp.Schema = schema.Schema{ MarkdownDescription: "A representation of a cluster you can deploy to.", Attributes: map[string]schema.Attribute{ @@ -85,7 +76,7 @@ func (r *ClusterResource) Schema(_ context.Context, _ resource.SchemaRequest, re } } -func (r *ClusterResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { +func (r *clusterResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { if req.ProviderData == nil { return } @@ -102,8 +93,8 @@ func (r *ClusterResource) Configure(_ context.Context, req resource.ConfigureReq r.client = client } -func (r *ClusterResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { - var data ClusterResourceModel +func (r *clusterResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var data model.Cluster resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) if resp.Diagnostics.HasError() { return @@ -145,8 +136,8 @@ func (r *ClusterResource) Create(ctx context.Context, req resource.CreateRequest resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) } -func (r *ClusterResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { - var data ClusterResourceModel +func (r *clusterResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var data model.Cluster resp.Diagnostics.Append(req.State.Get(ctx, &data)...) if resp.Diagnostics.HasError() { return @@ -167,8 +158,8 @@ func (r *ClusterResource) Read(ctx context.Context, req resource.ReadRequest, re resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) } -func (r *ClusterResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { - var data ClusterResourceModel +func (r *clusterResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var data model.Cluster resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) if resp.Diagnostics.HasError() { return @@ -190,8 +181,8 @@ func (r *ClusterResource) Update(ctx context.Context, req resource.UpdateRequest resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) } -func (r *ClusterResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { - var data ClusterResourceModel +func (r *clusterResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var data model.Cluster resp.Diagnostics.Append(req.State.Get(ctx, &data)...) if resp.Diagnostics.HasError() { return @@ -206,6 +197,6 @@ func (r *ClusterResource) Delete(ctx context.Context, req resource.DeleteRequest tflog.Trace(ctx, "deleted the cluster") } -func (r *ClusterResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { +func (r *clusterResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) }