Skip to content

Move software self-service, labels, categories, and setup experience to team level in GitOps - #32245

Merged
iansltx merged 6 commits into
30095-gitops-changesfrom
31163-bitops-changes
Aug 30, 2025
Merged

Move software self-service, labels, categories, and setup experience to team level in GitOps#32245
iansltx merged 6 commits into
30095-gitops-changesfrom
31163-bitops-changes

Conversation

@iansltx

@iansltx iansltx commented Aug 24, 2025

Copy link
Copy Markdown
Contributor

Fixes #31163.

This also enables setup experience inclusion for FMAs. Includes checks and errors for invalid states.

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.

  • Input data is properly validated, SELECT * is avoided, SQL injection is prevented (using placeholders for values in statements)

Testing

  • Added/updated automated tests

  • QA'd all new/changed functionality manually

New Fleet configuration settings

  • Verified that the setting is exported via fleetctl generate-gitops
  • Verified the setting is documented in a separate PR to the GitOps documentation
  • Verified that the setting is cleared on the server if it is not supplied in a YAML file (or that it is documented as being optional)

Summary by CodeRabbit

  • New Features
    • Team-level control of software settings in GitOps: setup experience (install during setup), self-service, labels (include/exclude), and categories for packages and App Store apps.
    • Team-level settings are applied to individual packages automatically.
  • Validation
    • Clear errors when setup experience is defined in multiple places (software/app vs. macOS setup).
  • Documentation
    • Removed outdated note about GitOps-generated outputs.
  • Tests
    • Added test cases covering valid team-level configuration and conflicting placement scenarios.

…to team level in GitOps

This also enables setup experience inclusion for FMAs. Includes checks and errors for invalid states.

TODO: error tests for setup experience defined in two places, fields defined in child objects that should be defined in parent
@codecov

codecov Bot commented Aug 24, 2025

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 56.25000% with 35 lines in your changes missing coverage. Please review.
⚠️ Please upload report for BASE (30095-gitops-changes@8effd4b). Learn more about missing BASE report.

Files with missing lines Patch % Lines
pkg/spec/gitops.go 62.50% 6 Missing and 6 partials ⚠️
cmd/fleetctl/fleetctl/gitops.go 57.14% 8 Missing and 1 partial ⚠️
server/fleet/software_installer.go 25.00% 9 Missing ⚠️
server/service/client.go 66.66% 4 Missing and 1 partial ⚠️
Additional details and impacted files
@@                   Coverage Diff                   @@
##             30095-gitops-changes   #32245   +/-   ##
=======================================================
  Coverage                        ?   62.08%           
=======================================================
  Files                           ?     1985           
  Lines                           ?   194089           
  Branches                        ?     6444           
=======================================================
  Hits                            ?   120500           
  Misses                          ?    64011           
  Partials                        ?     9578           
Flag Coverage Δ
backend 63.15% <56.25%> (?)

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

☔ View full report in Codecov by Sentry.
📢 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.

@iansltx
iansltx marked this pull request as ready for review August 25, 2025 03:56
@iansltx
iansltx requested a review from a team as a code owner August 25, 2025 03:56
Comment thread pkg/spec/gitops.go Outdated
Comment thread pkg/spec/gitops.go Outdated
Comment thread pkg/spec/gitops.go Outdated
Co-authored-by: Anthony Maxwell <133805840+Illbjorn@users.noreply.github.com>
@iansltx

iansltx commented Aug 27, 2025

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 27, 2025

Copy link
Copy Markdown
Contributor
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai

coderabbitai Bot commented Aug 27, 2025

Copy link
Copy Markdown
Contributor

Walkthrough

Moved software attributes (labels, categories, self_service, setup_experience) from package files to team-level YAML, added hydration to propagate them to package-level, and enforced mutual exclusivity of setup_experience between macos_setup and per-software entries. Updated fleetctl GitOps parsing/tests, adjusted specs, and modified server models and services accordingly.

Changes

