Skip to content

Don't throw gitops-exceptions-related errors on Free tier - #44118

Merged
sgress454 merged 3 commits into
mainfrom
sgress454/ignore-exceptions-on-free-tier
Apr 24, 2026
Merged

Don't throw gitops-exceptions-related errors on Free tier#44118
sgress454 merged 3 commits into
mainfrom
sgress454/ignore-exceptions-on-free-tier

Conversation

@sgress454

@sgress454 sgress454 commented Apr 24, 2026

Copy link
Copy Markdown
Contributor

Related issue: Resolves #44098

Details

We set the "secrets" exception on for all new instances (and the label exception for existing instances), but you can't turn them off in the free tier. That means GitOps runs (including the one we use to initialize new instances) would fail with the "you can't use this key because the exception is on" error. This PR fixes the issue by not enforcing that rule for free tier instances.

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.
    n/a, unreleased

Testing

  • Added/updated automated tests
    • added test verifying that the free tier can run gitops using excepted keys w/out error, and verified that it fails on main and passes on this branch
  • QA'd all new/changed functionality manually
    • spun up a new free-tier server successfully

For unreleased bug fixes in a release candidate, one of:

  • Confirmed that the fix is not expected to adversely impact load test results

Summary by CodeRabbit

  • Bug Fixes
    • GitOps exception enforcement no longer blocks free-tier users; enforcement is applied only for premium licenses, allowing GitOps applies on free tiers.
  • Tests
    • Added an integration test validating free-tier GitOps behavior to prevent regressions.

Copilot AI review requested due to automatic review settings April 24, 2026 14:29
@sgress454
sgress454 requested a review from a team as a code owner April 24, 2026 14:29

@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 and subscribe this PR to future pushes, or @claude review once for a one-time review.

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

@coderabbitai

coderabbitai Bot commented Apr 24, 2026

Copy link
Copy Markdown
Contributor

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: 73feffff-dabd-4843-8138-90a929d2f903

📥 Commits

Reviewing files that changed from the base of the PR and between 47fdefa and 64eb33a.

📒 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

GitOps exception enforcement for YAML-present keys (labels, secrets, software) is no longer applied unconditionally when appConfig exists. The code now reads appConfig.GitOpsConfig.Exceptions but only performs the “key is excepted → return error” checks when appConfig.License.IsPremium() is true; non‑premium requests skip those enforcement errors. Existing error messages and the downstream delete-no-op behavior (based on exceptions.* and key absence) are unchanged. An integration test was added to validate free‑tier behavior when persisted exception flags exist.

Possibly related PRs

  • fleetdm/fleet PR 42013: Introduced/modified GitOps exceptions handling and related server-side code paths that this change builds on.
  • fleetdm/fleet PR 42191: Adjusts GitOps exception enforcement and tests in the same server/service/client.go and gitops test areas touched by this change.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% 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 clearly and concisely summarizes the main change: GitOps exception errors should not be enforced on Free tier instances.
Description check ✅ Passed The description is mostly complete with context about the problem, the fix, and testing confirmation, though some optional template sections were appropriately deleted.
Linked Issues check ✅ Passed The PR successfully addresses #44098 by conditionally enforcing GitOps exception rules only on premium tier, allowing free-tier GitOps operations with excepted keys.
Out of Scope Changes check ✅ Passed All changes directly address the issue of preventing GitOps exception enforcement errors on free-tier instances; no out-of-scope modifications detected.

✏️ 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/ignore-exceptions-on-free-tier

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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
server/service/client.go (1)

1906-1925: ⚠️ Potential issue | 🟠 Major

Gating the entire exceptions block behind IsPremium() silently wipes enroll secrets on Free tier and creates inconsistent exception handling.

The system's migration and default initialization (from server/fleet/app.go:1091 and the migration at 20260323144117_AddGitOpsExceptionsToAppConfig.go:18) explicitly set Exceptions.Secrets = true for all tiers and instances, intending to preserve existing secrets when a GitOps file omits the secrets: key. With this change, the entire exceptions block is gated behind IsPremium(), so on Free tier:

  • exceptions remains zero-valued locally in DoGitOps
  • Lines 1938 and 2179 evaluate !exceptions.Secrets as true
  • If incoming YAML omits secrets:, both conditions are met, and incoming.OrgSettings["secrets"] is overwritten with an empty slice
  • All enroll secrets are deleted at both org and team scope

