|
@@ -9,6 +9,8 @@ import (
|
|
|
"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"
|
|
|
)
|
|
|
|
|
@@ -23,35 +25,50 @@ func NewOrderResource() resource.Resource {
|
|
|
return &orderResource{}
|
|
|
}
|
|
|
|
|
|
+// orderResource is the resource implementation.
|
|
|
type orderResource struct {
|
|
|
client *hashicups.Client
|
|
|
}
|
|
|
|
|
|
-func (r *orderResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
|
|
|
- // Add a nil check when handling ProviderData because Terraform
|
|
|
- // sets that data after it calls the ConfigureProvider RPC.
|
|
|
- if req.ProviderData == nil {
|
|
|
- return
|
|
|
- }
|
|
|
+// 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"`
|
|
|
+}
|
|
|
|
|
|
- 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
|
|
|
+// 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"`
|
|
|
}
|
|
|
|
|
|
-func (r *orderResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
|
|
|
+// Metadata returns the resource type name.
|
|
|
+func (r *orderResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
|
|
|
resp.TypeName = req.ProviderTypeName + "_order"
|
|
|
}
|
|
|
|
|
|
-func (r *orderResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
|
|
|
+// 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,
|
|
@@ -93,6 +110,27 @@ func (r *orderResource) Schema(ctx context.Context, req resource.SchemaRequest,
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+// 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
|
|
|
|
|
@@ -114,7 +152,7 @@ func (r *orderResource) Create(ctx context.Context, req resource.CreateRequest,
|
|
|
return
|
|
|
}
|
|
|
|
|
|
- // 3. Generates an API request body from the plan values.
|
|
|
+ // 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
|
|
@@ -133,12 +171,12 @@ func (r *orderResource) Create(ctx context.Context, req resource.CreateRequest,
|
|
|
if err != nil {
|
|
|
resp.Diagnostics.AddError(
|
|
|
"Error creating order",
|
|
|
- err.Error(),
|
|
|
+ "Could not create order, unexpected error: "+err.Error(),
|
|
|
)
|
|
|
return
|
|
|
}
|
|
|
|
|
|
- // 5. Maps response body to resource schema attributes.
|
|
|
+ // 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 {
|
|
@@ -166,6 +204,7 @@ func (r *orderResource) Create(ctx context.Context, req resource.CreateRequest,
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+// Read resource information.
|
|
|
func (r *orderResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
|
|
|
var state orderResourceModel
|
|
|
|
|
@@ -194,8 +233,8 @@ func (r *orderResource) Read(ctx context.Context, req resource.ReadRequest, resp
|
|
|
order, err := r.client.GetOrder(id)
|
|
|
if err != nil {
|
|
|
resp.Diagnostics.AddError(
|
|
|
- "Error reading HashiCups order",
|
|
|
- "Could not read HashiCups order "+state.ID.ValueString()+": "+err.Error(),
|
|
|
+ "Error Reading HashiCups Order",
|
|
|
+ "Could not read HashiCups order ID "+state.ID.ValueString()+": "+err.Error(),
|
|
|
)
|
|
|
return
|
|
|
}
|
|
@@ -205,7 +244,7 @@ func (r *orderResource) Read(ctx context.Context, req resource.ReadRequest, resp
|
|
|
// 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 {
|
|
|
- itemState := orderItemModel{
|
|
|
+ state.Items = append(state.Items, orderItemModel{
|
|
|
Coffee: orderItemCoffeeModel{
|
|
|
ID: types.Int64Value(int64(item.Coffee.ID)),
|
|
|
Name: types.StringValue(item.Coffee.Name),
|
|
@@ -215,8 +254,7 @@ func (r *orderResource) Read(ctx context.Context, req resource.ReadRequest, resp
|
|
|
Image: types.StringValue(item.Coffee.Image),
|
|
|
},
|
|
|
Quantity: types.Int64Value(int64(item.Quantity)),
|
|
|
- }
|
|
|
- state.Items = append(state.Items, itemState)
|
|
|
+ })
|
|
|
}
|
|
|
|
|
|
// 5. Set Terraform's state with the order's model.
|
|
@@ -229,33 +267,84 @@ func (r *orderResource) Read(ctx context.Context, req resource.ReadRequest, resp
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+// 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) {
|
|
|
- return
|
|
|
-}
|
|
|
+ // 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
|
|
|
+ }
|
|
|
|
|
|
-func (r *orderResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
|
|
|
- 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()),
|
|
|
+ })
|
|
|
+ }
|
|
|
|
|
|
-// 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"`
|
|
|
-}
|
|
|
+ // 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(),
|
|
|
+ )
|
|
|
+ }
|
|
|
|
|
|
-type orderItemModel struct {
|
|
|
- Quantity types.Int64 `tfsdk:"quantity"`
|
|
|
- Coffee orderItemCoffeeModel `tfsdk:"coffee"`
|
|
|
+ // 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
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
-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"`
|
|
|
+// Delete deletes the resource and removes the Terraform state on success.
|
|
|
+func (r *orderResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
|
|
|
+ return
|
|
|
}
|