Skip to content

Enforce GitOps exceptions - #42191

Merged
sgress454 merged 38 commits into
mainfrom
sgress454/gitops-exceptions-pr2-enforcement
Mar 27, 2026
Merged

Enforce GitOps exceptions#42191
sgress454 merged 38 commits into
mainfrom
sgress454/gitops-exceptions-pr2-enforcement

Conversation

@sgress454

@sgress454 sgress454 commented Mar 20, 2026

Copy link
Copy Markdown
Contributor

Related issue: Resolves #42180

Summary by CodeRabbit

  • New Features

    • Enhanced GitOps exception handling for labels, secrets, and software with clearer enforcement and omission semantics.
    • Server-side prefetch of team software so omitted team software can preserve existing installers during validation.
    • Presence flags track whether top-level keys (labels, secrets, software) were provided versus omitted.
  • Behavior Changes

    • Omitted vs empty sections are now distinguished: omission can mean “no-op” or “delete-all” depending on exception settings.
    • GitOps YAML can define and manage labels directly; validations now reject YAML that includes keys marked as excepted.

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.

Testing

  • Added/updated automated tests

  • QA'd all new/changed functionality manually

    • Labels
      • Validated that with label exceptions off, omitting labels: key from default.yml clears all global labels
      • Validated that with label exceptions off, omitting labels: key from a fleet .yml clears all labels for that fleet
      • Validated that with label exceptions off, setting empty labels: key from default.yml clears all global labels
      • Validated that with label exceptions off, setting empty labels: key from a fleet .yml clears all labels for that fleet
      • Validated that with label exceptions on, omitting labels: key from default .yml leaves existing global labels as-is
      • Validated that with label exceptions on, omitting labels: key from a fleet .yml leaves existing labels as-is
      • Validated that with label exceptions on, setting labels: key on default .yml generates an error
      • Validated that with label exceptions on, setting labels: key on a fleet .yml generates an error
      • Validated that with label exceptions on, a policy using labels_include_any referencing an existing label succeeds without labels: key
      • Validated that with label exceptions on, a query using labels_include_any referencing an existing label succeeds without labels: key
      • Validated that with label exceptions on, an MDM profile using labels_include_any referencing an existing label succeeds without labels: key
      • Validated that with label exceptions on, a software package using labels_include_any referencing an existing label succeeds without labels: key (requires software exceptions off)
      • Validated that with label exceptions on, an app store app using labels_include_any referencing an existing label succeeds without labels: key (requires software exceptions off)
      • Validated that with label exceptions on, a fleet maintained app using labels_include_any referencing an existing label succeeds without labels: key (requires software exceptions off)
    • Secrets
      • Validated that with secrets exceptions off, omitting secrets: key from default.yml clears all global secrets
      • Validated that with secrets exceptions off, omitting secrets: key from a fleet .yml clears all secrets for that fleet
      • Validated that with secrets exceptions on, omitting secrets: key from default .yml leaves existing global secrets as-is
      • Validated that with secrets exceptions on, omitting secrets: key from a fleet .yml leaves existing secrets as-is
      • Validated that with secrets exceptions on, setting secrets: key on default .yml generates an error
      • Validated that with secrets exceptions on, setting secrets: key on a fleet .yml generates an error
    • Software
      • Validated that with software exceptions off, omitting software: key from no-team.yml/unassigned.yml clears all software for "no team"
      • Validated that with software exceptions off, omitting software: key from a fleet .yml clears all software for that fleet
      • Validated that with software exceptions off, setting empty software: key on a fleet .yml clears all software for that fleet
      • Validated that with software exceptions off, setting empty software: key on no-team.yml/unassigned.yml clears all software for "no team
      • Validated that with software exceptions on, omitting software: key from a fleet .yml leaves existing software as-is
      • Validated that with software exceptions on, setting software: key on a fleet .yml generates an error
      • Validated that with software exceptions on, omitting software: key from no-team.yml/unassigned.yml leaves existing software as-is for "no team"
      • Validated that with software exceptions on, setting software: key on no-team.yml/unassigned.yml generates an error
      • Validated that with software exceptions on, a policy using install_software.hash_sha256 referencing an existing package succeeds without software: key
      • Validated that with software exceptions on, a policy using install_software.app_store_id referencing an existing VPP app succeeds without software: key
      • Validated that with software exceptions on, a patch policy using fleet_maintained_app_slug referencing an existing FMA succeeds without software: key
      • Validated that with software exceptions on, omitting software: from no-team.yml/unassigned.yml preserves existing no-team software
      • Validated that with software exceptions on, a policy in no-team.yml/unassigned.yml using install_software.hash_sha256 referencing existing no-team software succeeds without software: key
        For unreleased bug fixes in a release candidate, one of:
  • Confirmed that the fix is not expected to adversely impact load test results
    I don't think so. There is a bit of overhead when this feature is used since we have to fetch software from the server, but it would be done in a specific test, so even if there is an impact it should affect existing load testing, only new, specific tests.

