Skip to content

gkarr 44008 setup experience - #45202

Merged
georgekarrv merged 9 commits into
mainfrom
gkarr-44008-setup-experience
May 15, 2026
Merged

gkarr 44008 setup experience#45202
georgekarrv merged 9 commits into
mainfrom
gkarr-44008-setup-experience

Conversation

@georgekarrv

@georgekarrv georgekarrv commented May 12, 2026

Copy link
Copy Markdown
Member
  • Adding vpp users table
  • Adding ManagedAppleID to HostMDM tables to pull when installing vpp apps to a BYOD user enrolled device
  • Adding user create vpp apis
  • Updating install application to support user enrolled devices
  • Handling already installed gracefully
  • Adding provision user logic
  • Adding logic to associate assets on install
  • Add license managemnt logic
  • Adding self service ui
  • Adding setup experience support
  • Adding setup experience flow
  • Fix issue with fleet's docker image in k8s environments (Fix issue with fleet's docker image in k8s environments #44373)
  • Changing how the appleid is captured to use idp mdm users

Summary by CodeRabbit

  • New Features

    • Account-based User Enrollment (iOS/iPadOS) supports VPP and in‑house .ipa installs, including self‑service installs.
    • Enrollment now applies the selected Setup experience automatically for user‑enrolled hosts.
    • Installs and license associations are scoped to account user associations (user‑scoped installs).
  • Bug Fixes

    • Improved error messaging for license/association failures (including per‑user device cap cases).
    • Mobile UI: self‑service now shows user‑enrolled iOS/iPadOS apps.
    • “Already installed” install results are treated as acknowledged successes.

Review Change Stack

@georgekarrv
georgekarrv force-pushed the gkarr-44008-setup-experience branch from 8b6dfd5 to 1b3b3e3 Compare May 13, 2026 14:17
@georgekarrv
georgekarrv marked this pull request as ready for review May 13, 2026 14:18
@georgekarrv
georgekarrv requested review from a team as code owners May 13, 2026 14:18
Copilot AI review requested due to automatic review settings May 13, 2026 14:18

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

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 expands Fleet’s Apple MDM software install and setup experience flows to support Account-Driven User Enrollment (BYOD) iOS/iPadOS devices by introducing user-scoped VPP licensing (via Managed Apple IDs and VPP “client users”), improving install command behavior for user enrollments, and updating the UI to enable self-service/software installs for BYOD iOS/iPadOS hosts.

Changes:

  • Persist Managed Apple ID for user-enrolled iOS/iPadOS hosts and route setup-experience enqueueing through the BYOD enrollment path.
  • Add VPP “client user” persistence + Apple VPP /users/create support and switch VPP asset association to clientUserIds for BYOD installs.
  • Update install handling to be more resilient (e.g., treat “already installed” as success) and enable BYOD iOS/iPadOS software installs in UI/self-service.

Reviewed changes

