Skip to content

Policies: combine include/exclude label targeting + add labels_exclude_all (#33441)#47444

Open
nulmete wants to merge 4 commits into
mainfrom
33441-policies-custom-targets-backend
Open

Policies: combine include/exclude label targeting + add labels_exclude_all (#33441)#47444
nulmete wants to merge 4 commits into
mainfrom
33441-policies-custom-targets-backend

Conversation

@nulmete

@nulmete nulmete commented Jun 11, 2026

Copy link
Copy Markdown
Member

Related issue: Resolves #46582

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 as part of the frontend PRs which have been merged to main.)

Testing

  • Added/updated automated tests

  • QA'd all new/changed functionality manually

Screen.Recording.2026-06-11.at.3.48.18.PM.mov

Summary by CodeRabbit

  • New Features

    • Added support for labels_exclude_all in policy targeting to exclude hosts that match all specified labels.
    • Allow combining include and exclude label scopes (e.g., include_any with exclude_any or exclude_all).
  • Improvements

    • Improved label-scope validation with distinct errors for include vs. exclude conflicts and explicit overlap reporting.
    • Premium gating extended to include/exclude_all label scopes.
  • Tests

    • Expanded tests to cover new exclude_all semantics and label-scope validation.

@codecov

codecov Bot commented Jun 11, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 79.66102% with 24 lines in your changes missing coverage. Please review.
✅ Project coverage is 67.18%. Comparing base (becf170) to head (b3db22f).
⚠️ Report is 7 commits behind head on main.

Files with missing lines Patch % Lines
server/datastore/mysql/policies.go 73.33% 8 Missing and 8 partials ⚠️
server/service/apple_mdm.go 0.00% 0 Missing and 2 partials ⚠️
server/service/team_policies.go 75.00% 0 Missing and 2 partials ⚠️
server/fleet/policies.go 95.23% 0 Missing and 1 partial ⚠️
server/service/global_policies.go 80.00% 0 Missing and 1 partial ⚠️
server/service/mdm.go 66.66% 0 Missing and 1 partial ⚠️
server/service/windows_mdm_profiles.go 0.00% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #47444      +/-   ##
==========================================
- Coverage   67.20%   67.18%   -0.03%     
==========================================
  Files        3350     3350              
  Lines      228159   228208      +49     
  Branches    11751    11751              
==========================================
- Hits       153342   153316      -26     
- Misses      60991    61046      +55     
- Partials    13826    13846      +20     
Flag Coverage Δ
backend 68.80% <79.66%> (-0.04%) ⬇️

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.

…ude_all

Allow policies to combine one include scope (labels_include_any/all) with one
exclude scope (labels_exclude_any/all), and add the new labels_exclude_all
option. Relaxes the previous one-of-three validation to one include + one
exclude (rejecting two-include, two-exclude, and include/exclude overlap),
persists all scopes, and adds the exclude_all gate to the host-targeting and
membership-cleanup SQL. Renames the shared overlap helper ProfileLabelOverlap
to a generic LabelOverlap. Premium-gated like labels_include_all; no DB
migration needed.

For #33441.
@nulmete nulmete force-pushed the 33441-policies-custom-targets-backend branch from f93536e to 967696f Compare June 11, 2026 18:44
@nulmete nulmete marked this pull request as ready for review June 11, 2026 19:07
@nulmete nulmete requested a review from a team as a code owner June 11, 2026 19:07
Copilot AI review requested due to automatic review settings June 11, 2026 19:07

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 extends policy label targeting to support combining one include scope with one exclude scope, adds the premium-only labels_exclude_all scope for policies, and centralizes include/exclude overlap detection into a shared fleet.LabelOverlap helper reused across policy and MDM/profile code paths.

Changes:

  • Add labels_exclude_all to policy API/request/response types, service handlers, datastore persistence, and host scoping queries.
  • Update policy label-scope validation rules to allow one include + one exclude scope, while still rejecting conflicting include/include and exclude/exclude combinations and preventing include/exclude overlaps.
  • Replace the MDM-specific ProfileLabelOverlap helper with a shared LabelOverlap, and update tests (unit, integration, datastore) for new semantics.

Reviewed changes

Copilot reviewed 16 out of 16 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
server/service/windows_mdm_profiles.go Switches overlap check to shared fleet.LabelOverlap.
server/service/team_policies.go Plumbs LabelsExcludeAll through team policy create/modify paths and adjusts premium gating and label association checks.
server/service/mdm.go Uses fleet.LabelOverlap for MDM profile include/exclude overlap validation.
server/service/integration_enterprise_test.go Updates integration expectations for new policy label-scope rules (include+exclude allowed; include/include and exclude/exclude rejected).
server/service/global_policies.go Plumbs LabelsExcludeAll through global policy create/spec apply paths and premium gating.
server/service/apple_mdm.go Switches overlap check to shared fleet.LabelOverlap.
server/fleet/policies.go Adds LabelsExcludeAll, splits conflicting-label errors into include vs exclude, and updates scope validation logic to allow include+exclude combinations and reject overlaps.
server/fleet/policies_test.go Adds unit tests for the new policy label-scope validation rules.
server/fleet/mdm.go Replaces ProfileLabelOverlap usage with LabelOverlap and removes the old helper.
server/fleet/labels.go Introduces shared LabelOverlap utility.
server/fleet/labels_test.go Adds tests for LabelOverlap.
server/fleet/api_policies.go Adds labels_exclude_all to policy API request structs (premium-only).
server/datastore/mysql/policies.go Persists/loads exclude_all scope and updates policy scoping SQL to account for exclude-all semantics.
server/datastore/mysql/policies_test.go Updates datastore tests to reflect allowed include+exclude combinations and exercises exclude_all round-tripping and membership cleanup.
server/datastore/mysql/hosts.go Updates ListPoliciesForHost scoping SQL to enforce exclude_all semantics.
cmd/fleetctl/fleetctl/gitops.go Switches config profile overlap validation to shared fleet.LabelOverlap.

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

Comment thread server/service/team_policies.go Outdated
Comment thread server/datastore/mysql/policies.go
@coderabbitai

coderabbitai Bot commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: e4815c0c-ad73-4eaf-859e-1fcd90b8e91f

📥 Commits

Reviewing files that changed from the base of the PR and between c98b9ad and b3db22f.

📒 Files selected for processing (5)
  • server/datastore/mysql/policies.go
  • server/datastore/mysql/policies_test.go
  • server/fleet/policies.go
  • server/fleet/policies_test.go
  • server/service/team_policies.go
🚧 Files skipped from review as they are similar to previous changes (4)
  • server/fleet/policies_test.go
  • server/service/team_policies.go
  • server/datastore/mysql/policies_test.go
  • server/fleet/policies.go

Walkthrough

Adds a LabelsExcludeAll label-scope to policy API/models, verification, MySQL persistence, host-targeting SQL, service endpoints (with premium gating), and tests. Introduces a shared fleet.LabelOverlap helper used by MDM, GitOps, and OS-specific profile creation paths, replaces the old ProfileLabelOverlap, and updates tests to validate exclude-all semantics and conflict/error behavior.

Possibly related PRs

  • fleetdm/fleet#47213: Frontend form/type support for labels_exclude_all in policy create/update payloads.
  • fleetdm/fleet#47354: Edit-policy UI updates to send labels_exclude_all, complementing this backend implementation.
  • fleetdm/fleet#44305: Prior work adding labels_include_all with database and SQL updates that this PR extends for exclude-all semantics.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 34.78% 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 PR title accurately describes the primary changes: combining include/exclude label targeting and adding labels_exclude_all support for policies.
Description check ✅ Passed The PR description provides a related issue reference and confirms testing completion, though it lacks comprehensive detail about implementation specifics and could be more detailed.
Linked Issues check ✅ Passed The code changes comprehensively implement all requirements: DB schema changes enable concurrent include/exclude labels, API endpoints accept and persist combined settings via new LabelsExcludeAll fields, and targeting logic validates and applies policies using the combined rules.
Out of Scope Changes check ✅ Passed All changes are directly related to combining include/exclude label targeting: validation logic, datastore queries, policy specs, and API contracts are all focused on the feature objectives with no unrelated modifications.

✏️ 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-backend

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 (1)
server/fleet/policies_test.go (1)

51-53: ⚡ Quick win

Add explicit cross-scope overlap cases to lock in validator intent.

Please add overlap tests for include_any + exclude_all and include_all + exclude_any using the same label; these are core to the newly combined-scope behavior.

Proposed test additions
 		{name: "overlap include_any/exclude_any", includeAny: []string{"a"}, excludeAny: []string{"a"}, wantErrContains: `label "a" cannot appear in both an include and an exclude list`},
 		{name: "overlap include_all/exclude_all", includeAll: []string{"a"}, excludeAll: []string{"a"}, wantErrContains: `label "a" cannot appear in both an include and an exclude list`},
+		{name: "overlap include_any/exclude_all", includeAny: []string{"a"}, excludeAll: []string{"a"}, wantErrContains: `label "a" cannot appear in both an include and an exclude list`},
+		{name: "overlap include_all/exclude_any", includeAll: []string{"a"}, excludeAny: []string{"a"}, wantErrContains: `label "a" cannot appear in both an include and an exclude list`},
 	}
