Skip to content

fix(presets): support PUT in Invoke-PfbApiRequest and fold Set-PfbPresetWorkload onto it - #78

Merged
juemerson-at-purestorage merged 2 commits into
dmann000:mainfrom
juemerson-at-purestorage:fix/invoke-put-support-preset-workload
Aug 2, 2026
Merged

fix(presets): support PUT in Invoke-PfbApiRequest and fold Set-PfbPresetWorkload onto it#78
juemerson-at-purestorage merged 2 commits into
dmann000:mainfrom
juemerson-at-purestorage:fix/invoke-put-support-preset-workload

Conversation

@juemerson-at-purestorage

Copy link
Copy Markdown
Collaborator

Closes #76.

Set-PfbPresetWorkload hand-rolled its own Invoke-RestMethod call, with a comment
explaining that the shared request path could not express PUT. That was true when the
cmdlet was written. It is no longer true after the first commit here, so the bypass goes
away.

What changed

Invoke-PfbApiRequest accepts PUT (cedf31a). PUT joins the ValidateSet, and the
body-serialisation guard stays an explicit whitelist (POST/PATCH/PUT) rather than
becoming a "not GET" test — a future verb has to opt into carrying a body deliberately.

Set-PfbPresetWorkload routes through it (b56165c). Twelve lines of hand-rolled
request construction become one call. Assert-PfbConnection and the ShouldProcess gate
are unchanged.

Three consequences, none of them silent:

  • Capability gating now runs. This cmdlet and Set-PfbWorkloadTag were the only two
    write cmdlets in the module exempt from it. A call against an array below REST 2.23 now
    throws locally instead of going out on the wire to be rejected.
  • Errors are normalised through ConvertTo-PfbApiError, matching every other write
    cmdlet, instead of surfacing a raw Invoke-RestMethod exception.
  • Certificate and OAuth2 sessions work. The hand-rolled block set x-auth-token
    unconditionally and never consulted $Array.BearerToken, so the cmdlet was broken for
    bearer-authenticated connections. The comment it carried did not mention this; it is
    fixed here as a side effect of the fold.

Body serialisation moves to the shared path's -Depth 10, down from the local -Depth 15.
The resolved PUT /presets/workload request schema nests 8 levels, so nothing truncates.

Please read this part before merging

This cmdlet does not work today, and it does not work after this PR either. That is not
a regression introduced here — it has never worked. I found this while live-testing, and it
is worth recording because the issue as filed implies the fold makes the cmdlet correct.

Every preset write, and every name-scoped preset read, requires context_names=<fleet>.
No cmdlet in Public/Presets/ has ever sent it. Tested against FB-A (Purity//FB 4.8.2,
REST 2.26) as a dynamic-authorization-model admin, against a preset that genuinely existed:

Cmdlet Result
Get-PfbPresetWorkload (unfiltered) works
Get-PfbPresetWorkload -Name Preset does not exist.
Set-PfbPresetWorkload -Name Preset does not exist.
New-PfbPresetWorkload Creating a preset in the array context is not supported.
Remove-PfbPresetWorkload -Name Preset does not exist.
Update-PfbPresetWorkload untested; expected to fail the same way

Five of six preset operations are non-functional. What this PR changes is that
Set-PfbPresetWorkload is now fixable in one place: it was the last preset cmdlet routing
around the shared request path, so whatever adds context_names centrally will reach it
without a special case. That is the value here — the rewiring, not a working cmdlet.

One finding for the context work, from the same testing: /presets/workload rejects a
member array as context (context_names=<member>code 13 Invalid context) and accepts
only the fleet name, on every verb. The shared Context_names component description
("an array in the same fleet or the name of the fleet itself") is wrong for this endpoint.

