Skip to content

feat(smartgroup): pro smart-group templates — 23 curated smart groups across 5 categories#205

Open
ktn-jamf wants to merge 25 commits into
mainfrom
claude/vigorous-davinci-88fbc0
Open

feat(smartgroup): pro smart-group templates — 23 curated smart groups across 5 categories#205
ktn-jamf wants to merge 25 commits into
mainfrom
claude/vigorous-davinci-88fbc0

Conversation

@ktn-jamf
Copy link
Copy Markdown
Collaborator

@ktn-jamf ktn-jamf commented May 13, 2026

Summary

Adds a new pro smart-group namespace (alias pro sg) with 4 subcommands and a curated library of 23 smart-group templates across 5 operational categories: encryption, software updates, MDM health, compliance basics, and lifecycle hygiene.

Admins go from "I need a smart group for [common operational case]" to a working smart group in one command. Templates encode operational knowledge sourced and verified byte-for-byte against the JSS server source for criterion-name strings.

New commands

Command Purpose
pro sg templates [--category X] List the 23 templates (table or JSON)
pro sg preview --template <slug> [params] Show the JSON body that would POST — no API call
pro sg apply --template <slug> --name <NAME> [params] [--recalculate] [--dry-run] [--yes] Idempotent create-or-update by name with post-apply membership check
pro sg verify-templates [--category X] [--no-cleanup] Smoke-test every template against the live tenant; report OK / zero-match / error per template

Template library (23 templates, 5 categories)

encryption (6): not-encrypted, invalid-recovery-key, escrow-missing, irk-only-deprecated, encryption-stalled, fv-ineligible.

updates (4): os-version-below, major-version-behind, rsr-not-applied, beta-os.

mdm (5): bootstrap-token-missing, user-approved-mdm-no, stale-checkin, mdm-cert-expiring, declarative-management-disabled.

compliance (4): gatekeeper-disabled, sip-disabled, firewall-disabled, non-compliant-baseline.

lifecycle (4): unsupervised, ade-enrolled, jamf-binary-outdated, fv-ineligible-hardware.

Architecture

  • New internal/smartgroup/ package — Template + ParamSpec types, criterion-name constants (JSS-cited), library registry with Register/Lookup/All/ByCategory/FuzzyMatch, membership-count helper, verify-runner. Tests in isolation: ~42 tests covering type validation, criterion uniqueness, registry behavior, per-template golden JSON, library integration (23 subtests).
  • New internal/commands/pro_smartgroup.go — cobra namespace + 4 subcommands. Tests cover all flags + happy/error paths with mocked HTTP.
  • Wire-up: pro.go (+1 line), groups.go (+1 entry under Computer Management), aliases.go (+1 entry pro sg).
  • No generator changes. No spec changes. Reuses existing cliCtx.Client.Do HTTP path and existing /v2/computer-groups/smart-groups endpoints.

Design docs

  • docs/solutions/design-patterns/pro-smart-group-templates-spec-2026-05-12.md — full design with criterion-name registry citations
  • docs/solutions/design-patterns/pro-smart-group-templates-plan-2026-05-12.md — 17-task TDD implementation plan

Live test results (ktn tenant)

Round 1 caught 4 string bugs. Round 2 caught 2 more. All 6 fixed (separate fix commits with JSS source citations in commit messages):

  • encryption/not-encrypted value: "Not Encrypted""No Partitions Encrypted" (per ComputerFileVault2Status enum)
  • CriterionJamfBinaryVersion: "Jamf Binary Version""JAMF Binary Version" (per JamfBinaryVersionMatcher.java @Component annotation)
  • compliance/gatekeeper-disabled value: "Disabled""Off" (per ComputerGatekeeperStatus)
  • mdm/mdm-cert-expiring searchType: "less than x days from now""in less than x days" (per SearchCriteriaOperator.IN_LESS_THAN_DAYS)
  • lifecycle/jamf-binary-outdated: parameterized "less than" operator was invalid → zero-param using "not current" operator (operationally better — no version-threshold maintenance burden)
  • lifecycle/ade-enrolled: wrong criterion (PreStage profile name) → correct Boolean criterion "Enrolled via Automated Device Enrollment"