Cohort / File(s) Summary
GitOps notes cleanup
changes/30095-gitops, changes/31164-gitops-generate
Updated/removed GitOps documentation snippets about team-level generation for categories/self_service/setup_experience.
fleetctl GitOps core
cmd/fleetctl/fleetctl/gitops.go
Renamed loop variables for clarity and aligned field references (software packages, App Store apps, maintained apps). No control-flow changes.
fleetctl GitOps testdata updates
cmd/fleetctl/fleetctl/testdata/gitops/*
Added/updated YAML fixtures to set self_service/setup_experience at team level; introduced scenarios for valid placement and conflicting definitions; removed self_service from package file referenced by lib/software_other.yml.
fleetctl integration tests
cmd/fleetctl/integrationtest/gitops/software_test.go
Added tests covering valid package-level setup_experience and conflict errors when setup_experience is specified in multiple places.
Spec parsing and hydration
pkg/spec/gitops.go, pkg/spec/gitops_test.go
Added SoftwarePackage.HydrateToPackageLevel to copy team-level fields (categories, labels include/exclude, self_service, setup_experience) into package-level specs; added validation to disallow these fields in package files; updated tests to assert hydrated fields.
Spec testdata reorg
pkg/spec/testdata/*
Moved categories/self_service from package files to team-level configs; added labels and setup_experience at team level; ensured “only sha256” cases hydrate install_during_setup.
Server data models
server/fleet/software_installer.go, server/fleet/teams.go
Added InstallDuringSetup (json:"setup_experience") to SoftwarePackageSpec, MaintainedAppSpec, and TeamSpecAppStoreApp; added IncludesFieldsDisallowedInPackageFile to SoftwarePackageSpec; propagated field in ToSoftwarePackageSpec.
Server service logic and tests
server/service/client.go, server/service/integration_enterprise_test.go
Enforced mutual exclusivity for setup_experience between macos_setup and per-app/package definitions across apply/build flows; updated enterprise integration test expectations.

Sequence Diagram(s)

sequenceDiagram
  participant User as User (YAML)
  participant fleetctl as fleetctl gitops
  participant Spec as Spec Parser
  participant Service as Service Client
  participant Server as Fleet API

  User->>fleetctl: Provide team YAML (labels, categories, self_service, setup_experience)
  fleetctl->>Spec: Parse team-level software definitions
  Spec->>Spec: Validate package files exclude team-only fields
  Spec->>Spec: Hydrate package-level spec with team-level fields
  Spec-->>fleetctl: Package specs (hydrated)
  fleetctl->>Service: Build payloads (packages, maintained apps, VPP apps)
  Service->>Service: Check setup_experience mutual exclusivity (macos_setup vs software)
  alt Exclusive OK
    Service->>Server: Apply team software config
    Server-->>Service: 200 OK
    Service-->>fleetctl: Success
  else Conflict
    Service-->>fleetctl: Error: setup_experience specified in both places
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Assessment against linked issues

Objective Addressed Explanation
Update fleetctl gitops to accept YAML per #30837 for packages, Fleet‑maintained apps, and VPP apps [#31163]
Add support in GitOps for Fleet‑maintained apps in macOS setup experience [#31163]
Maintain documented backward compatibility or breaks [#31163] Tests and validations changed; explicit compatibility matrix or migration notes not shown here.
Changes limited to fleetctl (APIs already in place) [#31163] Server model and service logic changes are present.

Assessment against linked issues: Out-of-scope changes

Code Change Explanation
Add InstallDuringSetup to server models and specs (server/fleet/software_installer.go) The issue specifies fleetctl-only changes; modifying server-side types is beyond that scope.
Add InstallDuringSetup to TeamSpecAppStoreApp (server/fleet/teams.go) Expands server API surface; not required for a fleetctl-only task.
Enforce mutual exclusivity in server/service (server/service/client.go) Introduces server-side validation/control flow; outside fleetctl-only mandate.
Update enterprise integration tests for server structs (server/service/integration_enterprise_test.go) Server-side test changes are unrelated to a client-only change objective.

Suggested reviewers

  • lucasmrod
  • getvictor
  • jahzielv

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch 31163-bitops-changes

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

‼️ IMPORTANT
Auto-reply has been disabled for this repository in the CodeRabbit settings. The CodeRabbit bot will not respond to your replies unless it is explicitly tagged.

  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@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: 1

♻️ Duplicate comments (3)
server/service/client.go (2)

1112-1118: Good guard: enforce mutual exclusivity for setup experience (packages/FMA).

Same suggestion to reuse a shared const for the error message to avoid drift.

- return nil, fmt.Errorf("Couldn't edit software (%s). Setup experience may only be specified directly on software or within macos_setup, but not both. See https://fleetdm.com/learn-more-about/yaml-software-setup-experience.", si.URL)
+ return nil, fmt.Errorf("%s", errSetupExperienceMutualExcl)

2178-2184: Good guard: enforce mutual exclusivity for setup experience (No team VPP apps).

Matches team path; reuse the shared const for consistency.

- return nil, nil, errors.New("Couldn't edit app store apps. Setup experience may only be specified directly on software or within macos_setup, but not both. See https://fleetdm.com/learn-more-about/yaml-software-setup-experience.")
+ return nil, nil, errors.New(errSetupExperienceMutualExcl)
pkg/spec/gitops.go (1)

1131-1161: Improve error message typos and clarity when reading package files.

There’s a typo in the environment expansion error message.

- multiError = multierror.Append(multiError, fmt.Errorf("failed to expand environmet in file %s: %w", *teamLevelPackage.Path, err))
+ multiError = multierror.Append(multiError, fmt.Errorf("failed to expand environment in file %s: %w", *teamLevelPackage.Path, err))

Also, great call to gate disallowed fields in package files and then hydrate from team-level.

🧹 Nitpick comments (13)
server/fleet/teams.go (1)

194-196: Add brief doc on new field and confirm tri-state semantics

Good addition. Please add a short comment clarifying that this is optional and only meaningful for macOS setup experience; also confirm downstream code treats “unset” differently from explicit false.

Apply this diff to document intent:

   // Categories is the list of names of software categories associated with this VPP app.
   Categories         []string     `json:"categories"`
-  InstallDuringSetup optjson.Bool `json:"setup_experience"`
+  // InstallDuringSetup indicates the app should be installed during macOS setup (aka setup experience).
+  // Optional (unset vs. false must be preserved via optjson.Bool).
+  InstallDuringSetup optjson.Bool `json:"setup_experience"`
changes/30095-gitops (1)

1-1: Changelog entry is concise; consider adding explicit key names

Add the literal keys for clarity (self_service, labels_include_any, labels_exclude_any, categories, setup_experience) to help users search.

pkg/spec/gitops_test.go (4)

201-210: Avoid brittle package identification; handle URL-less Teams (SHA-only) too

Relying on URL substring fails when Teams’ URL is empty (SHA-only case). Identify Teams by URL OR known SHA to keep this assertion stable across both test branches.

Apply:

-					for _, pkg := range gitops.Software.Packages {
-						if strings.Contains(pkg.URL, "MicrosoftTeams") {
+					for _, pkg := range gitops.Software.Packages {
+						if strings.Contains(pkg.URL, "MicrosoftTeams") || pkg.SHA256 == teamsSHA {
 							assert.Equal(t, "testdata/lib/uninstall.sh", pkg.UninstallScript.Path)
 							assert.Contains(t, pkg.LabelsIncludeAny, "a")
 							assert.Contains(t, pkg.Categories, "Communication")
 						} else {
 							assert.Empty(t, pkg.UninstallScript.Path)
 							assert.Contains(t, pkg.LabelsExcludeAny, "a")
 						}
 					}

Add outside the range (near the start of TestValidGitOpsYaml):

const teamsSHA = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"

204-205: Strengthen assertions to catch inverted hydration

Also assert the opposite label set is empty for Teams to prevent accidental leakage/misplacement.

 							assert.Equal(t, "testdata/lib/uninstall.sh", pkg.UninstallScript.Path)
 							assert.Contains(t, pkg.LabelsIncludeAny, "a")
 							assert.Contains(t, pkg.Categories, "Communication")
+							require.Empty(t, pkg.LabelsExcludeAny)

208-208: Symmetric negative assertion for non-Teams packages

Mirror the check to ensure we don't have IncludeAny populated where ExcludeAny is expected.

 							assert.Empty(t, pkg.UninstallScript.Path)
 							assert.Contains(t, pkg.LabelsExcludeAny, "a")
+							require.Empty(t, pkg.LabelsIncludeAny)

318-324: Don’t assert by index; verify install_during_setup across all packages

Index-based checks are fragile. Loop over packages so the test remains correct if ordering changes.

 						require.Empty(t, gitops.Software.Packages[0].URL)
-						require.True(t, gitops.Software.Packages[0].InstallDuringSetup.Value)
-						require.True(t, gitops.Software.Packages[1].InstallDuringSetup.Value)
+						for i, pkg := range gitops.Software.Packages {
+							require.True(t, pkg.InstallDuringSetup.Value, fmt.Sprintf("package[%d] not marked install_during_setup", i))
+						}
server/service/integration_enterprise_test.go (1)

11690-11714: Minor DRY: reuse a const-ish helper for “true”

Reduce repetition/noise by introducing a local var and using it in literals.

@@
-    wantSoftwarePackages := []fleet.SoftwarePackageSpec{
+    setupTrue := optjson.Bool{Set: true, Value: true}
+    wantSoftwarePackages := []fleet.SoftwarePackageSpec{
@@
-            InstallDuringSetup: optjson.Bool{Set: true, Value: true},
+            InstallDuringSetup: setupTrue,
@@
-            InstallDuringSetup: optjson.Bool{Set: true, Value: true},
+            InstallDuringSetup: setupTrue,
@@
-    wantAppStoreApps := []fleet.TeamSpecAppStoreApp{
+    wantAppStoreApps := []fleet.TeamSpecAppStoreApp{
@@
-            InstallDuringSetup: optjson.Bool{Set: true, Value: true},
+            InstallDuringSetup: setupTrue,
@@
-            InstallDuringSetup: optjson.Bool{Set: true, Value: true},
+            InstallDuringSetup: setupTrue,
cmd/fleetctl/integrationtest/gitops/software_test.go (2)

61-63: Avoid brittle match: remove leading space from expected error substring

The leading space in wantErr makes the test fragile. Drop it so ErrorContains matches regardless of incidental whitespace.

Apply this diff:

-        {"testdata/gitops/team_setup_software_defined_in_conflicting_places.yml", " Setup experience may only be specified directly on software or within macos_setup, but not both."},
-        {"testdata/gitops/team_setup_software_defined_in_conflicting_places_vpp.yml", " Setup experience may only be specified directly on software or within macos_setup, but not both."},
+        {"testdata/gitops/team_setup_software_defined_in_conflicting_places.yml", "Setup experience may only be specified directly on software or within macos_setup, but not both."},
+        {"testdata/gitops/team_setup_software_defined_in_conflicting_places_vpp.yml", "Setup experience may only be specified directly on software or within macos_setup, but not both."},

27-27: Typo in test name: “Sofware” → “Software”

Keeps grepability and consistency.

Apply this diff:

-func TestGitOpsTeamSofwareInstallers(t *testing.T) {
+func TestGitOpsTeamSoftwareInstallers(t *testing.T) {
server/service/client.go (1)

773-779: Good guard: enforce mutual exclusivity for setup experience (VPP apps).

Prevents mixing macos_setup and per-app flags in a team spec. Consider deduplicating the repeated error string into a const to keep the doc URL in one place.

Apply this minimal change locally here (calls shown below); see added const outside this hunk:

- return nil, nil, nil, nil, errors.New("Couldn't edit app store apps. Setup experience may only be specified directly on software or within macos_setup, but not both. See https://fleetdm.com/learn-more-about/yaml-software-setup-experience.")
+ return nil, nil, nil, nil, errors.New(errSetupExperienceMutualExcl)

Additional code to add near other consts in this file:

const errSetupExperienceMutualExcl = "Couldn't edit app store apps. Setup experience may only be specified directly on software or within macos_setup, but not both. See https://fleetdm.com/learn-more-about/yaml-software-setup-experience."
server/fleet/software_installer.go (1)

584-588: Package-file validation misses explicit self_service: false.

IncludesFieldsDisallowedInPackageFile flags presence of setup_experience via Valid, but self_service uses bool so self_service: false in a package file won’t be detected and will slip through. If the intent is “field must not be present at package level regardless of value,” consider:

  • Switching SelfService to optjson.Bool in SoftwarePackageSpec, or
  • Detecting presence during YAML parsing (pkg/spec/gitops.go) by inspecting a raw map for the key and surfacing an error.

Your current approach is otherwise sound.

pkg/spec/gitops.go (2)

1174-1176: Fix “hash_256” typo in validation error.

Should be “hash_sha256”.

- multiError = multierror.Append(multiError, fmt.Errorf("hash_256 value %q must be a valid lower-case hex-encoded (64-character) SHA-256 hash value", softwarePackageSpec.SHA256))
+ multiError = multierror.Append(multiError, fmt.Errorf("hash_sha256 value %q must be a valid lower-case hex-encoded (64-character) SHA-256 hash value", softwarePackageSpec.SHA256))

709-750: Redundant “item := item” copies are no longer needed in Go ≥1.22.

Given Fleet builds with Go 1.24, the loop-var semantics fix is active; these defensive copies can be removed to simplify code (applies similarly in policies/queries loops).

- for _, item := range labels {
-   item := item
+ for _, item := range labels {
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 8effd4b and 8755e0c.

📒 Files selected for processing (24)
  • changes/30095-gitops (1 hunks)
  • changes/31164-gitops-generate (0 hunks)
  • cmd/fleetctl/fleetctl/gitops.go (1 hunks)
  • cmd/fleetctl/fleetctl/testdata/gitops/lib/software_other.yml (0 hunks)
  • cmd/fleetctl/fleetctl/testdata/gitops/no_team_setup_software_invalid_script.yml (1 hunks)
  • cmd/fleetctl/fleetctl/testdata/gitops/no_team_setup_software_invalid_software_package.yml (1 hunks)
  • cmd/fleetctl/fleetctl/testdata/gitops/no_team_setup_software_invalid_vpp_app.yml (1 hunks)
  • cmd/fleetctl/fleetctl/testdata/gitops/no_team_setup_software_valid.yml (1 hunks)
  • cmd/fleetctl/fleetctl/testdata/gitops/team_setup_software_defined_in_conflicting_places.yml (1 hunks)
  • cmd/fleetctl/fleetctl/testdata/gitops/team_setup_software_defined_in_conflicting_places_vpp.yml (1 hunks)
  • cmd/fleetctl/fleetctl/testdata/gitops/team_setup_software_on_package.yml (1 hunks)
  • cmd/fleetctl/fleetctl/testdata/gitops/team_setup_software_valid.yml (1 hunks)
  • cmd/fleetctl/integrationtest/gitops/software_test.go (1 hunks)
  • pkg/spec/gitops.go (2 hunks)
  • pkg/spec/gitops_test.go (2 hunks)
  • pkg/spec/testdata/microsoft-teams.nourl.pkg.software.yml (0 hunks)
  • pkg/spec/testdata/microsoft-teams.pkg.software.yml (1 hunks)
  • pkg/spec/testdata/team_config.yml (2 hunks)
  • pkg/spec/testdata/team_config_no_paths.yml (2 hunks)
  • pkg/spec/testdata/team_config_only_sha256.yml (2 hunks)
  • server/fleet/software_installer.go (3 hunks)
  • server/fleet/teams.go (1 hunks)
  • server/service/client.go (3 hunks)
  • server/service/integration_enterprise_test.go (1 hunks)
💤 Files with no reviewable changes (3)
  • cmd/fleetctl/fleetctl/testdata/gitops/lib/software_other.yml
  • pkg/spec/testdata/microsoft-teams.nourl.pkg.software.yml
  • changes/31164-gitops-generate
🧰 Additional context used
📓 Path-based instructions (1)
**/*.go