🤖 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 `@server/fleet/policies_test.go` around lines 51 - 53, Add two table-driven
test cases to policies_test.go to assert cross-scope overlap validation: one
named "overlap include_any/exclude_all" with includeAny: []string{"a"} and
excludeAll: []string{"a"}, and one named "overlap include_all/exclude_any" with
includeAll: []string{"a"} and excludeAny: []string{"a"}; both should expect
wantErrContains: `label "a" cannot appear in both an include and an exclude
list` so the combined-scope validator behavior is locked in (place these
alongside the existing overlap cases in the test table).
🤖 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 `@server/fleet/policies_test.go`:
- Around line 51-53: Add two table-driven test cases to policies_test.go to
assert cross-scope overlap validation: one named "overlap
include_any/exclude_all" with includeAny: []string{"a"} and excludeAll:
[]string{"a"}, and one named "overlap include_all/exclude_any" with includeAll:
[]string{"a"} and excludeAny: []string{"a"}; both should expect wantErrContains:
`label "a" cannot appear in both an include and an exclude list` so the
combined-scope validator behavior is locked in (place these alongside the
existing overlap cases in the test table).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 1ce83527-fc8f-4be7-9ea1-787eb738938f

📥 Commits

Reviewing files that changed from the base of the PR and between becf170 and c98b9ad.

