Skip to content

Add backend changes for team labels#37116

Merged
lucasmrod merged 7 commits intomainfrom
36781-team-labels-backend
Dec 14, 2025
Merged

Add backend changes for team labels#37116
lucasmrod merged 7 commits intomainfrom
36781-team-labels-backend

Conversation

@lucasmrod
Copy link
Copy Markdown
Member

@lucasmrod lucasmrod commented Dec 11, 2025

Resolves #36781.


  • Changes file added for user-visible changes in changes/, orbit/changes/ or ee/fleetd-chrome/changes.
  • Input data is properly validated, SELECT * is avoided, SQL injection is prevented (using placeholders for values in statements)

Testing

Summary by CodeRabbit

  • New Features

    • Team labels are now supported, allowing labels to be assigned to specific teams.
  • Bug Fixes

    • Label memberships properly clean up when hosts are transferred between teams.
    • Team deletion now cascades cleanup of associated team labels.
    • Label query results are scoped to prevent stale or misapplied results.

✏️ Tip: You can customize this high-level summary in your review settings.

@codecov
Copy link
Copy Markdown

codecov Bot commented Dec 11, 2025

Codecov Report

❌ Patch coverage is 55.55556% with 28 lines in your changes missing coverage. Please review.
✅ Project coverage is 65.92%. Comparing base (76b6a25) to head (1621c78).
⚠️ Report is 19 commits behind head on main.

Files with missing lines Patch % Lines
server/datastore/mysql/labels.go 61.29% 7 Missing and 5 partials ⚠️
server/datastore/mysql/teams.go 46.66% 4 Missing and 4 partials ⚠️
server/datastore/mysql/policies.go 50.00% 2 Missing and 2 partials ⚠️
server/datastore/mysql/hosts.go 0.00% 1 Missing and 1 partial ⚠️
server/service/osquery.go 71.42% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #37116      +/-   ##
==========================================
- Coverage   65.92%   65.92%   -0.01%     
==========================================
  Files        2329     2329              
  Lines      184953   185260     +307     
  Branches     7707     7707              
==========================================
+ Hits       121925   122124     +199     
- Misses      51883    51969      +86     
- Partials    11145    11167      +22     
Flag Coverage Δ
backend 67.72% <55.55%> (-0.01%) ⬇️

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.

@lucasmrod
Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Dec 11, 2025

✅ 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
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Dec 11, 2025

Walkthrough

This PR implements backend support for team-scoped labels where label membership respects team context. Changes include adding team_id to label creation, introducing transactional label deletion with related membership cleanup, filtering label queries by team, and cascading label cleanup when teams are deleted. Label memberships are cleaned up when hosts transfer between teams.

Changes