⚙️ CodeRabbit configuration file

When reviewing SQL queries that are added or modified, ensure that appropriate filtering criteria are applied—especially when a query is intended to return data for a specific entity (e.g., a single host). Check for missing WHERE clauses or incorrect filtering that could lead to incorrect or non-deterministic results (e.g., returning the first row instead of the correct one). Flag any queries that may return unintended results due to lack of precise scoping.

Files:

  • cmd/fleetctl/integrationtest/gitops/software_test.go
  • cmd/fleetctl/fleetctl/gitops.go
  • pkg/spec/gitops_test.go
  • server/service/client.go
  • server/fleet/teams.go
  • server/service/integration_enterprise_test.go
  • pkg/spec/gitops.go
  • server/fleet/software_installer.go
🧠 Learnings (2)
📚 Learning: 2025-07-03T15:13:36.494Z
Learnt from: lucasmrod
PR: fleetdm/fleet#30550
File: server/datastore/mysql/policies_test.go:6368-6378
Timestamp: 2025-07-03T15:13:36.494Z
Learning: Go 1.22 introduced a significant change to loop variable semantics where each iteration of a for loop creates a new variable, automatically fixing the classic closure capture bug that existed in earlier Go versions. Code that captures loop variables in closures no longer needs explicit variable copying in Go 1.22+.