Final live-test result on ktn: 0 errors, 18 OK, 5 zero-match warnings — and the 5 remaining warnings are characteristic of a healthy fleet (Gatekeeper enabled, SIP enabled, no legacy IRK, no MDM certs expiring soon, no beta OS).

Test plan

  • Unit tests: ~42 pass in internal/smartgroup/ (types, criteria, library, 23 template goldens, library integration, membership, verify)
  • Unit tests: pass in internal/commands/ (templates / preview / apply / verify-templates with mocked HTTP)
  • go test ./... — all 29 packages green
  • make lint — 0 issues
  • make build — clean
  • Live test: bin/jamf-cli pro sg verify-templates against ktn tenant — 0 errors, 18 OK
  • Reviewer: spot-check criterion-name citations against a recent JSS sync
  • Reviewer: try a no-op apply --dry-run of a few templates
  • Reviewer: run pro sg verify-templates --category encryption against their own tenant

🤖 Generated with Claude Code

ktn-jamf and others added 22 commits May 12, 2026 20:43
…ub subcommands

Wires the new `pro smart-group` (alias `sg`) namespace into the CLI under
the Computer Management help group. Four stub subcommands (templates, preview,
apply, verify-templates) compile and resolve correctly; full implementations
follow in Tasks 12-15.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…gory, table/json output)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…aram flags

Replaces the preview stub with a real RunE that looks up the template,
collects param flags (union of all template params registered via
registerTemplateParamFlags), resolves defaults/required checks via
ResolveOpts, and prints the POST header + indented JSON body without making
an API call. Also introduces collectParamValues, unknownTemplateError (with
fuzzy-match suggestions), and the flagReader interface — reused by apply
(Task 14).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…based create/update and post-apply membership check
…tenant smoke test

Adds RunOneVerification to internal/smartgroup (create temp group, recalc,
count members, optional cleanup) and wires it into the verify-templates
subcommand under pro smart-group with --category, --no-cleanup, and --json flags.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Wrap fmt.Fprintf/Fprintln calls to io.Writer with _, _ = per project convention
- Convert untagged switch to tagged switch existingID per staticcheck QF1002
- Wrap resp.Body.Close() in defer func closures to satisfy errcheck
- Apply gofumpt formatting across touched and pre-existing files

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Captures the design rationale and step-by-step implementation plan for
the pro smart-group namespace. Spec documents the 23-template inventory,
verified criterion-name registry sourced from JSS, and the verify-
templates safety net. Plan documents the 17-task TDD breakdown.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@neilmartin83
Copy link
Copy Markdown
Member

Review — pro smart-group templates

Tested live against pro-nmartin and platform-nmartin. Core library, criterion-name registry, and API integration are solid. Three issues need fixing before merge; one more worth fixing.


Must Fix

1. --dry-run flag collision breaks global -n shorthand

newSmartGroupApplyCmd defines cmd.Flags().BoolVar(&dryRun, "dry-run", ...) locally. Root already defines cmd.PersistentFlags().BoolVarP(&dryRun, "dry-run", "n", ...) as a persistent flag. Two definitions of --dry-run in the same cobra flag set cause the global -n shorthand to fail:

$ bin/jamf-cli -p pro-nmartin -n pro smart-group apply --template encryption/not-encrypted --name x
unknown shorthand flag: 'n' in -n

Local --dry-run works fine; global -n doesn't. Fix: remove the local --dry-run from apply and route through the global flag (read cmd.Root().PersistentFlags().GetBool("dry-run") or pass via cliCtx), or rename the local flag to --preview to avoid the clash.

2. verify-templates --json is inconsistent with the rest of the CLI

Every other command uses -o json / --output. verify-templates has its own --json bool flag. Change to -o/--output string with table|json, matching the templates subcommand.

3. Invalid --category silently exits 0

$ bin/jamf-cli pro smart-group verify-templates --category bogus
Verifying 0 templates...
Summary: 0 OK, 0 zero-match warnings, 0 errors.

