Skip to content

Add host name enforcement persistence layer for Apple hosts#48693

Merged
juan-fdz-hawa merged 2 commits into
38806-macos-iosipados-rename-hostsfrom
48620-rename-hosts-enforcement-state-migrations-and-datastore-methods
Jul 6, 2026
Merged

Add host name enforcement persistence layer for Apple hosts#48693
juan-fdz-hawa merged 2 commits into
38806-macos-iosipados-rename-hostsfrom
48620-rename-hosts-enforcement-state-migrations-and-datastore-methods

Conversation

@juan-fdz-hawa

@juan-fdz-hawa juan-fdz-hawa commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Resolves #48620

Ships the datastore layer for host name templates (macOS, iOS, iPadOS) with no user-visible behavior. Adds the host_mdm_apple_device_names enforcement-state table and every Datastore method the rest of the feature consumes.

Checklist for submitter

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

  • Input data is properly validated, SELECT * is avoided, SQL injection is prevented (using placeholders for values in statements), JS inline code is prevented especially for url redirects, and untrusted data interpolated into shell scripts/commands is validated against shell metacharacters.

Testing

  • Added/updated automated tests

Database migrations

  • Checked schema for all modified table for columns that will auto-update timestamps during migration.
  • Confirmed that updating the timestamps is acceptable, and will not cause unwanted side effects.
  • Ensured the correct collation is explicitly set for character columns (COLLATE utf8mb4_unicode_ci).

Summary by CodeRabbit

  • New Features

    • Added support for enforcing Apple host device names from team templates.
    • Hosts can now be queued, sent commands, verified, resent, and reconciled automatically as their enrollment or team changes.
    • Added tracking for pending, sent, verified, and failed states so device-name updates are easier to manage.
  • Bug Fixes

    • Improved handling of outdated command responses so newer updates are not overwritten by stale results.
    • Added better recovery when a device name does not match the expected template.

@juan-fdz-hawa

Copy link
Copy Markdown
Contributor Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Full review finished.

@coderabbitai

coderabbitai Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

This PR adds the persistence layer for Apple host device name enforcement: a new host_mdm_apple_device_names MySQL table with migration and schema updates, new HostDeviceNameEnforcement and HostDeviceNamePending types, and nine new Datastore interface methods covering bulk upsert, deletion, listing pending commands, marking commands sent, updating status from command acknowledgments, verification, retrieval, resend, and reconciliation. Corresponding MySQL implementations, datastore mocks, and MySQL-backed tests are included.

Possibly related issues

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title concisely describes the new Apple host-name enforcement persistence layer.
Description check ✅ Passed The description includes the related issue, checklist items, testing, and migration notes required by the template.
Linked Issues check ✅ Passed The migration, datastore methods, mocks, and tests align with issue #48620's persistence-layer requirements.
Out of Scope Changes check ✅ Passed No unrelated or out-of-scope changes are evident beyond the Apple host-name persistence work.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ 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 48620-rename-hosts-enforcement-state-migrations-and-datastore-methods

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.

@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

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

Inline comments:
In `@server/datastore/mysql/apple_device_names.go`:
- Around line 91-126: The UpdateHostDeviceNameStatusFromCommand flow can return
stale hostUUID/expectedName if the row is removed or superseded after the SELECT
but before the UPDATE. In Datastore.UpdateHostDeviceNameStatusFromCommand, check
the affected row count from the UPDATE on host_mdm_apple_device_names; if it is
0, return the same notFound-style result used for a missing command_uuid so
callers can ignore the stale command. Keep the existing SELECT/UPDATE logic and
field extraction, but make the update step authoritative before returning
hostUUID and expectedName.

In `@server/fleet/datastore.go`:
- Around line 1781-1839: The host cleanup path is missing removal of Apple
device-name enforcement rows, so stale `host_mdm_apple_device_names` records can
remain after a host is deleted. Update the cleanup logic in `deleteHosts` to
also delete enforcement rows keyed by `host_uuid`, using the host-device-name
datastore methods or the same deletion flow used when clearing a team. Make sure
the new cleanup covers `host_mdm_apple_device_names` alongside the existing
`additionalHostRefsByUUID` removals.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 917ec94c-fc33-4e13-a50d-8b84a77ca877

📥 Commits