Applied to files:

  • pkg/spec/gitops.go
📚 Learning: 2025-07-03T15:13:36.494Z
Learnt from: lucasmrod
PR: fleetdm/fleet#30550
File: server/datastore/mysql/policies_test.go:6368-6378
Timestamp: 2025-07-03T15:13:36.494Z
Learning: Go 1.22 introduced a significant change to loop variable semantics where each iteration of a for loop creates a new variable, automatically fixing the classic closure capture bug that existed in earlier Go versions. Code that captures loop variables in closures no longer needs explicit variable copying in Go 1.22+. This fix is only enabled if the module's go.mod file specifies go 1.22 or higher.

Applied to files:

  • pkg/spec/gitops.go
🔇 Additional comments (27)
cmd/fleetctl/fleetctl/testdata/gitops/no_team_setup_software_valid.yml (1)

16-16: Team-level self_service migration verified

  • Confirmed cmd/fleetctl/fleetctl/testdata/gitops/lib/software_other.yml contains no self_service entries.
  • All per-package self_service flags have been removed as intended.

Approving code changes.

pkg/spec/testdata/microsoft-teams.pkg.software.yml (1)

5-5: Removal of per-package fields aligns with hydration

OK to drop self_service and categories at package level given team-level hydration.

cmd/fleetctl/fleetctl/testdata/gitops/no_team_setup_software_invalid_software_package.yml (1)

