Skip to content

Restore host_mdm.server_url on Apple MDM re-enrollment - #50234

Draft
ScottifyShopson wants to merge 4 commits into
fleetdm:mainfrom
ScottifyShopson:scottifyshopson/fix-apple-mdm-server-url-minimal
Draft

Restore host_mdm.server_url on Apple MDM re-enrollment#50234
ScottifyShopson wants to merge 4 commits into
fleetdm:mainfrom
ScottifyShopson:scottifyshopson/fix-apple-mdm-server-url-minimal

Conversation

@ScottifyShopson

@ScottifyShopson ScottifyShopson commented Jul 30, 2026

Copy link
Copy Markdown

Related issue: Fixes #50187

Problem

MDMTurnOff clears host_mdm.server_url. On re-enroll, upsertMDMAppleHostMDMInfoDB only updated enrolled and is_personal_enrollment on conflict, so the cleared URL stayed empty.

macOS often self-heals via osquery. iOS/iPadOS do not, so they keep mdm.server_url: "" forever while still showing connected_to_fleet: true.

Fix

Also set server_url on ON DUPLICATE KEY UPDATE.

Minimal variant of #50188 (server_url only).

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.

Testing

🤖 Generated with xai/grok-4.5 via pi

MDMTurnOff clears server_url. upsertMDMAppleHostMDMInfoDB only rewrote
enrolled and is_personal_enrollment on conflict, so iOS/iPadOS hosts that
unenrolled and re-enrolled kept an empty server_url.

Also update server_url on duplicate key.

Fixes fleetdm#50187

🤖 Generated with xai/grok-4.5 via pi
Copilot AI review requested due to automatic review settings July 30, 2026 17:06
Covers unenroll → re-enroll for iOS/iPadOS so an empty server_url after
conflict upsert cannot regress silently.

🤖 Generated with xai/grok-4.5 via pi

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 updates the Apple MDM host enrollment upsert logic so that when an already-known host re-enrolls, host_mdm.server_url is refreshed instead of remaining blank after an unenroll.

Changes:

  • Update upsertMDMAppleHostMDMInfoDB to set server_url in the ON DUPLICATE KEY UPDATE clause for host_mdm.

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

_, err = tx.ExecContext(ctx, fmt.Sprintf(`
INSERT INTO host_mdm (enrolled, server_url, installed_from_dep, mdm_id, is_server, host_id, is_personal_enrollment) VALUES %s
ON DUPLICATE KEY UPDATE enrolled = VALUES(enrolled), is_personal_enrollment = VALUES(is_personal_enrollment)`, strings.Join(parts, ",")), args...)
ON DUPLICATE KEY UPDATE enrolled = VALUES(enrolled), server_url = VALUES(server_url), is_personal_enrollment = VALUES(is_personal_enrollment)`, strings.Join(parts, ",")), args...)
_, err = tx.ExecContext(ctx, fmt.Sprintf(`
INSERT INTO host_mdm (enrolled, server_url, installed_from_dep, mdm_id, is_server, host_id, is_personal_enrollment) VALUES %s
ON DUPLICATE KEY UPDATE enrolled = VALUES(enrolled), is_personal_enrollment = VALUES(is_personal_enrollment)`, strings.Join(parts, ",")), args...)
ON DUPLICATE KEY UPDATE enrolled = VALUES(enrolled), server_url = VALUES(server_url), is_personal_enrollment = VALUES(is_personal_enrollment)`, strings.Join(parts, ",")), args...)
Copilot AI review requested due to automatic review settings July 30, 2026 17:12

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

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

Comments suppressed due to low confidence (6)

