Skip to content

Allow fleet host ID when specifying Gitops manual label hosts - #33078

Merged
sgress454 merged 11 commits into
mainfrom
sgress454/32014-allow-fleet-id-in-host-label-spec
Sep 22, 2025
Merged

Allow fleet host ID when specifying Gitops manual label hosts#33078
sgress454 merged 11 commits into
mainfrom
sgress454/32014-allow-fleet-id-in-host-label-spec

Conversation

@sgress454

@sgress454 sgress454 commented Sep 16, 2025

Copy link
Copy Markdown
Contributor

for #32014

Checklist for submitter

If some of the following don't apply, delete the relevant line.

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

Testing

Summary by CodeRabbit

  • New Features

    • GitOps manual labels can now reference hosts by Fleet host ID in addition to hostname, hardware serial, or UUID.
    • GitOps YAML/JSON accepts integers for host IDs; numeric IDs are handled seamlessly alongside strings.
  • Validation

    • Stronger input validation for label hosts: only strings or integers are allowed.
    • Clear error returned for invalid types (e.g., floats) in hosts lists.

@sgress454
sgress454 requested a review from a team as a code owner September 16, 2025 21:01
@sgress454

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Sep 16, 2025

Copy link
Copy Markdown
Contributor
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai

coderabbitai Bot commented Sep 16, 2025

Copy link
Copy Markdown
Contributor

Walkthrough

Enables manual GitOps labels to accept Fleet host IDs in addition to string identifiers. Introduces custom JSON unmarshalling for label hosts to accept strings or integers (serialized as strings), updates SQL to match hosts by ID, and adjusts tests and testdata to reflect the new parsing and matching behavior.

Changes

Cohort / File(s) Summary
GitOps label parsing and type update
server/fleet/labels.go
Adds HostsSlice []string with UnmarshalJSON to accept hosts as strings or integers (rejects non-integer numbers), and updates LabelSpec.Hosts to use HostsSlice.
Datastore label membership matching
server/datastore/mysql/labels.go
Extends ApplyLabelSpecsWithAuthor to include OR h.id IN (?) in the INSERT…SELECT for manual labels; updates sqlx.In bindings to include ID list.
Unit tests for parsing and datastore
pkg/spec/gitops_test.go, server/datastore/mysql/labels_test.go
Adjusts expectations to treat numeric host entries as IDs (e.g., "2"); adds invalid-host float test; adds host with numeric ID and asserts membership includes it.
Test data updates
pkg/spec/testdata/global_config_no_paths.yml, pkg/spec/testdata/top.labels.yml
Replaces a string host entry (host2) with a numeric 2 to exercise ID-based host parsing.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User as GitOps YAML
  participant Loader as Config Loader
  participant API as Fleet API
  participant Parser as HostsSlice Unmarshal
  participant DB as MySQL (labels)
  participant LM as label_membership

  User->>Loader: Provide label specs (hosts: ["host1", 2, ...])
  Loader->>API: ApplyLabelSpecsWithAuthor(specs)
  API->>Parser: Unmarshal LabelSpec.Hosts
  Note right of Parser: Accept strings and integers<br/>Reject non-integer numbers
  Parser-->>API: Hosts normalized as []string
  API->>DB: INSERT…SELECT for manual labels<br/>(match by hostname, serial, uuid, or ID)
  DB-->>LM: Upsert matching host memberships
  DB-->>API: Result
  API-->>Loader: Done
Loading
sequenceDiagram
  autonumber
  actor User as GitOps YAML (invalid)
  participant Loader
  participant API
  participant Parser

  User->>Loader: hosts: [2.5]
  Loader->>API: ApplyLabelSpecsWithAuthor
  API->>Parser: Unmarshal hosts
  Parser-->>API: Error "hosts must be strings or integers, got float 2.5"
  API-->>Loader: Validation error
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • getvictor
  • iansltx

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Description Check ⚠️ Warning The PR description contains a submitter checklist noting a changes file, tests, and QA, but it omits or does not explicitly address several template sections that are relevant to this change. Missing confirmations include input data validation/SQL-injection safeguards for the new host parsing, explicit names/paths of the added/updated tests, whether any database migrations or schema impacts exist, and any GitOps documentation or configuration exclusion notes. If any of those items are not applicable they should be removed or marked N/A; otherwise please provide the missing details so reviewers can verify validation, DB impact, and documentation. Update the PR description to state which template items are N/A or add the missing information: confirm input validation and SQL-injection mitigation for host ID parsing, list the specific tests added or updated (file/test names), declare whether database migrations or schema changes are required (or explicitly state none), and note any GitOps documentation or configuration updates or exclusions.
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 (1 passed)
Check name Status Explanation
Title Check ✅ Passed The PR title succinctly and accurately describes the primary change: allowing Fleet host IDs when specifying GitOps manual label hosts. It is a single focused sentence that matches the code changes (LabelSpec hosts parsing, MySQL label matching by ID, and updated tests/testdata). The phrasing is clear and concise for teammates scanning history.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch sgress454/32014-allow-fleet-id-in-host-label-spec