Verification

  • Tests/Set-PfbPresetWorkload.Tests.ps1 is new — the cmdlet had no tests at all. Beyond
    verb/endpoint/body/query coverage it asserts Invoke-RestMethod -Times 0, which is the
    regression that would quietly undo this change.
  • Tests/Invoke-PfbApiRequest.Tests.ps1 gains PUT coverage: body serialisation, PUT with no
    body, GET/DELETE still sending no body, and an out-of-set verb failing parameter binding.
  • Scoped run, both editions: 18 passed / 0 failed, containers healthy, under pwsh 7 and
    Windows PowerShell 5.1 (Pester 6.0.1, CI's pin).
  • Live-tested on FB-A: full create → PUT (revision 1 → 2) → delete round trip via fleet
    context, plus the matrix above. Lab left clean.

No version or CHANGELOG changes.

🤖 Generated with Claude Code

Two PUT endpoints exist in the FlashBlade REST API (PUT /presets/workload and
PUT /workloads/tags/batch). Neither could be expressed through the shared
request path, so both cmdlets hand-rolled their own Invoke-RestMethod call and
in doing so skipped capability gating, error normalisation, and Bearer-token
auth. This adds the verb; folding the cmdlets back on follows separately.

Both edits are additive and no existing caller changes behaviour:

  - 'PUT' joins the -Method ValidateSet.
  - The body-serialisation guard gains PUT. It stays a whitelist of verbs that
    may carry a body rather than becoming a "not GET" test, so a future verb
    has to opt in deliberately instead of inheriting serialisation.

Nothing else in the function branches on $Method -- the only other site is the
pass-through into $restParams, and pagination keys off -AutoPaginate plus
continuation_token, never the verb. Assert-PfbApiCapability takes -Method as an
unconstrained [string] and Data/PfbCapabilityMap.json already carries both PUT
keys, so neither needed a change.

Tests cover the verb reaching Invoke-RestMethod with a serialised body, a PUT
without a body sending no Body key, and -- as a guard against widening the body
guard too far -- GET and DELETE still dropping a body when one is supplied.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The cmdlet hand-rolled its own Invoke-RestMethod call because the shared path
could not express PUT. That is no longer true, so the bypass goes away and the
cmdlet picks up everything Invoke-PfbApiRequest layers on.

Three user-visible consequences, none of them silent:

  - Capability gating now runs. Set-PfbPresetWorkload and Set-PfbWorkloadTag
    were the only two write cmdlets in the module exempt from it. A call
    against an array below REST 2.23 now throws locally instead of being sent
    and rejected on the wire.
  - Errors now come from ConvertTo-PfbApiError rather than surfacing a raw
    Invoke-RestMethod exception, matching every other write cmdlet.
  - OAuth2/Certificate sessions work. The hand-rolled block set x-auth-token
    unconditionally and never consulted $Array.BearerToken, so the cmdlet was
    broken for certificate-authenticated connections. This was not mentioned in
    the comment it carried and is fixed here as a side effect.

Body serialisation moves to the shared path's -Depth 10, down from the local
-Depth 15. The resolved PUT /presets/workload request schema nests 8 levels, so
nothing truncates.

Assert-PfbConnection and the ShouldProcess gate are unchanged; ShouldProcess
still wraps the call rather than the parameter construction.

Adds Tests/Set-PfbPresetWorkload.Tests.ps1 -- the cmdlet had no tests. Beyond
verb/endpoint/body/query coverage it asserts that no direct Invoke-RestMethod
call escapes the cmdlet, which is the regression that would undo this change.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@juemerson-at-purestorage
juemerson-at-purestorage merged commit f130f6a into dmann000:main Aug 2, 2026
4 checks passed
juemerson-at-purestorage added a commit that referenced this pull request Aug 3, 2026
…m order (#86)

* fix(tools): emit report records in canonical order, not filesystem order

Get-PfbCmdletParameterInventory and Get-PfbModuleCalledEndpoints both walked
Public//Private/ with an unsorted recursive Get-ChildItem and let that walk
become the emit order of their records. Regenerating Reports/ on a Linux CI
runner instead of a Windows workstation therefore produced a 10,218-line diff
across PfbFieldCmdletMap.json/.md and PfbApiDriftReport.json/.md with zero
semantic change -- all 2015 entries moved, not one changed content, and the two
files were even identical in byte length. That is what makes the
update-api-capability-map auto-PR unreviewable.

Sorted at emit rather than only on the file list: FullName carries
platform-specific separators, so sorting the walk alone is the fragile fix. The
walk is sorted too, but only as belt-and-braces for intermediate debugging
output. Every sort pins -Culture '' (invariant) so the runner locale cannot
reintroduce the divergence.

Also replaces Select-Object -Unique with Sort-Object -Unique at the intra-row
cmdlet list in Get-PfbParameterCoverageGaps (-Unique preserves input order and
does not sort -- the observed `Get-PfbArray, Test-PfbConnection` flip), and
sorts the Group-Object groups themselves, whose order is first-appearance in
the input and so was also file-walk-derived.

The regression tests deliberately do not regenerate twice on one machine --
that is the assertion Tests/Build-PfbApiDriftReport.Tests.ps1 already makes, and
enumeration order is stable within a filesystem, so it can never fail. Instead
two fixture trees hold the same cmdlets with the cmdlet-to-filename mapping
swapped, which reproduces the divergence on any single platform and needs no
tools/specs (so it will not silently skip in a fresh clone -- see #63).

Refs #85

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* chore(reports): regenerate under the canonical emit order

Do NOT review this line by line. Two of the four files are a pure re-sort; the
review that matters is the canonical comparison, reproduced below.

Reports/PfbFieldCmdletMap.json and .md -- pure re-sort, zero content change:

  order-insensitive-equal vs committed: true
  entries         2015 -> 2015   multiset-equal   2015/2015 repositioned
  attributesOnly    68 -> 68     multiset-equal     65/68  repositioned
  typedUnresolved   39 -> 39     multiset-equal     35/39  repositioned
  markdown: identical line multiset, 161 lines both sides

Generating the same artifact with HEAD~1's tools/lib and with this branch, from
identical inputs, gives byte-length-identical files (586,461 both) that are
order-insensitive-equal -- so the sort provably reorders and never rewrites.
Reports/PfbApiDriftReport.json came out raw-identical between those two runs:
on NTFS the unsorted walk already happened to yield the canonical order, which
is precisely why this defect was invisible from a Windows workstation.

Reports/PfbApiDriftReport.json and .md -- a real, expected content refresh,
NOT part of the re-sort. The committed copies predate PRs #78/#81:

  uncoveredEndpoints  98 -> 96   PUT /presets/workload, PUT /workloads/tags/batch
  parameterGaps      436 -> 438  the same two, now as gap rows
  systemicGaps       252 -> 252  context_names 269 -> 270; 13 body-property
                                 names +1 each, all from Set-PfbPresetWorkload

Those PRs routed Set-PfbPresetWorkload/Set-PfbWorkloadTag through
Invoke-PfbApiRequest, which is the only thing the AST resolver can see -- so the
endpoints leave the uncovered list and Set-PfbPresetWorkload immediately
reappears carrying 14 missing body properties. "Uncovered -> covered" here means
visible to the scanner, not finished; see #85 and the #45/#44 caveat.

Per #85, the two artifacts that must NOT move were regenerated and hashed to
confirm they did not: Data/PfbCapabilityMap.json (632 endpoints, 29 versions,
-MaxVersion 2.28) and Reports/PfbValueEnumMap.json are both byte-identical to
their committed copies.

Tests/Build-PfbApiDriftReport.Tests.ps1's "no serialization-only divergence"
invariant was already failing on main with 19 differences, unchanged by the
sort, and passes again now the report is current.

Refs #85

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
@juemerson-at-purestorage
juemerson-at-purestorage deleted the fix/invoke-put-support-preset-workload branch August 9, 2026 18:55
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.

Set-PfbPresetWorkload: add PUT to Invoke-PfbApiRequest and fold the cmdlet onto the shared path

1 participant