@codecov

codecov Bot commented Mar 20, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 80.40201% with 39 lines in your changes missing coverage. Please review.
✅ Project coverage is 68.49%. Comparing base (d6b6ddb) to head (5082ce2).
⚠️ Report is 15 commits behind head on main.

Files with missing lines Patch % Lines
cmd/fleetctl/fleetctl/generate_gitops.go 71.26% 17 Missing and 8 partials ⚠️
cmd/fleetctl/fleetctl/gitops.go 78.26% 5 Missing and 5 partials ⚠️
server/service/client.go 91.30% 2 Missing and 2 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #42191      +/-   ##
==========================================
+ Coverage   66.64%   68.49%   +1.85%     
==========================================
  Files        2532     1506    -1026     
  Lines      202882   175149   -27733     
  Branches     9180        0    -9180     
==========================================
- Hits       135218   119977   -15241     
+ Misses      55430    43014   -12416     
+ Partials    12234    12158      -76     
Flag Coverage Δ
android ?
backend 68.49% <80.40%> (+0.01%) ⬆️
backend-activity ?
fleetd-chrome ?
frontend ?

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.

Base automatically changed from worktree-gitops-exceptions to main March 23, 2026 15:47
@sgress454
sgress454 force-pushed the sgress454/gitops-exceptions-pr2-enforcement branch from 1dad91e to 9a34f73 Compare March 23, 2026 16:22
@sgress454
sgress454 force-pushed the sgress454/gitops-exceptions-pr2-enforcement branch from b385f59 to 21935f8 Compare March 23, 2026 18:05
Comment thread cmd/fleetctl/fleetctl/testing_utils/testing_utils.go
Comment thread cmd/fleetctl/fleetctl/gitops_test.go
Comment thread cmd/fleetctl/fleetctl/gitops_test.go
Comment thread cmd/fleetctl/fleetctl/gitops_test.go
Comment thread cmd/fleetctl/fleetctl/gitops_test.go

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.

same as the non-deprecated: switching test to expect that top-level key omission means 🔪

Comment thread cmd/fleetctl/fleetctl/gitops_test.go
@sgress454
sgress454 marked this pull request as ready for review March 24, 2026 20:59
@sgress454
sgress454 requested a review from a team as a code owner March 24, 2026 20:59
Copilot AI review requested due to automatic review settings March 24, 2026 20:59

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Claude Code Review

This repository is configured for manual code reviews. Comment @claude review to trigger a review.

Tip: disable this comment in your organization's Code Review settings.

@sgress454

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Mar 24, 2026

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.

Copilot AI 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.

Pull request overview

Implements enforcement of GitOps “exceptions” for labels, secrets, and software so that excepted entities cannot be managed via GitOps (error if key is present), and non-excepted entities treat missing keys as “delete all”.

Changes:

  • Track YAML key presence for labels, secrets, and software during GitOps parsing.
  • Enforce exception rules during fleetctl gitops apply (error on present keys; delete-all semantics when not excepted and key omitted).
  • Update/extend unit + integration tests to cover presence tracking and exception behavior.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