Cohort / File(s) Summary
Label team scope and creation
server/datastore/mysql/labels.go
Added team_id column to label INSERT statement; label creation now accepts and persists team_id for team-scoped labels.
Label deletion refactoring
server/datastore/mysql/labels.go
Introduced deleteLabelsInTx() helper for transactional label deletion with cascading cleanup of label_membership and pack_targets; DeleteLabel now uses this helper with improved error wrapping; refined error handling for label retrieval.
Label query team filtering
server/datastore/mysql/labels.go
LabelQueriesForHost now includes team_id filter to scope label queries to the host's team context.
Host team transfer cleanup
server/datastore/mysql/hosts.go
AddHostsToTeam now calls cleanupLabelMembershipOnTeamChange() to remove label memberships for labels belonging to other teams when hosts transfer.
Label membership cleanup helper
server/datastore/mysql/policies.go
Added cleanupLabelMembershipOnTeamChange() internal helper that deletes label_membership entries for team-scoped labels when hosts move between teams.
Team deletion cascade
server/datastore/mysql/teams.go
DeleteTeam now retrieves team-scoped label IDs and clears all related label rows via teamLabelsRefs tables before deleting labels with deleteLabelsInTx(); ensures proper cascade cleanup order (policies → teamRefs → labels → team → pack_targets).
Label result filtering
server/service/osquery.go
Distributed query result handling now filters label results to remove those no longer applicable to the host before recording executions, preventing stale label assignments.
Label datastore tests
server/datastore/mysql/labels_test.go
Added tests for team labels (TeamLabels, UpdateLabelMembershipForTransferredHost); refactored testUpdateLabelMembershipByHostIDs to exercise team-scoped label behavior, global vs. per-team labels, and membership propagation.
Team deletion tests
server/datastore/mysql/teams_test.go
Extended testTeamsGetSetDelete to create team-scoped labels and hosts; added verification that team deletion cascades to remove label memberships, label entries, and host label associations.
Integration tests
server/service/integration_enterprise_test.go
Extensive test additions covering label membership with distributed query results, host software management with label scoping, label and policy interactions, and enterprise workflow validation across multiple platforms.
Team label MDM test
server/service/integration_mdm_test.go
Added TestTeamLabelsTeamDeletion() to verify label cleanup and membership removal when a team is deleted, ensuring label-related host configurations are properly reset without affecting other teams.
Osquery test data
server/service/osquery_test.go
Updated test mock data; LabelQueriesForHost now returns numeric string keys; added label-query path coverage in error distribution test.
Migration test formatting
server/datastore/mysql/migrations/tables/20251207050413_TeamLabels_test.go
Added nolint:gosec comments to suppress linting warnings; no behavioral changes.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Label deletion transactional logic (labels.go): Verify deleteLabelsInTx() correctly handles cascading cleanup of label_membership and pack_targets within a single transaction.
  • Team deletion cascade order (teams.go): Confirm deletion sequence (policies → teamRefs → labels → team → pack_targets) prevents foreign key constraint violations and completes cleanup.
  • Host transfer cleanup (hosts.go + policies.go): Validate that cleanupLabelMembershipOnTeamChange() correctly identifies and removes only team-scoped labels belonging to other teams.
  • Label query filtering (osquery.go): Review logic for clearing stale label results to ensure only invalid results are filtered.
  • Integration test coverage: Extensive test additions require verification that edge cases (multi-team scenarios, cascading deletes, label membership transitions) are correctly validated.

Suggested reviewers

  • getvictor
  • sgress454
  • iansltx

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: backend support for team labels functionality.
Description check ✅ Passed The description addresses the template with linked issue, changes file confirmation, validation and security checks, and testing completion markers.
Linked Issues check ✅ Passed All coding objectives from #36781 are met: team_id handling in label membership, team label cascading cleanup, and distributed query/host transfer logic updates.
Out of Scope Changes check ✅ Passed All changes are scoped to implement team label support as defined in #36781; no unrelated modifications detected.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch 36781-team-labels-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.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (5)
server/datastore/mysql/policies.go (1)

1461-1474: Label membership cleanup on team change is correct; optional empty-slice guard

The helper correctly mirrors cleanupPolicyMembershipOnTeamChange by deleting label_membership rows for team-scoped labels (labels.team_id IS NOT NULL) for the given hosts, which is what you want when hosts move between teams.

You may optionally add a fast-path guard:

if len(hostIDs) == 0 {
	return nil
}

to avoid sqlx.In being called with an empty slice if a future caller ever passes hostIDs == nil/len==0. Not strictly required given current patterns, but it makes the API more robust against misuse.

server/datastore/mysql/teams.go (1)

139-149: teamLabelsRefs list is reasonable; keep it aligned with schema

The new teamLabelsRefs slice cleanly centralizes the set of tables that must be purged before deleting team labels, mirroring the hostRefs pattern. This looks good; just keep this list in sync with any future FK additions/changes for label‑related tables.

server/service/integration_mdm_test.go (1)

18891-18891: Unused test fixture: ubuntuHostNoTeam is created but never asserted upon.