11-11: Intentional invalid path scenario preserved; self_service placement correct

This keeps the test’s invalid package path while moving self_service to team level. LGTM.

cmd/fleetctl/fleetctl/testdata/gitops/no_team_setup_software_invalid_vpp_app.yml (1)

15-15: Verified: No duplicate self_service declarations in package files

I ran a recursive search in cmd/ and pkg/ for self_service: in software_other.yml and confirmed there are no occurrences. The self_service: true in your testdata file is the sole declaration, so there’s no conflict during hydration/validation.

pkg/spec/testdata/team_config_only_sha256.yml (3)

52-55: Label filters and setup_experience on packages: consistent with objectives.

References to label a are defined above; no issues spotted.

Also applies to: 57-60


49-49: No conflicting macOS setup entries present

Inspected pkg/spec/testdata/controls.yml – the macos_setup section has bootstrap_package: null and no per-package install instructions, so there’s no overlap with the setup_experience: true entry in team_config_only_sha256.yml.


8-12: Confirm YAML key for label_membership_type
Top-level label definition looks correct, but the spec parser must recognize the label_membership_type key (not just membership_type). Please verify:

  • Inspect the fleet.LabelSpec struct in server/fleet/labels.go to ensure its LabelMembershipType field has the corresponding YAML tag.
  • Check the pkg/spec conversion code (pkg/spec/gitops.go) to confirm it maps the label_membership_type key into the internal model.
cmd/fleetctl/fleetctl/testdata/gitops/no_team_setup_software_invalid_script.yml (1)

10-10: No duplicate self_service in software_other.yml

