Skip to content

Change self-service categories GitOps to not require dedicated key#47439

Merged
jkatz01 merged 9 commits into
mainfrom
39018-categories-from-packages
Jun 11, 2026
Merged

Change self-service categories GitOps to not require dedicated key#47439
jkatz01 merged 9 commits into
mainfrom
39018-categories-from-packages

Conversation

@jkatz01

@jkatz01 jkatz01 commented Jun 11, 2026

Copy link
Copy Markdown
Member

Related issue: Resolves #

Checklist for submitter

If some of the following don't apply, delete the relevant line.

  • Changes file added for user-visible changes in changes/, orbit/changes/ or ee/fleetd-chrome/changes.
    See Changes files for more information.

    • Not needed
  • Input data is properly validated, SELECT * is avoided, SQL injection is prevented (using placeholders for values in statements), JS inline code is prevented especially for url redirects, and untrusted data interpolated into shell scripts/commands is validated against shell metacharacters.

  • Timeouts are implemented and retries are limited to avoid infinite loops

  • If paths of existing endpoints are modified without backwards compatibility, checked the frontend/CLI for any necessary changes

Testing

Summary by CodeRabbit

  • New Features

    • Batch software installer and app-association endpoints now return the list of referenced self-service categories.
    • Category fields support an “omit when unset” JSON behavior so omitted vs empty categories are distinguishable.
  • Bug Fixes

    • Improved category validation (trim + case-insensitive dedupe) and GitOps reconciliation to remove unused categories.
  • Chores

    • GitOps schema simplified: no separate top-level self_service_categories; categories are defined inline with packages.

jkatz01 added 7 commits June 10, 2026 12:12
Removes obsolete tests for the client-side/spec category logic that was
removed (spec derivation, generate-gitops top-level emission) and reworks
the gitops reconcile test. Committed separately so the production diff for
the server-side category handling can be reviewed on its own.
Derive each fleet's self-service categories from the categories referenced by
its software rather than a top-level key. Categories are created server-side in
the software and VPP batch endpoints (including FMA manifest defaults, which are
only known after manifest resolution), and the GitOps client prunes categories
no software references — skipped when software is excepted from GitOps, while an
omitted software section still prunes (consistent with software being deleted).

