Remove "do not enqueue setup experience items >24 hours after enrollment" logic for macOS hosts (#40739)

This commit is contained in:
Jordan Montgomery
2026-02-27 16:15:34 -06:00
committed by GitHub
parent 57c4f8c59f
commit d4237f8a4f
3 changed files with 136 additions and 39 deletions
+1
View File
@@ -0,0 +1 @@
* Fixed a bug where macOS systems previous enrolled in fleet wouldn't always go through setup experience after a wipe
+27 -25
View File
@@ -23,32 +23,34 @@ func (ds *Datastore) ResetSetupExperienceItemsAfterFailure(ctx context.Context,
}
func (ds *Datastore) enqueueSetupExperienceItems(ctx context.Context, hostPlatformLike string, hostUUID string, teamID uint, resetFailedSetupSteps bool) (bool, error) {
// Find the host with the given UUID and platform. If it's already been enrolled for > the cutoff,
// don't enqueue any items. This handles the edge case where an enrolled host upgrades from an
// Orbit version that didn't support setup experience to one that does.
// See https://github.com/fleetdm/fleet/issues/35717
stmtHost := `
SELECT
last_enrolled_at
FROM
hosts
WHERE uuid = ? AND platform = ?
`
var lastEnrolledAt sql.NullTime
if err := sqlx.GetContext(ctx, ds.reader(ctx), &lastEnrolledAt, stmtHost, hostUUID, hostPlatformLike); err != nil {
if errors.Is(err, sql.ErrNoRows) {
// This shouldn't happen but we don't check for it elsewhere,
// so we'll log a warning and continue.
ds.logger.WarnContext(ctx, "Host not found while enqueueing setup experience items", "host_uuid", hostUUID, "platform_like", hostPlatformLike)
} else {
return false, ctxerr.Wrap(ctx, err, "finding host for enqueueing setup experience items")
if hostPlatformLike != "darwin" && hostPlatformLike != "ios" && hostPlatformLike != "ipados" {
// Find the host with the given UUID and platform. If it's already been enrolled for > the cutoff,
// don't enqueue any items. This handles the edge case where an enrolled host upgrades from an
// Orbit version that didn't support setup experience to one that does.
// See https://github.com/fleetdm/fleet/issues/35717
stmtHost := `
SELECT
last_enrolled_at
FROM
hosts
WHERE uuid = ? AND platform = ?
`
var lastEnrolledAt sql.NullTime
if err := sqlx.GetContext(ctx, ds.reader(ctx), &lastEnrolledAt, stmtHost, hostUUID, hostPlatformLike); err != nil {
if errors.Is(err, sql.ErrNoRows) {
// This shouldn't happen but we don't check for it elsewhere,
// so we'll log a warning and continue.
ds.logger.WarnContext(ctx, "Host not found while enqueueing setup experience items", "host_uuid", hostUUID, "platform_like", hostPlatformLike)
} else {
return false, ctxerr.Wrap(ctx, err, "finding host for enqueueing setup experience items")
}
}
// If the host was enrolled more than 24 hours ago, don't enqueue any items.
// Note: if the last enroll date is our "zero date" (1/1/2000), treat it as if it's never enrolled.
if lastEnrolledAt.Valid && lastEnrolledAt.Time.Before(time.Now().Add(-24*time.Hour)) && lastEnrolledAt.Time.After(time.Date(2000, time.January, 1, 0, 0, 0, 0, time.UTC)) {
ds.logger.DebugContext(ctx, "Host enrolled more than 24 hours ago, skipping enqueueing setup experience items", "host_uuid", hostUUID, "platform_like", hostPlatformLike, "last_enrolled_at", lastEnrolledAt.Time)
return false, nil
}
}
// If the host was enrolled more than 24 hours ago, don't enqueue any items.
// Note: if the last enroll date is our "zero date" (1/1/2000), treat it as if it's never enrolled.
if lastEnrolledAt.Valid && lastEnrolledAt.Time.Before(time.Now().Add(-24*time.Hour)) && lastEnrolledAt.Time.After(time.Date(2000, time.January, 1, 0, 0, 0, 0, time.UTC)) {
ds.logger.DebugContext(ctx, "Host enrolled more than 24 hours ago, skipping enqueueing setup experience items", "host_uuid", hostUUID, "platform_like", hostPlatformLike, "last_enrolled_at", lastEnrolledAt.Time)
return false, nil
}
// NOTE: currently, the Android platform does not use the "enqueue setup experience items" flow as it
+108 -14
View File
@@ -32,6 +32,7 @@ func TestSetupExperience(t *testing.T) {
{"TestHostInSetupExperience", testHostInSetupExperience},
{"TestGetSetupExperienceScriptByID", testGetSetupExperienceScriptByID},
{"TestUpdateSetupExperienceScriptWhileEnqueued", testUpdateSetupExperienceScriptWhileEnqueued},
{"TestEnqueueSetupExperienceItemsWindows", testEnqueueSetupExperienceItemsWindows},
}
for _, c := range cases {
@@ -230,6 +231,103 @@ func testEnqueueSetupExperienceLinuxScriptPackages(t *testing.T, ds *Datastore)
})
}
func testEnqueueSetupExperienceItemsWindows(t *testing.T, ds *Datastore) {
ctx := context.Background()
team1, err := ds.NewTeam(ctx, &fleet.Team{Name: "team1"})
require.NoError(t, err)
team2, err := ds.NewTeam(ctx, &fleet.Team{Name: "team2"})
require.NoError(t, err)
user1 := test.NewUser(t, ds, "Alice", "alice@example.com", true)
// Create some software installers and add them to setup experience
tfr1, err := fleet.NewTempFileReader(strings.NewReader("hello"), t.TempDir)
require.NoError(t, err)
installerID1, _, err := ds.MatchOrCreateSoftwareInstaller(ctx, &fleet.UploadSoftwareInstallerPayload{
InstallScript: "hello",
PreInstallQuery: "SELECT 1",
PostInstallScript: "world",
UninstallScript: "goodbye",
InstallerFile: tfr1,
StorageID: "storage1",
Filename: "file1",
Title: "Software1",
Version: "1.0",
Source: "apps",
UserID: user1.ID,
TeamID: &team1.ID,
Platform: "windows",
ValidatedLabels: &fleet.LabelIdentsWithScope{},
})
require.NoError(t, err)
tfr2, err := fleet.NewTempFileReader(strings.NewReader("hello"), t.TempDir)
require.NoError(t, err)
installerID2, _, err := ds.MatchOrCreateSoftwareInstaller(ctx, &fleet.UploadSoftwareInstallerPayload{
InstallScript: "banana",
PreInstallQuery: "SELECT 3",
PostInstallScript: "apple",
InstallerFile: tfr2,
StorageID: "storage3",
Filename: "file3",
Title: "Software2",
Version: "3.0",
Source: "apps",
SelfService: true,
UserID: user1.ID,
TeamID: &team2.ID,
Platform: "windows",
ValidatedLabels: &fleet.LabelIdentsWithScope{},
})
require.NoError(t, err)
ExecAdhocSQL(t, ds, func(q sqlx.ExtContext) error {
_, err := q.ExecContext(ctx, "UPDATE software_installers SET install_during_setup = 1 WHERE id IN (?, ?)", installerID1, installerID2)
return err
})
host1UUID := "11111111-1111-1111-1111-111111111111"
host2UUID := "22222222-2222-2222-2222-222222222222"
// Freshly enrolled host, should get items enqueued
_, err = ds.NewHost(ctx, &fleet.Host{
Hostname: "windows-test-1",
OsqueryHostID: ptr.String("osquery-windows-1"),
NodeKey: ptr.String("node-key-windows-1"),
UUID: host1UUID,
Platform: "windows",
HardwareSerial: "654321a-1",
})
require.NoError(t, err)
ExecAdhocSQL(t, ds, func(q sqlx.ExtContext) error {
_, err := q.ExecContext(ctx, "UPDATE hosts SET last_enrolled_at = ? WHERE uuid = ?", time.Now().Add(-1*time.Hour), host1UUID)
return err
})
// Enroll date > 24 hours ago and is windows. This should not get items enqueued.
_, err = ds.NewHost(ctx, &fleet.Host{
Hostname: "windows-test-2",
OsqueryHostID: ptr.String("osquery-windows-2"),
NodeKey: ptr.String("node-key-windows-2"),
UUID: host2UUID,
Platform: "windows",
HardwareSerial: "654321b-2",
})
require.NoError(t, err)
ExecAdhocSQL(t, ds, func(q sqlx.ExtContext) error {
_, err := q.ExecContext(ctx, "UPDATE hosts SET last_enrolled_at = ? WHERE uuid = ?", time.Now().Add(-25*time.Hour), host2UUID)
return err
})
anythingEnqueued, err := ds.EnqueueSetupExperienceItems(ctx, "windows", host1UUID, team1.ID)
require.NoError(t, err)
require.True(t, anythingEnqueued)
anythingEnqueued, err = ds.EnqueueSetupExperienceItems(ctx, "windows", host2UUID, team2.ID)
require.NoError(t, err)
require.False(t, anythingEnqueued)
}
func testEnqueueSetupExperienceItems(t *testing.T, ds *Datastore) {
ctx := context.Background()
test.CreateInsertGlobalVPPToken(t, ds)
@@ -352,7 +450,7 @@ func testEnqueueSetupExperienceItems(t *testing.T, ds *Datastore) {
// Deliberately not adding a record for the hostTeam2Missing, to verify that
// we still enqueue items for it if it doesn't exist in the database.
// Enroll date > 24 hours ago. This should NOT have items enqueued.
// Enroll date > 24 hours ago but is macOS. This should get items enqueued.
_, err = ds.NewHost(ctx, &fleet.Host{
Hostname: "macos-test-4",
OsqueryHostID: ptr.String("osquery-macos-4"),
@@ -422,12 +520,11 @@ func testEnqueueSetupExperienceItems(t *testing.T, ds *Datastore) {
anythingEnqueued, err = ds.EnqueueSetupExperienceItems(ctx, "darwin", hostTeam1Old, team1.ID)
require.NoError(t, err)
require.False(t, anythingEnqueued)
// This host enrolled > 24 hours ago, so we shouldn't enqueue any items for it.
require.True(t, anythingEnqueued)
// This host enrolled > 24 hours ago, but it's darwin, so we should enqueue items for it.
awaitingConfig, err = ds.GetHostAwaitingConfiguration(ctx, hostTeam1Old)
require.Error(t, err)
require.True(t, fleet.IsNotFound(err))
require.False(t, awaitingConfig)
require.NoError(t, err)
require.True(t, awaitingConfig)
seRows := []setupExperienceInsertTestRows{}
@@ -435,8 +532,8 @@ func testEnqueueSetupExperienceItems(t *testing.T, ds *Datastore) {
return sqlx.SelectContext(ctx, q, &seRows, "SELECT host_uuid, name, status, software_installer_id, setup_experience_script_id, vpp_app_team_id FROM setup_experience_status_results")
})
// four hosts with three items enqueued each.
require.Len(t, seRows, 12)
// five hosts with three items enqueued each.
require.Len(t, seRows, 15)
for _, tc := range []setupExperienceInsertTestRows{
{
@@ -525,9 +622,9 @@ func testEnqueueSetupExperienceItems(t *testing.T, ds *Datastore) {
return sqlx.SelectContext(ctx, q, &seRows, "SELECT host_uuid, name, status, software_installer_id, setup_experience_script_id, vpp_app_team_id FROM setup_experience_status_results")
})
// Only the team 1 host should have items enqueued now.
// Only the team 1 and team 3 hosts should have items enqueued now.
// Two hosts with three items each.
require.Len(t, seRows, 6)
require.Len(t, seRows, 9)
for _, tc := range []setupExperienceInsertTestRows{
{
@@ -562,11 +659,8 @@ func testEnqueueSetupExperienceItems(t *testing.T, ds *Datastore) {
}
for _, row := range seRows {
if row.HostUUID == hostTeam3 || row.HostUUID == hostTeam2 {
if row.HostUUID == hostTeam2 {
team := 2
if row.HostUUID == hostTeam3 {
team = 3
}
t.Errorf("team %d shouldn't have any any entries", team)
}
}