Skip to content

fix(sdk/go): honor explicitly-set zero option values in oidc defaults - #3235

Open
rhuss wants to merge 1 commit into
NVIDIA:mainfrom
rhuss:fix-go-sdk-option-default-sentinels
Open

fix(sdk/go): honor explicitly-set zero option values in oidc defaults#3235
rhuss wants to merge 1 commit into
NVIDIA:mainfrom
rhuss:fix-go-sdk-option-default-sentinels

Conversation

@rhuss

@rhuss rhuss commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Summary

oidc.loginConfig.applyDefaults() decided whether to apply a default by inspecting the field's value (len(c.scopes) == 0, c.timeout == 0) rather than whether the caller had set it. As a result, a caller who explicitly passed a zero value had it silently replaced:

  • WithTimeout(0) became the 2-minute default.
  • WithScopes() (explicit empty) became the default scopes, even though WithScopes already records scopesSet.

This makes applyDefaults consult the *Set sentinels instead, so defaults fill only genuinely-unset fields. Unset behavior and non-zero explicit values are unchanged.

Related Issue

Follow-up to a review comment on #3232 (raised by @elezar): #3232 (comment). This is a small, localized pre-existing bug fix, so no separate issue is filed.

Changes

  • oidc/options.go: add a timeoutSet sentinel (set by WithTimeout); applyDefaults now checks !scopesSet / !timeoutSet instead of the zero value.
  • oidc/credentials_auth.go: guard the client-credentials token exchange so a zero timeout means "no deadline" rather than an already-expired context (matching the existing guards in Login and DeviceFlow).
  • Tests: explicit empty scopes and explicit zero timeout are honored; unset still defaults; a client-credentials exchange with WithTimeout(0) succeeds instead of failing on a born-expired context.

Behavior change: WithTimeout(0) now means "no timeout" instead of the 2-minute default.

Testing

  • mise run go:ci green (build, golangci-lint, gofmt, full go test, proto-check, docs-check).
  • New oidc unit tests plus an end-to-end client-credentials test covering the zero-timeout path.

Checklist

  • Conventional Commit message, DCO sign-off
  • Tests added and passing
  • No public option-type or entry-point signature changed
  • Behavior change (WithTimeout(0)) documented above

`loginConfig.applyDefaults()` keyed off the zero value rather than
set-ness, so it could not distinguish an unset field from one a caller
explicitly set to its zero value:

- `WithTimeout(0)` was silently replaced by the 2m default.
- `WithScopes()` (explicit empty) was replaced by the default scopes,
  even though `WithScopes` already records `scopesSet`.

Switch `applyDefaults` to consult the `*Set` sentinels, add a
`timeoutSet` sentinel set by `WithTimeout`, and guard the
client-credentials exchange so a zero timeout means "no deadline"
instead of creating an already-expired context (matching Login and
DeviceFlow).

`WithTimeout(0)` now means "no timeout". Unset fields still receive
their defaults; non-zero explicit values are unaffected.

Signed-off-by: Roland Huß <rhuss@redhat.com>
@copy-pr-bot

copy-pr-bot Bot commented Sep 9, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

Comment on lines +74 to +80
// A zero timeout means "no deadline"; only bound the exchange when a
// positive timeout was configured (mirrors Login and DeviceFlow).
exchangeCtx := context.Background()
cancel := context.CancelFunc(func() {})
if a.cfg.timeout > 0 {
exchangeCtx, cancel = context.WithTimeout(exchangeCtx, a.cfg.timeout)
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does context.WithTimeout() interpret a 0 value as no deadline, or does it explicitly need this handling?

@rhuss rhuss Sep 9, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

context.WithTimeout(parent, 0) doesn't mean "no deadline". It's defined as WithDeadline(parent, time.Now().Add(timeout)), so a 0 (or negative) timeout sets the deadline to now and the context is born already-expired; any call using it returns context.DeadlineExceeded immediately. So the explicit timeout > 0 guard is needed to actually get "no deadline" behavior, and it mirrors what Login and DeviceFlow already do. Without it, WithTimeout(0) would break the client-credentials exchange instead of disabling the timeout (covered by TestClientCredentialsAuthZeroTimeoutHasNoDeadline).

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.

2 participants