Copilot reviewed 32 out of 33 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
server/service/apple_mdm.go Extends TokenUpdate/setup-experience logic for user enrollment; treats “already installed” InstallApplication errors as success; allows scheduled updates for BYOD iOS/iPadOS.
server/service/apple_mdm_test.go Adds coverage for Managed Apple ID persistence, user-enrollment setup experience enqueueing, and “already installed” InstallApplication handling.
server/mock/datastore_mock.go Adds datastore mock methods for Managed Apple ID and VPP client user persistence.
server/mdm/nanomdm/push/nanopush/provider.go Simplifies worker count calculation using min.
server/mdm/apple/vpp/api.go Adds VPP helpers: max-devices error detection, associate-assets validation + user-scoped request support, and create-users endpoint support.
server/mdm/apple/vpp/api_test.go Adds tests for associate-assets validation, clientUserIds payloads, create-users, and max-devices error matching.
server/mdm/apple/util.go Adds helper to detect “app already installed” InstallApplication error chains.
server/mdm/apple/util_test.go Adds tests for “app already installed” error-chain detection.
server/mdm/apple/install_application.go Adds IsUserEnrollment flag to omit ChangeManagementState for user-enrolled install commands.
server/mdm/apple/install_application_test.go Tests that ChangeManagementState is omitted for user enrollment and plist remains valid.
server/fleet/vpp.go Introduces VPPClientUser model and status enum.
server/fleet/hosts.go Adds ManagedAppleID field to HostMDM struct.
server/fleet/datastore.go Extends Datastore interface with Managed Apple ID + VPP client user methods.
server/datastore/mysql/vpp.go Implements VPP client user CRUD; updates VPP install enqueue to pass user-enrollment flag into InstallApplication command build.
server/datastore/mysql/vpp_test.go Adds integration tests for vpp_client_users CRUD/upsert/list + FK cascade behavior.
server/datastore/mysql/schema.sql Adds host_mdm.managed_apple_id column and vpp_client_users table.
server/datastore/mysql/migrations/tables/20260512173250_AddManagedAppleIDToHostMDM.go Migration adding managed_apple_id to host_mdm.
server/datastore/mysql/migrations/tables/20260512173250_AddManagedAppleIDToHostMDM_test.go Migration test for managed_apple_id semantics (NULL default, set/update).
server/datastore/mysql/migrations/tables/20260512173249_CreateTableVPPClientUsers.go Migration creating vpp_client_users table and constraints.
server/datastore/mysql/migrations/tables/20260512173249_CreateTableVPPClientUsers_test.go Migration test for constraints/defaults/FK cascade.
server/datastore/mysql/hosts.go Extends GetHostMDM query to read managed_apple_id; adds Get/SetHostManagedAppleID methods.
server/datastore/mysql/hosts_test.go Adds tests for Managed Apple ID getters/setters and behavior before enrollment.
server/datastore/mysql/activities.go Ensures in-house InstallApplication command generation omits ChangeManagementState for personal enrollments.
frontend/pages/hosts/details/HostDetailsPage/HostDetailsPage.tsx Removes BYOD iOS/iPadOS “software install unsupported” gating; keeps Android gate only.
frontend/pages/hosts/details/cards/Software/SelfService/SelfService.tsx Removes BYOD mobile-view self-service “unsupported” message gate.
frontend/pages/hosts/details/cards/Software/SelfService/SelfService.tests.tsx Updates test to expect BYOD hosts see self-service list (not an unsupported message).
frontend/pages/hosts/details/cards/Software/SelfService/_styles.scss Removes styles for the deleted BYOD unsupported self-service empty state.
ee/server/service/vpp_users.go Adds ensureVPPClientUser to provision/cache VPP client users for user-scoped licensing.
ee/server/service/vpp_users_test.go Adds tests for ensureVPPClientUser behavior (new, cached, retry reuse, partial failure, missing MAID).
ee/server/service/software_installers.go Removes BYOD install block; routes BYOD VPP installs via client user provisioning + clientUserIds association + improved error surfacing.
ee/server/service/software_installers_test.go Updates tests to assert personally-enrolled iOS/iPadOS are no longer blocked at entry points.
ee/server/service/install_vpp_associate_test.go Adds focused tests ensuring associate-assets and assignments routing uses clientUserIds for BYOD and serialNumbers otherwise; tests max-devices messaging.
changes/31138-user-enrolled-software-install Adds release notes describing BYOD iOS/iPadOS install + self-service + setup experience behavior changes.

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

Comment thread server/mdm/apple/vpp/api.go
Comment thread server/fleet/hosts.go Outdated
Comment thread server/fleet/datastore.go
Comment thread changes/31138-user-enrolled-software-install Outdated
@coderabbitai

coderabbitai Bot commented May 13, 2026

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