The host ubuntuHostNoTeam is created and has mock live queries set up, but there are no assertions verifying its label memberships remain unaffected after team t1 deletion. Consider either:

  1. Adding an assertion to verify "No team" hosts are unaffected, or
  2. Removing this unused fixture to keep the test focused.

Also applies to: 18896-18896

server/datastore/mysql/labels.go (1)

461-487: Deletion order should delete child rows before parent rows.

The function deletes from labels table first (line 466), then from label_membership (line 474). This order is inverted—child rows in label_membership should be deleted before the parent rows in labels to avoid potential foreign key violations if constraints are added in the future.

 func deleteLabelsInTx(ctx context.Context, tx sqlx.ExtContext, labelIDs []uint) error {
+	// Delete child rows first
+	query, args, err := sqlx.In(`DELETE FROM label_membership WHERE label_id IN (?)`, labelIDs)
+	if err != nil {
+		return ctxerr.Wrap(ctx, err, "build sqlx.In statement for label_membership")
+	}
+	if _, err = tx.ExecContext(ctx, query, args...); err != nil {
+		return ctxerr.Wrap(ctx, err, "delete label_membership")
+	}
+
+	query, args, err = sqlx.In(`DELETE FROM pack_targets WHERE type = ? AND target_id IN (?)`, fleet.TargetLabel, labelIDs)
+	if err != nil {
+		return ctxerr.Wrap(ctx, err, "build sqlx.In statement for pack_targets")
+	}
+	if _, err = tx.ExecContext(ctx, query, args...); err != nil {
+		return ctxerr.Wrap(ctx, err, "deleting pack_targets")
+	}
+
+	// Delete parent rows last
 	query, args, err := sqlx.In(`DELETE FROM labels WHERE id IN (?)`, labelIDs)
 	if err != nil {
 		return ctxerr.Wrap(ctx, err, "build sqlx.In statement for labels")
 	}
 	if _, err = tx.ExecContext(ctx, query, args...); err != nil {
 		return ctxerr.Wrap(ctx, err, "delete label")
 	}
-
-	query, args, err = sqlx.In(`DELETE FROM label_membership WHERE label_id IN (?)`, labelIDs)
-	if err != nil {
-		return ctxerr.Wrap(ctx, err, "build sqlx.In statement for label_membership")
-	}
-	if _, err = tx.ExecContext(ctx, query, args...); err != nil {
-		return ctxerr.Wrap(ctx, err, "delete label_membership")
-	}
-
-	query, args, err = sqlx.In(`DELETE FROM pack_targets WHERE type = ? AND target_id IN (?)`, fleet.TargetLabel, labelIDs)
-	if err != nil {
-		return ctxerr.Wrap(ctx, err, "build sqlx.In statement for pack_targets")
-	}
-	if _, err = tx.ExecContext(ctx, query, args...); err != nil {
-		return ctxerr.Wrap(ctx, err, "deleting pack_targets")
-	}

 	return nil
 }
server/service/integration_enterprise_test.go (1)

22651-22876: Strong, end‑to‑end coverage of team‑scoped label behavior

This test does a good job stitching together the whole flow: team/global label creation, distributed reads filtered by team and built‑ins, distributed writes (including cross‑team label IDs) feeding back into host label membership, manual labels, host transfer clearing team labels, and team deletion cascading labels and membership while preserving labels on other teams. The use of a global admin User with TeamFilter keeps the authorization story consistent with how the datastore behaves.

One optional improvement for future hardening would be to add a small distributed write scenario for the no‑team host, similar to the macOST1 cross‑team case, to explicitly assert that results for team‑scoped labels are ignored when the host has no team (and that only global labels can be applied). That would mirror the current cross‑team protection and close the last gap in the matrix of (team1, team2, no‑team) × (read, write).

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c557bcb and 5296c8c.

