Skip to content

Commit

Permalink
Merge pull request #43 from ronati/document-implementation
Browse files Browse the repository at this point in the history
Document implementation
  • Loading branch information
adanylenko authored Mar 22, 2024
2 parents 221058f + 4929a77 commit 6c89d32
Show file tree
Hide file tree
Showing 10 changed files with 346 additions and 2 deletions.
48 changes: 48 additions & 0 deletions docs/resources/document.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "typesense_document Resource - typesense"
subcategory: ""
description: |-
Every record you index in Typesense is called a Document
---

# typesense_document (Resource)

Every record you index in Typesense is called a Document

## Example Usage

```terraform
resource "typesense_document" "my-document" {
name = "test-document"
collection_name = typesense_collection.test_collection.name
document = <<EOF
{
"field1":"testValue1",
"field2":"testValue2"
}
EOF
}
```

<!-- schema generated by tfplugindocs -->
## Schema

### Required

- `collection_name` (String) Collection name
- `document` (String) Document object in JSON format
- `name` (String) Name identifier, it will be used as id, so needs to be URL-friendly

### Read-Only

- `id` (String) Id identifier

## Import

Import is supported using the following syntax:

```shell
terraform import typesense_document.my_document document-id
```
14 changes: 14 additions & 0 deletions examples/provider-install-verification/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,17 @@ resource "typesense_synonym" "test" {
synonyms = ["updated1", "value2", "value3"]

}

resource "typesense_document" "test" {
name = "test-document-2"
collection_name = typesense_collection.test_collection.name

document = <<EOF
{
"ronati_product_height_imp":"testValue1Updated5",
"test_field":"testValue2_2V4",
"newField1":"newFieldValue1",
"newField2":"newFieldValue2"
}
EOF
}
1 change: 1 addition & 0 deletions examples/resources/typesense_document/import.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
terraform import typesense_document.my_document document-id
11 changes: 11 additions & 0 deletions examples/resources/typesense_document/resource.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
resource "typesense_document" "my-document" {
name = "test-document"
collection_name = typesense_collection.test_collection.name

document = <<EOF
{
"field1":"testValue1",
"field2":"testValue2"
}
EOF
}
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ require (
github.com/hashicorp/logutils v1.0.0 // indirect
github.com/hashicorp/terraform-exec v0.20.0 // indirect
github.com/hashicorp/terraform-json v0.21.0 // indirect
github.com/hashicorp/terraform-plugin-framework-jsontypes v0.1.0 // indirect
github.com/hashicorp/terraform-plugin-sdk/v2 v2.30.0 // indirect
github.com/hashicorp/terraform-registry-address v0.2.3 // indirect
github.com/hashicorp/terraform-svchost v0.1.1 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,8 @@ github.com/hashicorp/terraform-plugin-docs v0.18.0 h1:2bINhzXc+yDeAcafurshCrIjtd
github.com/hashicorp/terraform-plugin-docs v0.18.0/go.mod h1:iIUfaJpdUmpi+rI42Kgq+63jAjI8aZVTyxp3Bvk9Hg8=
github.com/hashicorp/terraform-plugin-framework v1.6.0 h1:hMPWoCiNGR+yzoDlXtZ/meGlUOCn8r1OFuPG84MkhWg=
github.com/hashicorp/terraform-plugin-framework v1.6.0/go.mod h1:QRG6J+m5QBJum+lzKi0Ci2CB8a/xflS3T/aWoz8WD4Y=
github.com/hashicorp/terraform-plugin-framework-jsontypes v0.1.0 h1:b8vZYB/SkXJT4YPbT3trzE6oJ7dPyMy68+9dEDKsJjE=
github.com/hashicorp/terraform-plugin-framework-jsontypes v0.1.0/go.mod h1:tP9BC3icoXBz72evMS5UTFvi98CiKhPdXF6yLs1wS8A=
github.com/hashicorp/terraform-plugin-framework-validators v0.12.0 h1:HOjBuMbOEzl7snOdOoUfE2Jgeto6JOjLVQ39Ls2nksc=
github.com/hashicorp/terraform-plugin-framework-validators v0.12.0/go.mod h1:jfHGE/gzjxYz6XoUwi/aYiiKrJDeutQNUtGQXkaHklg=
github.com/hashicorp/terraform-plugin-go v0.22.0 h1:1OS1Jk5mO0f5hrziWJGXXIxBrMe2j/B8E+DVGw43Xmc=
Expand Down
1 change: 1 addition & 0 deletions internal/provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ func (p *TypesenseProvider) Resources(_ context.Context) []func() resource.Resou
return []func() resource.Resource{
NewCollectionResource,
NewSynonymResource,
NewDocumentResource,
}
}

Expand Down
243 changes: 243 additions & 0 deletions internal/provider/resource_document.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,243 @@
package provider

import (
"context"
"fmt"
"strings"

"github.com/hashicorp/terraform-plugin-framework-jsontypes/jsontypes"
"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"
)

// Ensure provider defined types fully satisfy framework interfaces.
var _ resource.Resource = &DocumentResource{}
var _ resource.ResourceWithImportState = &DocumentResource{}