This PR enables iOS/iPadOS installs on Account‑Driven User Enrollment hosts by persisting Managed Apple IDs, adding a vpp_client_users table and types, implementing Service.ensureVPPClientUser (CreateUsers integration, pending/registered caching), validating AssociateAssets payloads (clientUserIds vs serialNumbers), removing BYOD gating from install flows, routing installs based on enrollment type, updating MDM token handling to store managed Apple IDs and enqueue setup-experience, and adjusting InstallApplication command generation and related tests.

Possibly related PRs

  • fleetdm/fleet#45378: Adds the same MSW GET mock for defaultEnrollmentProfileUrl in SetupAssistant tests (similar test adjustment to ensure the component receives the expected default enrollment profile payload).
  • fleetdm/fleet#44934: Modifies the Apple MDM InstallApplication command builder; both PRs change InstallApplicationParams/command generation for iOS installs.
  • fleetdm/fleet#32822: Related TokenUpdate/setup-experience logic changes for iOS MDM enrollment flows.
🚥 Pre-merge checks | ✅ 2 | ❌ 3

❌ Failed checks (2 warnings, 1 inconclusive)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description includes a bulleted list of multiple features but lacks the structured format of the template, missing issue/story number, testing details, and migration verification sections. Rewrite to follow the template structure: add 'Related issue:', complete checklist items (changes file, testing, migrations), and verify all sections are addressed.
Docstring Coverage ⚠️ Warning Docstring coverage is 19.30% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'gkarr 44008 setup experience' is vague and uses non-descriptive terms that don't convey meaningful information about the changeset. Replace with a clear, concise title describing the main change, such as 'Support VPP app installation and setup experience for user-enrolled iOS devices' or similar.
✅ Passed checks (2 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch gkarr-44008-setup-experience

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


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: 8

Caution

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

⚠️ Outside diff range comments (1)
ee/server/service/software_installers_test.go (1)

375-380: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Set the host fixture to personal enrollment in this test.

Line 375 builds an iOS host, but it doesn’t set host.MDM.EnrollmentStatus to personal. This can pass without covering the BYOD regression path the test name describes.

Suggested patch
 	host := &fleet.Host{
 		UUID:         "personal-ios",
 		OrbitNodeKey: ptr.String("orbit_key"),
 		Platform:     "ios",
 		TeamID:       ptr.Uint(1),
+		MDM: fleet.MDMHostData{
+			EnrollmentStatus: ptr.String(string(fleet.MDMEnrollStatusPersonal)),
+		},
 	}
🤖 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 `@ee/server/service/software_installers_test.go` around lines 375 - 380, The
test builds an iOS host but never sets the host to a personal enrollment, so
update the host fixture (the host variable of type fleet.Host) to set its
MDM.EnrollmentStatus to the personal enrollment constant (e.g.,
fleet.EnrollmentStatusPersonal or the project’s equivalent) so the test
exercises the BYOD/personal enrollment code path; locate the host creation block
and add the EnrollmentStatus assignment on host.MDM.
🤖 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 `@ee/server/service/vpp_users.go`:
- Around line 90-99: The call to svc.ds.InsertVPPClientUser currently discards
its error when recording a pending VPPClientUser after u.HasError(); update this
to check the return value, and if non-nil wrap/return that error (similar to the
pattern used around InsertVPPClientUser earlier at lines 67-76) instead of
ignoring it—locate the block that creates &fleet.VPPClientUser with VPPTokenID,
ManagedAppleID, ClientUserID and Status Pending, call
svc.ds.InsertVPPClientUser(ctx, ...), capture err, and return a wrapped ctxerr
(or the same error handling helper used elsewhere) so failures persist back to
the caller.
- Around line 67-76: The InsertVPPClientUser error is being ignored when
persisting a pending VPPClientUser after CreateUsers fails; update the error
handling in the block around svc.ds.InsertVPPClientUser so the
InsertVPPClientUser call's error is not discarded—either log it via the existing
logger or return a wrapped error that includes both the original CreateUsers
error and the InsertVPPClientUser error (reference symbols:
svc.ds.InsertVPPClientUser, fleet.VPPClientUser, VPPClientUserStatusPending,
token.ID, clientUserID); ensure the final return uses ctxerr.Wrap (or similar)
to surface both failures instead of silently discarding the insert error.

In `@server/datastore/mysql/hosts.go`:
- Around line 4909-4912: The GetHostManagedAppleID function currently queries
the write-sensitive managed_apple_id using ds.reader(ctx), which can hit a
replica and suffer lag; change the DB handle to ds.writer(ctx) so the SELECT in
GetHostManagedAppleID uses the primary. Update the sqlx.GetContext call in
GetHostManagedAppleID to use ds.writer(ctx) instead of ds.reader(ctx) so the
managed_apple_id lookup reads from the primary to avoid replica-lag misses.
- Around line 4922-4929: The SetHostManagedAppleID function currently treats
zero-row updates as success; modify it to check the Exec result's RowsAffected()
and if it returns 0, return a not-found error (e.g., ctxerr.Wrapf(ctx,
sql.ErrNoRows or a custom not-found error) indicating no host_mdm row for the
given host_id). Concretely, capture the result from ds.writer(ctx).ExecContext,
call RowsAffected(), and when it's 0 return a clear not-found error mentioning
hostID; otherwise proceed to return nil.

In `@server/datastore/mysql/vpp.go`:
- Around line 1858-1860: The not-found error returned by
notFound("VPPClientUser") currently includes the managedAppleID
(managed_apple_id) which is PII; change the WithMessage call in that return so
it does not include managedAppleID (e.g. use fmt.Sprintf("no VPP client user for
vpp_token_id=%d", tokenID)) and remove any reference to the managedAppleID
variable in this error construction (locate the return that wraps
notFound("VPPClientUser") and adjust the fmt.Sprintf/WithMessage accordingly).

In `@server/fleet/vpp.go`:
- Around line 213-226: Add an IsValid() method on the VPPClientUserStatus type
that mirrors the pattern used by HostStatus.IsValid() and others: implement func
(s VPPClientUserStatus) IsValid() bool that returns true only when s equals one
of the defined constants VPPClientUserStatusPending,
VPPClientUserStatusRegistered, or VPPClientUserStatusRetired; place the method
alongside the type/consts so callers can validate VPPClientUserStatus values
consistently.

In `@server/mdm/apple/vpp/api.go`:
- Around line 192-195: In AssociateAssets ensure you nil-check the params
pointer before calling params.Validate to avoid a panic; update AssociateAssets
to return a clear error when params is nil (e.g., "nil params" or validation
error) and only call params.Validate() if params != nil, keeping the rest of the
function logic unchanged and referencing AssociateAssets and params.Validate to
locate where to add the guard.

In `@server/service/apple_mdm.go`:
- Around line 3928-3932: Currently the code only writes a non-empty
managedAppleID for mdm.UserEnrollmentDevice, leaving old values in
host_mdm.managed_apple_id when resolution fails; change the logic in the r.Type
== mdm.UserEnrollmentDevice branch to always call
svc.ds.SetHostManagedAppleID(r.Context, info.HostID, managedAppleID) (passing
the empty string when resolution failed) so stale IDs are cleared, and
propagate/wrap any error from SetHostManagedAppleID as before.

---

Outside diff comments:
In `@ee/server/service/software_installers_test.go`:
- Around line 375-380: The test builds an iOS host but never sets the host to a
personal enrollment, so update the host fixture (the host variable of type
fleet.Host) to set its MDM.EnrollmentStatus to the personal enrollment constant
(e.g., fleet.EnrollmentStatusPersonal or the project’s equivalent) so the test
exercises the BYOD/personal enrollment code path; locate the host creation block
and add the EnrollmentStatus assignment on host.MDM.
🪄 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: 4e6693e8-e4b8-4a87-b92f-3b5be9b273ff

📥 Commits

Reviewing files that changed from the base of the PR and between b1ecaef and 1b3b3e3.

📒 Files selected for processing (33)
  • changes/31138-user-enrolled-software-install
  • ee/server/service/install_vpp_associate_test.go
  • ee/server/service/software_installers.go
  • ee/server/service/software_installers_test.go
  • ee/server/service/vpp_users.go
  • ee/server/service/vpp_users_test.go
  • frontend/pages/hosts/details/HostDetailsPage/HostDetailsPage.tsx
  • frontend/pages/hosts/details/cards/Software/SelfService/SelfService.tests.tsx
  • frontend/pages/hosts/details/cards/Software/SelfService/SelfService.tsx
  • frontend/pages/hosts/details/cards/Software/SelfService/_styles.scss
  • server/datastore/mysql/activities.go
  • server/datastore/mysql/hosts.go
  • server/datastore/mysql/hosts_test.go
  • server/datastore/mysql/migrations/tables/20260512173249_CreateTableVPPClientUsers.go
  • server/datastore/mysql/migrations/tables/20260512173249_CreateTableVPPClientUsers_test.go
  • server/datastore/mysql/migrations/tables/20260512173250_AddManagedAppleIDToHostMDM.go
  • server/datastore/mysql/migrations/tables/20260512173250_AddManagedAppleIDToHostMDM_test.go
  • server/datastore/mysql/schema.sql
  • server/datastore/mysql/vpp.go
  • server/datastore/mysql/vpp_test.go
  • server/fleet/datastore.go
  • server/fleet/hosts.go
  • server/fleet/vpp.go
  • server/mdm/apple/install_application.go
  • server/mdm/apple/install_application_test.go
  • server/mdm/apple/util.go
  • server/mdm/apple/util_test.go
  • server/mdm/apple/vpp/api.go
  • server/mdm/apple/vpp/api_test.go
  • server/mdm/nanomdm/push/nanopush/provider.go
  • server/mock/datastore_mock.go
  • server/service/apple_mdm.go
  • server/service/apple_mdm_test.go
💤 Files with no reviewable changes (1)
  • frontend/pages/hosts/details/cards/Software/SelfService/_styles.scss

Comment thread ee/server/service/vpp_users.go
Comment thread ee/server/service/vpp_users.go
Comment thread server/datastore/mysql/hosts.go
Comment thread server/datastore/mysql/hosts.go
Comment thread server/datastore/mysql/vpp.go
Comment thread server/fleet/vpp.go
Comment thread server/mdm/apple/vpp/api.go
Comment thread server/service/apple_mdm.go Outdated

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

Overall this looks good. I think maybe the upgrade case should be handled if possible(backfilling old enrollments). Also would it be possible to have an end to end integration test added? We have them for several other setup experience paths. That could be a follow-on

@codecov

codecov Bot commented May 13, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 78.54890% with 68 lines in your changes missing coverage. Please review.
✅ Project coverage is 66.76%. Comparing base (ba4d68d) to head (7b88b86).
⚠️ Report is 11 commits behind head on main.

Files with missing lines Patch % Lines
ee/server/service/vpp_users.go 57.62% 17 Missing and 8 partials ⚠️
server/datastore/mysql/vpp.go 88.33% 4 Missing and 3 partials ⚠️
ee/server/service/software_installers.go 78.57% 3 Missing and 3 partials ⚠️
...ables/20260512173250_AddManagedAppleIDToHostMDM.go 72.72% 4 Missing and 2 partials ⚠️
server/mdm/apple/vpp/api.go 85.71% 3 Missing and 3 partials ⚠️
server/service/apple_mdm.go 62.50% 5 Missing and 1 partial ⚠️
server/datastore/mysql/hosts.go 78.26% 3 Missing and 2 partials ⚠️
...tables/20260512173249_CreateTableVPPClientUsers.go 82.60% 3 Missing and 1 partial ⚠️
server/datastore/mysql/activities.go 90.47% 1 Missing and 1 partial ⚠️
.../hosts/details/HostDetailsPage/HostDetailsPage.tsx 0.00% 1 Missing ⚠️
Additional details and impacted files
@@           Coverage Diff            @@
##             main   #45202    +/-   ##
========================================
  Coverage   66.75%   66.76%            
========================================
  Files        2740     2743     +3     
  Lines      219199   219459   +260     
  Branches    10832    10826     -6     
========================================
+ Hits       146331   146516   +185     
- Misses      59637    59699    +62     
- Partials    13231    13244    +13     
Flag Coverage Δ
backend 68.59% <78.79%> (-0.01%) ⬇️
frontend 55.59% <0.00%> (+0.04%) ⬆️

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.

Squashed branch commits:
- Add vpp_client_users table and CRUD
- Add ManagedAppleID to HostMDM
- Create users on Apple VPP
- Update install application to support user enrollment (new installapp package)
- Self-service UI for setup experience
- License management for VPP
- Associate assets on install for user-enrolled hosts
- Handle 'already installed' gracefully
- Capture Apple ID via IDP MDM users
- Lint fixes (require -> assert in handler closures, min() modernization)
- Rename `cap` to `capt` in install_vpp_associate_test.go to avoid
  shadowing the builtin cap() (revive: redefines-builtin-id).
- Add explicit `COLLATE utf8mb4_unicode_ci` to vpp_client_users.status
  ENUM so TestCollation passes on MySQL 8 (which would otherwise default
  to utf8mb4_0900_ai_ci).
- Add missing defaultEnrollmentProfileUrl mock to the "MDM not
  configured" SetupAssistant test so isLoading resolves and the empty
  state renders.
Mirror what TokenUpdate does at enrollment time so already-enrolled
User Enrollment (BYOD) hosts get their Managed Apple ID populated
without having to re-enroll. The source is the IDP account email
linked via host_mdm_idp_accounts, filtered to is_personal_enrollment=1
to match the application path that sets the column on new enrollments.
The iphone_ipad_refetcher cron only runs every hour and skips hosts whose
details_updated_at is less than 1 hour stale, so a freshly BYOD-enrolled
iPhone/iPad has no app inventory in Fleet for up to an hour. That blocks
software install (the host page can't show installable software until
apps come back), forcing the user to click "Refetch" manually.

Send InstalledApplicationList, CertificateList, and DeviceInformation
from the post-manual-enrollment worker so the inventory populates as
soon as enrollment finishes, and track the commands in host_mdm_commands
so the cron skips them on its next tick.
Exercises the Account-Driven User Enrollment path end-to-end (SSO bearer
token → AccountDriven enrollment → post-manual-enrollment worker) and
verifies the four things Fleet now does for a freshly BYOD-enrolled
iPhone:

- persists the Managed Apple ID on host_mdm from the IdP account email,
- sets refetch_requested so the UI surfaces that inventory is pending,
- sends the three iOS refetch MDM commands with managedAppsOnly=true on
  InstalledApplicationList (Apple rejects the full-inventory variant on
  User Enrollments), and
- records them in host_mdm_commands so the hourly iphone_ipad_refetcher
  cron skips them on its next tick.

The setup-experience VPP install half of the BYOD flow is exercised by
TestInstallVPPAppPostValidation_AssociateAssetsRouting; reproducing the
full Apple /users/create + /assets/associate clientUserId dance here
would need a more complete VPP mock backend.
- vpp/api.go: nil-check params in Validate() so AssociateAssets returns a
  clean error instead of dereferencing a nil pointer (Copilot, CodeRabbit).
- fleet/hosts.go: rewrite ManagedAppleID field comment to reflect the
  actual source (IdP account email at TokenUpdate), not UserLongName
  which Apple doesn't reliably populate on User Enrollment (Copilot).
- fleet/datastore.go: clarify GetHostManagedAppleID/SetHostManagedAppleID
  docs — NotFound when no host_mdm row, empty string when NULL (Copilot).
- changelog: "Account-based" → "Account-Driven" to match Apple's term
  used elsewhere in code/comments (Copilot).
- vpp_users.go: don't silently swallow InsertVPPClientUser errors when
  persisting the pending state after CreateUsers/per-user failures —
  log them so an insert failure doesn't get masked (CodeRabbit).
- mysql/hosts.go: GetHostManagedAppleID reads from the primary (the
  value is set during TokenUpdate and consumed seconds later by VPP
  user-provisioning, so replica lag would cause false misses);
  SetHostManagedAppleID returns NotFound on zero RowsAffected so a
  missing host_mdm row isn't silently dropped (CodeRabbit).
- mysql/vpp.go: drop managed_apple_id (PII) from VPPClientUser
  not-found error message (CodeRabbit).
- apple_mdm.go: always call SetHostManagedAppleID for UserEnrollmentDevice
  — including with an empty value — so a stale Managed Apple ID from a
  prior enrollment is cleared and can't be reused for user-scoped VPP
  actions; log (not fail) NotFound since the lifecycle reset above
  should have inserted the host_mdm row already (CodeRabbit).
- software_installers_test.go: set host.MDM.EnrollmentStatus to personal
  so TestInstallSoftwareTitleAllowsPersonallyEnrolledDevices actually
  exercises the BYOD code path (CodeRabbit).

Tests updated: hosts_test.go asserts NotFound from SetHostManagedAppleID
when host_mdm is missing; apple_mdm_test.go asserts the empty-value
clear path for UserEnrollmentDevice without an IdP account.
Earlier this branch had runPostManualEnrollment send InstalledApplicationList /
CertificateList / DeviceInformation directly so freshly-enrolled BYOD iPhones
would have inventory immediately. That broke a number of existing integration
tests (TestInHouseAppInstall, TestInstalledApplicationListCommandForBYODiDevices,
TestIOSiPadOSRefetch, …) — their command-drain loops only acknowledge specific
RequestTypes, so the unexpected refetch commands looped forever and the suite
timed out.

Move the trigger into the existing iphone_ipad_refetcher cron instead:

- mdmHostEnrollFields now flags iOS/iPadOS hosts with refetch_requested=true on
  enrollment (osquery-capable hosts already did this; iOS has no osquery, so it
  needs MDM-driven refetch as its inventory source).
- ListIOSAndIPadOSToRefetch's WHERE clause adds `OR h.refetch_requested = 1`,
  so the next 10-minute cron tick picks up a freshly-enrolled host. The flag
  is cleared by the DeviceInformation ack handler, so we don't re-send on
  every tick after the first refetch.
- runPostManualEnrollment drops the synchronous refetchIOSIPadOSAfterEnrollment
  call (and the helper); the cron is now the single source of truth for
  emitting refetch commands.

Tests updated to match:
- testMDMAppleUpsertHostIOSIPadOS now asserts RefetchRequested=true for iOS/iPadOS.
- testListIOSAndIPadOSToRefetch clears refetch_requested between setup and the
  staleness-path assertions so the same coverage still applies.
- TestSetupExperienceBYODiOS drives IOSiPadOSRefetch explicitly (the suite
  doesn't tick the cron schedule) and drops the immediate-command assertions.
- The worker-level "schedules refetch for iOS BYOD enrollments" test is
  removed since the worker no longer queues commands.
@georgekarrv
georgekarrv force-pushed the gkarr-44008-setup-experience branch from 94b14e4 to 00d7634 Compare May 15, 2026 13:03

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

It's hard to fully review a PR this size but I think this looks good overall

JOIN hosts h ON h.id = hm.host_id
JOIN host_mdm_idp_accounts hmia ON hmia.host_uuid = h.uuid
JOIN mdm_idp_accounts mia ON mia.uuid = hmia.account_uuid
SET hm.managed_apple_id = mia.email

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 think this is OK just calling out this could be a long running query during migration since it does a few joins and might require some full table scans. Might be worth reviewing or trying to test with synthetic data

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.

4 participants