Reviewing files that changed from the base of the PR and between 5fc14b8 and 11ea7f8.

📒 Files selected for processing (7)
  • server/datastore/mysql/apple_device_names.go
  • server/datastore/mysql/apple_device_names_test.go
  • server/datastore/mysql/migrations/tables/20260703135230_CreateHostMDMAppleDeviceNames.go
  • server/datastore/mysql/schema.sql
  • server/fleet/datastore.go
  • server/fleet/hosts.go
  • server/mock/datastore_mock.go

Comment thread server/datastore/mysql/apple_device_names.go
Comment thread server/fleet/datastore.go
@codecov

codecov Bot commented Jul 3, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 78.98089% with 33 lines in your changes missing coverage. Please review.
⚠️ Please upload report for BASE (38806-macos-iosipados-rename-hosts@5fc14b8). Learn more about missing BASE report.

Files with missing lines Patch % Lines
server/datastore/mysql/apple_device_names.go 78.35% 15 Missing and 14 partials ⚠️
...es/20260703135230_CreateHostMDMAppleDeviceNames.go 82.60% 3 Missing and 1 partial ⚠️
Additional details and impacted files
@@                          Coverage Diff                          @@
##             38806-macos-iosipados-rename-hosts   #48693   +/-   ##
=====================================================================
  Coverage                                      ?   68.02%           
=====================================================================
  Files                                         ?     3681           
  Lines                                         ?   233963           
  Branches                                      ?    12268           
=====================================================================
  Hits                                          ?   159157           
  Misses                                        ?    60477           
  Partials                                      ?    14329           
Flag Coverage Δ
backend 69.67% <78.98%> (?)

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.

@juan-fdz-hawa juan-fdz-hawa force-pushed the 48620-rename-hosts-enforcement-state-migrations-and-datastore-methods branch from 11ea7f8 to 6e56363 Compare July 3, 2026 15:26
@juan-fdz-hawa juan-fdz-hawa marked this pull request as ready for review July 3, 2026 15:35
@juan-fdz-hawa juan-fdz-hawa requested a review from a team as a code owner July 3, 2026 15:35

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

Resolves #48620

Ships the datastore layer for host name templates (macOS, iOS, iPadOS)
with no user-visible behavior. Adds the host_mdm_apple_device_names
enforcement-state table and every Datastore method the rest of the
feature consumes.
@juan-fdz-hawa juan-fdz-hawa force-pushed the 48620-rename-hosts-enforcement-state-migrations-and-datastore-methods branch from 6e56363 to 57939b5 Compare July 3, 2026 15:39

@nulmete nulmete left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Did a first pass and overall LGTM 👍
I have a few clarifying questions due to my unfamiliarity with MDM commands and also to get the full picture of the upcoming changes (e.g. the cron mentioned in some comments).
The new table makes sense to me.