- batch endpoints collect/validate/create referenced categories and return them
- GitOps client unions the installer and VPP categories and deletes the rest
- TranslateLegacySoftwareCategoryNames is now case-insensitive
- remove the old client-side reconcile and spec-level derivation
A fleet-maintained app's categories key now parses into an optjson.Slice so the
server can tell an omitted key (use the manifest's default categories) from a
present-but-null/empty one (zero categories). Categories on MaintainedAppSpec,
SoftwarePackageSpec, and SoftwareInstallerPayload are optjson.Slice tagged
omitzero so the set/unset distinction survives the gitops client's internal
team-spec re-marshal — omitempty is a no-op on struct types and would collapse
omitted into null. softwareInstallerPayloadFromSlug checks Categories.Set.

Adds parse coverage (pkg/spec) and a batch round-trip case for omitted vs
explicit-empty FMA categories (integration_enterprise_test).
@codecov

codecov Bot commented Jun 11, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 58.37563% with 82 lines in your changes missing coverage. Please review.
✅ Project coverage is 67.22%. Comparing base (6fcd88a) to head (66d7f86).
⚠️ Report is 67 commits behind head on main.

Files with missing lines Patch % Lines
ee/server/service/vpp.go 38.00% 29 Missing and 2 partials ⚠️
ee/server/service/software_installers.go 59.67% 16 Missing and 9 partials ⚠️
server/service/client_software.go 66.66% 7 Missing and 3 partials ⚠️
...d/fleetctl/fleetctl/testing_utils/testing_utils.go 50.00% 2 Missing and 2 partials ⚠️
server/service/client.go 73.33% 2 Missing and 2 partials ⚠️
server/service/software_installers.go 50.00% 4 Missing ⚠️
server/service/client_teams.go 66.66% 3 Missing ⚠️
cmd/fleetctl/fleetctl/get.go 50.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #47439      +/-   ##
==========================================
+ Coverage   67.19%   67.22%   +0.02%     
==========================================
  Files        3068     3073       +5     
  Lines      226816   227552     +736     
  Branches    11867    11867              
==========================================
+ Hits       152417   152968     +551     
- Misses      60658    60756      +98     
- Partials    13741    13828      +87     
Flag Coverage Δ
backend 68.84% <58.37%> (+0.02%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@jkatz01 jkatz01 marked this pull request as ready for review June 11, 2026 18:10
@jkatz01 jkatz01 requested a review from a team as a code owner June 11, 2026 18:10
@coderabbitai

coderabbitai Bot commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 144a54a2-3c68-4a42-b05e-e0735a045220

📥 Commits

Reviewing files that changed from the base of the PR and between 670ed8c and 66d7f86.

📒 Files selected for processing (1)
  • server/service/client.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • server/service/client.go

Walkthrough

This PR migrates self-service software category reconciliation from a centralized fleetctl GitOps generation phase to per-operation collection within batch service methods. The schema for GitOps YAML is simplified by removing the self_service_categories field and its validation logic. The Categories field is unified across data structures to use optjson.Slice[string] for optional JSON serialization. Software installer and VPP app batch operations are extended to collect, validate, persist, and return category lists via new Redis storage and updated method signatures. The service client refactors to aggregate returned categories per team during GitOps applies and deletes unused categories inline rather than in a separate reconciliation step.

Possibly related PRs

  • fleetdm/fleet#47098: The main PR removes self_service_categories support from GitOps generation/parsing (e.g., deleting ListSelfServiceCategories usage and removing self_service_categories fields/validation in pkg/spec/gitops.go), which directly contradicts the retrieved PR’s addition of self_service_categories generation and pkg/spec/gitops.go parsing/reconciliation.
  • fleetdm/fleet#44932: Both PRs modify ee/server/service/vpp.go in BatchAssociateVPPApps, so the main PR’s self-service category propagation/change in that method is code-level related to the retrieved PR’s iOS/iPadOS managed configuration handling there.
  • fleetdm/fleet#46106: Both PRs modify ee/server/service/vpp.go within BatchAssociateVPPApps—one changes the dry-run flow to skip VPP label DB validation, while the main PR changes the same method’s dry-run behavior/returns to include self-service categories.
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description is incomplete. The PR template requires a related issue number but none is provided, critical security validation checklist items are unchecked without clear justification, and database migration considerations are not addressed despite schema-affecting changes. Fill in the related issue number, check or explain unchecked security/validation items, verify database migration requirements, and confirm no user-visible changes file is needed (or add one).
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main change: removing the requirement for a dedicated GitOps key for self-service categories.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch 39018-categories-from-packages

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (1)
ee/server/service/software_installers.go (1)

2360-2368: 💤 Low value

Category validation occurs before batch is started, but errors may be silent for individual payloads.

The trimAndValidateCategories call validates categories per payload, but the error returned doesn't identify which payload failed. Consider including the payload index or URL in the error context for easier debugging.

-		if err := trimAndValidateCategories(ctx, payload.Categories.Value); err != nil {
-			return "", ctxerr.Wrap(ctx, err, "validating software categories")
+		if err := trimAndValidateCategories(ctx, payload.Categories.Value); err != nil {
+			return "", ctxerr.Wrap(ctx, err, fmt.Sprintf("validating software categories for payload %d (url: %s)", i, payload.URL))
		}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ee/server/service/software_installers.go` around lines 2360 - 2368, The
validation call trimAndValidateCategories(payload.Categories.Value) returns
errors without identifying which payload failed; update the loop that calls
trimAndValidateCategories (the code handling payload and
payload.Categories.Value before calling svc.batchAddSelfServiceCategories) to
wrap or augment the returned error with the payload's index and/or payload URL
(e.g., include payloadIndex and payload.URL) so the ctxerr.Wrap or returned
error includes that context; keep using trimAndValidateCategories and
batchAddSelfServiceCategories but ensure any validation error references the
specific payload (index or URL) for easier debugging.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@ee/server/service/vpp.go`:
- Around line 304-308: The code calls svc.batchAddSelfServiceCategories(...) too
early and persists categories before subsequent platform/token/asset/config
validations complete; move the call to batchAddSelfServiceCategories (or the DB
persistence it performs) to after the later validation steps in the enclosing
method (the platform/token/asset/config checks), or implement a transactional
rollback/cleanup when those validations fail (e.g., track created category IDs
from batchAddSelfServiceCategories and delete them on error). Ensure you
reference and update the logic around batchAddSelfServiceCategories, the
validation blocks (platform/token/asset/config checks), and the error paths so
categories are only persisted on overall success or removed on failure.

In `@pkg/spec/gitops.go`:
- Around line 312-316: Add a parse-only deprecated alias field to the existing
Software struct so existing GitOps YAML with software.self_service_categories
doesn't hard-fail: add a new field SelfServiceCategories of type json.RawMessage
(or interface{}) to type Software with tags
json:"self_service_categories,omitempty" and
yaml:"self_service_categories,omitempty" and a comment like "// Deprecated:
parse-only alias for backward compatibility" and ensure parseSoftware continues
to validate against Software but any code paths that produce/derive categories
ignore this field (do not use it in upstream logic).

In `@server/service/client.go`:
- Around line 1098-1135: The categories keep-set is being stored under the
original YAML name (tmName) but the rest of the batch uses the resolved name
(currentTeamName), causing mismatches during pruning; update both places where
categories are appended (after ApplyTeamSoftwareInstallers and after
ApplyTeamAppStoreAppsAssociation) to use currentTeamName as the map key
(categoriesByTeam[currentTeamName] = append(...)) so the
deleteUnusedSelfServiceCategories call that iterates teamIDsByName finds the
correct keep-set; references: categoriesByTeam, tmName, currentTeamName,
ApplyTeamSoftwareInstallers, ApplyTeamAppStoreAppsAssociation,
deleteUnusedSelfServiceCategories, teamIDsByName.

---

Nitpick comments:
In `@ee/server/service/software_installers.go`:
- Around line 2360-2368: The validation call
trimAndValidateCategories(payload.Categories.Value) returns errors without
identifying which payload failed; update the loop that calls
trimAndValidateCategories (the code handling payload and
payload.Categories.Value before calling svc.batchAddSelfServiceCategories) to
wrap or augment the returned error with the payload's index and/or payload URL
(e.g., include payloadIndex and payload.URL) so the ctxerr.Wrap or returned
error includes that context; keep using trimAndValidateCategories and
batchAddSelfServiceCategories but ensure any validation error references the
specific payload (index or URL) for easier debugging.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: cf272350-67a2-40d8-aaae-41cdf27e5f73

📥 Commits

Reviewing files that changed from the base of the PR and between 2febf63 and 670ed8c.

📒 Files selected for processing (26)
  • cmd/fleetctl/fleetctl/generate_gitops.go
  • cmd/fleetctl/fleetctl/generate_gitops_test.go
  • cmd/fleetctl/fleetctl/get.go
  • cmd/fleetctl/fleetctl/gitops_test.go
  • cmd/fleetctl/fleetctl/testing_utils/testing_utils.go
  • cmd/fleetctl/fleetctl/testing_utils_test.go
  • ee/server/service/categories.go
  • ee/server/service/software_installers.go
  • ee/server/service/software_installers_test.go
  • ee/server/service/vpp.go
  • ee/server/service/vpp_test.go
  • pkg/spec/gitops.go
  • pkg/spec/gitops_test.go
  • server/fleet/scripts.go
  • server/fleet/service.go
  • server/fleet/software.go
  • server/fleet/software_installer.go
  • server/mock/service/service_mock.go
  • server/service/client.go
  • server/service/client_software.go
  • server/service/client_teams.go
  • server/service/integration_enterprise_test.go
  • server/service/integration_mdm_test.go
  • server/service/software_installers.go
  • server/service/software_installers_test.go
  • tools/cloner-check/generated_files/teamconfig.txt
💤 Files with no reviewable changes (2)
  • cmd/fleetctl/fleetctl/generate_gitops.go
  • cmd/fleetctl/fleetctl/generate_gitops_test.go

Comment thread ee/server/service/vpp.go
Comment on lines +304 to +308
// since we actually add categories here, it's possible they can stay orphaned until the next run if the run fails
categories, err := svc.batchAddSelfServiceCategories(ctx, teamID, categoryNames, dryRun)
if err != nil {
return nil, nil, err
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Delay category creation until the batch has passed the later validations.

Lines 304-308 persist categories before the method can still fail on platform/token/asset/config checks later in the function. That leaves categories in the database with no associated software, and the client-side prune only runs after a successful GitOps apply. Please move category creation after the failure-prone validation steps, or add rollback/cleanup on error.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ee/server/service/vpp.go` around lines 304 - 308, The code calls
svc.batchAddSelfServiceCategories(...) too early and persists categories before
subsequent platform/token/asset/config validations complete; move the call to
batchAddSelfServiceCategories (or the DB persistence it performs) to after the
later validation steps in the enclosing method (the platform/token/asset/config
checks), or implement a transactional rollback/cleanup when those validations
fail (e.g., track created category IDs from batchAddSelfServiceCategories and
delete them on error). Ensure you reference and update the logic around
batchAddSelfServiceCategories, the validation blocks
(platform/token/asset/config checks), and the error paths so categories are only
persisted on overall success or removed on failure.

@jkatz01 jkatz01 Jun 11, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I think this is acceptable, since it just leaves orphaned rows that will be cleaned up (or used) in the next run (if your non-dry gitops run failed you will probably want to run another one to fix it anyway). Fixing this would take a bit more than I would like to include in this PR. ~50-60 extra lines in that function according to claude.

Let me know if I should address this though.

Comment thread pkg/spec/gitops.go
Comment on lines 312 to 316
type Software struct {
Packages []SoftwarePackage `json:"packages"`
AppStoreApps []fleet.TeamSpecAppStoreApp `json:"app_store_apps"`
FleetMaintainedApps []fleet.MaintainedAppSpec `json:"fleet_maintained_apps"`
SelfServiceCategories optjson.Slice[string] `json:"self_service_categories"`
Packages []SoftwarePackage `json:"packages"`
AppStoreApps []fleet.TeamSpecAppStoreApp `json:"app_store_apps"`
FleetMaintainedApps []fleet.MaintainedAppSpec `json:"fleet_maintained_apps"`
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Keep self_service_categories as a deprecated parse-only alias.

Removing the field from Software turns existing GitOps YAML that still includes software.self_service_categories: into a hard parse failure, because parseSoftware still validates raw keys against this struct before the new server-side derivation path runs. That makes the migration breaking instead of merely “not required”.

Minimal compatibility shim
 type Software struct {
+	// Deprecated: categories are now derived from referenced software.
+	// Keep this field temporarily so older GitOps repos still parse.
+	SelfServiceCategories optjson.Slice[string] `json:"self_service_categories"`
 	Packages            []SoftwarePackage           `json:"packages"`
 	AppStoreApps        []fleet.TeamSpecAppStoreApp `json:"app_store_apps"`
 	FleetMaintainedApps []fleet.MaintainedAppSpec   `json:"fleet_maintained_apps"`
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
type Software struct {
Packages []SoftwarePackage `json:"packages"`
AppStoreApps []fleet.TeamSpecAppStoreApp `json:"app_store_apps"`
FleetMaintainedApps []fleet.MaintainedAppSpec `json:"fleet_maintained_apps"`
SelfServiceCategories optjson.Slice[string] `json:"self_service_categories"`
Packages []SoftwarePackage `json:"packages"`
AppStoreApps []fleet.TeamSpecAppStoreApp `json:"app_store_apps"`
FleetMaintainedApps []fleet.MaintainedAppSpec `json:"fleet_maintained_apps"`
}
type Software struct {
// Deprecated: categories are now derived from referenced software.
// Keep this field temporarily so older GitOps repos still parse.
SelfServiceCategories optjson.Slice[string] `json:"self_service_categories"`
Packages []SoftwarePackage `json:"packages"`
AppStoreApps []fleet.TeamSpecAppStoreApp `json:"app_store_apps"`
FleetMaintainedApps []fleet.MaintainedAppSpec `json:"fleet_maintained_apps"`
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/spec/gitops.go` around lines 312 - 316, Add a parse-only deprecated alias
field to the existing Software struct so existing GitOps YAML with
software.self_service_categories doesn't hard-fail: add a new field
SelfServiceCategories of type json.RawMessage (or interface{}) to type Software
with tags json:"self_service_categories,omitempty" and
yaml:"self_service_categories,omitempty" and a comment like "// Deprecated:
parse-only alias for backward compatibility" and ensure parseSoftware continues
to validate against Software but any code paths that produce/derive categories
ignore this field (do not use it in upstream logic).

@jkatz01 jkatz01 Jun 11, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Not needed, the self_service_categories key is never going to be shipped

Comment thread server/service/client.go
@jkatz01 jkatz01 merged commit 89b2a5e into main Jun 11, 2026
47 checks passed
@jkatz01 jkatz01 deleted the 39018-categories-from-packages branch June 11, 2026 20:19
jkatz01 added a commit that referenced this pull request Jun 11, 2026
…icated key (#47458)

<!-- Add the related story/sub-task/bug number, like Resolves #123, or
remove if NA -->
**Related issue:** Resolves #

Cherry-picks #47439 to 4.87.0
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