fix(platform): add --device-type flag to platform-device-groups name lookup#210
Conversation
…me lookup When a COMPUTER and MOBILE group share the same name, `get --name` (and all other --name-based ops) previously silently returned the wrong group. - Override generated get/delete/patch/members/patch-members with handwritten versions that accept --device-type COMPUTER|MOBILE (case-insensitive) - apply auto-derives device type from input JSON's deviceType field - add-members / remove-members gain --device-type - ResolveIDByNameFiltered added to internal/platform for filtered name walks - ResolveIDByName now errors on ambiguous matches (same name twice per page) Fixes #209 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
ktn-jamf
left a comment
There was a problem hiding this comment.
Important
Verdict: Needs minor changes. The core fix is correct and addresses #209, CI passes, and tests cover the new ambiguity behavior. Four cleanup items worth addressing before merge — none block, but one ships a misleading error to consumers of a shared helper.
Risk: medium / single-module — bug fix for platform-device-groups name resolution. One file accounts for ~300 lines of new CLI code; the resolver helper change is the load-bearing piece.
Findings
firstMatch in internal/platform/resolve_generic.go returns:
```
ambiguous match: %d items named %q; use a positional ID or add --type to narrow
```
Two problems:
- The actual flag is `--device-type`, not `--type`. Anyone hitting this on a `platform-device-groups` command will hunt for `--type` and find nothing.
- `ResolveIDByName` is shared by `baselines`, `benchmark-reports`, `device-actions`, `device-reports`, and `device-groups` (8+ callers in generated commands). Most of those resources have no `--device-type` flag at all — so the hint is wrong everywhere except this PR's commands.
A generic resolver should not name caller-specific flags in its errors. Either drop the flag suggestion ("disambiguate by passing the positional ID") or thread an optional `hint string` parameter into `ResolveIDByNameFiltered` so each caller supplies its own narrowing flag.
Suggested fix: change the message to e.g. `ambiguous match: %d items named %q; pass the positional ID instead, or narrow the list before resolution`, or thread a hint string from the caller.
Acceptance: error message no longer references `--type`, and `platform-device-groups get --name "X"` produces guidance that matches its actual flags.
```go
// guards against unused-import errors
var (
_ = strings.Replace
_ = strconv.Itoa
)
```
- `strconv` isn't used anywhere else in the file. Remove the import and the dummy reference.
- `strings.ToUpper` (used in `normalizeDeviceTypeFlag`) already keeps the `strings` import alive, so `_ = strings.Replace` is redundant.
Reads like cruft from a refactor that removed callers without removing the imports. It will also confuse anyone grepping for `strings.Replace` usage in this package.
Suggested fix: delete the `var (...)` block and remove `"strconv"` from the import list.
Acceptance: file compiles with no `_ = ...` placeholders and `strconv` not imported.
The handwritten `apply` command uses `printScaffold(deviceGroupScaffold())`. The new `newPDGPatchCmd` and `newPDGPatchMembersCmd` instead hardcode:
```go
fmt.Println("{\n \"criteria\": [],\n \"description\": \"\",\n \"name\": \"\"\n}")
```
`printScaffold` exists in `pro_platform_helpers.go` precisely so scaffold output flows through `cliCtx.Output` and honours `--no-color`, `--out-file`, etc. `fmt.Println` skips all of that.
Suggested fix: define scaffold structs (or `map[string]any`) for patch and patch-members and route them through `printScaffold`.
Acceptance: `pdg patch --scaffold --out-file foo.json` writes to `foo.json`, and global output flags are respected.
💡 (4) (simplification) — Five new commands repeat the same ~15-line name-or-ID resolution and path-building block.
`newPDGGetCmd`, `newPDGDeleteCmd`, `newPDGPatchCmd`, `newPDGMembersCmd`, `newPDGPatchMembersCmd` each contain:
```go
dt, err := normalizeDeviceTypeFlag(deviceTypeFlag)
if err != nil { return err }
var resolvedID string
if nameFlag != "" {
id, err := pdgResolveID(...)
...
} else if len(args) == 1 {
resolvedID = args[0]
} else {
return fmt.Errorf("provide a positional ID or --name")
}
path := "/api/device-groups/v1/tenant/" + url.PathEscape(...) + "/device-groups/" + url.PathEscape(resolvedID)
```
~75 lines of duplicate logic. A `resolvePDGTarget(ctx, cliCtx, args, nameFlag, deviceTypeFlag)` helper plus a `pdgItemPath(c, id)` helper would collapse each command body to its API-method-specific lines. Not blocking — worth doing while the file is small enough to refactor cheaply.
Suggested fix: extract `resolvePDGTarget` and `pdgItemPath` helpers; each command becomes `id, err := resolvePDGTarget(...)` + the unique HTTP call.
Acceptance: no per-command repetition of the name/positional/path-build pattern.
This covers all findings — addressing the above gets this PR to merge-ready.
- Fix ambiguous-match error to not reference --type (shared helper used by resources without that flag); now says "pass the positional ID" - Remove strconv import and dead _ = ... import guards left from refactor - Route patch/patch-members --scaffold through printScaffold so --out-file and -o flags are honoured - Extract resolvePDGTarget + pdgItemPath helpers; collapse repeated ~15-line name/positional/path-build block in all 5 commands Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
All four findings addressed in f0dd80a:
|
Summary
--device-type COMPUTER|MOBILEflag (case-insensitive) to all--name-basedplatform-device-groupsoperations:get,delete,patch,members,patch-members,add-members,remove-membersapplyauto-derives device type from the input JSON'sdeviceTypefield — no extra flag neededResolveIDByNameFilteredadded tointernal/platformfor narrowed name walks with an RSQL filter expressionResolveIDByNamenow errors on ambiguous matches (two items with the same name on the same page) instead of silently returning the first resultgo fixmodernisations: range-over-int,strings.SplitSeq, scope JSON tagFixes #209
Test plan
go test ./...passesget --name "X" --device-type mobilereturns the MOBILE group (live,platform-nmartin)get --name "X" --device-type COMPUTER— not-found when no COMPUTER group by that name (live,platform-nmartin)get --name "X" --device-type computer(lowercase) normalises to COMPUTER and works (live)--device-type invalidreturns a clear validation error (live)get --name "X"when COMPUTER and MOBILE group share name → ambiguous error (unit-tested only; no duplicate-name groups in test tenant)applywith"deviceType": "COMPUTER"in input JSON resolves against COMPUTER groups only🤖 Generated with Claude Code