func NewDocumentResource() resource.Resource {
return &DocumentResource{}
}

type DocumentResource struct {
client *typesense.Client
}

type DocumentResourceModel struct {
Id types.String `tfsdk:"id"`
Name types.String `tfsdk:"name"`
CollectionName types.String `tfsdk:"collection_name"`
Document jsontypes.Normalized `tfsdk:"document"`
}

func (r *DocumentResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_document"
}

func (r *DocumentResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
resp.Schema = schema.Schema{
MarkdownDescription: "Every record you index in Typesense is called a Document",

Attributes: map[string]schema.Attribute{
"id": schema.StringAttribute{
Computed: true,
MarkdownDescription: "Id identifier",
PlanModifiers: []planmodifier.String{
stringplanmodifier.UseStateForUnknown(),
},
},
"name": schema.StringAttribute{
MarkdownDescription: "Name identifier, it will be used as id, so needs to be URL-friendly",
Required: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
},
"collection_name": schema.StringAttribute{
MarkdownDescription: "Collection name",
Required: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
},
"document": schema.StringAttribute{
Required: true,
MarkdownDescription: "Document object in JSON format",
CustomType: jsontypes.NormalizedType{},
},
},
}
}

func (r *DocumentResource) 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 *DocumentResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
var data DocumentResourceModel

// Read Terraform plan data into the model
resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)

if resp.Diagnostics.HasError() {
return
}

document, err := parseJsonStringToMap(data.Document.ValueString())

if err != nil {
resp.Diagnostics.AddError("JSON format error", fmt.Sprintf("Unable to parse document json, got error: %s", err))
return
}

document["id"] = data.Name.ValueString()

result, err := r.client.Collection(data.CollectionName.ValueString()).Documents().Create(ctx, document)

if err != nil {
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to create document, got error: %s", err))
return
}

data.Id = types.StringValue(result["id"].(string))

delete(result, "id")

data.Document, err = parseMapToJsonString(result)

if err != nil {
resp.Diagnostics.AddError("JSON format error", fmt.Sprintf("Unable to parse json response, got error: %s", err))
return
}

resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
}

func (r *DocumentResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
var data DocumentResourceModel

// Read Terraform prior state data into the model
resp.Diagnostics.Append(req.State.Get(ctx, &data)...)

if resp.Diagnostics.HasError() {
return
}

result, err := r.client.Collection(data.CollectionName.ValueString()).Document(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 retrieve document, got error: %s", err))
} else {
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to retrieve document, got error: %s", err))
}

return
}

data.Id = types.StringValue(result["id"].(string))
data.Name = types.StringValue(result["id"].(string))

delete(result, "id")

data.Document, err = parseMapToJsonString(result)

if err != nil {
resp.Diagnostics.AddError("JSON format error", fmt.Sprintf("Unable to parse json response, got error: %s", err))
return
}

resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
}

func (r *DocumentResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
var data DocumentResourceModel

// Read Terraform plan data into the model
resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)

if resp.Diagnostics.HasError() {
return
}

document, err := parseJsonStringToMap(data.Document.ValueString())

if err != nil {
resp.Diagnostics.AddError("JSON format error", fmt.Sprintf("Unable to parse document json, got error: %s", err))
return
}

document["id"] = data.Id.ValueString()

result, err := r.client.Collection(data.CollectionName.ValueString()).Document(data.Id.ValueString()).Update(ctx, document)
_ = result // result is empty

if err != nil {

//check if error contains 201 response
if strings.Contains(err.Error(), "201") {
//ignore, sometimes typesense returns 201 code
} else {
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to update document, got error: %s", err))
return
}

}

resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
}

func (r *DocumentResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
var data DocumentResourceModel

// 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 Document with id="+data.Id.ValueString())

_, err := r.client.Collection(data.CollectionName.ValueString()).Document(data.Id.ValueString()).Delete(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 delete document, got error: %s", err))
} else {
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to delete document, got error: %s", err))
}

return
}

data.Id = types.StringValue("")
}

func (r *DocumentResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp)
}
5 changes: 3 additions & 2 deletions internal/provider/resource_synonym.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ func (r *SynonymResource) Create(ctx context.Context, req resource.CreateRequest
synonym, err := r.client.Collection(data.CollectionName.ValueString()).Synonyms().Upsert(ctx, data.Name.ValueString(), schema)

if err != nil {
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to create collection, got error: %s", err))
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to create synonym, got error: %s", err))
return
}

Expand All @@ -151,6 +151,7 @@ func (r *SynonymResource) Read(ctx context.Context, req resource.ReadRequest, re
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 synonym %s, removing from state", data.Id.ValueString()))
} else {
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to retrieve synonym, got error: %s", err))
}
Expand Down Expand Up @@ -190,7 +191,7 @@ func (r *SynonymResource) Update(ctx context.Context, req resource.UpdateRequest
synonym, err := r.client.Collection(data.CollectionName.ValueString()).Synonyms().Upsert(ctx, data.Id.ValueString(), schema)

if err != nil {
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to create collection, got error: %s", err))
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to create synonym, got error: %s", err))
return
}

Expand Down
Loading

0 comments on commit 6c89d32

Please sign in to comment.