server/service/client.go Enforces GitOps exceptions and adjusts how secrets/labels/software are applied/cleared.
pkg/spec/gitops.go Adds presence tracking fields and relaxes software requirement for team files; adds HasChanges() helper.
pkg/spec/gitops_test.go Adds tests validating presence tracking behavior.
cmd/fleetctl/fleetctl/gitops.go Adjusts label-change computation to treat omitted labels as delete-all when not excepted.
cmd/fleetctl/fleetctl/gitops_test.go Adds/updates tests for exception enforcement and omitted-key semantics.
cmd/fleetctl/integrationtest/gitops/*.go Updates integration tests to align with new exception + omitted-key behavior.
cmd/fleetctl/fleetctl/testing_utils/testing_utils.go Extends label mocks to support new label-apply flow.
cmd/fleetctl/fleetctl/testdata/gitops/*.yml Updates GitOps fixtures to include labels where needed.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread server/service/client.go
Comment thread server/service/client.go
Comment thread cmd/fleetctl/fleetctl/gitops.go Outdated
@coderabbitai

coderabbitai Bot commented Mar 24, 2026

Copy link
Copy Markdown
Contributor

Walkthrough

Adds server-side handling and presence tracking for GitOps top-level keys (labels, secrets, software): parsing now records whether each key was present, software parsing can inject server-generated synthetic software per team, and label-change computation gains a processMissingLabels flag to change "no labels specified" semantics. Client/server GitOps flows enforce configured exceptions (error if an excepted key is provided; treat omitted keys as delete-all when exceptions are off). Tests and generation/validation paths updated to exercise these behaviors.

Possibly related PRs

  • gitops changes for team labels #37801: Modifies the same computeLabelChanges helper and team-aware label operations—direct code-level overlap with the computeLabelChanges signature/semantics changes.
  • Make most GitOps top-level optional #41138: Changes GitOps top-level key parsing/semantics (presence vs omission) similar to the added presence flags and parsing adjustments in pkg/spec/gitops.go.
  • Add "exceptions" GitOps config #42013: Implements/enforces GitOps exception handling and wires exception semantics into processing flows, closely related to the new exception enforcement logic here.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 36.36% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Enforce GitOps exceptions' clearly and concisely summarizes the primary change—implementing exception enforcement during GitOps runs.
Linked Issues check ✅ Passed The code changes implement all primary objectives from #42180: exception enforcement for software/secrets/labels, error on excepted keys present, no-op on excepted keys absent, and delete-all on non-excepted keys absent.
Out of Scope Changes check ✅ Passed All changes align with the GitOps exception enforcement objective. Modifications to label handling, software prefetching, test data, and test suites directly support the primary feature without introducing unrelated functionality.
Description check ✅ Passed The PR description includes the issue reference, a comprehensive testing checklist with specific validation scenarios for labels, secrets, and software exceptions, and addresses load testing impact.

✏️ 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 sgress454/gitops-exceptions-pr2-enforcement

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: 4

🧹 Nitpick comments (2)
cmd/fleetctl/fleetctl/testing_utils/testing_utils.go (1)

826-828: Avoid unconditional success in LabelByNameFunc mock.

Returning a label for any name can mask “missing label” paths in tests that rely on AddLabelMocks. Prefer matching the constrained behavior already used in LabelsByNameFunc.

Suggested mock tightening
 ds.LabelByNameFunc = func(ctx context.Context, name string, filter fleet.TeamFilter) (*fleet.Label, error) {
-	return &fleet.Label{ID: 1, Name: name}, nil
+	validLabels := map[string]*fleet.Label{
+		"a": {ID: 1, Name: "a"},
+		"b": {ID: 2, Name: "b"},
+	}
+	lbl, ok := validLabels[name]
+	if !ok {
+		return nil, &notFoundError{}
+	}
+	return lbl, nil
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmd/fleetctl/fleetctl/testing_utils/testing_utils.go` around lines 826 - 828,
The mock for LabelByNameFunc currently returns a label for any name which hides
missing-label behavior; update LabelByNameFunc to mirror the constrained
behavior used in LabelsByNameFunc (and AddLabelMocks) by only returning a
non-nil *fleet.Label when the requested name exists in the test's expected label
set (or matches the same lookup/filters used by LabelsByNameFunc), otherwise
return (nil, sql.ErrNoRows) or a not-found error so tests exercising
missing-label paths behave correctly.
cmd/fleetctl/integrationtest/gitops/gitops_enterprise_integration_test.go (1)

135-137: Avoid leaking GitOps exception state across tests.

SetupSuite now disables exceptions on the shared server, and the comment explicitly expects some tests to re-enable them. Without a reset in TearDownTest or a helper with t.Cleanup, any exception-on case will leak config into later tests and make the suite order-dependent.

♻️ One simple reset point
 func (s *enterpriseIntegrationGitopsTestSuite) TearDownTest() {
 	t := s.T()
 	ctx := context.Background()
+
+	appConf, err := s.DS.AppConfig(ctx)
+	require.NoError(t, err)
+	appConf.GitOpsConfig.Exceptions = fleet.GitOpsExceptions{}
+	require.NoError(t, s.DS.SaveAppConfig(ctx, appConf))
 
 	mysql.ExecAdhocSQL(t, s.DS, func(q sqlx.ExtContext) error {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmd/fleetctl/integrationtest/gitops/gitops_enterprise_integration_test.go`
around lines 135 - 137, SetupSuite currently sets
appConf.GitOpsConfig.Exceptions = fleet.GitOpsExceptions{} which can be mutated
by tests and leak into others; add a reset to restore a known default after each
test — either modify TearDownTest to set appConf.GitOpsConfig.Exceptions =
fleet.GitOpsExceptions{} (or the original saved value) or ensure tests that
change exceptions call t.Cleanup to restore the original value; reference the
SetupSuite mutation and the TearDownTest helper to implement the per-test reset.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@cmd/fleetctl/fleetctl/gitops_test.go`:
- Around line 790-845: The tests currently only check populated `labels:` and
`secrets:` blocks; add additional bare-key YAML cases mirroring the `software:`
test so presence-only (null) keys are rejected too. In gitops_test.go, create
temp files like tmpFile and tmpFile2 but write YAML that contains only `labels:`
(no items) and only `secrets:` respectively, then call
RunAppNoChecks([]string{"gitops", "-f", tmpFileX.Name()}) and assert an error
contains `"labels" is excepted from GitOps management` and `"secrets" is
excepted from GitOps management` to match the existing `software` bare-key
check.
- Around line 913-931: The test currently only inspects deletedLabels and
appliedSecrets which can be misleading (nil can mean "never called" or "called
with nil"); change the mocks to track explicit invocations/counters and
parameter captures for ApplyLabelSpecsWithAuthorFunc, SetAsideLabelsFunc,
DeleteLabelFunc, LabelsByNameFunc, and ApplyEnrollSecretsFunc (e.g., boolean
flags or call counters and saved args) and assert those flags/counters remain
zero/false and saved args unchanged after the operation so the test verifies
that no write-path functions were called or mutated; also apply the same
explicit invocation assertions for the corresponding mocks referenced around
lines 1029-1032.

In `@pkg/spec/gitops.go`:
- Around line 1735-1747: The code currently returns early when the "software"
key is absent which clears result.Software and prevents downstream resolution
from inheriting server-side software; instead, when softwareRaw is not present
(ok == false) you should not return early—set result.SoftwarePresent = false but
leave result.Software untouched (or mark it to mean "inherit server state") and
continue, so downstream logic that resolves titles/IDs and exception-mode
behavior will treat omitted software as "preserve existing" rather than "delete
all"; adjust the branch around result.global(), the ok checks, and the return of
multiError accordingly (use the existing symbols softwareRaw,
result.SoftwarePresent, result.global(), multiError) so only explicit "software:
null" or non-exception cases cause deletion or errors.

In `@server/service/client.go`:
- Around line 2141-2147: The code currently skips populating team["software"]
only for regular teams but still calls doGitOpsNoTeamSetupAndSoftware (via
gitopsCommand) for the no-team/unassigned path, allowing an empty software
payload to wipe Unassigned when exceptions.Software is true; update the logic so
that when exceptions.Software is true you do not call or synthesize the no-team
software path: add a guard around the call to doGitOpsNoTeamSetupAndSoftware (or
inside gitopsCommand) that checks exceptions.Software and skips creating/sending
an empty software payload for Unassigned, or alternatively have
doGitOpsNoTeamSetupAndSoftware return early if exceptions.Software is true to
avoid touching team["software"] for the Unassigned/no-team case.

---

Nitpick comments:
In `@cmd/fleetctl/fleetctl/testing_utils/testing_utils.go`:
- Around line 826-828: The mock for LabelByNameFunc currently returns a label
for any name which hides missing-label behavior; update LabelByNameFunc to
mirror the constrained behavior used in LabelsByNameFunc (and AddLabelMocks) by
only returning a non-nil *fleet.Label when the requested name exists in the
test's expected label set (or matches the same lookup/filters used by
LabelsByNameFunc), otherwise return (nil, sql.ErrNoRows) or a not-found error so
tests exercising missing-label paths behave correctly.

In `@cmd/fleetctl/integrationtest/gitops/gitops_enterprise_integration_test.go`:
- Around line 135-137: SetupSuite currently sets appConf.GitOpsConfig.Exceptions
= fleet.GitOpsExceptions{} which can be mutated by tests and leak into others;
add a reset to restore a known default after each test — either modify
TearDownTest to set appConf.GitOpsConfig.Exceptions = fleet.GitOpsExceptions{}
(or the original saved value) or ensure tests that change exceptions call
t.Cleanup to restore the original value; reference the SetupSuite mutation and
the TearDownTest helper to implement the per-test reset.
🪄 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: 2eb4f006-1b3a-4654-b552-89185c1bdf89

📥 Commits

Reviewing files that changed from the base of the PR and between 6816a82 and 098a805.

📒 Files selected for processing (11)
  • cmd/fleetctl/fleetctl/gitops.go
  • cmd/fleetctl/fleetctl/gitops_test.go
  • cmd/fleetctl/fleetctl/testdata/gitops/global_macos_windows_custom_settings_valid.yml
  • cmd/fleetctl/fleetctl/testing_utils/testing_utils.go
  • cmd/fleetctl/integrationtest/gitops/gitops_enterprise_integration_deprecated_test.go
  • cmd/fleetctl/integrationtest/gitops/gitops_enterprise_integration_test.go
  • cmd/fleetctl/integrationtest/gitops/gitops_integration_test.go
  • cmd/fleetctl/integrationtest/gitops/software_test.go
  • pkg/spec/gitops.go
  • pkg/spec/gitops_test.go
  • server/service/client.go

Comment thread cmd/fleetctl/fleetctl/gitops_test.go
Comment thread cmd/fleetctl/fleetctl/gitops_test.go Outdated
Comment thread pkg/spec/gitops.go
Comment thread server/service/client.go
@sgress454

Copy link
Copy Markdown
Contributor Author

@iansltx some valid-seeming pickups from coderabbit, looking now

@sgress454
sgress454 force-pushed the sgress454/gitops-exceptions-pr2-enforcement branch from 489afd5 to 2e87777 Compare March 27, 2026 15:56
@sgress454
sgress454 force-pushed the sgress454/gitops-exceptions-pr2-enforcement branch from ee98099 to 2721898 Compare March 27, 2026 16:33
Comment thread cmd/fleetctl/fleetctl/gitops.go Outdated
}
// When labels are excepted and the key is omitted, preserve
// existing labels (no-op). Otherwise delete/update as normal.
preserveLabels := appConfig.GitOpsConfig.Exceptions.Labels && !config.LabelsPresent

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.

Seems like the config.LabelsPresent check here doesn't really do anything for us, because if the exception is set then labels being present will error in doGitops. We do get different label ops out of this, but GitOps will fail anyway.

Can probably leave this as-is but I had to do an LLM-assisted trace through to figure out why we'd care about the one case where config.LabelsPresent would matter for preserveLabels calculation.

iansltx
iansltx previously approved these changes Mar 27, 2026

@iansltx iansltx 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.

Before merging this, please make sure the Fleet Free scenario I mentioned is good to go. Also, might be worth editing the comment for clarity on the preserveLabels bit; if you do that, happy to re-approve after that's done.

@sgress454
sgress454 force-pushed the sgress454/gitops-exceptions-pr2-enforcement branch from 2721898 to 5082ce2 Compare March 27, 2026 19:50
@sgress454
sgress454 merged commit 6598b60 into main Mar 27, 2026
52 of 53 checks passed
@sgress454
sgress454 deleted the sgress454/gitops-exceptions-pr2-enforcement branch March 27, 2026 20:38
kitzy added a commit that referenced this pull request Aug 5, 2026
Omitting the labels key deletes existing labels unless the labels
exception is enabled (see computeLabelChanges). The previous text said
omission preserved them, which stopped being true in #42191.
rachaelshaw pushed a commit that referenced this pull request Aug 6, 2026
…ons (#50552)

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

Two published pages still describe pre-4.84 behavior, telling users that
GitOps mode doesn't restrict label editing in the UI. Since 4.84 that's
only true when the labels exception is enabled. This PR corrects both
pages and documents the exceptions framework.

**`docs/Configuration/yaml-files.md`**
- `labels` section: rewrote the note around the two exception states.
Beyond the sentence the issue flagged, the premise it rested on was also
stale: the note said omitting the `labels` key leaves existing labels
intact. Since #42191, `computeLabelChanges`
(`cmd/fleetctl/fleetctl/gitops.go:947`) branches on
`len(specifiedLabels) == 0`, so omitting the key deletes every custom
label in that scope unless the labels exception is enabled. Its own
tests name this behavior ("labels omitted removes all regular labels
when not excepted"). The note now spells out both states and fixes a
`label` / `labels` typo.
- `gitops` section: added a note that exceptions can't be set in YAML.
`Client.DoGitOps` strips the `exceptions` key defensively
(`server/service/client.go:726`), so this was worth stating explicitly.

**`articles/gitops-mode.md`**
- Added an "Exceptions" section covering the three exception types, what
an exception does to both the UI and `fleetctl gitops`, and the enroll
secrets default. Upgrade behavior is left to the release notes. It notes
that exceptions affect `fleetctl gitops` whether or not GitOps mode is
on, since neither the apply-path check nor `computeLabelChanges` reads
`gitops_mode_enabled`.
- "Still available" no longer lists "Add and edit labels"
unconditionally. It now points at the exceptions section for labels,
software, and enroll secrets.

Behavior the docs now match:
- UI gating is `GitOpsModeTooltipWrapper` with `entityType="labels"`
(`frontend/pages/labels/components/LabelForm/LabelForm.tsx:172`,
`NewLabelPage.tsx:676`, `HostsFilterBlock.tsx:223`). `useGitOpsMode`
treats an enabled exception as GitOps mode being off for that entity.
- Apply-path enforcement is in `server/service/client.go:2219-2242`
(premium only).
- Defaults: `server/fleet/app.go:1216` for new installs, migration
`20260323144117_AddGitOpsExceptionsToAppConfig.go` for upgrades.

The backend is unchanged and was already correct. `ModifyLabel` applies
no GitOps check, and the per-host label endpoints stay available
regardless of GitOps mode or exception state, so this PR is docs-only.

# Checklist for submitter

- [x] Changes file added for user-visible changes in `changes/`,
`orbit/changes/` or `ee/fleetd-chrome/changes`.
  Not applicable: documentation-only change, no product behavior change.

## Testing

- [x] QA'd all new/changed functionality manually
Verified the described behavior against the UI gating, the `fleetctl
gitops` apply path, and the exception defaults in code (references
above).
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.

Enforce exceptions in GitOps

3 participants