From 16cfd8cd4ce7a008ea2856f5ad6edc6f5cd51cff Mon Sep 17 00:00:00 2001 From: Alexander Danylenko Date: Mon, 1 Apr 2024 10:09:46 +0300 Subject: [PATCH] feat: Implemented alias resource --- docs/resources/alias.md | 40 ++++ docs/resources/collection.md | 2 +- examples/resources/typesense_alias/import.sh | 1 + .../resources/typesense_alias/resource.tf | 4 + internal/provider/provider.go | 1 + internal/provider/resource_alias.go | 199 ++++++++++++++++++ 6 files changed, 246 insertions(+), 1 deletion(-) create mode 100644 docs/resources/alias.md create mode 100644 examples/resources/typesense_alias/import.sh create mode 100644 examples/resources/typesense_alias/resource.tf create mode 100644 internal/provider/resource_alias.go diff --git a/docs/resources/alias.md b/docs/resources/alias.md new file mode 100644 index 0000000..79b201b --- /dev/null +++ b/docs/resources/alias.md @@ -0,0 +1,40 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "typesense_alias Resource - typesense" +subcategory: "" +description: |- + Alias +--- + +# typesense_alias (Resource) + +Alias + +## Example Usage + +```terraform +resource "typesense_alias" "my_alias" { + name = "my-alias" + collection_name = typesense_collection.my_collection.name +} +``` + + +## Schema + +### Required + +- `collection_name` (String) Collection name +- `name` (String) Name identifier + +### Read-Only + +- `id` (String) Id identifier + +## Import + +Import is supported using the following syntax: + +```shell +terraform import typesense_alias.my_alias my-alias +``` diff --git a/docs/resources/collection.md b/docs/resources/collection.md index 944804b..60ea934 100644 --- a/docs/resources/collection.md +++ b/docs/resources/collection.md @@ -45,7 +45,7 @@ resource "typesense_collection" "my_collection" { - `default_sorting_field` (String) Default sorting field - `enable_nested_fields` (Boolean) Enable nested fields, must be enabled to use object/object[] types -- `fields` (Block List) (see [below for nested schema](#nestedblock--fields)) +- `fields` (Block Set) (see [below for nested schema](#nestedblock--fields)) ### Read-Only diff --git a/examples/resources/typesense_alias/import.sh b/examples/resources/typesense_alias/import.sh new file mode 100644 index 0000000..cfa67ab --- /dev/null +++ b/examples/resources/typesense_alias/import.sh @@ -0,0 +1 @@ +terraform import typesense_alias.my_alias my-alias diff --git a/examples/resources/typesense_alias/resource.tf b/examples/resources/typesense_alias/resource.tf new file mode 100644 index 0000000..ce5e148 --- /dev/null +++ b/examples/resources/typesense_alias/resource.tf @@ -0,0 +1,4 @@ +resource "typesense_alias" "my_alias" { + name = "my-alias" + collection_name = typesense_collection.my_collection.name +} diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 83332db..39afddf 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -139,6 +139,7 @@ func (p *TypesenseProvider) Resources(_ context.Context) []func() resource.Resou NewCollectionResource, NewSynonymResource, NewDocumentResource, + NewAliasResource, } } diff --git a/internal/provider/resource_alias.go b/internal/provider/resource_alias.go new file mode 100644 index 0000000..44441b8 --- /dev/null +++ b/internal/provider/resource_alias.go @@ -0,0 +1,199 @@ +package provider + +import ( + "context" + "fmt" + "strings" + + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-log/tflog" + + "github.com/typesense/typesense-go/typesense" + "github.com/typesense/typesense-go/typesense/api" +) + +// Ensure provider defined types fully satisfy framework interfaces. +var _ resource.Resource = &AliasResource{} +var _ resource.ResourceWithImportState = &AliasResource{} + +func NewAliasResource() resource.Resource { + return &AliasResource{} +} + +type AliasResource struct { + client *typesense.Client +} + +type AliasResourceModel struct { + Id types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` + CollectionName types.String `tfsdk:"collection_name"` +} + +func (r *AliasResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_alias" +} + +func (r *AliasResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + MarkdownDescription: "An alias is a virtual collection name that points to a real collection. If you're familiar with symbolic links on Linux, it's very similar to that.", + + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Computed: true, + MarkdownDescription: "Id identifier", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "name": schema.StringAttribute{ + MarkdownDescription: "Name identifier", + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "collection_name": schema.StringAttribute{ + MarkdownDescription: "Collection name", + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + }, + } +} + +func (r *AliasResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + // Prevent panic if the provider has not been configured. + if req.ProviderData == nil { + return + } + + client, ok := req.ProviderData.(*typesense.Client) + + if !ok { + resp.Diagnostics.AddError( + "Unexpected Resource Configure Type", + fmt.Sprintf("Expected *http.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + + return + } + + r.client = client +} + +func (r *AliasResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var data AliasResourceModel + + // Read Terraform plan data into the model + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + body := &api.CollectionAliasSchema{CollectionName: data.CollectionName.ValueString()} + + alias, err := r.client.Aliases().Upsert(ctx, data.Name.ValueString(), body) + + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to create alias, got error: %s", err)) + return + } + + data.Id = types.StringPointerValue(alias.Name) + + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r *AliasResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var data AliasResourceModel + + // Read Terraform prior state data into the model + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + alias, err := r.client.Alias(data.Id.ValueString()).Retrieve(ctx) + + if err != nil { + if strings.Contains(err.Error(), "Not Found") { + resp.State.RemoveResource(ctx) + resp.Diagnostics.AddWarning("Resource Not Found", fmt.Sprintf("Unable to find alias %s, removing from state", data.Id.ValueString())) + } else { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to retrieve alias, got error: %s", err)) + } + + return + } + + data.Name = types.StringPointerValue(alias.Name) + data.CollectionName = types.StringValue(alias.CollectionName) + + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r *AliasResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var data AliasResourceModel + + // Read Terraform plan data into the model + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + body := &api.CollectionAliasSchema{CollectionName: data.CollectionName.ValueString()} + + alias, err := r.client.Aliases().Upsert(ctx, data.Name.ValueString(), body) + + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to create alias, got error: %s", err)) + return + } + + data.Name = types.StringPointerValue(alias.Name) + data.CollectionName = types.StringValue(alias.CollectionName) + + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r *AliasResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var data AliasResourceModel + + // Read Terraform prior state data into the model + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + tflog.Warn(ctx, "###Delete alias with id="+data.Id.ValueString()) + + _, err := r.client.Alias(data.Id.ValueString()).Delete(ctx) + + if err != nil { + if strings.Contains(err.Error(), "Not Found") { + resp.State.RemoveResource(ctx) + } else { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to delete alias, got error: %s", err)) + } + + return + } + + data.Id = types.StringValue("") +} + +func (r *AliasResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) +}