| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368 | package providerimport (	"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/resource/schema/planmodifier"	"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"	"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}// The *Model types describe the types known by the API.// They are basically DTOs for the TF provider to talk to the API, while// its native type is the Plan with its Schema.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"`}// 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,				PlanModifiers: []planmodifier.String{					stringplanmodifier.UseStateForUnknown(),				},			},			"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,								},							},						},					},				},			},		},	}}// 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 Resource Configure Type",			fmt.Sprintf("Expected *hashicups.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData),		)		return	}	r.client = client}// Create a new resource.func (r *orderResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {	var plan orderResourceModel	// 1. Checks whether the API Client is configured.	if r.client == nil {		resp.Diagnostics.AddError(			"cannot create order",			"client is not configured in orderResource.Create",		)		return	}	// 2. Retrieves values from the plan.	// The function will attempt to retrieve values from the plan and convert it to an orderResourceModel.	// If not, the resource responds with an error.	diags := req.Plan.Get(ctx, &plan)	resp.Diagnostics.Append(diags...)	if resp.Diagnostics.HasError() {		return	}	// 3. Generates API request body from  plan	// The function loops through each plan item and maps it to a hashicups.OrderItem.	// This is what the API client needs to create a new order.	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()),		})	}	// 4. Creates a new order.	// The function invokes the API client's CreateOrder method.	order, err := r.client.CreateOrder(items)	if err != nil {		resp.Diagnostics.AddError(			"Error creating order",			"Could not create order, unexpected error: "+err.Error(),		)		return	}	// 5. Maps response body to resource schema attributes and populate Computed attribute values	// After the function creates an order, it maps the hashicups.Order response to []OrderItem so the provider can update the Terraform state.	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)) // Why not RFC3339 ?	// 6. Sets Terraform's state with the new order's details.	diags = resp.State.Set(ctx, plan)	resp.Diagnostics.Append(diags...)	// Useless code in tutorial, why ?	if resp.Diagnostics.HasError() {		return	}}// Read resource information.func (r *orderResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {	var state orderResourceModel	// 0. Checks whether the API Client is configured.	if r.client == nil {		resp.Diagnostics.AddError(			"cannot read order",			"client is not configured in orderResource.Read",		)		return	}	// 1. Gets the current state.	// If it is unable to, the provider responds with an error.	diags := req.State.Get(ctx, &state)	resp.Diagnostics.Append(diags...)	if resp.Diagnostics.HasError() {		return	}	// 2. Retrieves the order ID from Terraform's state.	id := state.ID.ValueString()	// 3. Retrieves the order details from the client.	// The function invokes the API client's GetOrder method with the order ID.	order, err := r.client.GetOrder(id)	if err != nil {		resp.Diagnostics.AddError(			"Error Reading HashiCups Order",			"Could not read HashiCups order ID "+state.ID.ValueString()+": "+err.Error(),		)		return	}	// 4. Maps the response body to resource schema attributes.	// == overwrite items with refreshed state	// After the function retrieves the order, it maps the hashicups.Order response to []OrderItem so the provider can update the Terraform state.	state.Items = []orderItemModel{}	for _, item := range order.Items {		state.Items = append(state.Items, orderItemModel{			Coffee: orderItemCoffeeModel{				ID:          types.Int64Value(int64(item.Coffee.ID)),				Name:        types.StringValue(item.Coffee.Name),				Teaser:      types.StringValue(item.Coffee.Teaser),				Description: types.StringValue(item.Coffee.Description),				Price:       types.Float64Value(item.Coffee.Price),				Image:       types.StringValue(item.Coffee.Image),			},			Quantity: types.Int64Value(int64(item.Quantity)),		})	}	// 5. Set Terraform's state with the order's model.	diags = resp.State.Set(ctx, state)	resp.Diagnostics.Append(diags...)	// Useless code in tutorial, why ?	if resp.Diagnostics.HasError() {		return	}}// 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) {	// 1. Retrieves values from the plan.	// The method will attempt to retrieve values from the plan and convert it to an orderResourceModel.	// The model includes the order's id attribute, which specifies which order to update.	var plan orderResourceModel	diags := req.Plan.Get(ctx, &plan)	resp.Diagnostics.Append(diags...)	if resp.Diagnostics.HasError() {		return	}	// 2. Generates an API request body from the plan values.	// The method loops through each plan item and maps it to a hashicups.OrderItem.	// This is what the API client needs to update an existing order.	var hashicupsItems []hashicups.OrderItem	for _, item := range plan.Items {		hashicupsItems = append(hashicupsItems, hashicups.OrderItem{			Coffee: hashicups.Coffee{				ID: int(item.Coffee.ID.ValueInt64()),			},			Quantity: int(item.Quantity.ValueInt64()),		})	}	// 3. Updates the order.	// 3.1 Apply changes.	// The method invokes the API client's UpdateOrder method with the order's ID and OrderItems.	_, err := r.client.UpdateOrder(plan.ID.ValueString(), hashicupsItems)	if err != nil {		resp.Diagnostics.AddError(			"Error Reading HashiCups Order",			"Could not read HashiCups order ID "+plan.ID.ValueString()+": "+err.Error(),		)	}	// 3.2 Fetch updated items from GetOrder as UpdateOrder items are not populated.	order, err := r.client.GetOrder(plan.ID.ValueString())	if err != nil {		resp.Diagnostics.AddError(			"Error Reading HashiCups Order",			"Could not read HashiCups order ID "+plan.ID.ValueString()+": "+err.Error(),		)		return	}	// 4. Maps the response body to resource schema attributes.	// After the method updates the order, it maps the hashicups.Order response to []OrderItem	// so the provider can update the Terraform state.	plan.Items = []orderItemModel{}	for _, orderItem := range order.Items {		plan.Items = append(plan.Items, 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)),		})	}	// 5. Sets the LastUpdated attribute.	plan.LastUpdated = types.StringValue(time.Now().Format(time.RFC850))	// 6. Sets Terraform's state with the updated order.	diags = resp.State.Set(ctx, plan)	resp.Diagnostics.Append(diags...)	// Useless code in tutorial, why ?	if resp.Diagnostics.HasError() {		return	}}// Delete deletes the resource and removes the Terraform state on success.func (r *orderResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {	// 1. Retrieves values from the state.	// 1.a The method will attempt to retrieve values from the state and convert it to an orderResourceModel struct	var state orderResourceModel	diags := req.State.Get(ctx, &state)	resp.Diagnostics.Append(diags...)	if resp.Diagnostics.HasError() {		return	}	// 2. Deletes an existing order.	// The method invokes the API client's DeleteOrder method.	err := r.client.DeleteOrder(state.ID.ValueString())	if err != nil {		resp.Diagnostics.AddError(			"Error Deleting HashiCups Order",			"Could not delete order, unexpected error: "+err.Error(),		)		return	}}
 |