Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

resources deleted out-of-bounds are not recreated #73

Open
ermul opened this issue Feb 26, 2025 · 0 comments · May be fixed by #75
Open

resources deleted out-of-bounds are not recreated #73

ermul opened this issue Feb 26, 2025 · 0 comments · May be fixed by #75

Comments

@ermul
Copy link

ermul commented Feb 26, 2025

Steps to Reproduce

I'm using rootly_form_field here, but I expect it could be reproduced with any resource.
versions:
terraform: 1.9.8
terraform-provider-rootly: 2.19.1

  1. Create a new form_field resource (Copied from examples/resources/rootly_form_field/resource.tf and added an output for the ID)
resource "rootly_form_field" "regions_affected" {
  name       = "Regions affected"
  kind       = "custom"
  input_kind = "multi_select"
  shown      = ["web_new_incident_form", "web_update_incident_form"]
  required   = ["web_new_incident_form", "web_update_incident_form"]
}

resource "rootly_form_field_option" "asia" {
  form_field_id = rootly_form_field.regions_affected.id
  value         = "Asia"
}

resource "rootly_form_field_option" "europe" {
  form_field_id = rootly_form_field.regions_affected.id
  value         = "Europe"
}

resource "rootly_form_field_option" "north_america" {
  form_field_id = rootly_form_field.regions_affected.id
  value         = "North America"
}

output form_field_id {
  value = rootly_form_field.regions_affected.id
}
  1. Delete the form_field via API
curl --request DELETE \
  --url 'https://api.rootly.com/v1/form_fields/<form-field-id>' \
  --header "Authorization: Bearer $ROOTLY_TOKEN"
  1. Run terraform apply -refresh-only -auto-approve -input=false -lock=false -json

Current Behavior

Fails with Error reading form_field: <form-field-id>

Expected Behavior

Refresh removes the form_field that was deleted so that it will be recreated.

Cause

I believe I've identified a problem with error handling that's causing this issue.

  • The generated client code is catching RequestErrors returned by the API, and returning a new error. i.e. currently:

    if err != nil {
      return nil, errors.Errorf("Error building request: %s", err.Error())
    }
  • In the generated resources e.g. provider/resource_form_field.go

    	item, err := c.GetFormField(d.Id())
      if err != nil {
      	// In the case of a NotFoundError, it means the resource may have been removed upstream
      	// We just remove it from the state.
      	if _, ok := err.(client.NotFoundError); ok && !d.IsNewResource() {
      		tflog.Warn(ctx, fmt.Sprintf("FormField (%s) not found, removing from state", d.Id()))
      		d.SetId("")
      		return nil
      	}
    
      	return diag.Errorf("Error reading form_field: %s", d.Id())
      }

Since a new error is being returned in the client code, this type assertion will always fail: err.(client.NotFoundError)

ermul added a commit to ermul/terraform-provider-rootly that referenced this issue Feb 26, 2025
  Motivation
Primarily meant to resolve rootlyhq#73 but the underlying bug may be causing
other unexpected behavior as well.

  Problem
The generated client code catches errors and then creates and returns new errors
using errors.Errorf(...).
The generated resources and other consumers of the generated clients catch the
exceptions returned by the generated client methods and branch control flow
based on the result of conditional type assertions w/ `client.RequestError`
and `client.NotFoundError` which are API response types.
Since the errors returned by the generated clients are never
`client.RequestError` or `client.NotFoundError` the conditional type assertions
always fail which prevents the expected error handling code from executing.

In this case of rootlyhq#73, the logic to handle out-of-bounds resource deletion is
there, but is never being executed due to the type assertion failing when
we would want it to succeed.

  Solution
At a high level this changes updates the code to use wrapped errors so that
clients can add additional context to errors before they're returned without
preventing consumers from accessing details from the underlying error.

- Update generated client code to wrap errors with `fmt.Errorf` and `%w`
- Update consumers to use `errors.Is` which supports wrapped errors.
- Implemented `Is(error) bool` for `client.NotFoundError` to return true
  when the HTTP Status Code of a (potentially wrapped) `client.RequestError`
  is 404.

fixes rootlyhq#73
@ermul ermul linked a pull request Feb 26, 2025 that will close this issue
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

1 participant