📒 Files selected for processing (16)
  • cmd/fleetctl/fleetctl/gitops.go
  • server/datastore/mysql/hosts.go
  • server/datastore/mysql/policies.go
  • server/datastore/mysql/policies_test.go
  • server/fleet/api_policies.go
  • server/fleet/labels.go
  • server/fleet/labels_test.go
  • server/fleet/mdm.go
  • server/fleet/policies.go
  • server/fleet/policies_test.go
  • server/service/apple_mdm.go
  • server/service/global_policies.go
  • server/service/integration_enterprise_test.go
  • server/service/mdm.go
  • server/service/team_policies.go
  • server/service/windows_mdm_profiles.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 in the failed_tests aggregation step because at least one downloaded job status
artifact contained fail.
- The file ./integration-enterprise-mysql8.0.44-status/status had status
content fail (log lines 179-180).
- The script collected this as a failed test and exited with code
1 (❌ One or more test jobs failed: integration-enterprise-mysql8.0.44, log line 189), causing the
workflow to fail (log line 190).

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/integration-mdm-mysql8.0.44-status/status
126:  Extracting artifact entry: /home/runner/work/fleet/fleet/mysql-mysql8.0.44-status/status
127:  Artifact download completed successfully.
128:  Artifact download completed successfully.
129:  Redirecting to blob download url: https://productionresultssa11.blob.core.windows.net/actions-results/ff71cdaa-6358-4308-9aa7-d2d70760abcf/workflow-job-run-0c82a000-72ae-5bcd-9945-10a8a93b8f12/artifacts/2a19863cf655520cd50bee6e879c62e59b658eec2669afff2f62cdd65167477f.zip
130:  Starting download of artifact to: /home/runner/work/fleet/fleet/scripts-status
131:  Extracting artifact entry: /home/runner/work/fleet/fleet/scripts-status/status
132:  Artifact download completed successfully.
133:  Redirecting to blob download url: https://productionresultssa11.blob.core.windows.net/actions-results/ff71cdaa-6358-4308-9aa7-d2d70760abcf/workflow-job-run-b76d84b3-69ac-5389-a916-5cb3a375069e/artifacts/e88bb41ccca5fefd123b3929fc59c0c84f82086ff9ceb535443496e75ee46d28.zip
134:  Starting download of artifact to: /home/runner/work/fleet/fleet/fast-status
135:  Extracting artifact entry: /home/runner/work/fleet/fleet/fast-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.

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] Backend

3 participants