Hello community, I’m trying to implement a custom provider resource for an api where the call to create will return an ID and then I loop and read the resource info until the resource ready and its working when there is no exception from the api
Here is a snippet of the implementation:
// Create creates the resource and sets the initial Terraform state.
func (r *aviResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
var model AviModel
diags := req.Plan.Get(ctx, &model)
// Call the API
respCre, err := r.client.CreateResource(payload)
if err != nil {
resp.Diagnostics.AddError(
"Error creating avi",
"Could not create avi, unexpected error: "+err.Error(),
)
return
}
respCreateAvi := make(map[string]interface{})
err = json.Unmarshal(respCre, &respCreateAvi)
fmt.Printf("Payload received :\t %v \n", respCreateAvi)
model.Id = types.StringValue(respCreateAvi["id"].(string))
// Store ID of the resource
diags = resp.State.Set(ctx, model)
resp.Diagnostics.Append(diags...)
// wait for resource to be ready
for {
time.Sleep(60 * time.Second)
if err != nil {
resp.Diagnostics.AddError(
"Error while waiting to for avi",
"Could not wait for avi to be ready, unexpected error: "+err.Error(),
)
return
}
// Check If resource ready
if aviReadDetails["state"] == "ACTIVE" && aviReadDetails["status"] == "deployed" {
// Set model values from API read response
break
} else if aviReadDetails["state"] == "TERMINATED" {
resp.Diagnostics.AddError(
"Error while creating avi",
"Could not create avi, received status 'failed to deploy' and state 'TERMINATED', unexpected error.... ",
)
return
}
}
// Set state to fully populated data
diags = resp.State.Set(ctx, model)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
}
But if something happen while waiting for the resource to be ready (like client abort) this code doesn’t store the ID of the resource and the next terraform apply create another new resource
Reading from terraform docs Returning an error diagnostic does not stop the state from being updated
I was expecting the State to be stored but its not the case
terraform apply works fine and my resource is created when there is no error from the api I’m trying to call
I was calling resp.State.Set() directly without assigning it to diags variable but it wasn’t storing the state in case of failure in the for loop so I tried to store the result in a diags and append it in the response, but doesn’t seems to work either,
Is what I’m trying to do is possible? I mean to store the state before the creation process finish and then return an error at the end ?
IIUC, any error returned during creation is going to record the resource as tainted, meaning that subsequent Terraform runs will delete and recreate it.
I’m not aware of any way to override this behaviour, so even if everything else worked as intended, once you’d returned that error, Terraform would still want to delete and create your API object by running your provider’s delete and create methods again on the next run.
That sounds like not what you want, so I guess, yes, it means its not possible. Perhaps you could investigate whether downgrading the error to a warning would be sufficient?
Using the following example, the state is stored but as @maxb points out it will mark the resource as tainted which will result in a destroy-create cycle on the next terraform apply.
func (e *exampleResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
var data exampleResourceData
diags := req.Plan.Get(ctx, &data)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
data.Id = types.StringValue("example-id")
tflog.Trace(ctx, "created a resource")
diags = resp.State.Set(ctx, &data)
resp.Diagnostics.Append(diags...)
// Triggering an error after storing ID
resp.Diagnostics.AddError("triggering error", "")
}
Ok I understand better now what @maxb mean, but I’m still missing something because using this code my terraform.tfstate was empty even after the first terraform apply
As previously mentioned, it sounds like your resp.State.Set call is failing but you’re leaking the returned diagnostics so this never gets reported.
I’ll complement @bendbennett 's example with an example of an even more trivial Create method that demonstrates what happens on error, and the surrounding framework for the rest of the resource:
As a workaround I implemented the resource just to call the api without waiting for it to be ready, and I implemented a datasource that wait for the status ready to read product infos