internal/services/azapi_resource_action_ephemeral.go (179 lines of code) (raw):
package services
import (
"context"
"fmt"
"slices"
"time"
"github.com/Azure/terraform-provider-azapi/internal/clients"
"github.com/Azure/terraform-provider-azapi/internal/docstrings"
"github.com/Azure/terraform-provider-azapi/internal/locks"
"github.com/Azure/terraform-provider-azapi/internal/retry"
"github.com/Azure/terraform-provider-azapi/internal/services/myvalidator"
"github.com/Azure/terraform-provider-azapi/internal/services/parse"
"github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts"
"github.com/hashicorp/terraform-plugin-framework-validators/listvalidator"
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
"github.com/hashicorp/terraform-plugin-framework/ephemeral"
"github.com/hashicorp/terraform-plugin-framework/ephemeral/schema"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
"github.com/hashicorp/terraform-plugin-log/tflog"
)
type ActionEphemeralModel struct {
ID types.String `tfsdk:"id"`
Type types.String `tfsdk:"type"`
ResourceId types.String `tfsdk:"resource_id"`
Action types.String `tfsdk:"action"`
Method types.String `tfsdk:"method"`
Body types.Dynamic `tfsdk:"body"`
Locks types.List `tfsdk:"locks"`
ResponseExportValues types.Dynamic `tfsdk:"response_export_values"`
Output types.Dynamic `tfsdk:"output"`
Timeouts timeouts.Value `tfsdk:"timeouts"`
Retry retry.RetryValue `tfsdk:"retry"`
Headers types.Map `tfsdk:"headers"`
QueryParameters types.Map `tfsdk:"query_parameters"`
}
type ActionEphemeral struct {
ProviderData *clients.Client
}
var _ ephemeral.EphemeralResource = &ActionEphemeral{}
var _ ephemeral.EphemeralResourceWithConfigure = &ActionEphemeral{}
func (r *ActionEphemeral) Metadata(ctx context.Context, request ephemeral.MetadataRequest, response *ephemeral.MetadataResponse) {
response.TypeName = request.ProviderTypeName + "_resource_action"
}
func (r *ActionEphemeral) Configure(ctx context.Context, request ephemeral.ConfigureRequest, response *ephemeral.ConfigureResponse) {
if v, ok := request.ProviderData.(*clients.Client); ok {
r.ProviderData = v
}
}
func (r *ActionEphemeral) Schema(ctx context.Context, request ephemeral.SchemaRequest, response *ephemeral.SchemaResponse) {
response.Schema = schema.Schema{
MarkdownDescription: "Performs an action on an existing Azure resource. ",
Attributes: map[string]schema.Attribute{
"id": schema.StringAttribute{
Computed: true,
MarkdownDescription: docstrings.ID(),
},
"type": schema.StringAttribute{
Required: true,
Validators: []validator.String{
myvalidator.StringIsResourceType(),
},
MarkdownDescription: docstrings.Type(),
},
"resource_id": schema.StringAttribute{
Required: true,
Validators: []validator.String{
myvalidator.StringIsResourceID(),
},
MarkdownDescription: "The ID of an existing Azure source.",
},
"action": schema.StringAttribute{
Optional: true,
MarkdownDescription: docstrings.ResourceAction(),
},
"method": schema.StringAttribute{
Optional: true,
Computed: true,
Validators: []validator.String{
stringvalidator.OneOf("POST", "PATCH", "PUT", "DELETE", "GET", "HEAD"),
},
MarkdownDescription: "Specifies the HTTP method of the azure resource action. Allowed values are `POST`, `PATCH`, `PUT` and `DELETE`. Defaults to `POST`.",
},
// The body attribute is a dynamic attribute that only allows users to specify the resource body as an HCL object
"body": schema.DynamicAttribute{
Optional: true,
MarkdownDescription: docstrings.Body(),
Validators: []validator.Dynamic{
myvalidator.DynamicIsNotStringValidator(),
},
},
"locks": schema.ListAttribute{
ElementType: types.StringType,
Optional: true,
Validators: []validator.List{
listvalidator.ValueStringsAre(myvalidator.StringIsNotEmpty()),
},
MarkdownDescription: docstrings.Locks(),
},
"response_export_values": schema.DynamicAttribute{
Optional: true,
MarkdownDescription: docstrings.ResponseExportValues(),
},
"output": schema.DynamicAttribute{
Computed: true,
MarkdownDescription: docstrings.Output("ephemeral.azapi_resource_action"),
},
"retry": retry.RetrySchema(ctx),
"headers": schema.MapAttribute{
ElementType: types.StringType,
Optional: true,
MarkdownDescription: "A map of headers to include in the request",
},
"query_parameters": schema.MapAttribute{
ElementType: types.ListType{
ElemType: types.StringType,
},
Optional: true,
MarkdownDescription: "A map of query parameters to include in the request",
},
},
Blocks: map[string]schema.Block{
"timeouts": timeouts.Block(ctx, timeouts.Opts{
Read: true,
}),
},
}
}
func (r *ActionEphemeral) Open(ctx context.Context, request ephemeral.OpenRequest, response *ephemeral.OpenResponse) {
var model ActionEphemeralModel
if response.Diagnostics.Append(request.Config.Get(ctx, &model)...); response.Diagnostics.HasError() {
return
}
readTimeout, diags := model.Timeouts.Read(ctx, 5*time.Minute)
if response.Diagnostics.Append(diags...); response.Diagnostics.HasError() {
return
}
ctx, cancel := context.WithTimeout(ctx, readTimeout)
defer cancel()
id, err := parse.ResourceIDWithResourceType(model.ResourceId.ValueString(), model.Type.ValueString())
if err != nil {
response.Diagnostics.AddError("Invalid configuration", err.Error())
return
}
ctx = tflog.SetField(ctx, "resource_id", id.ID())
var requestBody interface{}
if err := unmarshalBody(model.Body, &requestBody); err != nil {
response.Diagnostics.AddError("Invalid body", fmt.Sprintf(`The argument "body" is invalid: %s`, err.Error()))
return
}
method := model.Method.ValueString()
if method == "" {
method = "POST"
}
lockIds := AsStringList(model.Locks)
slices.Sort(lockIds)
for _, lockId := range lockIds {
locks.ByID(lockId)
defer locks.UnlockByID(lockId)
}
// Ensure the context deadline has been set before calling ConfigureClientWithCustomRetry().
client := r.ProviderData.ResourceClient.ConfigureClientWithCustomRetry(ctx, model.Retry, false)
responseBody, err := client.Action(ctx, id.AzureResourceId, model.Action.ValueString(), id.ApiVersion, method, requestBody, clients.NewRequestOptions(AsMapOfString(model.Headers), AsMapOfLists(model.QueryParameters)))
if err != nil {
response.Diagnostics.AddError("Failed to perform action", fmt.Errorf("performing action %s of %q: %+v", model.Action.ValueString(), id, err).Error())
return
}
resourceId := id.ID()
if actionName := model.Action.ValueString(); actionName != "" {
resourceId = fmt.Sprintf("%s/%s", id.ID(), actionName)
}
model.ID = basetypes.NewStringValue(resourceId)
output, err := buildOutputFromBody(responseBody, model.ResponseExportValues, responseBody)
if err != nil {
response.Diagnostics.AddError("Failed to build output", err.Error())
return
}
model.Output = output
response.Diagnostics.Append(response.Result.Set(ctx, model)...)
}