server/datastore/mysql/apple_mdm.go:2076

  • MDMTurnOff clears host_mdm.mdm_id (sets it to NULL), but this upsert still doesn’t restore mdm_id (or installed_from_dep) on the ON DUPLICATE KEY UPDATE path. That means a re-enrolled device can end up with server_url restored but mdm_id still NULL, which doesn’t match the expected “same as first-time enrollment” behavior described in #50187.
		ON DUPLICATE KEY UPDATE enrolled = VALUES(enrolled), server_url = VALUES(server_url), is_personal_enrollment = VALUES(is_personal_enrollment)`, strings.Join(parts, ",")), args...)

server/datastore/mysql/apple_mdm_test.go:8213

  • Add an assertion that mdm_id becomes NULL after MDMTurnOff (it is set to NULL in the UPDATE), so the test verifies the unenroll state before reenrolling.
		afterTurnOff := readHostMDM(h.ID)
		require.False(t, afterTurnOff.Enrolled)
		require.Empty(t, afterTurnOff.ServerURL)

server/datastore/mysql/apple_mdm_test.go:8227

  • Add an assertion that mdm_id is restored on re-enroll (ON DUPLICATE KEY UPDATE path), ideally matching the initial mdm_id.
		afterReenroll := readHostMDM(h.ID)
		require.True(t, afterReenroll.Enrolled, "platform %s", platform)
		require.Equal(t, expectedServerURL, afterReenroll.ServerURL, "platform %s", platform)

server/datastore/mysql/apple_mdm_test.go:8206

  • Add assertions that mdm_id is cleared on unenroll and restored on re-enroll; otherwise the test can pass even if reenroll still leaves mdm_id NULL.
		initial := readHostMDM(h.ID)
		require.True(t, initial.Enrolled)
		require.Equal(t, expectedServerURL, initial.ServerURL)

server/datastore/mysql/apple_mdm_test.go:8181

  • This regression test only verifies server_url. Since MDMTurnOff also clears host_mdm.mdm_id, it’s worth asserting mdm_id is NULL after unenroll and restored after re-enroll so the test fully covers the re-enrollment restoration behavior.

This issue also appears in the following locations of the same file:

  • line 8204
  • line 8211
  • line 8225
	type hostMDMRow struct {
		Enrolled  bool   `db:"enrolled"`
		ServerURL string `db:"server_url"`
	}

server/datastore/mysql/apple_mdm_test.go:8187

  • If you include mdm_id in hostMDMRow, update the SELECT to fetch it; otherwise the new assertion will never be populated.
				`SELECT enrolled, server_url FROM host_mdm WHERE host_id = ?`, hostID)
		})

@codecov

codecov Bot commented Jul 30, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 68.16%. Comparing base (d2946b7) to head (a5e3d57).
⚠️ Report is 11 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main   #50234      +/-   ##
==========================================
+ Coverage   68.14%   68.16%   +0.02%     
==========================================
  Files        3934     3935       +1     
  Lines      250914   250969      +55     
  Branches    13436    13436              
==========================================
+ Hits       170983   171077      +94     
+ Misses      64599    64561      -38     
+ Partials    15332    15331       -1     
Flag Coverage Δ
backend 69.46% <100.00%> (+0.02%) ⬆️

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.

🤖 Generated with xai/grok-4.5 via pi
Copilot AI review requested due to automatic review settings July 30, 2026 17:43

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

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

Comments suppressed due to low confidence (1)

server/datastore/mysql/apple_mdm.go:2076

  • MDMTurnOff clears both host_mdm.server_url and host_mdm.mdm_id (see MDMTurnOff UPDATE host_mdm), but this upsert still only restores server_url on the duplicate-key path. After a device unenrolls and re-enrolls, mdm_id will remain NULL, which can break joins/filters that rely on hmdm.mdm_id (e.g. host list filtering by MDM ID/name). This also doesn’t fully match the linked issue’s expected behavior (restore server_url and mdm_id).
	_, err = tx.ExecContext(ctx, fmt.Sprintf(`
		INSERT INTO host_mdm (enrolled, server_url, installed_from_dep, mdm_id, is_server, host_id, is_personal_enrollment) VALUES %s
		ON DUPLICATE KEY UPDATE enrolled = VALUES(enrolled), server_url = VALUES(server_url), is_personal_enrollment = VALUES(is_personal_enrollment)`, strings.Join(parts, ",")), args...)

Comment on lines +8178 to +8187
type hostMDMRow struct {
Enrolled bool `db:"enrolled"`
ServerURL string `db:"server_url"`
}
readHostMDM := func(hostID uint) hostMDMRow {
var row hostMDMRow
ExecAdhocSQL(t, ds, func(q sqlx.ExtContext) error {
return sqlx.GetContext(ctx, q, &row,
`SELECT enrolled, server_url FROM host_mdm WHERE host_id = ?`, hostID)
})
Comment on lines +8204 to +8213
initial := readHostMDM(h.ID)
require.True(t, initial.Enrolled)
require.Equal(t, expectedServerURL, initial.ServerURL)

_, _, err = ds.MDMTurnOff(ctx, uuid)
require.NoError(t, err)

afterTurnOff := readHostMDM(h.ID)
require.False(t, afterTurnOff.Enrolled)
require.Empty(t, afterTurnOff.ServerURL)
Comment on lines +8225 to +8227
afterReenroll := readHostMDM(h.ID)
require.True(t, afterReenroll.Enrolled, "platform %s", platform)
require.Equal(t, expectedServerURL, afterReenroll.ServerURL, "platform %s", platform)
🤖 Generated with openai/gpt-5.6-sol via pi
Copilot AI review requested due to automatic review settings July 30, 2026 20:53

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

Copilot reviewed 2 out of 3 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (5)

server/datastore/mysql/apple_mdm_test.go:8187

  • Once hostMDMRow includes mdm_id, the SELECT should fetch it; otherwise mdm_id assertions will always use the zero value.
		ExecAdhocSQL(t, ds, func(q sqlx.ExtContext) error {
			return sqlx.GetContext(ctx, q, &row,
				`SELECT enrolled, server_url FROM host_mdm WHERE host_id = ?`, hostID)
		})

server/datastore/mysql/apple_mdm_test.go:8213

  • After MDMTurnOff, mdm_id is expected to be NULL (and should be restored on re-enroll). Add an assertion so the test exercises that full unenroll/reenroll state transition.
		afterTurnOff := readHostMDM(h.ID)
		require.False(t, afterTurnOff.Enrolled)
		require.Empty(t, afterTurnOff.ServerURL)

server/datastore/mysql/apple_mdm_test.go:8227

  • To fully prevent regressions, assert that mdm_id is restored and non-zero after re-enrollment (the duplicate-key update path).
		afterReenroll := readHostMDM(h.ID)
		require.True(t, afterReenroll.Enrolled, "platform %s", platform)
		require.Equal(t, expectedServerURL, afterReenroll.ServerURL, "platform %s", platform)

server/datastore/mysql/apple_mdm.go:2076

  • MDMTurnOff clears host_mdm.mdm_id to NULL (apple_mdm.go:2207), but this upsert only restores server_url on the duplicate-key path. On re-enrollment the row will keep mdm_id=NULL, which can break invariants and does not match the expected behavior in #50187. Update the ON DUPLICATE KEY UPDATE clause to restore mdm_id as well.
	_, err = tx.ExecContext(ctx, fmt.Sprintf(`
		INSERT INTO host_mdm (enrolled, server_url, installed_from_dep, mdm_id, is_server, host_id, is_personal_enrollment) VALUES %s
		ON DUPLICATE KEY UPDATE enrolled = VALUES(enrolled), server_url = VALUES(server_url), is_personal_enrollment = VALUES(is_personal_enrollment)`, strings.Join(parts, ",")), args...)

server/datastore/mysql/apple_mdm_test.go:8181

  • This regression test only reads enrolled/server_url; it should also read mdm_id so it can assert that re-enrollment restores mdm_id after MDMTurnOff sets it to NULL.

This issue also appears in the following locations of the same file:

  • line 8184
  • line 8211
  • line 8225
	type hostMDMRow struct {
		Enrolled  bool   `db:"enrolled"`
		ServerURL string `db:"server_url"`
	}

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.

iOS/iPadOS host_mdm.server_url stays empty after MDM unenroll and re-enroll

2 participants