Skip to content

fix(sfs): guard against nil responses from the API - #1741

Open
devpie wants to merge 1 commit into
stackitcloud:mainfrom
devpie:fix/sfs-nil-response
Open

fix(sfs): guard against nil responses from the API#1741
devpie wants to merge 1 commit into
stackitcloud:mainfrom
devpie:fix/sfs-nil-response

Conversation

@devpie

@devpie devpie commented Sep 4, 2026

Copy link
Copy Markdown

Description

relates to #1743

The SFS resources and data sources crash the provider process when the API answers with a body the SDK decodes to
nil. Found while writing tests for #1740; filed separately because it is an independent bug and touches five files.

How a response becomes nil

Two routes, both without any cooperation from the SFS API:

  1. A 2xx with an empty body. The generated decoder starts with if len(b) == 0 { return nil }, so
    localVarReturnValue stays nil and Execute() returns (nil, nil).
  2. A single retryable gateway error during a wait. WaiterHelper.Wait returns true, nil, err for any fetch
    error; handleError sees a 502 or 504 in RetryHttpErrorStatusCodes, increments the counter and returns a nil
    error; the loop then hits if done { return res, nil } and hands back the nil response. Because done is already
    true the retry never happens. This applies to the create and update wait handlers, not only to delete.
    Reported upstream as core/wait: waiter returns (nil, nil) on a retryable 502/504 and never retries stackit-sdk-go#11084 with a runnable reproduction; this PR guards the call
    sites so the provider reports a diagnostic instead of crashing while that is open.

What was wrong

The call sites dereferenced those responses unchecked. Several of the checks meant to validate a response were
themselves the dereference — they test x.Field == nil without first testing x == nil:

if response.ResourcePool == nil || response.ResourcePool.Id == nil {   // panics when response is nil

And in both create paths the wait handler result is read by tflog.SetField one line before its guard.

Reproduced for resourcepool Read with terraform-plugin-testing: refreshing against a 200 with an empty body panics
with invalid memory address or nil pointer dereference. Both new tests panic without this change and pass with it.

Scope

Fixed: resourcepool/resource.go, resourcepool/datasource.go, share/resource.go, share/datasource.go,
snapshots/datasource.go.

Left alone on purpose: export-policy already guards its responses (resource.go:293, and mapFields nil-checks the
whole response for Read and Update), snapshot-policy nil-checks in mapFields, project-lock uses nil-receiver-safe
getters, and every SFS Delete discards the response with _, err :=, so the delete waiter's legitimate (nil, nil)
on a 404 is harmless. No SFS ImportState calls the API.

The guards follow the shape already used in the package — one combined condition covering the response and the fields
actually used, then core.LogAndAddError and return. Two error titles in the resource pool and share update paths
said "creating"; they are corrected while their lines are touched.

The two regression tests cover the two shapes (a plain response guard, and a guard that was itself the dereference).
The data source guards are mechanically identical to the tested resource guards and are not separately covered.

Checklist

  • Issue was linked above
  • Code format was applied: make fmt
  • Examples were added / adjusted (see examples/ directory) — not applicable, no configuration surface changes
  • Docs are up-to-date: make generate-docs (will be checked by CI) — no schema change, so no doc change
  • Unit tests got implemented or updated
  • Acceptance tests got implemented or updated — an empty-body or gateway-error response cannot be provoked against the real API; the unit tests drive it through a mock server
  • Unit tests are passing: make test (will be checked by CI)
  • No linter issues: make lint (will be checked by CI)

🤖 Generated with Claude Code

The generated SDK decoder returns early for an empty body, so Execute() hands
back (nil, nil) for a 2xx response that carries no body, and a wait handler
returns (nil, nil) when a single poll hits a retryable 502 or 504:
WaiterHelper reports (true, nil, err), handleError swallows the retryable
status and returns a nil error, and the loop then returns the nil response
because it is already done.

The SFS resources and data sources dereferenced those responses unchecked and
crashed the provider process. Several of the checks meant to validate a
response were themselves the dereference - they tested `x.Field == nil` without
first testing `x == nil` - and in create the wait handler result was read by
tflog.SetField one line before its guard.

Reproduced for resourcepool Read with terraform-plugin-testing: a refresh
against a 200 with an empty body panics with "invalid memory address or nil
pointer dereference".

The guards follow the shape already used in the package, one combined condition
covering the response and the fields actually used. Two error titles in the
resource pool and share update paths said "creating" and are corrected while
their lines are touched.

Left unchanged: export-policy and snapshot-policy already guard their
responses, project-lock uses nil-receiver-safe getters, and every Delete
discards the response, so the delete waiter's legitimate nil return on a 404
is harmless.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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 this pull request may close these issues.

1 participant