📒 Files selected for processing (12)
  • changes/36781-team-labels-backend (1 hunks)
  • server/datastore/mysql/hosts.go (1 hunks)
  • server/datastore/mysql/labels.go (4 hunks)
  • server/datastore/mysql/labels_test.go (3 hunks)
  • server/datastore/mysql/migrations/tables/20251207050413_TeamLabels_test.go (1 hunks)
  • server/datastore/mysql/policies.go (1 hunks)
  • server/datastore/mysql/teams.go (2 hunks)
  • server/datastore/mysql/teams_test.go (6 hunks)
  • server/service/integration_enterprise_test.go (1 hunks)
  • server/service/integration_mdm_test.go (1 hunks)
  • server/service/osquery.go (1 hunks)
  • server/service/osquery_test.go (2 hunks)
🧰 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:

  • server/service/integration_mdm_test.go
  • server/service/osquery_test.go
  • server/service/osquery.go
  • server/datastore/mysql/teams.go
  • server/datastore/mysql/hosts.go
  • server/datastore/mysql/migrations/tables/20251207050413_TeamLabels_test.go
  • server/datastore/mysql/teams_test.go
  • server/service/integration_enterprise_test.go
  • server/datastore/mysql/labels_test.go
  • server/datastore/mysql/labels.go
  • server/datastore/mysql/policies.go
🧠 Learnings (4)
📚 Learning: 2025-10-03T18:16:11.482Z
Learnt from: MagnusHJensen
Repo: fleetdm/fleet PR: 33805
File: server/service/integration_mdm_test.go:1248-1251
Timestamp: 2025-10-03T18:16:11.482Z
Learning: In server/service/integration_mdm_test.go, the helper createAppleMobileHostThenEnrollMDM(platform string) is exclusively for iOS/iPadOS hosts (mobile). Do not flag macOS model/behavior issues based on changes within this helper; macOS provisioning uses different helpers such as createHostThenEnrollMDM.

Applied to files:

  • server/service/integration_mdm_test.go
  • server/service/integration_enterprise_test.go
  • server/datastore/mysql/labels_test.go
📚 Learning: 2025-08-08T07:40:05.301Z
Learnt from: getvictor
Repo: fleetdm/fleet PR: 31726
File: server/datastore/mysql/labels_test.go:2031-2031
Timestamp: 2025-08-08T07:40:05.301Z
Learning: In fleetdm/fleet repository tests (server/datastore/mysql/labels_test.go and similar), using testing.T.Context() is valid because the project targets a recent Go version where testing.T.Context() exists. Do not suggest replacing t.Context() with context.Background() in this codebase.

Applied to files:

  • server/datastore/mysql/migrations/tables/20251207050413_TeamLabels_test.go
  • server/datastore/mysql/teams_test.go
  • server/datastore/mysql/labels_test.go
  • server/datastore/mysql/labels.go
📚 Learning: 2025-08-08T07:40:05.301Z
Learnt from: getvictor
Repo: fleetdm/fleet PR: 31726
File: server/datastore/mysql/labels_test.go:2031-2031
Timestamp: 2025-08-08T07:40:05.301Z
Learning: Fleet repo targets Go 1.24.5 (root go.mod), which supports testing.T.Context(). Do not flag usage of t.Context() or suggest replacing it with context.Background() in tests (e.g., server/datastore/mysql/labels_test.go Line 2031 and similar).

Applied to files:

  • server/datastore/mysql/migrations/tables/20251207050413_TeamLabels_test.go
  • server/datastore/mysql/labels_test.go
  • server/datastore/mysql/labels.go
📚 Learning: 2025-08-13T18:20:42.136Z
Learnt from: titanous
Repo: fleetdm/fleet PR: 31075
File: tools/redis-tests/elasticache/iam_auth.go:4-10
Timestamp: 2025-08-13T18:20:42.136Z
Learning: For test harnesses and CLI tools in the Fleet codebase, resource cleanup on error paths (like closing connections before log.Fatalf) may not be necessary since the OS handles cleanup when the process exits. These tools prioritize simplicity over defensive programming patterns used in production code.