Tip

👮 Agentic pre-merge checks are now available in preview!

Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.

  • Built-in checks – Quickly apply ready-made checks to enforce title conventions, require pull request descriptions that follow templates, validate linked issues for compliance, and more.
  • Custom agentic checks – Define your own rules using CodeRabbit’s advanced agentic capabilities to enforce organization-specific policies and workflows. For example, you can instruct CodeRabbit’s agent to verify that API documentation is updated whenever API schema files are modified in a PR. Note: Upto 5 custom checks are currently allowed during the preview period. Pricing for this feature will be announced in a few weeks.

Please see the documentation for more information.

Example:

reviews:
  pre_merge_checks:
    custom_checks:
      - name: "Undocumented Breaking Changes"
        mode: "warning"
        instructions: |
          Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).

Please share your feedback with us on this Discord post.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

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

⚠️ Outside diff range comments (1)
server/datastore/mysql/labels.go (1)

170-188: Batch size must account for 4 IN lists now (not 3).

Adjust batchHostnames to avoid exceeding MySQL’s parameter limit when 4 copies are bound.

Apply this diff:

 func batchHostnames(hostnames []string) [][]string {
@@
-	// WARNING: This is used in ApplyLabelSpecsWithAuthor and the batch sizes have to be small
-	// enough to allow for three copies each hostname list in the query. The batch size is 20_000
-	// because 60_001 binding arguments is less than the maximum of 65,535.
-
-	const batchSize = 20_000 // Large, but well under the undocumented limit
+	// WARNING: Used in ApplyLabelSpecsWithAuthor. The query binds the host list 4 times
+	// (hostname, hardware_serial, uuid, id) plus 1 for label_id. Keep 1 + 4*N <= 65_535.
+	// Choose 16_000 to stay comfortably under the limit: 1 + 4*16_000 = 64,001.
+	const batchSize = 16_000
🧹 Nitpick comments (2)
server/fleet/labels.go (1)

186-209: Unmarshal robustness: handle null and tighten error formatting.

  • Handle JSON null for hosts to avoid failing on “hosts: ” cases.
  • Switch error formatting to %g (matches tests).

Apply this diff:

 func (s *HostsSlice) UnmarshalJSON(data []byte) error {
-	var raw []interface{}
+	// Accept null (treat as empty slice)
+	if string(data) == "null" {
+		*s = nil
+		return nil
+	}
+	var raw []interface{}
 	if err := json.Unmarshal(data, &raw); err != nil {
 		return err
 	}
 	var result []string
 	for _, v := range raw {
 		switch val := v.(type) {
 		case string:
 			result = append(result, val)
 		case float64:
 			// Check if the float64 is actually an integer
 			if val != float64(int64(val)) {
-				return fmt.Errorf("hosts must be strings or integers, got float %f", val)
+				return fmt.Errorf("hosts must be strings or integers, got float %g", val)
 			}
 			// Convert to string
-			result = append(result, fmt.Sprintf("%.0f", val))
+			result = append(result, fmt.Sprintf("%.0f", val))
 		default:
 			return fmt.Errorf("hosts must be strings or integers, got %T", v)
 		}
 	}
 	*s = result
 	return nil
 }
server/datastore/mysql/labels_test.go (1)

31-44: Update expectations if batch size changes to 16k.

If you adopt the 16k batch to respect 4×IN, this test must reflect 7 batches with the last sized 14k.

Proposed update:

-	require.Equal(t, 6, len(batched))
-	assert.Equal(t, large[:20_000], batched[0])
-	assert.Equal(t, large[20_000:40_000], batched[1])
-	assert.Equal(t, large[40_000:60_000], batched[2])
-	assert.Equal(t, large[60_000:80_000], batched[3])
-	assert.Equal(t, large[80_000:100_000], batched[4])
-	assert.Equal(t, large[100_000:110_000], batched[5])
+	require.Equal(t, 7, len(batched))
+	assert.Equal(t, large[:16_000], batched[0])
+	assert.Equal(t, large[16_000:32_000], batched[1])
+	assert.Equal(t, large[32_000:48_000], batched[2])
+	assert.Equal(t, large[48_000:64_000], batched[3])
+	assert.Equal(t, large[64_000:80_000], batched[4])
+	assert.Equal(t, large[80_000:96_000], batched[5])
+	assert.Equal(t, large[96_000:110_000], batched[6])
📜 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 a230eb2 and fc9ee4b.

📒 Files selected for processing (7)
  • changes/32014-allow-fleet-host-ids-in-gitops-labels (1 hunks)
  • pkg/spec/gitops_test.go (2 hunks)
  • pkg/spec/testdata/global_config_no_paths.yml (1 hunks)
  • pkg/spec/testdata/top.labels.yml (1 hunks)
  • server/datastore/mysql/labels.go (1 hunks)
  • server/datastore/mysql/labels_test.go (2 hunks)
  • server/fleet/labels.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/datastore/mysql/labels.go
  • server/fleet/labels.go
  • server/datastore/mysql/labels_test.go
  • pkg/spec/gitops_test.go
🔇 Additional comments (6)
changes/32014-allow-fleet-host-ids-in-gitops-labels (1)

1-1: Changelog entry reads well.

Clear, scoped, and matches the PR behavior.

pkg/spec/testdata/top.labels.yml (1)

9-9: LGTM: numeric host ID in testdata.

Matches new parsing semantics (strings or integers).

pkg/spec/testdata/global_config_no_paths.yml (1)

205-205: LGTM: numeric host ID in testdata.

Consistent with the updated unmarshalling rules.

pkg/spec/gitops_test.go (1)

275-276: LGTM: expecting stringified host ID.

This validates the “int in YAML → string in struct” behavior.

server/fleet/labels.go (1)

219-221: LabelSpec.Hosts type swap looks correct.

The switch to HostsSlice will engage the custom unmarshalling; no API breakage for code using []string semantics.

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

2008-2016: LGTM: coverage for host ID membership.

Good addition validating ID-based matching.

Also applies to: 2026-2039

Comment thread pkg/spec/gitops_test.go
Comment thread server/datastore/mysql/labels.go Outdated
@codecov

codecov Bot commented Sep 16, 2025

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 82.14286% with 5 lines in your changes missing coverage. Please review.
✅ Project coverage is 63.87%. Comparing base (a230eb2) to head (02f29d9).
⚠️ Report is 106 commits behind head on main.

Files with missing lines Patch % Lines
server/fleet/labels.go 79.16% 4 Missing and 1 partial ⚠️
Additional details and impacted files
@@           Coverage Diff           @@
##             main   #33078   +/-   ##
=======================================
  Coverage   63.87%   63.87%           
=======================================
  Files        2046     2046           
  Lines      201924   201962   +38     
  Branches     6686     6686           
=======================================
+ Hits       128971   129011   +40     
  Misses      62779    62779           
+ Partials    10174    10172    -2     
Flag Coverage Δ
backend 65.03% <82.14%> (+<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.

Comment thread pkg/spec/gitops_test.go
Comment on lines 274 to +275
assert.Equal(t, "host1", gitops.Labels[1].Hosts[0])
assert.Equal(t, "host2", gitops.Labels[1].Hosts[1])
assert.Equal(t, "2", gitops.Labels[1].Hosts[1])

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Testing that we can use a number in the YAML (see updates to the global_config_no_paths.yml and top.labels.yml test files.

Comment on lines -177 to +180
// enough to allow for three copies each hostname list in the query. The batch size is 20_000
// enough to allow for three copies each hostname list in the query. The batch size is 15_000
// because 60_001 binding arguments is less than the maximum of 65,535.

const batchSize = 20_000 // Large, but well under the undocumented limit
const batchSize = 15_000 // Large, but well under the undocumented limit

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Because we're adding another arg binding to the SQL query above, we now have 4 bindings so to keep it at 60k total per patch we need to drop the batch size down.

Comment on lines +793 to 797
// Because `Hosts` for manual labels matches both host name AND host ID,
// specifying "1" will match both host with ID 1 (whose name is "0")
// and host with name "1".
expectedSpecs[4].Hosts = []string{"0", "1", "2", "3", "4"}
return expectedSpecs

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I could have updated the test so that the host names have letters in them, but decided to keep them as-is to reinforce that we can now have collisions between ID and host name, serial or uuid.

"foo.local",
"hwd2",
"uuid3",
strconv.Itoa(int(host4.ID)), //nolint:gosec // dismiss G115

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Again testing that numbers in the label spec make it through ok.

@sgress454
sgress454 merged commit 162346c into main Sep 22, 2025
44 checks passed
@sgress454
sgress454 deleted the sgress454/32014-allow-fleet-id-in-host-label-spec branch September 22, 2025 18:54
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.

2 participants