Skip to content

GitOps: combined include/exclude policy label targeting + labels_exclude_all (#33441)#47505

Merged
nulmete merged 7 commits into
mainfrom
33441-policies-custom-targets-gitops
Jun 15, 2026
Merged

GitOps: combined include/exclude policy label targeting + labels_exclude_all (#33441)#47505
nulmete merged 7 commits into
mainfrom
33441-policies-custom-targets-gitops

Conversation

@nulmete

@nulmete nulmete commented Jun 12, 2026

Copy link
Copy Markdown
Member

Related issue: Resolves #46584

Checklist for submitter

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

Testing

  • Added/updated automated tests

  • QA'd all new/changed functionality manually

generate-gitops:

Screen.Recording.2026-06-12.at.9.29.29.AM.mov

gitops:

Screen.Recording.2026-06-12.at.9.31.58.AM.mov

Summary by CodeRabbit

  • New Features

    • Added labels_exclude_all field support for refining policy label scopes (available with Fleet Premium license).
  • Bug Fixes

    • Enhanced validation of policy label scope configurations to prevent invalid field combinations and enforce license requirements.

@nulmete nulmete marked this pull request as ready for review June 12, 2026 12:13
@nulmete nulmete requested a review from a team as a code owner June 12, 2026 12:13
@codecov

codecov Bot commented Jun 12, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 83.33333% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 67.18%. Comparing base (32e5342) to head (c4d946a).
⚠️ Report is 2 commits behind head on main.

Files with missing lines Patch % Lines
cmd/fleetctl/fleetctl/gitops.go 75.00% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #47505      +/-   ##
==========================================
- Coverage   67.18%   67.18%   -0.01%     
==========================================
  Files        3547     3547              
  Lines      228820   228820              
  Branches    11872    11872              
==========================================
- Hits       153728   153726       -2     
  Misses      61247    61247              
- Partials    13845    13847       +2     
Flag Coverage Δ
backend 68.82% <83.33%> (-0.01%) ⬇️

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

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

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

@@ -1056,21 +1056,20 @@ func getLabelUsage(config *spec.GitOps) (map[string][]LabelUsage, error) {

// Get policy label usage.
for _, policy := range config.Policies {

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.

This whole block can be simplified if you use the fleet.VerifyPolicyLabelScopes validator here, no?

@juan-fdz-hawa

Copy link
Copy Markdown
Contributor

Could we add a pre-flight check in here to make sure non-premium users can't use policy.LabelsExcludeAll ?

@nulmete nulmete force-pushed the 33441-policies-custom-targets-gitops branch from 0c7b9c9 to 877723f Compare June 12, 2026 17:57
Base automatically changed from 33441-policies-custom-targets-backend to main June 12, 2026 18:05
nulmete added 7 commits June 12, 2026 15:07
…ude_all

Update fleetctl gitops label-usage validation to allow one include scope
(any/all) combined with one exclude scope (any/all) — rejecting two include
scopes, two exclude scopes, and overlapping labels (via fleet.LabelOverlap).
Emit labels_exclude_all in generate-gitops output (premium-gated, mirroring
labels_include_all). Adds validation + emission tests.

For #33441.
Exercises the real GitOps apply path: a global policy combining
labels_include_any with the premium labels_exclude_all scope, a team policy
combining labels_include_all with labels_exclude_any, and rejection of a
policy that declares two include scopes.
getLabelUsage now validates policy label scopes via PolicySpec.VerifyLabelScopes
instead of a hand-rolled mutex/overlap block, and a non-premium pre-flight check
rejects policies using the premium labels_include_all / labels_exclude_all scopes.
Matches PolicySpec.VerifyLabelScopes so both policy types expose label-scope
validation under the same descriptive name (PolicySpec.Verify is already taken
by full validation).
@nulmete nulmete force-pushed the 33441-policies-custom-targets-gitops branch from 877723f to c4d946a Compare June 12, 2026 18:08
@nulmete nulmete requested a review from juan-fdz-hawa June 12, 2026 18:09
@coderabbitai

coderabbitai Bot commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

This PR adds support for the labels_exclude_all policy label-scope field in GitOps with Fleet Premium license enforcement. The change refactors policy label validation from Verify() to a focused VerifyLabelScopes() method, adds premium license checks in GitOps for both labels_include_all and labels_exclude_all, updates label usage tracking to include all scope types, and conditionally serializes labels_exclude_all only for premium licenses. The implementation includes comprehensive test coverage for validation enforcement, free-tier rejection, and valid/invalid label scope combinations.

Possibly related PRs

  • fleetdm/fleet#47213: Adds frontend form payload and UI support for the labels_exclude_all policy label-scope field that this PR wires on the backend and GitOps side.
  • fleetdm/fleet#46437: Extends getLabelUsage logic in gitops.go to incorporate additional label-scope fields, similar to how this PR adds labels_exclude_all tracking to the label usage computation.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 12.50% 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 summarizes the main changes: adding combined include/exclude policy label targeting with labels_exclude_all support for GitOps.
Description check ✅ Passed The description covers the linked issue, testing (automated and manual QA), and includes video evidence, but lacks detail on GitOps documentation and some database/security considerations.
Linked Issues check ✅ Passed The PR addresses #46584 by implementing GitOps changes for custom label targets: validating combined include/exclude scopes, emitting labels_exclude_all in generate-gitops (premium-gated), and adding comprehensive tests.
Out of Scope Changes check ✅ Passed All changes are within scope: policy label scope validation refactoring, labels_exclude_all emission, test updates, and method renaming (Verify to VerifyLabelScopes) directly support the linked issue objectives.

✏️ 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 33441-policies-custom-targets-gitops

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.

🧹 Nitpick comments (2)
cmd/fleetctl/integrationtest/gitops/gitops_enterprise_integration_test.go (2)

5082-5082: 💤 Low value

Consider adding dry-run before the real run for consistency.

Most tests in this file perform a dry-run before the real run (e.g., lines 1004-1005, 1141-1142, 1178-1181). Adding a dry-run assertion here would be consistent with the established pattern and provide an extra validation layer.

s.assertDryRunOutput(t, fleetctltest.RunAppForTest(t, []string{"gitops", "--config", fleetctlConfig.Name(), "-f", globalFile, "-f", teamFile, "--dry-run"}))
s.assertRealRunOutput(t, fleetctltest.RunAppForTest(t, []string{"gitops", "--config", fleetctlConfig.Name(), "-f", globalFile, "-f", teamFile}))
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/fleetctl/integrationtest/gitops/gitops_enterprise_integration_test.go` at
line 5082, Add a dry-run assertion prior to the real run to match the pattern
used elsewhere: call s.assertDryRunOutput with the same
fleetctltest.RunAppForTest invocation but include the "--dry-run" flag, then
keep the existing s.assertRealRunOutput call unchanged; this uses the same
helper functions s.assertDryRunOutput, s.assertRealRunOutput and
fleetctltest.RunAppForTest so you only need to insert the dry-run invocation
before the existing real-run line.

5112-5143: 💤 Low value

Consider adding test coverage for conflicting exclude scopes.

The test currently verifies rejection of two include scopes (lines 5133-5137), but doesn't test the analogous case of two exclude scopes. For completeness, you could add a test case with both labels_exclude_any and labels_exclude_all to verify that ErrPolicyConflictingExcludeLabels is properly enforced.

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

In `@cmd/fleetctl/integrationtest/gitops/gitops_enterprise_integration_test.go`
around lines 5112 - 5143, Add a parallel negative test for conflicting exclude
scopes: create a new YAML string (e.g., conflictExcludeConfig) modeled after
conflictConfig but using labels_exclude_any and labels_exclude_all together,
write it to a file (like conflictExcludeFile similar to conflictFile), call
fleetctltest.RunAppNoChecks with that file, and assert the run returns an error
containing the exclude-conflict message or constant (e.g.,
require.ErrorContains(t, err, "at most one of labels_exclude_any or
labels_exclude_all") or the ErrPolicyConflictingExcludeLabels symbol) using the
same patterns (os.WriteFile, require.NoError, RunAppNoChecks,
require.ErrorContains) as the existing include-scope test.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@cmd/fleetctl/integrationtest/gitops/gitops_enterprise_integration_test.go`:
- Line 5082: Add a dry-run assertion prior to the real run to match the pattern
used elsewhere: call s.assertDryRunOutput with the same
fleetctltest.RunAppForTest invocation but include the "--dry-run" flag, then
keep the existing s.assertRealRunOutput call unchanged; this uses the same
helper functions s.assertDryRunOutput, s.assertRealRunOutput and
fleetctltest.RunAppForTest so you only need to insert the dry-run invocation
before the existing real-run line.
- Around line 5112-5143: Add a parallel negative test for conflicting exclude
scopes: create a new YAML string (e.g., conflictExcludeConfig) modeled after
conflictConfig but using labels_exclude_any and labels_exclude_all together,
write it to a file (like conflictExcludeFile similar to conflictFile), call
fleetctltest.RunAppNoChecks with that file, and assert the run returns an error
containing the exclude-conflict message or constant (e.g.,
require.ErrorContains(t, err, "at most one of labels_exclude_any or
labels_exclude_all") or the ErrPolicyConflictingExcludeLabels symbol) using the
same patterns (os.WriteFile, require.NoError, RunAppNoChecks,
require.ErrorContains) as the existing include-scope test.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: aa6479b1-ec61-4930-a3ec-faa2e1a7f729

📥 Commits

Reviewing files that changed from the base of the PR and between 32e5342 and c4d946a.

📒 Files selected for processing (9)
  • cmd/fleetctl/fleetctl/generate_gitops.go
  • cmd/fleetctl/fleetctl/generate_gitops_test.go
  • cmd/fleetctl/fleetctl/gitops.go
  • cmd/fleetctl/fleetctl/gitops_test.go
  • cmd/fleetctl/fleetctl/testdata/generateGitops/expectedGlobalPolicies.yaml
  • cmd/fleetctl/fleetctl/testdata/generateGitops/test_dir_premium/default.yml
  • cmd/fleetctl/integrationtest/gitops/gitops_enterprise_integration_test.go
  • server/datastore/mysql/policies.go
  • server/fleet/policies.go

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

CI Feedback 🧐

A test triggered by this PR failed. Here is an AI-generated analysis of the failure:

Action: aggregate-result

Failed stage: Check for failures [❌]

Failed test name: integration-enterprise-mysql8.0.44

Failure summary:

The action failed because the workflow aggregated per-job status artifacts and detected a failing
test job:
- The integration-enterprise-mysql8.0.44-status/status file contained fail, so the script
marked integration-enterprise-mysql8.0.44 as failed.
- The aggregator then exited with code 1 at the
check if [[ -n "$failed_tests" ]]; then ... exit 1, producing: One or more test jobs failed:
integration-enterprise-mysql8.0.44.

Relevant error logs:
1:  ##[group]Runner Image Provisioner
2:  Hosted Compute Agent
...

124:  Artifact download completed successfully.
125:  Extracting artifact entry: /home/runner/work/fleet/fleet/mysql-mysql8.0.44-status/status
126:  Artifact download completed successfully.
127:  Extracting artifact entry: /home/runner/work/fleet/fleet/vuln-mysql8.0.44-status/status
128:  Artifact download completed successfully.
129:  Extracting artifact entry: /home/runner/work/fleet/fleet/service-mysql8.0.44-status/status
130:  Artifact download completed successfully.
131:  Extracting artifact entry: /home/runner/work/fleet/fleet/integration-core-mysql8.0.44-status/status
132:  Artifact download completed successfully.
133:  Extracting artifact entry: /home/runner/work/fleet/fleet/fast-status/status
134:  Artifact download completed successfully.
135:  Extracting artifact entry: /home/runner/work/fleet/fleet/scripts-status/status
136:  Artifact download completed successfully.
137:  Total of 10 artifact(s) downloaded
138:  Download artifact has finished successfully
139:  ##[group]Run failed_tests=""
140:  �[36;1mfailed_tests=""�[0m
141:  �[36;1mstatus_count=0�[0m
142:  �[36;1m# Find all status files (they are in directories like 'fleetctl-mysql8.0.44-status/status')�[0m
143:  �[36;1mfor status_file in $(find ./ -type f -name 'status'); do�[0m
144:  �[36;1m  status_count=$((status_count + 1))�[0m
145:  �[36;1m  # Extract test name from parent directory (e.g., 'fleetctl-mysql8.0.44-status')�[0m
146:  �[36;1m  test_dir=$(basename $(dirname "$status_file"))�[0m
147:  �[36;1m  # Remove '-status' suffix to get the test name�[0m
148:  �[36;1m  test_name="${test_dir%-status}"�[0m
149:  �[36;1m  status_content=$(cat "$status_file")�[0m
150:  �[36;1m  echo "Processing: $status_file (Test: $test_name) with status content: $status_content"�[0m
151:  �[36;1m  if grep -q "fail" "$status_file"; then�[0m
152:  �[36;1m    echo "  ❌ Test failed: $test_name"�[0m
153:  �[36;1m    failed_tests="${failed_tests}${test_name}, "�[0m
154:  �[36;1m  else�[0m
155:  �[36;1m    echo "  ✅ Test passed: $test_name"�[0m
156:  �[36;1m  fi�[0m
157:  �[36;1mdone�[0m
158:  �[36;1mif [[ $status_count -eq 0 ]]; then�[0m
159:  �[36;1m  echo "❌ ERROR: No status files found! This indicates a workflow issue."�[0m
160:  �[36;1m  exit 1�[0m
161:  �[36;1mfi�[0m
162:  �[36;1mif [[ -n "$failed_tests" ]]; then�[0m
163:  �[36;1m  echo "❌ One or more test jobs failed: ${failed_tests%, }"�[0m
164:  �[36;1m  exit 1�[0m
165:  �[36;1mfi�[0m
166:  �[36;1mecho "✅ All test jobs succeeded."�[0m
167:  shell: /usr/bin/bash --noprofile --norc -e -o pipefail {0}
168:  ##[endgroup]
169:  Processing: ./fleetctl-mysql8.0.44-status/status (Test: fleetctl-mysql8.0.44) with status content: success
170:  ✅ Test passed: fleetctl-mysql8.0.44
171:  Processing: ./vuln-mysql8.0.44-status/status (Test: vuln-mysql8.0.44) with status content: success
172:  ✅ Test passed: vuln-mysql8.0.44
173:  Processing: ./service-mysql8.0.44-status/status (Test: service-mysql8.0.44) with status content: success
174:  ✅ Test passed: service-mysql8.0.44
175:  Processing: ./integration-core-mysql8.0.44-status/status (Test: integration-core-mysql8.0.44) with status content: success
176:  ✅ Test passed: integration-core-mysql8.0.44
177:  Processing: ./mysql-mysql8.0.44-status/status (Test: mysql-mysql8.0.44) with status content: success
178:  ✅ Test passed: mysql-mysql8.0.44
179:  Processing: ./integration-enterprise-mysql8.0.44-status/status (Test: integration-enterprise-mysql8.0.44) with status content: fail
180:  ❌ Test failed: integration-enterprise-mysql8.0.44
181:  Processing: ./integration-mdm-mysql8.0.44-status/status (Test: integration-mdm-mysql8.0.44) with status content: success
182:  ✅ Test passed: integration-mdm-mysql8.0.44
183:  Processing: ./scripts-status/status (Test: scripts) with status content: success
184:  ✅ Test passed: scripts
185:  Processing: ./fast-status/status (Test: fast) with status content: success
186:  ✅ Test passed: fast
187:  Processing: ./main-mysql8.0.44-status/status (Test: main-mysql8.0.44) with status content: success
188:  ✅ Test passed: main-mysql8.0.44
189:  ❌ One or more test jobs failed: integration-enterprise-mysql8.0.44
190:  ##[error]Process completed with exit code 1.
191:  Post job cleanup.

@nulmete nulmete merged commit 5e27628 into main Jun 15, 2026
46 of 48 checks passed
@nulmete nulmete deleted the 33441-policies-custom-targets-gitops branch June 15, 2026 11:55
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.

[Policies custom label targets] GitOps

2 participants