Ran a repository-wide search for self_service: in all software_other.yml fixtures under cmd/ and pkg/. Only one file was found:

  • cmd/fleetctl/fleetctl/testdata/gitops/lib/software_other.yml — no self_service: entries

Since there are no duplicate keys, this fixture aligns with the new model and is safe to hydrate.

pkg/spec/testdata/team_config.yml (3)

51-55: labels_include_any and categories on Teams: good coverage for hydration behavior.

No action needed.


58-59: labels_exclude_any on Firefox: matches new team-level filtering approach.

Looks good.


8-12: Key naming consistency confirmed. All occurrences in pkg/spec/** use the label_membership_type key—no stray membership_type entries were found. Changes can be approved.

cmd/fleetctl/fleetctl/testdata/gitops/team_setup_software_valid.yml (1)

26-26: Verified no self_service in underlying lib – approving code changes

  • Searched cmd/fleetctl/fleetctl/testdata/gitops/lib/software_other.yml for any self_service: entries; none were found.
  • The testdata’s self_service: true on the second package stands alone and is a valid scenario.
cmd/fleetctl/fleetctl/testdata/gitops/team_setup_software_on_package.yml (1)

17-26: Valid positive fixture for per-item setup_experience

Looks good: setup_experience is specified directly on software items (pkg and VPP), with macos_setup only providing a script. This aligns with the mutual-exclusivity rule.

cmd/fleetctl/fleetctl/testdata/gitops/team_setup_software_defined_in_conflicting_places_vpp.yml (1)

12-22: Good conflict fixture (macos_setup.software vs top-level app_store_apps)

This should reliably trigger the exclusivity error path and complements the package-based conflict case.

cmd/fleetctl/fleetctl/testdata/gitops/team_setup_software_defined_in_conflicting_places.yml (1)

12-26: Conflict fixture (macos_setup.software vs package setup_experience)

Appropriately exercises the mutual-exclusivity validation for setup_experience.

pkg/spec/testdata/team_config_no_paths.yml (2)

65-69: Team-level label definition is correct and referenced below

Defining label “a” here enables include/exclude scoping in the software section and supports hydration to package-level.


153-161: Package scoping via labels and categories aligns with new hydration behavior

Using labels_include_any and categories here matches the intended move of metadata to team level; ensures downstream resolution and validation paths are exercised.

cmd/fleetctl/fleetctl/gitops.go (3)

472-484: Renames/readability: software package label checks LGTM.

Variable rename improves clarity; logic unchanged and correct.


487-499: Renames/readability: App Store app label checks LGTM.

Consistent error paths and identifiers; no functional change.


501-513: Renames/readability: Fleet-maintained app label checks LGTM.

Consistent with other sections; identifiers (Slug) used appropriately.

server/service/client.go (1)

780-789: Payload mapping LGTM.

install_during_setup is correctly propagated with labels/categories preserved.

server/fleet/software_installer.go (3)

549-558: Add setup_experience to SoftwarePackageSpec — LGTM.

Field type optjson.Bool is appropriate for presence detection.


598-608: Add setup_experience to MaintainedAppSpec — LGTM.

Consistent with package spec.


611-621: Propagate FMA setup_experience into package spec — LGTM.

ToSoftwarePackageSpec maps all relevant fields.

pkg/spec/gitops.go (3)

164-172: Hydration helper is clean and focused — LGTM.

Copies only the intended team-level fields down to package-level.


1096-1130: Maintained apps: path resolution and secret scanning LGTM.

Validations (mutually exclusive label scopes) and secret gathering look correct.


1153-1160: Order of checks is sensible — LGTM.

Validate disallowed fields before hydration; avoids accidental overrides from package file.

Comment on lines +11690 to 11714
URL: "http://foo.com",
SelfService: true,
InstallScript: fleet.TeamSpecSoftwareAsset{Path: "./foo/install-script.sh"},
PostInstallScript: fleet.TeamSpecSoftwareAsset{Path: "./foo/post-install-script.sh"},
PreInstallQuery: fleet.TeamSpecSoftwareAsset{Path: "./foo/query.yaml"},
InstallDuringSetup: optjson.Bool{Set: true},
},
{
URL: "http://bar.com",
SelfService: false,
InstallScript: fleet.TeamSpecSoftwareAsset{Path: "./bar/install-script.sh"},
PostInstallScript: fleet.TeamSpecSoftwareAsset{Path: "./bar/post-install-script.sh"},
PreInstallQuery: fleet.TeamSpecSoftwareAsset{Path: "./bar/query.yaml"},
URL: "http://bar.com",
SelfService: false,
InstallScript: fleet.TeamSpecSoftwareAsset{Path: "./bar/install-script.sh"},
PostInstallScript: fleet.TeamSpecSoftwareAsset{Path: "./bar/post-install-script.sh"},
PreInstallQuery: fleet.TeamSpecSoftwareAsset{Path: "./bar/query.yaml"},
InstallDuringSetup: optjson.Bool{Set: true},
},
}
wantAppStoreApps := []fleet.TeamSpecAppStoreApp{
{
AppStoreID: "1234",
AppStoreID: "1234",
InstallDuringSetup: optjson.Bool{Set: true},
},
{
AppStoreID: "5678",
AppStoreID: "5678",
InstallDuringSetup: optjson.Bool{Set: true},
},

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.

💡 Verification agent

🧩 Analysis chain

optjson.Bool likely missing Value: true — tests intend “true” but encode default false

optjson.Bool generally requires both Set and Value to represent an explicit boolean. As written, optjson.Bool{Set: true} will marshal as a present field with the default zero value (false). If the intent is “true”, set Value: true as well; otherwise expectations and behavior may diverge from runtime structs.

Apply this diff:

@@
-            InstallDuringSetup: optjson.Bool{Set: true},
+            InstallDuringSetup: optjson.Bool{Set: true, Value: true},
@@
-            InstallDuringSetup: optjson.Bool{Set: true},
+            InstallDuringSetup: optjson.Bool{Set: true, Value: true},
@@
-            InstallDuringSetup: optjson.Bool{Set: true},
+            InstallDuringSetup: optjson.Bool{Set: true, Value: true},
@@
-            InstallDuringSetup: optjson.Bool{Set: true},
+            InstallDuringSetup: optjson.Bool{Set: true, Value: true},

Run to confirm optjson.Bool semantics and existing usage patterns:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 1) Inspect the type and JSON behavior
rg -nP -C3 'package\s+optjson\b' | head -n 50 || true
rg -nP -C3 'type\s+Bool\s+struct\b' || true
rg -nP -C2 'MarshalJSON|UnmarshalJSON' | sed -n '1,200p' || true

# 2) See how Bool is typically instantiated elsewhere
rg -nP -C2 'optjson\.Bool\s*{\s*Set:\s*true(?:\s*,\s*Value:\s*(true|false))?' || true

# 3) Check where InstallDuringSetup is compared or marshaled
rg -nP -C3 '\bInstallDuringSetup\b'

Length of output: 70575


Ensure explicit “true” for InstallDuringSetup in integration tests

The optjson.Bool type has a Value field that defaults to false when omitted. Instantiating it with only Set: true will still represent false, not the intended true. Update the four occurrences in integration_enterprise_test.go to include Value: true:

• server/service/integration_enterprise_test.go:11695
• server/service/integration_enterprise_test.go:11703
• server/service/integration_enterprise_test.go:11709
• server/service/integration_enterprise_test.go:11713

Apply this diff:

@@ server/service/integration_enterprise_test.go:11690
-           InstallDuringSetup: optjson.Bool{Set: true},
+           InstallDuringSetup: optjson.Bool{Set: true, Value: true},

@@ server/service/integration_enterprise_test.go:11698
-           InstallDuringSetup: optjson.Bool{Set: true},
+           InstallDuringSetup: optjson.Bool{Set: true, Value: true},

@@ server/service/integration_enterprise_test.go:11707
-           InstallDuringSetup: optjson.Bool{Set: true},
+           InstallDuringSetup: optjson.Bool{Set: true, Value: true},

@@ server/service/integration_enterprise_test.go:11711
-           InstallDuringSetup: optjson.Bool{Set: true},
+           InstallDuringSetup: optjson.Bool{Set: true, Value: true},
📝 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
URL: "http://foo.com",
SelfService: true,
InstallScript: fleet.TeamSpecSoftwareAsset{Path: "./foo/install-script.sh"},
PostInstallScript: fleet.TeamSpecSoftwareAsset{Path: "./foo/post-install-script.sh"},
PreInstallQuery: fleet.TeamSpecSoftwareAsset{Path: "./foo/query.yaml"},
InstallDuringSetup: optjson.Bool{Set: true},
},
{
URL: "http://bar.com",
SelfService: false,
InstallScript: fleet.TeamSpecSoftwareAsset{Path: "./bar/install-script.sh"},
PostInstallScript: fleet.TeamSpecSoftwareAsset{Path: "./bar/post-install-script.sh"},
PreInstallQuery: fleet.TeamSpecSoftwareAsset{Path: "./bar/query.yaml"},
URL: "http://bar.com",
SelfService: false,
InstallScript: fleet.TeamSpecSoftwareAsset{Path: "./bar/install-script.sh"},
PostInstallScript: fleet.TeamSpecSoftwareAsset{Path: "./bar/post-install-script.sh"},
PreInstallQuery: fleet.TeamSpecSoftwareAsset{Path: "./bar/query.yaml"},
InstallDuringSetup: optjson.Bool{Set: true},
},
}
wantAppStoreApps := []fleet.TeamSpecAppStoreApp{
{
AppStoreID: "1234",
AppStoreID: "1234",
InstallDuringSetup: optjson.Bool{Set: true},
},
{
AppStoreID: "5678",
AppStoreID: "5678",
InstallDuringSetup: optjson.Bool{Set: true},
},
URL: "http://foo.com",
SelfService: true,
InstallScript: fleet.TeamSpecSoftwareAsset{Path: "./foo/install-script.sh"},
PostInstallScript: fleet.TeamSpecSoftwareAsset{Path: "./foo/post-install-script.sh"},
PreInstallQuery: fleet.TeamSpecSoftwareAsset{Path: "./foo/query.yaml"},
InstallDuringSetup: optjson.Bool{Set: true, Value: true},
},
{
URL: "http://bar.com",
SelfService: false,
InstallScript: fleet.TeamSpecSoftwareAsset{Path: "./bar/install-script.sh"},
PostInstallScript: fleet.TeamSpecSoftwareAsset{Path: "./bar/post-install-script.sh"},
PreInstallQuery: fleet.TeamSpecSoftwareAsset{Path: "./bar/query.yaml"},
InstallDuringSetup: optjson.Bool{Set: true, Value: true},
},
}
wantAppStoreApps := []fleet.TeamSpecAppStoreApp{
{
AppStoreID: "1234",
InstallDuringSetup: optjson.Bool{Set: true, Value: true},
},
{
AppStoreID: "5678",
InstallDuringSetup: optjson.Bool{Set: true, Value: true},
},
🤖 Prompt for AI Agents
In server/service/integration_enterprise_test.go around lines 11690–11714 the
tests set InstallDuringSetup using optjson.Bool{Set: true} which leaves Value
false by default; update the four occurrences at lines 11695, 11703, 11709, and
11713 to explicitly set Value: true (i.e., optjson.Bool{Set: true, Value: true})
so the flag is actually true in the test objects.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This semantically changes the result, though what I actually wanted here was Valid: true I believe. I didn't want to turn on setup experience actually being true as part of this.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Actually, scratch that. set: true is always true for packages from YAML, but Valid is false unless the value is provided explicitly. So Set being true here is the correct thing to expect given how optjson works, and coderabbit is off base here.

Comment thread server/fleet/software_installer.go
@ksykulev

Copy link
Copy Markdown
Contributor

@iansltx I assume we don't need to apply the coderabbit suggestions?

@iansltx

iansltx commented Aug 30, 2025

Copy link
Copy Markdown
Contributor Author

@ksykulev CodeRabbit suggestions are hit or miss on accuracy, but some of them are actually good advice (chatted with @Illbjorn on this earlier today). Going to merge this and then follow up with the additional tweaks.

@iansltx
iansltx merged commit 69c4902 into 30095-gitops-changes Aug 30, 2025
39 of 42 checks passed
@iansltx
iansltx deleted the 31163-bitops-changes branch August 30, 2025 01:03
iansltx added a commit that referenced this pull request Aug 30, 2025
iansltx added a commit that referenced this pull request Sep 2, 2025
…to team level in GitOps (#32245)

Fixes #31163.

This also enables setup experience inclusion for FMAs. Includes checks
and errors for invalid states.

# Checklist for submitter

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

- [x] Changes file added for user-visible changes in `changes/`,
`orbit/changes/` or `ee/fleetd-chrome/changes`.
See [Changes
files](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/guides/committing-changes.md#changes-files)
for more information.

- [x] Input data is properly validated, `SELECT *` is avoided, SQL
injection is prevented (using placeholders for values in statements)

## Testing

- [x] Added/updated automated tests

- [x] QA'd all new/changed functionality manually

## New Fleet configuration settings
- [x] Verified that the setting is exported via `fleetctl
generate-gitops`
- [x] Verified the setting is documented in a separate PR to [the GitOps
documentation](https://github.com/fleetdm/fleet/blob/main/docs/Configuration/yaml-files.md#L485)
- [x] Verified that the setting is cleared on the server if it is not
supplied in a YAML file (or that it is documented as being optional)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- New Features
- Team-level control of software settings in GitOps: setup experience
(install during setup), self-service, labels (include/exclude), and
categories for packages and App Store apps.
- Team-level settings are applied to individual packages automatically.
- Validation
- Clear errors when setup experience is defined in multiple places
(software/app vs. macOS setup).
- Documentation
  - Removed outdated note about GitOps-generated outputs.
- Tests
- Added test cases covering valid team-level configuration and
conflicting placement scenarios.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Anthony Maxwell <133805840+Illbjorn@users.noreply.github.com>
iansltx added a commit that referenced this pull request Sep 3, 2025
iansltx added a commit that referenced this pull request Sep 3, 2025
…pdate work (#32482)

# Checklist for submitter

- [x] Added/updated automated tests
@coderabbitai coderabbitai Bot mentioned this pull request Jan 6, 2026
2 tasks
@coderabbitai coderabbitai Bot mentioned this pull request Mar 4, 2026
14 tasks
@coderabbitai coderabbitai Bot mentioned this pull request Apr 23, 2026
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.

3 participants