Skip to content

fix: prepare for gophercloud 2.15.0 — json.Number rendering and clouds.yaml domains - #30

Merged
ftarasenko merged 2 commits into
masterfrom
claude/elegant-ptolemy-4lmsb2
Sep 22, 2026
Merged

ftarasenko merged 2 commits into
masterfrom
claude/elegant-ptolemy-4lmsb2

Conversation

@ftarasenko

@ftarasenko ftarasenko commented Sep 22, 2026

Copy link
Copy Markdown
Owner

Prerequisite for #28. That bump (gophercloud 2.14.0 → 2.15.0) fails go test ./... on master today, and carries a second, silent auth regression that no test covers. Both fixes are inert against the vendored 2.14.0, so they land ahead of the bump and keep master green through the sequence.

1. json.Number rendering and sorting (9eb36b6)

2.15.0 added UseNumber() to three decode paths (provider_client.go, results.go, pagination/http.go) so integers too large for float64 survive a round-trip. The side effect: every number koc reads out of a map[string]any is now a json.Number, not a float64. Three type switches only knew the float64 spelling:

Site Under 2.15.0, before this fix
powerStateLabel falls to defaultserver show prints 1 instead of Running
numericValue returns not-a-number → a number column sorts as text, putting 10 before 9
scalarString falls to json.Marshal, right only by accident

json.Number is a named string type, so the existing case string: does not catch it either — the miss is silent in all three. Caught by TestRunServerShow_TableHumanized; the sorting and scalar paths had no coverage for it at all.

2. clouds.yaml domain coalescing (b27bdce)

2.15.0 also rewrote config/clouds scope resolution, which koc uses for --os-cloud. Keystone qualifies two different things by domain — the user being authenticated and the project being scoped to. 2.14.0 folded them together: DomainName fell back through user_domain_nameproject_domain_namedomain_name, and Scope was left nil for gophercloud to derive. 2.15.0 splits them and returns an explicit Scope.

A clouds.yaml naming only one of the pair loses the other half. Driven against the real 2.15.0 clouds.Parse with a file carrying project_domain_name and no user_domain_name:

RAW from clouds.Parse: user domain=""/""        scope={ProjectName:"demo", DomainName:"Default"}
AFTER reconcile:       user domain=""/"Default" scope={ProjectName:"demo", DomainName:"Default"}

An empty user domain makes Keystone reject the password grant as ambiguous. The mirror case — only user_domain_name — leaves the project-by-name scope unqualified. Single-domain clouds write one of the two and mean it for both, so reconcileCloudDomains lets each half fall back to the other: what 2.14.0 did, and what upstream OSC does.

This was reachable only through clouds.yaml: koc's applyDomainScope runs afterwards and overwrites both halves, but is entered only when a domain flag or OS_*_DOMAIN_* variable is actually set. System- and trust-scoped tokens are not domain-qualified and are left exactly as gophercloud built them.

Tests

Written against the shapes clouds.Parse and the SDK decoder return, not against the SDK itself, so they hold whichever version is vendored:

  • TestPowerStateLabel_NumberKindsjson.Number, float64, int all humanize; unmapped code and non-integer pass through.
  • TestScalarString_JSONNumber — renders the literal sent, agrees with the float64 path.
  • TestCompareCells_NumericAndString — five json.Number cases, mixed against float64 and int.
  • TestRunServerShow_PowerStateFromDecodedBody — end-to-end guard on a real decoded body, so a future bump that changes the number kind again fails here rather than in production output.
  • TestReconcileCloudDomains (+ _IDs, _SystemAndTrustUntouched, _FlagsStillWin) — both one-sided clouds.yaml shapes, both-named, project-by-ID, the 2.14.0 nil-scope case, and that an explicit koc flag still wins.

Verification

Full suite green both ways: on this branch with 2.14.0 vendored, and on a local merge of #28 with 2.15.0 vendored — where together they are what turns the suite from failing to green. gofmt, go vet ./... and the offline static build are clean.

🤖 Generated with Claude Code

https://claude.ai/code/session_01EkWzWSEyrjJ1gd1v5zJNtE

gophercloud v2.15.0 decodes JSON numbers into `any` with UseNumber(), so
every number koc reads out of a map[string]any is a json.Number rather
than a float64. Three type switches only knew the float64 spelling:

- powerStateLabel fell through to its default and printed nova's raw
  OS-EXT-STS:power_state, so `server show` showed "1" where it used to
  show "Running";
- numericValue returned not-a-number, so a column carrying one sorted as
  text and put "10" before "9" (json.Number is a named string type, so
  the existing string case does not match it either);
- scalarString fell through to json.Marshal, which happens to be right
  for a well-formed number but relies on the default branch by accident.

Teach all three the json.Number spelling. The change is inert against the
currently vendored v2.14.0 -- nothing produces a json.Number there -- and
is what keeps `server show` correct once the bump lands, so it goes in
ahead of it rather than with it.

Tests pin the handling directly rather than through the SDK, so they hold
whichever decoder the vendored version uses, plus one end-to-end guard
that decodes a real response body and fails if a future bump changes the
number kind again.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EkWzWSEyrjJ1gd1v5zJNtE
Keystone qualifies two different things by domain: the user being
authenticated, and the project being scoped to. gophercloud v2.14.0 and
earlier folded them together in clouds.Parse -- AuthOptions.DomainName
fell back through user_domain_name, project_domain_name, domain_name, and
Scope was left nil for gophercloud to derive from TenantName and that one
domain. v2.15.0 splits them: DomainName no longer falls back to
project_domain_name, and Parse returns an explicit Scope carrying the
project's domain.

A clouds.yaml that names only one of the pair loses the other half. Driven
against the real v2.15.0 clouds.Parse, a file with project_domain_name and
no user_domain_name yields an empty user domain, which Keystone rejects as
an ambiguous password grant; the mirror case leaves the project-by-name
scope unqualified. Single-domain clouds write one of the two and mean it
for both, so each half now falls back to the other -- what v2.14.0 did and
what upstream OSC does.

koc's own flags are unaffected either way: applyDomainScope runs after this
and overwrites both halves, and it is only reached when a domain flag or
OS_*_DOMAIN_* variable is actually set, which is why the clouds.yaml path
was exposed at all. A system- or trust-scoped token is not domain-qualified
and is left exactly as gophercloud built it.

Inert against the vendored v2.14.0, which returns a nil Scope here, so it
lands ahead of the bump rather than with it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EkWzWSEyrjJ1gd1v5zJNtE
@ftarasenko ftarasenko changed the title fix(output): render and sort json.Number the way float64 was rendered fix: prepare for gophercloud 2.15.0 — json.Number rendering and clouds.yaml domains Sep 22, 2026
@ftarasenko
ftarasenko merged commit a5481cb into master Sep 22, 2026
27 checks passed
ftarasenko added a commit that referenced this pull request Sep 22, 2026
…o 2.15.0 (#28)

2.15.0 decodes JSON numbers into any with UseNumber() and splits the user
domain from the project domain in clouds.Parse. Both changes are handled
by #30, which landed first; this bump is green against it.
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