Exit 0. Same for templates --category bogus. Both commands should validate against smartgroup.Categories() and return an error on unknown input.


Also Fix Before Merge

VerifyResult JSON output has no struct tags — serialises ugly

{ "Slug": "...", "Outcome": 0, "MemberCount": 2, "Error": "" }
  • Fields are PascalCase (should be camelCase with json: tags)
  • Outcome serialises as int 0/1/2 rather than "OK"/"ZERO_MATCH"/"ERROR" — add MarshalText on VerifyOutcome or convert to string before encode

Minor / Nits

Leftover lint-suppression vars in pro_smartgroup_test.go:85–89context and http are already used directly in the tests now; delete the block.

TestTemplates_JSONOutput hardcodes 23 — will fail when a template is added without updating the test. Use len(smartgroup.All()) instead.

defaultVerifyOpts in verify.go hardcodes major-below: 15 — add a comment pointing to where to bump when macOS 16 ships.

encryption/escrow-missing uses Value: "" — empty string for is searchType is unusual. Confirmed it matched 4 devices live, but add a comment explaining JSS interprets empty string as "no recovery key type set."


Live Test Results

Profile Category Result
pro-nmartin encryption 4 OK, 2 zero-match (irk-only-deprecated, not-encrypted — expected on healthy fleet)
pro-nmartin mdm 5/5 OK
platform-nmartin mdm 5/5 OK (Pro API routed through gateway)
platform-nmartin updates 3 OK, 1 zero-match (beta-os — expected)
Create + update + delete cycle All working
Idempotency guard (missing --yes) Correct error returned

Criterion strings are clean. The two zero-match encryption results match the author's own test report for a healthy fleet.


Verdict: Fix the four items above (three must-fix + JSON tags), then this is good to merge.

…rs, docs placement

Five fixes from the PR #205 principal-engineer review:

(1) Reject unknown template param flags. `registerTemplateParamFlags`
    declares the union of every template's params on every preview/apply
    command, so `apply --template encryption/not-encrypted --stalled-after 14`
    was silently dropping the bogus flag. Now `collectParamValues` walks the
    command's full flag set and errors on any Changed param-style flag that
    isn't in the chosen template's ParamSpec list. Tests cover apply, preview,
    and the "this param does belong to this template" happy path.

(2) Reject smart-group names containing `"` or `\`. `lookupSmartGroupByName`
    built `name=="<name>"` as an RSQL filter without escaping internal quotes;
    a name with `"` produced a malformed filter the server may parse
    unpredictably. Validate at the top of `runApplyFlow` with a clear error
    rather than relying on RSQL escaping rules.

(3) `deleteGroup` (verify.go) now checks HTTP status and returns errors.
    Previously returned nil regardless of status, so verify-templates could
    leave orphaned `__verify_*` smart groups silently. `RunOneVerification`
    surfaces the cleanup error via the result's Error field — the verify
    itself still passes; only the cleanup failed.

(4) `verify.go:createTempGroup` now checks the json.Marshal error, matching
    the pattern used in `pro_smartgroup.go:createSmartGroup`. Style consistency
    fix; the error path is essentially impossible for the struct shape but the
    inconsistency was a real code smell.

(5) Move spec + plan docs from `docs/solutions/design-patterns/` to
    `docs/plans/`. Per `docs/solutions/README.md`, solutions are postmortems
    and reusable patterns; these are implementation plans for one feature and
    belong with the other plans.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@ktn-jamf ktn-jamf enabled auto-merge (squash) May 13, 2026 20:04
tonyyo11 added a commit to tonyyo11/jamf-reports-community that referenced this pull request May 14, 2026
Prepares the JamfReports app for the `pro smart-group` namespace
landing in jamf-cli v1.17 (upstream PR Jamf-Concepts/jamf-cli#205,
OPEN at commit time). Pure additive Swift: no UI changes, no
destructive operations yet, no version-floor bump.

Adds four CLICommand cases mirroring the upstream contract:
- proSmartGroupTemplates — lists the 23 curated templates
- proSmartGroupPreview   — renders criteria JSON for a template + params
- proSmartGroupApply     — destructive create-or-update (Stage 2 wraps it)
- proSmartGroupVerifyTemplates — diagnostic smoke-test runner