Applied to files:

  • server/datastore/mysql/teams_test.go
🧬 Code graph analysis (9)
server/service/osquery_test.go (1)
server/mock/datastore_mock.go (1)
  • LabelQueriesForHostFunc (168-168)
server/service/osquery.go (3)
server/contexts/ctxerr/ctxerr.go (1)
  • Wrap (199-202)
tools/github-manage/pkg/ghapi/models.go (1)
  • Sprint (41-46)
server/ptr/ptr.go (1)
  • Bool (25-27)
server/datastore/mysql/teams.go (1)
server/contexts/ctxerr/ctxerr.go (2)
  • Wrapf (211-214)
  • Wrap (199-202)
server/datastore/mysql/hosts.go (1)
server/contexts/ctxerr/ctxerr.go (1)
  • Wrap (199-202)
server/datastore/mysql/teams_test.go (4)
server/ptr/ptr.go (2)
  • String (10-12)
  • Bool (25-27)
server/fleet/software_installer.go (1)
  • NewTempFileReader (950-970)
server/fleet/labels.go (4)
  • LabelIdentsWithScope (302-305)
  • LabelScope (288-288)
  • LabelScopeIncludeAny (296-296)
  • LabelIdent (281-284)
server/datastore/mysql/testing_utils.go (1)
  • ExecAdhocSQL (420-424)
server/service/integration_enterprise_test.go (4)
server/fleet/hosts.go (1)
  • Host (276-405)
server/fleet/teams.go (2)
  • Team (69-95)
  • TeamFilter (577-586)
server/fleet/labels.go (4)
  • LabelTypeRegular (65-65)
  • LabelMembershipTypeDynamic (100-100)
  • LabelMembershipTypeManual (102-102)
  • LabelTypeBuiltIn (68-68)
server/fleet/datastore.go (1)
  • IsNotFound (2758-2764)
server/datastore/mysql/labels_test.go (1)
server/fleet/labels.go (3)
  • Label (142-155)
  • LabelType (61-61)
  • LabelMembershipType (95-95)
server/datastore/mysql/labels.go (2)
server/contexts/ctxerr/ctxerr.go (1)
  • Wrap (199-202)
server/fleet/labels.go (1)
  • LabelMembershipTypeDynamic (100-100)
server/datastore/mysql/policies.go (1)
server/contexts/ctxerr/ctxerr.go (1)
  • Wrap (199-202)
🔇 Additional comments (24)
server/datastore/mysql/migrations/tables/20251207050413_TeamLabels_test.go (1)

51-54: Nolint G115 annotations are appropriate for test-only ID casts

Casting auto-increment IDs to uint in tests is safe here, and the explicit //nolint:gosec comments keep gosec noise down without affecting behavior.

changes/36781-team-labels-backend (1)

1-1: Changelog entry matches PR scope

The brief note cleanly reflects the backend-only addition of team label support and is sufficient for the changeset.

server/service/osquery.go (1)

1118-1135: Stale team-label result clearing is well targeted; confirm key format assumption

The new logic correctly protects against the timing race you describe: by re-fetching the host’s current label queries and forcing false for labels not present in that set, stale results from a previous team can’t reintroduce membership when RecordLabelQueryExecutions runs.

This relies on LabelQueriesForHost returning a map keyed by stringified numeric label IDs (e.g. "42"), which matches how ingestMembershipQuery parses IDs from fleet_label_query_<id>. Please confirm that invariant holds across the datastore implementation and tests so this filter never silently misses labels.

server/datastore/mysql/hosts.go (1)

3292-3294: Label membership cleanup on team change is wired correctly

Hooking cleanupLabelMembershipOnTeamChange into AddHostsToTeam alongside the existing policy/query/conditional-access cleanup keeps team‑scoped label memberships consistent when hosts move between teams; no issues spotted with ordering or scoping here.