// created before the cron resolves the template into a concrete name.
_, err := tx.Exec(`
CREATE TABLE host_mdm_apple_device_names (
host_uuid varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

If a host is deleted, would we still have a record in this new table for that host_uuid?

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.

nope - this table was added to additionalHostRefsByUUID, so it will be cleared out if the host is removed.

Comment on lines +14 to +17
// template for Apple hosts (macOS, iOS, iPadOS). A NULL status means the row
// is queued for the cron to pick up and send a Settings/DeviceName command.
// expected_device_name is nullable because rows are
// created before the cron resolves the template into a concrete name.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I see the cron is mentioned in many places but there's no cron (yet?).
I believe I get the idea of why we would need a cron to enqueue the sending of commands, but maybe we can avoid mentioning "the cron" in the datastore layer?

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.

The cron will be implemented in another PR at a later time, after this and the other two PRs currently open are merged in.

// host (the row keeps only the latest). Callers must treat it as a stale
// result to ignore.
staleErr := func() error {
return ctxerr.Wrap(ctx, notFound("HostDeviceNameEnforcement").WithMessage("unable to match command to host"))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

should we surface the commandUUID in all the errors we return here for potential debugging purposes?

Comment on lines +112 to +133
const selectStmt = `
SELECT host_uuid, expected_device_name
FROM host_mdm_apple_device_names
WHERE command_uuid = ?`
if err := sqlx.GetContext(ctx, tx, &row, selectStmt, commandUUID); err != nil {
if errors.Is(err, sql.ErrNoRows) {
return staleErr()
}
return ctxerr.Wrap(ctx, err, "get host device name enforcement by command")
}

const updateStmt = `
UPDATE host_mdm_apple_device_names
SET status = ?, detail = ?
WHERE command_uuid = ?`
res, err := tx.ExecContext(ctx, updateStmt, status, detail, commandUUID)
if err != nil {
return ctxerr.Wrap(ctx, err, "update host device name status from command")
}
// The UPDATE is authoritative: if the row's command_uuid changed between
// the SELECT and here (a concurrent supersede), no row matches and the
// SELECTed host/expected values are stale — treat as not-found.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Do we actually need to perform both queries?
I think that if we just kept the update statement and return staleErr when affected == 0 it'd be equivalent, correct?

return nil
}

func (ds *Datastore) UpdateHostDeviceNameStatusFromCommand(ctx context.Context, commandUUID string, status fleet.MDMDeliveryStatus, detail string) (hostUUID string, expectedName string, err error) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nit: maybe we could return a struct instead of hostUUID, expectedName, err? Like (hostDeviceNameEnforcement HostDeviceNameEnforcement, err error). And in the caller we just pull out whatever values we need from that struct

Perhaps we could even reuse that for the arguments this function receives, so it could be something like (ctx context.Context, hostDeviceNameArgs HostDeviceNameEnforcement)

Maybe this could also be applied to other functions in this file.

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'm inclined to keep this as it is, and see if the refactoring make sense when implementing the service layer.

return hostUUID, expectedName, err
}

func (ds *Datastore) VerifyHostDeviceName(ctx context.Context, hostUUID, reportedName string) error {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

where would the reportedName come from?

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.

From the osquery ingestion logic, added a comment clarifying where it comes from.

Comment thread server/fleet/hosts.go
Comment on lines +764 to +773
type HostDeviceNamePending struct {
HostID uint `db:"host_id"`
HostUUID string `db:"host_uuid"`
HardwareSerial string `db:"hardware_serial"`
Platform string `db:"platform"`
// ComputerName is the host's current name in Fleet; the cron uses it to skip
// sending a command when the device already matches the resolved name.
ComputerName string `db:"computer_name"`
TeamID *uint `db:"team_id"`
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

is there a chance we just reuse the Host struct?

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'm not a big fan of having sparsely populated struct unless there's a good reason for it (like method reuse), the Host struct is huge, using it here will mean having a bunch of nil properties. I'll keep this for now and see we can refactor later once the service layer is implemented.

return &enforcement, nil
}

func (ds *Datastore) ResendHostDeviceName(ctx context.Context, hostUUID string) error {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nit: should this be renamed to reflect the fact that we're just resetting the status to null? The fact that this is used to "resend" something (I guess the command to apply a new name to a device) could be surfaced maybe in the service layer / cron workers layer explicitly

Comment thread server/fleet/datastore.go
Comment on lines +1830 to +1832
// ResendHostDeviceName resets a host's enforcement row to a NULL status so the
// cron re-enqueues the Settings/DeviceName command on its next run.
ResendHostDeviceName(ctx context.Context, hostUUID string) error

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Asking because I'm not fully familiar: this method sets status=NULL and leaves the commandUUID untouched. If I follow correctly "resending" would mean to send the DeviceName command a second time... is the commandUUID the same in that case, or would we have to also modify commandUUID when we switch to status=NULL?

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.

Good catch, this should also set the commandUUID to null

@juan-fdz-hawa juan-fdz-hawa merged commit 815c4a3 into 38806-macos-iosipados-rename-hosts Jul 6, 2026
32 of 35 checks passed
@juan-fdz-hawa juan-fdz-hawa deleted the 48620-rename-hosts-enforcement-state-migrations-and-datastore-methods branch July 6, 2026 12:11
@juan-fdz-hawa juan-fdz-hawa restored the 48620-rename-hosts-enforcement-state-migrations-and-datastore-methods branch July 6, 2026 12:21
@juan-fdz-hawa juan-fdz-hawa deleted the 48620-rename-hosts-enforcement-state-migrations-and-datastore-methods branch July 6, 2026 12:23
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.

Rename hosts: enforcement-state migrations and datastore methods

2 participants