This contradicts the migration's stated intent to "preserve current implicit behavior" and creates a silent regression for Free-tier users who relied on omitting secrets: to keep existing secrets.

Additionally, line 407 in ApplyGroup reads appconfig.GitOpsConfig.Exceptions.Software without a premium check, creating inconsistency: if exceptions are truly tier-gated, this read should also be gated.

The fix is to load exceptions unconditionally and gate only the error-raising on premium:

Suggested fix
 	var exceptions fleet.GitOpsExceptions
-	if appConfig != nil && appConfig.License.IsPremium() {
+	if appConfig != nil {
 		exceptions = appConfig.GitOpsConfig.Exceptions
-		if exceptions.Labels && incoming.LabelsPresent {
-			return nil, errors.New(
-				`"labels" is excepted from GitOps management. Remove the "labels:" key from your GitOps file or disable the exception in Fleet settings.`)
-		}
-		if exceptions.Secrets && incoming.SecretsPresent {
-			return nil, errors.New(
-				`"secrets" is excepted from GitOps management. Remove the "secrets:" key from your GitOps file or disable the exception in Fleet settings.`)
-		}
-		if exceptions.Software && incoming.SoftwarePresent && incoming.TeamName != nil {
-			return nil, errors.New(
-				`"software" is excepted from GitOps management. Remove the "software:" key from your GitOps file or disable the exception in Fleet settings.`)
+		// Exception enforcement (raising errors) is premium-only, but the "preserve on
+		// absence" semantics are honored on all tiers per the migration's intent to
+		// prevent data loss when Exceptions.Secrets=true on Free tier.
+		if appConfig.License.IsPremium() {
+			if exceptions.Labels && incoming.LabelsPresent {
+				return nil, errors.New(
+					`"labels" is excepted from GitOps management. Remove the "labels:" key from your GitOps file or disable the exception in Fleet settings.`)
+			}
+			if exceptions.Secrets && incoming.SecretsPresent {
+				return nil, errors.New(
+					`"secrets" is excepted from GitOps management. Remove the "secrets:" key from your GitOps file or disable the exception in Fleet settings.`)
+			}
+			if exceptions.Software && incoming.SoftwarePresent && incoming.TeamName != nil {
+				return nil, errors.New(
+					`"software" is excepted from GitOps management. Remove the "software:" key from your GitOps file or disable the exception in Fleet settings.`)
+			}
 		}
 	}

This preserves the tier-gated error feature while maintaining consistent exception-reading logic between DoGitOps and ApplyGroup, and honors the migration's intent to prevent Free-tier secrets loss.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/service/client.go` around lines 1906 - 1925, Do not gate loading of
GitOps exceptions behind IsPremium() in DoGitOps; always set exceptions =
appConfig.GitOpsConfig.Exceptions (if appConfig != nil) so zero-valued migration
defaults like Exceptions.Secrets are respected and secrets are not implicitly
wiped, but only perform the premium-only error checks (the errors that return
when incoming.SecretsPresent/incoming.LabelsPresent/incoming.SoftwarePresent)
when appConfig.License.IsPremium() is true; update logic in DoGitOps to read
exceptions unconditionally and only wrap the error-return branches with
IsPremium() checks (mirroring ApplyGroup which already reads
appconfig.GitOpsConfig.Exceptions without a premium gate) and ensure when
exceptions.Secrets is true you do not overwrite incoming.OrgSettings["secrets"]
or incoming.TeamSettings["secrets"] with empty values.
🧹 Nitpick comments (1)
server/service/client.go (1)

941-949: Consistency nit: softwareExcepted here still reads the exception flag without a license check.

Now that DoGitOps gates all exception handling behind appConfig.License.IsPremium(), ApplyGroup is the lone remaining reader that trusts appconfig.GitOpsConfig.Exceptions.Software on any tier. For a tenant that previously had Software=true persisted (e.g., Premium → Free downgrade), this path will still skip validation in ApplyGroup while DoGitOps will process software normally — a subtle split-brain.

Consider aligning the check so both call sites have the same notion of when exceptions apply:

♻️ Suggested alignment
-		softwareExcepted := viaGitOps && appconfig != nil && appconfig.GitOpsConfig.Exceptions.Software
+		softwareExcepted := viaGitOps && appconfig != nil && appconfig.License.IsPremium() && appconfig.GitOpsConfig.Exceptions.Software
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/service/client.go` around lines 941 - 949, The code uses
softwareExcepted := viaGitOps && appconfig != nil &&
appconfig.GitOpsConfig.Exceptions.Software but does not check the license;
update softwareExcepted to the same gated definition used by DoGitOps (e.g.,
include appconfig.License.IsPremium() in the boolean) so exception logic is
consistent with validateTeamOrNoTeamMacOSSetupSoftware and avoid split-brain
with ApplyGroup/DoGitOps; locate the variable construction (softwareExcepted)
and adjust the condition accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@server/service/client.go`:
- Around line 1906-1925: Do not gate loading of GitOps exceptions behind
IsPremium() in DoGitOps; always set exceptions =
appConfig.GitOpsConfig.Exceptions (if appConfig != nil) so zero-valued migration
defaults like Exceptions.Secrets are respected and secrets are not implicitly
wiped, but only perform the premium-only error checks (the errors that return
when incoming.SecretsPresent/incoming.LabelsPresent/incoming.SoftwarePresent)
when appConfig.License.IsPremium() is true; update logic in DoGitOps to read
exceptions unconditionally and only wrap the error-return branches with
IsPremium() checks (mirroring ApplyGroup which already reads
appconfig.GitOpsConfig.Exceptions without a premium gate) and ensure when
exceptions.Secrets is true you do not overwrite incoming.OrgSettings["secrets"]
or incoming.TeamSettings["secrets"] with empty values.

---

Nitpick comments:
In `@server/service/client.go`:
- Around line 941-949: The code uses softwareExcepted := viaGitOps && appconfig
!= nil && appconfig.GitOpsConfig.Exceptions.Software but does not check the
license; update softwareExcepted to the same gated definition used by DoGitOps
(e.g., include appconfig.License.IsPremium() in the boolean) so exception logic
is consistent with validateTeamOrNoTeamMacOSSetupSoftware and avoid split-brain
with ApplyGroup/DoGitOps; locate the variable construction (softwareExcepted)
and adjust the condition accordingly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 8136417a-0cfc-40f8-8981-3cd5ff9d3887

📥 Commits

Reviewing files that changed from the base of the PR and between 57b5d7e and c02613b.

📒 Files selected for processing (1)
  • server/service/client.go

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

This PR adjusts fleetctl gitops client-side GitOps exception enforcement so that “excepted from GitOps management” validation errors are only triggered for Premium-tier licenses, aligning behavior with Free-tier expectations.

Changes:

  • Gate GitOps exception enforcement behind appConfig.License.IsPremium().
  • Update the inline comment to indicate the enforcement is premium-only.

💡 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 Outdated
@codecov

codecov Bot commented Apr 24, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 66.77%. Comparing base (09987f3) to head (64eb33a).
⚠️ Report is 8 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main   #44118      +/-   ##
==========================================
+ Coverage   63.40%   66.77%   +3.37%     
==========================================
  Files        2628     2628              
  Lines      211127   211158      +31     
  Branches     9536     9536              
==========================================
+ Hits       133856   141002    +7146     
+ Misses      64922    57334    -7588     
- Partials    12349    12822     +473     
Flag Coverage Δ
backend 68.55% <100.00%> (+3.90%) ⬆️

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.

cdcme
cdcme previously approved these changes Apr 24, 2026

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

🧹 Nitpick comments (1)
cmd/fleetctl/fleetctl/gitops_test.go (1)

632-717: LGTM — test correctly validates free-tier bypass of exception enforcement.

The test appropriately mirrors TestGitOpsExceptionEnforcement with inverted assertions for free tier, and the handling of the software/team-file case is pragmatic given free-tier team limitations.

One small nit: the software case at lines 712–716 only verifies the specific exception-enforcement error isn't present, but otherwise accepts any error silently. If the team-file path on free tier ever starts failing for an unexpected new reason, this branch will still pass. Consider documenting the allowed failure modes or capturing the expected error to tighten the contract:

Optional: tighten the software case assertion
 	_, err = RunAppNoChecks([]string{"gitops", "-f", tmpFile3.Name()})
-	// Free tier may reject team files for other reasons, but it must NOT be the exception error.
+	// Free tier rejects team files (teams are premium), but the failure must NOT be the
+	// exception-enforcement error — any other error is acceptable.
 	if err != nil {
 		assert.NotContains(t, err.Error(), `"software" is excepted from GitOps management`)
+		// Sanity check: confirm the failure is the expected premium-gated-teams error,
+		// so regressions in free-tier behavior don't silently pass this test.
+		assert.ErrorContains(t, err, "Teams are available in Fleet Premium")
 	}

(Adjust the expected substring to whatever free-tier currently emits for team files.)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmd/fleetctl/fleetctl/gitops_test.go` around lines 632 - 717, The
software/team-file branch in TestGitOpsExceptionEnforcementFreeTier currently
only checks that the specific exception message is not present and otherwise
ignores any error; update the assertion around the RunAppNoChecks call for
tmpFile3 to more tightly specify allowed outcomes: either require.NoError(t,
err) if free-tier should accept team files, or require.ErrorContains(t, err,
"<expected free-tier team-file error>") combined with assert.NotContains(t,
err.Error(), `"software" is excepted from GitOps management`) to ensure we still
fail if the exception-enforcement message appears; modify the block that
references tmpFile3/RunAppNoChecks accordingly (replace the current if err !=
nil { assert.NotContains... } with the chosen stricter assertion).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@cmd/fleetctl/fleetctl/gitops_test.go`:
- Around line 632-717: The software/team-file branch in
TestGitOpsExceptionEnforcementFreeTier currently only checks that the specific
exception message is not present and otherwise ignores any error; update the
assertion around the RunAppNoChecks call for tmpFile3 to more tightly specify
allowed outcomes: either require.NoError(t, err) if free-tier should accept team
files, or require.ErrorContains(t, err, "<expected free-tier team-file error>")
combined with assert.NotContains(t, err.Error(), `"software" is excepted from
GitOps management`) to ensure we still fail if the exception-enforcement message
appears; modify the block that references tmpFile3/RunAppNoChecks accordingly
(replace the current if err != nil { assert.NotContains... } with the chosen
stricter assertion).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: f89f8cbf-9ed8-4ac7-acb9-01e6f998960d

📥 Commits

Reviewing files that changed from the base of the PR and between c02613b and 47fdefa.

📒 Files selected for processing (1)
  • cmd/fleetctl/fleetctl/gitops_test.go

@sgress454
sgress454 merged commit 9b01710 into main Apr 24, 2026
42 checks passed
@sgress454
sgress454 deleted the sgress454/ignore-exceptions-on-free-tier branch April 24, 2026 15:46
lukeheath pushed a commit that referenced this pull request Apr 24, 2026
AndreyKizimenko pushed a commit that referenced this pull request Apr 24, 2026
<!-- Add the related story/sub-task/bug number, like Resolves #123, or
remove if NA -->
**Related issue:** Resolves #44118 

# Details

On free tier, ignore exceptions and always apply enroll secrets when
present.

# 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](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/guides/committing-changes.md#changes-files)
for more information.
n/a, unreleased

## Testing

- [X] Added/updated automated tests
- [X] QA'd all new/changed functionality manually
  @AndreyKizimenko QA'd manually 

For unreleased bug fixes in a release candidate, one of:

- [X] Confirmed that the fix is not expected to adversely impact load
test results
sgress454 added a commit that referenced this pull request Apr 27, 2026
<!-- Add the related story/sub-task/bug number, like Resolves #123, or
remove if NA -->
**Related issue:** Resolves #44118

# Details

On free tier, ignore exceptions and always apply enroll secrets when
present.

# 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](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/guides/committing-changes.md#changes-files)
for more information.
n/a, unreleased

## Testing

- [X] Added/updated automated tests
- [X] Where appropriate, [automated tests simulate multiple hosts and
test for host
isolation](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/reference/patterns-backend.md#unit-testing)
(updates to one hosts's records do not affect another)
- [X] QA'd all new/changed functionality manually
@AndreyKizimenko QA'd manually

For unreleased bug fixes in a release candidate, one of:

- [X] Confirmed that the fix is not expected to adversely impact load
test results


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

## Summary by CodeRabbit

* **Bug Fixes**
* Fixed GitOps to correctly apply enrollment secrets and labels on free
tier licenses, even when exception flags are configured.

* **Tests**
* Added tests validating that GitOps properly applies secrets and labels
for free tier customers.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
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.

Gitops run fails on Fleet Free instance if secret is configured

4 participants