server/datastore/mysql/teams.go (3)

160-167: Existing teamRefs cleanup remains correctly scoped by team_id

The loop over teamRefs still issues DELETE FROM <table> WHERE team_id = ?, so team deletion remains properly constrained to the target team and uses only static table names via fmt.Sprintf, which is safe here.


199-201: Pack target cleanup for deleted team remains correctly filtered

The DELETE FROM pack_targets WHERE type = ? AND target_id = ? call continues to scope cleanup to TargetTeam rows for this team only; formatting change doesn’t alter behavior.


169-192: Verify that other label deletion paths handle FK-constrained tables

The team label cleanup sequence at these lines is correct: labels are properly removed from in_house_app_labels, software_installer_labels, and vpp_app_team_labels (using sqlx.In to avoid FK RESTRICT failures) before calling deleteLabelsInTx.

However, the generic DeleteLabel function (in server/datastore/mysql/labels.go) calls deleteLabelsInTx directly without pre-cleaning these three FK-restricted tables. Since all three tables have explicit ON DELETE RESTRICT constraints on label_id (as confirmed in the migrations), attempting to delete a label that is referenced in any of these tables will fail at runtime with a foreign key constraint violation.

Ensure that the DeleteLabel function (or any other generic label deletion path) either:

  • Pre-deletes from these three tables before calling deleteLabelsInTx, or
  • Extends deleteLabelsInTx to handle cleanup of all FK-restricted label tables.
server/service/osquery_test.go (2)

1476-1481: Align label query keys with numeric label IDs

Using "1", "2", "3" as keys matches how label results are reported (hostLabelQueryPrefix + "<id>") and keeps TestLabelQueries consistent with the team/ID-based label semantics exercised later in the test. Looks good.


3041-3043: Mocking a label query here correctly drives the error-logging path

Providing a non-empty LabelQueriesForHostFunc with key "1" ensures the hostLabelQueryPrefix + "1" results in this test actually invoke RecordLabelQueryExecutionsFunc, so the combined error logging being asserted is meaningfully exercised. No changes needed.

server/service/integration_mdm_test.go (4)

18898-18918: LGTM!

Good test setup with labels scoped to different teams (t1, t2) and a global label, providing proper coverage for verifying cascading deletion only affects the targeted team's labels.


18920-18932: LGTM!

Proper simulation of label membership via the distributed query API, ensuring both team-scoped and global labels are assigned to respective hosts.


18934-19013: LGTM!

Comprehensive setup creating various entities (software installer, VPP app, profiles, policy, query) that reference the team label. This ensures the deletion cascade handles all dependent relationships properly.


19015-19043: LGTM!

The assertions thoroughly validate:

  1. Team label (l1t1) is deleted with the team
  2. Other team's label (l2t2) remains unaffected
  3. Label membership cleanup for hosts in the deleted team
  4. Label membership preservation for hosts in other teams

The sorting and ID-based ordering assumption is valid given l2t2 is created before globalLabel.

server/datastore/mysql/labels_test.go (3)

1817-1895: Test coverage for host transfer label cleanup looks good.

The test correctly validates that when hosts are transferred to "No team", their team-scoped label memberships are removed while global label memberships are preserved. This aligns with the PR objective of ensuring label membership respects team context.


2378-2486: Team labels test coverage is comprehensive.

The test validates:

  • LabelQueriesForHost returns both global and team-scoped labels for team hosts
  • Platform filtering works correctly with team labels
  • Manual team labels can be added to hosts via AddLabelsToHost
  • Global hosts only receive global label queries

This provides solid coverage for the new team-scoped label functionality.


2211-2376: Test for UpdateLabelMembershipByHostIDs is well-structured.

The test thoroughly exercises adding, removing, and replacing hosts in manual labels, including edge cases like:

  • Adding/removing multiple hosts
  • Clearing all hosts from a label
  • Hosts with duplicate hostnames
  • Verifying GetLabelSpec returns host IDs correctly