Argv construction sorts params alphabetically for deterministic
testing, and `apply` always emits `--yes` because the GUI has no TTY
for interactive confirmation prompts. Caller-supplied user consent
is the only gate (collected by Stage 2's UI flow).

Adds SnapshotKind.smartGroupTemplates so the template list can be
cached on disk (it's stable per jamf-cli version). Preview, apply,
and verify outputs are intentionally NOT cached — they're either
parameterized per call or destructive.

Adds SmartGroupTemplateService for read-only template+preview:
- listTemplates: decodes the 23-template registry, sorts by category
  then slug so the UI gets a deterministic order
- preview: tolerates both `{body, estimated_match_count}` envelope
  shape and the bare-body fallback (PR contract isn't fully locked)
- Translates CLIExecutorError into typed service errors:
  * unknown command / binary not found → featureNotAvailable
    (so dashboards can hide buttons on older jamf-cli)
  * unknown template → unknownTemplate (so the GUI can show a
    meaningful error rather than a generic "exit 2")
  * everything else → executionFailed(code, stderr)

24 new tests:
- 11 argv-shape tests across the four new cases (including param
  sorting determinism and the --yes / --dry-run / --recalculate
  combinations)
- 4 snapshotKind cacheability tests
- 9 service-level tests using a MockCLIExecutor that's keyed on
  argv (not on CLICommand-as-Hashable — Swift 6 rejects retroactive
  Hashable conformance across the test boundary)

Total Swift tests: 1213 → 1237. ruff: clean. Build: clean.

Source: planning for upstream PR Jamf-Concepts/jamf-cli#205.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
tonyyo11 added a commit to tonyyo11/jamf-reports-community that referenced this pull request May 14, 2026
Adds the first write-to-Jamf-tenant surface in the app, gated by an
always-preview-always-confirm UX (operator-chosen at Stage 2 kickoff).
Stage 1 landed the read-only foundation; this commit adds the
destructive apply path plus the reusable SwiftUI sheet that dashboards
will consume in Stage 3.

SmartGroupApplyService:
- Wraps `pro sg apply` via the CLICommand from Stage 1
- Decodes the apply result envelope (id, name, member_count, created),
  tolerating both snake_case and camelCase since PR #205's JSON
  serializer convention isn't fully locked
- Classifies errors into typed cases: featureNotAvailable (unknown
  command), apiError(httpStatus, message) extracted from
  cliCtx.Client.Do "HTTP <code>: <message>" patterns, networkFailure
  for connection-refused / i/o-timeout / no-such-host / TLS, and
  executionFailed as the unclassified fallback

SmartGroupApplySheetViewModel:
- @observable @mainactor state machine: loadingPreview → previewReady
  → applying → applied | applyFailed (or previewFailed at any point)
- canApply gate: requires non-empty name AND every required param
  populated AND phase ∈ {previewReady, applyFailed} — protects the
  destructive call from racing or fat-fingered submits
- applyFailed preserves the preview the user just approved so the
  retry screen doesn't lose context
- defaultName generator: slug-cased title with " (Jamf Reports)"
  suffix (user-editable in the sheet)

SmartGroupApplySheet:
- 560×520 modal sheet bound to the view-model
- Phases: spinner during preview/apply, name+params form + readable
  criteria JSON + estimated match count during previewReady,
  success card with "Reveal in Jamf Pro" link during applied,
  inline error banner with preserved preview during applyFailed
- Action bar adapts: Cancel/Create during preview phases, Done after
  apply, Close after preview-fatal-failure

Tests: 31 new (1237 → 1268). Coverage includes:
- Apply argv shape and result-decoder (snake/camel/missing fields)
- Error classification (HTTP 401/403/429 extraction, network patterns,
  unknown-command, fallthrough)
- View-model state transitions on success and failure
- canApply gate behavior under all four blocking conditions
- Preview preservation across apply failures
- Default-name generation for single-word and multi-word slugs

Stage 3 (dashboard wiring) is the next commit. Each dashboard gets its
own commit so the integration can be reviewed and rolled back per-view.

Source: planning for upstream PR Jamf-Concepts/jamf-cli#205.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
tonyyo11 added a commit to tonyyo11/jamf-reports-community that referenced this pull request May 14, 2026
Adds the first dashboard entry point to the SmartGroupApplySheet
landed in Stage 2. Operators viewing the Inactive (91-180d) or Dormant
(180d+) tier now get a "Create smart group" button next to "Copy
email list" that opens the apply sheet pre-filled with the
`stale-checkin` template and a "Stale Macs 90+ days (Jamf Reports)"
suggested name.

Feature-detect on view appear: lists templates once per profile via
SmartGroupTemplateService and stores the stale-checkin entry. Any
failure (jamf-cli < v1.17, network, missing) leaves the state nil and
hides the button — older jamf-cli installs see no UI regression.

Button is hidden on the Offline (31-90d) tier because the stale-checkin
template's threshold is hardcoded to 90 days upstream — surfacing it
there would mislead operators into thinking they were targeting the
31-90d bucket.

Source: planning for upstream PR Jamf-Concepts/jamf-cli#205.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
tonyyo11 added a commit to tonyyo11/jamf-reports-community that referenced this pull request May 14, 2026
Adds a "Create smart group" action card under the kpiGrid that opens a
template-driven sheet for each of the four update-category templates:

- os-version-below (devices below a threshold OS version)
- major-version-behind (a full major version behind)
- rsr-not-applied (Rapid Security Response missing)
- beta-os (running a beta OS)

The dashboard cards (planStateCard / statusSummaryCard / failedPlans /
errorDevices) don't break down by OS version, so per-row buttons would
have no anchor. A single menu picker fits the read flow: operator sees
a problem in a card, then picks the matching template from the menu.

Templates are loaded once per profile via SmartGroupTemplateService.
Failure on load (jamf-cli < v1.17, network, missing) leaves the
template list empty, which hides the action bar — older installs see
no regression.

Source: planning for upstream PR Jamf-Concepts/jamf-cli#205.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
tonyyo11 added a commit to tonyyo11/jamf-reports-community that referenced this pull request May 14, 2026
Adds an "Encryption remediation" action card between kpiGrid and
actionItemsCard with a menu picker exposing the three encryption
templates from jamf-cli `pro sg`:

- not-encrypted (devices with FileVault off)
- invalid-recovery-key (FV on but IRK is invalid)
- escrow-missing (FV on but recovery key not escrowed)

Sits between the KPI grid and the existing action-items card so the
operator can scan from "FV coverage is N%" KPI → "Create smart group
for the gap" → action items without scrolling. Same load-on-appear
feature-detect pattern as the OutreachView and UpdatesView wirings;
older jamf-cli installs see no UI regression.

Source: planning for upstream PR Jamf-Concepts/jamf-cli#205.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
tonyyo11 added a commit to tonyyo11/jamf-reports-community that referenced this pull request May 14, 2026
Adds the final dashboard wiring for jamf-cli `pro sg`. A "Compliance
remediation" action card sits between the controlCoverageCard and the
per-OS breakdown so the operator can scan "which control is failing"
→ "create a smart group for that control" without leaving the page.

Menu picker exposes all four compliance templates:

- gatekeeper-disabled
- sip-disabled
- firewall-disabled
- non-compliant-baseline (mSCP baseline rollup)

Same menu-picker shape as SecurityPostureView and UpdatesView for
visual consistency across the new dashboard family. Originally the
plan called for per-control inline buttons in controlBar, but the
controlBar already renders a progress-bar + percentage layout and an
inline button would conflict with that shape — a single menu picker
keeps the chrome focused.

Stage 3 complete. All four target dashboards (Outreach, Updates,
SecurityPosture, CompliancePosture) now expose smart-group creation
via the reusable SmartGroupApplySheet from Stage 2, with feature
detection that hides the UI on jamf-cli < v1.17.

Source: planning for upstream PR Jamf-Concepts/jamf-cli#205.

Co-Authored-By: Claude Opus 4.7 (1M context) <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.

2 participants