Put Android profiles in pending immediately on upload (#39330)

<!-- Add the related story/sub-task/bug number, like Resolves #123, or
remove if NA -->
**Related issue:** Resolves #35613
Android profiles will now show up in the UI as "pending" immediately on
a profile upload, to match Apple profiles behavior. Previously, you
would have to wait until the `mdm_android_profile_manager` cron job for
them to show up and actually install them.
This could cost a little bit of performance, but is the existing
behavior for Apple profiles.
# Checklist for submitter

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

- [x] Changes file added for user-visible changes in `changes/`,
`orbit/changes/` or `ee/fleetd-chrome/changes`.
See [Changes
files](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/guides/committing-changes.md#changes-files)
for more information.

- [ ] Input data is properly validated, `SELECT *` is avoided, SQL
injection is prevented (using placeholders for values in statements)
- [ ] If paths of existing endpoints are modified without backwards
compatibility, checked the frontend/CLI for any necessary changes

## Testing

- [x] Added/updated automated tests
- [ ] Where appropriate, [automated tests simulate multiple hosts and
test for host
isolation](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/reference/patterns-backend.md#unit-testing)
(updates to one hosts's records do not affect another)

- [x] QA'd all new/changed functionality manually:
- Adding an Android profile adds it to null in
host_mdm_android_profiles, which makes it show up as “pending”
immediately in the UI. When `mdm_android_profile_manager` runs it sets
them to pending in the database instead of null.
- Deleting a profile will also cause all profiles to become pending
immediately.
This commit is contained in:
Jonathan Katz
2026-02-04 14:18:06 -05:00
committed by GitHub
parent 0f815423f6
commit 707f71c14d
5 changed files with 54 additions and 1 deletions
+1
View File
@@ -0,0 +1 @@
Updated Android MDM profiles to show up as pending on upload, the same as Apple MDM profiles.
@@ -138,7 +138,7 @@ func (s *integrationMDMTestSuite) createEnrolledAndroidHost(t *testing.T, ctx co
AppliedPolicyID: ptr.String("1"),
},
}
androidHostInput.SetNodeKey(enterpriseID)
androidHostInput.SetNodeKey("android/" + hostUUID)
createdAndroidHost, err := s.ds.NewAndroidHost(ctx, androidHostInput)
require.NoError(t, err)
@@ -8691,3 +8691,49 @@ func (s *integrationMDMTestSuite) TestWindowsProfileRetry() {
require.Len(t, cmds, 1) // only ack returned
})
}
func (s *integrationMDMTestSuite) TestHostMDMAndroidProfilesStatus() {
t := s.T()
ctx := context.Background()
s.setSkipWorkerJobs(t)
testTeam, err := s.ds.NewTeam(ctx, &fleet.Team{Name: "TestTeam"})
require.NoError(t, err)
// Ensure MDM is turned on
appConfig, err := s.ds.AppConfig(ctx)
require.NoError(t, err)
appConfig.MDM.AndroidEnabledAndConfigured = true
err = s.ds.SaveAppConfig(ctx, appConfig)
require.NoError(t, err)
enterpriseID := s.enableAndroidMDM(t)
s.createEnrolledAndroidHost(t, ctx, enterpriseID, &testTeam.ID, "host-1")
s.createEnrolledAndroidHost(t, ctx, enterpriseID, &testTeam.ID, "host-2")
var hosts listHostsResponse
s.DoJSON("GET", "/api/latest/fleet/hosts", nil, http.StatusOK, &hosts)
assert.Len(t, hosts.Hosts, 2)
bytes := []byte(`{
"removeUserDisabled": false
}`)
fields := make(map[string][]string)
fields["team_id"] = []string{fmt.Sprintf("%d", testTeam.ID)}
body, headers := generateNewProfileMultipartRequest(t, "remove-user-disabled.json", bytes, s.token, fields)
res := s.DoRawWithHeaders("POST", "/api/latest/fleet/configuration_profiles", body.Bytes(), http.StatusOK, headers)
require.NotNil(t, res)
// profiles should be added with NULL status even before the cron job (s.awaitTriggerAndroidProfileSchedule(t))
var profiles []fleet.HostMDMAndroidProfile
mysql.ExecAdhocSQL(t, s.ds, func(q sqlx.ExtContext) error {
err := sqlx.SelectContext(ctx, q, &profiles, "SELECT host_uuid, status, operation_type FROM host_mdm_android_profiles")
require.NoError(t, err)
return nil
})
require.Len(t, profiles, 2)
require.Nil(t, profiles[0].Status)
require.Nil(t, profiles[1].Status)
}
+3
View File
@@ -1801,6 +1801,9 @@ func (svc *Service) NewMDMAndroidConfigProfile(ctx context.Context, teamID uint,
}
return nil, ctxerr.Wrap(ctx, err)
}
if _, err := svc.ds.BulkSetPendingMDMHostProfiles(ctx, nil, nil, []string{newCP.ProfileUUID}, nil); err != nil {
return nil, ctxerr.Wrap(ctx, err, "bulk set pending host profiles")
}
var (
actTeamID *uint
+3
View File
@@ -2669,6 +2669,9 @@ func TestNewMDMProfilePremiumOnlyAndroid(t *testing.T) {
ds.NewMDMAndroidConfigProfileFunc = func(ctx context.Context, cp fleet.MDMAndroidConfigProfile) (*fleet.MDMAndroidConfigProfile, error) {
return &fleet.MDMAndroidConfigProfile{}, nil
}
ds.BulkSetPendingMDMHostProfilesFunc = func(ctx context.Context, hostIDs, teamIDs []uint, profileUUIDs, hostUUIDs []string) (updates fleet.MDMProfilesUpdates, err error) {
return fleet.MDMProfilesUpdates{}, nil
}
testCases := []struct {
name string