server/datastore/mysql/teams_test.go (4)

56-64: Good setup for team label deletion tests.

Creating the admin user and global VPP token provides the necessary context for the software installer and VPP app creation that follows. This ensures the test can validate cascading deletions across all related entities.


128-277: Comprehensive test data setup for team label associations.

The test creates team labels with associations across multiple tables:

  • in_house_app_labels - in-house apps scoped to the team label
  • software_installer_labels - software installers scoped to the team label
  • vpp_app_labels - VPP apps scoped to the team label
  • label_membership - host membership in the team label

This ensures the team deletion path properly cleans up all label-related foreign key references.


329-337: Good validation of team label cleanup after deletion.

The assertions verify that:

  1. The team label is no longer retrievable by name
  2. The host's label memberships are empty after the team (and its labels) are deleted

This confirms the cascading deletion works correctly.


298-309: teamLabelsRefs is correctly defined with all required tables.

The variable includes all three tables that reference label_id with ON DELETE RESTRICT constraints: in_house_app_labels, software_installer_labels, and vpp_app_team_labels. The definition correctly excludes tables using ON DELETE CASCADE or ON DELETE SET NULL, which don't require explicit deletion before label removal.

server/datastore/mysql/labels.go (3)

397-412: Team ID properly added to label creation.

The INSERT now correctly includes team_id as the 9th parameter, enabling team-scoped labels. The placeholder count and parameter list are consistent.


570-574: Team-scoped label query filter is correctly implemented.

The WHERE clause (team_id IS NULL OR team_id = ?) correctly returns:

  • Global labels (team_id IS NULL) for all hosts
  • Team-specific labels only for hosts belonging to that team

This aligns with the PR objective of scoping label membership by team_id. Based on the coding guidelines, this SQL query has appropriate filtering criteria for the intended entity scope.


441-459: DeleteLabel properly wraps the transaction and handles errors.

The refactored DeleteLabel correctly:

  • Fetches the label ID first to return a proper NotFoundError if missing
  • Uses the new deleteLabelsInTx helper for transactional cleanup
  • Preserves foreign key error handling for labels referenced by other entities
server/service/integration_enterprise_test.go (1)

22621-22648: Helper correctly models distributed label result states

This helper cleanly captures the three label result cases (failed execution, match, no match) and builds a realistic distributed write payload keyed by hostLabelQueryPrefix + labelID. Given hosts in this test are always created with a non-nil NodeKey, the implementation looks solid and is a nice reusable building block for other label-related tests.

Comment thread server/datastore/mysql/labels_test.go Outdated
@lucasmrod lucasmrod marked this pull request as draft December 12, 2025 12:58
Copy link
Copy Markdown
Contributor

@iansltx iansltx left a comment

Choose a reason for hiding this comment

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

Apologies for the delay on review (tests took a little longer). A few questions + minor feedback items, otherwise looks good.

There was also the typo CodeRabbit caught (that I didn't), so you'll want to fix that too.

Comment thread server/datastore/mysql/policies.go
Comment thread server/service/osquery.go Outdated
Comment thread server/service/osquery.go
Comment thread server/datastore/mysql/labels.go
Comment thread server/datastore/mysql/labels_test.go Outdated
Comment thread server/datastore/mysql/labels_test.go Outdated
Comment thread server/datastore/mysql/labels_test.go
@lucasmrod lucasmrod requested a review from iansltx December 12, 2025 22:57
@lucasmrod lucasmrod marked this pull request as ready for review December 12, 2025 22:58
@lucasmrod lucasmrod merged commit 78b926d into main Dec 14, 2025
48 checks passed
@lucasmrod lucasmrod deleted the 36781-team-labels-backend branch December 14, 2025 23:02
@coderabbitai coderabbitai Bot mentioned this pull request Jan 6, 2026
2 tasks
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.

Backend changes to for label membership to respect team_id

2 participants