From 76c0b3b94bf390c5f4c2261741e85fd1e8d38a85 Mon Sep 17 00:00:00 2001 From: mouuii <49775493+mouuii@users.noreply.github.com> Date: Thu, 30 Nov 2023 16:09:56 +0800 Subject: [PATCH] terraform provider 04 --- examples/order/main.tf | 33 +++++ internal/provider/order_resource.go | 192 ++++++++++++++++++++++++++++ internal/provider/provider.go | 4 +- 3 files changed, 228 insertions(+), 1 deletion(-) create mode 100644 examples/order/main.tf create mode 100644 internal/provider/order_resource.go diff --git a/examples/order/main.tf b/examples/order/main.tf new file mode 100644 index 0000000..5e3b9f0 --- /dev/null +++ b/examples/order/main.tf @@ -0,0 +1,33 @@ +terraform { + required_providers { + hashicups = { + source = "hashicorp.com/edu/hashicups" + } + } + required_version = ">= 1.1.0" +} + +provider "hashicups" { + username = "education" + password = "test123" + host = "http://localhost:19090" +} + +resource "hashicups_order" "edu" { + items = [{ + coffee = { + id = 3 + } + quantity = 2 + }, { + coffee = { + id = 1 + } + quantity = 2 + } + ] +} + +output "edu_order" { + value = hashicups_order.edu +} diff --git a/internal/provider/order_resource.go b/internal/provider/order_resource.go new file mode 100644 index 0000000..d17da9c --- /dev/null +++ b/internal/provider/order_resource.go @@ -0,0 +1,192 @@ +package provider + +import ( + "context" + "fmt" + "strconv" + "time" + + "github.com/hashicorp-demoapp/hashicups-client-go" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +// Ensure the implementation satisfies the expected interfaces. +var ( + _ resource.Resource = &orderResource{} + _ resource.ResourceWithConfigure = &orderResource{} +) + +// NewOrderResource is a helper function to simplify the provider implementation. +func NewOrderResource() resource.Resource { + return &orderResource{} +} + +// orderResource is the resource implementation. +type orderResource struct { + client *hashicups.Client +} + +// Metadata returns the resource type name. +func (r *orderResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_order" +} + +// Schema defines the schema for the resource. +func (r *orderResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Computed: true, + }, + "last_updated": schema.StringAttribute{ + Computed: true, + }, + "items": schema.ListNestedAttribute{ + Required: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "quantity": schema.Int64Attribute{ + Required: true, + }, + "coffee": schema.SingleNestedAttribute{ + Required: true, + Attributes: map[string]schema.Attribute{ + "id": schema.Int64Attribute{ + Required: true, + }, + "name": schema.StringAttribute{ + Computed: true, + }, + "teaser": schema.StringAttribute{ + Computed: true, + }, + "description": schema.StringAttribute{ + Computed: true, + }, + "price": schema.Float64Attribute{ + Computed: true, + }, + "image": schema.StringAttribute{ + Computed: true, + }, + }, + }, + }, + }, + }, + }, + } +} + +// Create a new resource. +func (r *orderResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + // Retrieve values from plan + var plan orderResourceModel + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + // Generate API request body from plan + var items []hashicups.OrderItem + for _, item := range plan.Items { + items = append(items, hashicups.OrderItem{ + Coffee: hashicups.Coffee{ + ID: int(item.Coffee.ID.ValueInt64()), + }, + Quantity: int(item.Quantity.ValueInt64()), + }) + } + + // Create new order + order, err := r.client.CreateOrder(items) + if err != nil { + resp.Diagnostics.AddError( + "Error creating order", + "Could not create order, unexpected error: "+err.Error(), + ) + return + } + + // Map response body to schema and populate Computed attribute values + plan.ID = types.StringValue(strconv.Itoa(order.ID)) + for orderItemIndex, orderItem := range order.Items { + plan.Items[orderItemIndex] = orderItemModel{ + Coffee: orderItemCoffeeModel{ + ID: types.Int64Value(int64(orderItem.Coffee.ID)), + Name: types.StringValue(orderItem.Coffee.Name), + Teaser: types.StringValue(orderItem.Coffee.Teaser), + Description: types.StringValue(orderItem.Coffee.Description), + Price: types.Float64Value(orderItem.Coffee.Price), + Image: types.StringValue(orderItem.Coffee.Image), + }, + Quantity: types.Int64Value(int64(orderItem.Quantity)), + } + } + plan.LastUpdated = types.StringValue(time.Now().Format(time.RFC850)) + + // Set state to fully populated data + diags = resp.State.Set(ctx, plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } +} + +// Read refreshes the Terraform state with the latest data. +func (r *orderResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { +} + +// Update updates the resource and sets the updated Terraform state on success. +func (r *orderResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { +} + +// Delete deletes the resource and removes the Terraform state on success. +func (r *orderResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { +} + +// Configure adds the provider configured client to the resource. +func (r *orderResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + + client, ok := req.ProviderData.(*hashicups.Client) + + if !ok { + resp.Diagnostics.AddError( + "Unexpected Data Source Configure Type", + fmt.Sprintf("Expected *hashicups.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + + return + } + + r.client = client +} + +// orderResourceModel maps the resource schema data. +type orderResourceModel struct { + ID types.String `tfsdk:"id"` + Items []orderItemModel `tfsdk:"items"` + LastUpdated types.String `tfsdk:"last_updated"` +} + +// orderItemModel maps order item data. +type orderItemModel struct { + Coffee orderItemCoffeeModel `tfsdk:"coffee"` + Quantity types.Int64 `tfsdk:"quantity"` +} + +// orderItemCoffeeModel maps coffee order item data. +type orderItemCoffeeModel struct { + ID types.Int64 `tfsdk:"id"` + Name types.String `tfsdk:"name"` + Teaser types.String `tfsdk:"teaser"` + Description types.String `tfsdk:"description"` + Price types.Float64 `tfsdk:"price"` + Image types.String `tfsdk:"image"` +} diff --git a/internal/provider/provider.go b/internal/provider/provider.go index c9d2676..78b4379 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -193,7 +193,9 @@ func (p *hashicupsProvider) DataSources(_ context.Context) []func() datasource.D // Resources defines the resources implemented in the provider. func (p *hashicupsProvider) Resources(_ context.Context) []func() resource.Resource { - return nil + return []func() resource.Resource{ + NewOrderResource, + } } // hashicupsProviderModel maps provider schema data to a Go type.