mirror of
https://github.com/fleetdm/fleet
synced 2026-04-21 13:37:30 +00:00
<!-- Add the related story/sub-task/bug number, like Resolves #123, or remove if NA --> **Related issue:** Resolves #34950 I changed from the original spec of 100 old commands to 3 due to load test results. Admittedly my load test meant a very large number of hosts all checked in and triggered deletion at once but at 100 per host and per command the load was too high. 3 still results in cleanup over time and doesn't seem to cause load issues. # 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. - [x] 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. - [x] If paths of existing endpoints are modified without backwards compatibility, checked the frontend/CLI for any necessary changes ## Testing - [x] Added/updated automated tests - [x] 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 --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
parent
b9a53136bf
commit
ee3bfb759d
8 changed files with 377 additions and 0 deletions
1
changes/34950-nano-tables-cleanup
Normal file
1
changes/34950-nano-tables-cleanup
Normal file
|
|
@ -0,0 +1 @@
|
|||
* Updated iOS/iPadOS refetch logic to slowly clear out old/stale results
|
||||
|
|
@ -1205,6 +1205,9 @@ func newCleanupsAndAggregationSchedule(
|
|||
}
|
||||
return nil
|
||||
}),
|
||||
schedule.WithJob("cleanup_orphaned_nano_refetch_commands", func(ctx context.Context) error {
|
||||
return ds.CleanupOrphanedNanoRefetchCommands(ctx)
|
||||
}),
|
||||
)
|
||||
|
||||
return s, nil
|
||||
|
|
|
|||
|
|
@ -6843,6 +6843,126 @@ WHERE (
|
|||
return nil
|
||||
}
|
||||
|
||||
func (ds *Datastore) CleanupStaleNanoRefetchCommands(ctx context.Context, enrollmentID string, commandUUIDPrefix string, currentCommandUUID string) error {
|
||||
// Step 1: Get up to 3 old command UUIDs from nano_enrollment_queue for this
|
||||
// enrollment. The PK is (id, command_uuid) so filtering by id first is efficient,
|
||||
// and the LIKE prefix on command_uuid narrows within that enrollment's entries.
|
||||
// 3 may seem like too few but in load testing because of how often this runs it
|
||||
// was found that larger numbers can cause too much contention
|
||||
const selectOldCmds = `
|
||||
SELECT command_uuid FROM nano_enrollment_queue
|
||||
WHERE id = ?
|
||||
AND command_uuid LIKE ?
|
||||
AND command_uuid != ?
|
||||
AND created_at < NOW() - INTERVAL 30 DAY
|
||||
LIMIT 3`
|
||||
|
||||
var oldCmdUUIDs []string
|
||||
if err := sqlx.SelectContext(ctx, ds.reader(ctx), &oldCmdUUIDs, selectOldCmds, enrollmentID, commandUUIDPrefix+"%", currentCommandUUID); err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "select old nano refetch commands")
|
||||
}
|
||||
if len(oldCmdUUIDs) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Step 2: From ncr, find which of those command UUIDs have been acknowledged or
|
||||
// errored for this enrollment. The PK (id, command_uuid) makes this efficient
|
||||
// since we provide both id and the command_uuid IN list.
|
||||
selectAckQuery, args, err := sqlx.In(`
|
||||
SELECT command_uuid FROM nano_command_results
|
||||
WHERE id = ? AND command_uuid IN (?) AND status IN ('Acknowledged', 'Error')`,
|
||||
enrollmentID, oldCmdUUIDs)
|
||||
if err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "build IN query for nano command results")
|
||||
}
|
||||
|
||||
var ackCmdUUIDs []string
|
||||
if err := sqlx.SelectContext(ctx, ds.reader(ctx), &ackCmdUUIDs, selectAckQuery, args...); err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "select acknowledged nano command results")
|
||||
}
|
||||
if len(ackCmdUUIDs) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Step 3: Delete from neq and ncr in the same transaction, scoped to this enrollment. We may not
|
||||
// need to delete in a transaction here but it's the easiest way to ensure we cleanup ncr and further
|
||||
// we absolutely must ensure we don't delete from ncr before deleting from neq to avoid command resends
|
||||
return ds.withRetryTxx(ctx, func(tx sqlx.ExtContext) error {
|
||||
deleteNEQ, delArgs, err := sqlx.In(
|
||||
`DELETE FROM nano_enrollment_queue WHERE id = ? AND command_uuid IN (?)`,
|
||||
enrollmentID, ackCmdUUIDs)
|
||||
if err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "build delete neq query")
|
||||
}
|
||||
if _, err := tx.ExecContext(ctx, deleteNEQ, delArgs...); err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "delete stale nano enrollment queue entries")
|
||||
}
|
||||
|
||||
deleteNCR, delArgs, err := sqlx.In(
|
||||
`DELETE FROM nano_command_results WHERE id = ? AND command_uuid IN (?)`,
|
||||
enrollmentID, ackCmdUUIDs)
|
||||
if err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "build delete ncr query")
|
||||
}
|
||||
if _, err := tx.ExecContext(ctx, deleteNCR, delArgs...); err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "delete stale nano command results entries")
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (ds *Datastore) CleanupOrphanedNanoRefetchCommands(ctx context.Context) error {
|
||||
// Find up to 100 old REFETCH- commands. Note I am doing this as two queries since nano_commands
|
||||
// can be large and I want to make sure in the case of a large number of active commands there's
|
||||
// not going to be a full table scan or something happening. This is a best effort deletion so it
|
||||
// is OK if our sample deletes nothing
|
||||
const selectStmt = `
|
||||
SELECT command_uuid FROM nano_commands nc
|
||||
WHERE nc.command_uuid LIKE 'REFETCH-%'
|
||||
AND nc.created_at < NOW() - INTERVAL 30 DAY
|
||||
LIMIT 100`
|
||||
|
||||
var cmdUUIDs []string
|
||||
if err := sqlx.SelectContext(ctx, ds.reader(ctx), &cmdUUIDs, selectStmt); err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "get mdm apple refetch commands")
|
||||
}
|
||||
if len(cmdUUIDs) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete those that don't have a corresponding entry in nano_enrollment_queue
|
||||
selectOrphanedCommandsStmt := `
|
||||
SELECT command_uuid FROM nano_commands nc
|
||||
WHERE nc.command_uuid IN (?) AND NOT EXISTS (
|
||||
SELECT 1 FROM nano_enrollment_queue neq
|
||||
WHERE neq.command_uuid = nc.command_uuid AND neq.active = 1
|
||||
LIMIT 1
|
||||
)`
|
||||
selectOrphanedCommandsStmt, args, err := sqlx.In(selectOrphanedCommandsStmt, cmdUUIDs)
|
||||
if err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "build IN query for orphaned refetch commands")
|
||||
}
|
||||
if err := sqlx.SelectContext(ctx, ds.reader(ctx), &cmdUUIDs, selectOrphanedCommandsStmt, args...); err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "get orphaned refetch commands")
|
||||
}
|
||||
if len(cmdUUIDs) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
deleteOrphanedCommandsStmt := `
|
||||
DELETE FROM nano_commands
|
||||
WHERE command_uuid IN (?)`
|
||||
deleteOrphanedCommandsStmt, args, err = sqlx.In(deleteOrphanedCommandsStmt, cmdUUIDs)
|
||||
if err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "build IN query for deleting orphaned refetch commands")
|
||||
}
|
||||
if _, err := ds.writer(ctx).ExecContext(ctx, deleteOrphanedCommandsStmt, args...); err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "delete orphaned refetch commands")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ds *Datastore) GetMDMAppleOSUpdatesSettingsByHostSerial(ctx context.Context, serial string) (string, *fleet.AppleOSUpdateSettings, error) {
|
||||
stmt := `
|
||||
SELECT
|
||||
|
|
|
|||
|
|
@ -123,6 +123,8 @@ func TestMDMApple(t *testing.T) {
|
|||
{"GetHostRecoveryLockPasswordStatus", testGetHostRecoveryLockPasswordStatus},
|
||||
{"ClaimHostsForRecoveryLockClear", testClaimHostsForRecoveryLockClear},
|
||||
{"RecoveryLockRotation", testRecoveryLockRotation},
|
||||
{"CleanupStaleNanoRefetchCommands", testCleanupStaleNanoRefetchCommands},
|
||||
{"CleanupOrphanedNanoRefetchCommands", testCleanupOrphanedNanoRefetchCommands},
|
||||
{"RecoveryLockAutoRotation", testRecoveryLockAutoRotation},
|
||||
}
|
||||
|
||||
|
|
@ -11392,6 +11394,202 @@ func testRecoveryLockRotation(t *testing.T, ds *Datastore) {
|
|||
})
|
||||
}
|
||||
|
||||
func testCleanupStaleNanoRefetchCommands(t *testing.T, ds *Datastore) {
|
||||
ctx := t.Context()
|
||||
|
||||
// Create a host and enroll it in nano MDM.
|
||||
host, err := ds.NewHost(ctx, &fleet.Host{
|
||||
Hostname: "test-cleanup-host",
|
||||
OsqueryHostID: ptr.String("cleanup-osquery-id"),
|
||||
NodeKey: ptr.String("cleanup-node-key"),
|
||||
UUID: "cleanup-test-uuid",
|
||||
Platform: "ios",
|
||||
DetailUpdatedAt: time.Now(),
|
||||
LabelUpdatedAt: time.Now(),
|
||||
PolicyUpdatedAt: time.Now(),
|
||||
SeenTime: time.Now(),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
nanoEnroll(t, ds, host, false)
|
||||
|
||||
enrollmentID := host.UUID
|
||||
|
||||
// Helper to insert a nano command with a specific created_at.
|
||||
insertNanoCmd := func(cmdUUID, reqType string, createdAt time.Time) {
|
||||
_, err := ds.writer(ctx).ExecContext(ctx,
|
||||
`INSERT INTO nano_commands (command_uuid, request_type, command, created_at) VALUES (?, ?, '<?xml', ?)`,
|
||||
cmdUUID, reqType, createdAt)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
// Helper to insert a nano_enrollment_queue entry.
|
||||
insertNEQ := func(id, cmdUUID string, createdAt time.Time) {
|
||||
_, err := ds.writer(ctx).ExecContext(ctx,
|
||||
`INSERT INTO nano_enrollment_queue (id, command_uuid, active, priority, created_at) VALUES (?, ?, 0, 0, ?)`,
|
||||
id, cmdUUID, createdAt)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
// Helper to insert a nano_command_results entry.
|
||||
insertNCR := func(id, cmdUUID, status string) {
|
||||
_, err := ds.writer(ctx).ExecContext(ctx,
|
||||
`INSERT INTO nano_command_results (id, command_uuid, status, result) VALUES (?, ?, ?, '<?xml')`,
|
||||
id, cmdUUID, status)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
oldTime := now.Add(-31 * 24 * time.Hour) // 31 days ago
|
||||
recentTime := now.Add(-1 * 24 * time.Hour) // 1 day ago
|
||||
|
||||
// Create old REFETCH-APPS- command (should be cleaned up).
|
||||
cmdUUID := "REFETCH-APPS-old-acknowledged"
|
||||
insertNanoCmd(cmdUUID, "InstalledApplicationList", oldTime)
|
||||
insertNEQ(enrollmentID, cmdUUID, oldTime)
|
||||
insertNCR(enrollmentID, cmdUUID, "Acknowledged")
|
||||
|
||||
// Create an old REFETCH-APPS- command that has Error status (should also be cleaned up).
|
||||
insertNanoCmd("REFETCH-APPS-old-error", "InstalledApplicationList", oldTime)
|
||||
insertNEQ(enrollmentID, "REFETCH-APPS-old-error", oldTime)
|
||||
insertNCR(enrollmentID, "REFETCH-APPS-old-error", "Error")
|
||||
|
||||
// Create an old REFETCH-APPS- command with no result (should NOT be cleaned up).
|
||||
insertNanoCmd("REFETCH-APPS-old-noresult", "InstalledApplicationList", oldTime)
|
||||
insertNEQ(enrollmentID, "REFETCH-APPS-old-noresult", oldTime)
|
||||
|
||||
// Create a recent REFETCH-APPS- command (should NOT be cleaned up).
|
||||
insertNanoCmd("REFETCH-APPS-recent", "InstalledApplicationList", recentTime)
|
||||
insertNEQ(enrollmentID, "REFETCH-APPS-recent", recentTime)
|
||||
insertNCR(enrollmentID, "REFETCH-APPS-recent", "Acknowledged")
|
||||
|
||||
// Create old REFETCH-DEVICE- commands (different prefix, should NOT be affected by APPS cleanup).
|
||||
insertNanoCmd("REFETCH-DEVICE-old-0", "DeviceInformation", oldTime)
|
||||
insertNEQ(enrollmentID, "REFETCH-DEVICE-old-0", oldTime)
|
||||
insertNCR(enrollmentID, "REFETCH-DEVICE-old-0", "Acknowledged")
|
||||
|
||||
// The "current" command that triggered the cleanup.
|
||||
currentCmdUUID := "REFETCH-APPS-current"
|
||||
insertNanoCmd(currentCmdUUID, "InstalledApplicationList", now)
|
||||
insertNEQ(enrollmentID, currentCmdUUID, now)
|
||||
insertNCR(enrollmentID, currentCmdUUID, "Acknowledged")
|
||||
|
||||
// Run cleanup for REFETCH-APPS- prefix, scoped to this enrollment.
|
||||
err = ds.CleanupStaleNanoRefetchCommands(ctx, enrollmentID, fleet.RefetchAppsCommandUUIDPrefix, currentCmdUUID)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Verify: old acknowledged/errored REFETCH-APPS- entries should be deleted from neq and ncr.
|
||||
var neqCount int
|
||||
err = sqlx.GetContext(ctx, ds.reader(ctx), &neqCount,
|
||||
`SELECT COUNT(*) FROM nano_enrollment_queue WHERE command_uuid LIKE 'REFETCH-APPS-old-%'`)
|
||||
require.NoError(t, err)
|
||||
// Only the one with no result should remain.
|
||||
assert.Equal(t, 1, neqCount, "only the no-result entry should remain in neq")
|
||||
|
||||
var ncrCount int
|
||||
err = sqlx.GetContext(ctx, ds.reader(ctx), &ncrCount,
|
||||
`SELECT COUNT(*) FROM nano_command_results WHERE command_uuid LIKE 'REFETCH-APPS-old-%'`)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 0, ncrCount, "all old acknowledged/errored ncr entries should be deleted")
|
||||
|
||||
// Verify: recent command should still exist.
|
||||
err = sqlx.GetContext(ctx, ds.reader(ctx), &neqCount,
|
||||
`SELECT COUNT(*) FROM nano_enrollment_queue WHERE command_uuid = 'REFETCH-APPS-recent'`)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 1, neqCount, "recent command should not be deleted")
|
||||
|
||||
// Verify: current command should still exist.
|
||||
err = sqlx.GetContext(ctx, ds.reader(ctx), &neqCount,
|
||||
`SELECT COUNT(*) FROM nano_enrollment_queue WHERE command_uuid = ?`, currentCmdUUID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 1, neqCount, "current command should not be deleted")
|
||||
|
||||
// Verify: REFETCH-DEVICE- command should not be affected.
|
||||
err = sqlx.GetContext(ctx, ds.reader(ctx), &neqCount,
|
||||
`SELECT COUNT(*) FROM nano_enrollment_queue WHERE command_uuid = 'REFETCH-DEVICE-old-0'`)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 1, neqCount, "different prefix should not be affected")
|
||||
}
|
||||
|
||||
func testCleanupOrphanedNanoRefetchCommands(t *testing.T, ds *Datastore) {
|
||||
ctx := t.Context()
|
||||
|
||||
// Create a host and enroll it for FK constraints.
|
||||
host, err := ds.NewHost(ctx, &fleet.Host{
|
||||
Hostname: "test-orphan-host",
|
||||
OsqueryHostID: ptr.String("orphan-osquery-id"),
|
||||
NodeKey: ptr.String("orphan-node-key"),
|
||||
UUID: "orphan-test-uuid",
|
||||
Platform: "ios",
|
||||
DetailUpdatedAt: time.Now(),
|
||||
LabelUpdatedAt: time.Now(),
|
||||
PolicyUpdatedAt: time.Now(),
|
||||
SeenTime: time.Now(),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
nanoEnroll(t, ds, host, false)
|
||||
|
||||
now := time.Now()
|
||||
oldTime := now.Add(-31 * 24 * time.Hour)
|
||||
recentTime := now.Add(-1 * 24 * time.Hour)
|
||||
|
||||
// Insert an old REFETCH command WITH a neq reference (should NOT be deleted).
|
||||
_, err = ds.writer(ctx).ExecContext(ctx,
|
||||
`INSERT INTO nano_commands (command_uuid, request_type, command, created_at) VALUES (?, ?, '<?xml', ?)`,
|
||||
"REFETCH-APPS-with-ref", "InstalledApplicationList", oldTime)
|
||||
require.NoError(t, err)
|
||||
_, err = ds.writer(ctx).ExecContext(ctx,
|
||||
`INSERT INTO nano_enrollment_queue (id, command_uuid, active, priority, created_at) VALUES (?, ?, 1, 0, ?)`,
|
||||
host.UUID, "REFETCH-APPS-with-ref", oldTime)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Insert an old REFETCH command WITHOUT neq reference (should be deleted).
|
||||
_, err = ds.writer(ctx).ExecContext(ctx,
|
||||
`INSERT INTO nano_commands (command_uuid, request_type, command, created_at) VALUES (?, ?, '<?xml', ?)`,
|
||||
"REFETCH-APPS-orphan", "InstalledApplicationList", oldTime)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Insert a recent REFETCH command WITHOUT neq reference (should NOT be deleted - too new).
|
||||
_, err = ds.writer(ctx).ExecContext(ctx,
|
||||
`INSERT INTO nano_commands (command_uuid, request_type, command, created_at) VALUES (?, ?, '<?xml', ?)`,
|
||||
"REFETCH-APPS-recent-orphan", "InstalledApplicationList", recentTime)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Insert an old non-REFETCH command WITHOUT neq reference (should NOT be deleted - wrong prefix).
|
||||
_, err = ds.writer(ctx).ExecContext(ctx,
|
||||
`INSERT INTO nano_commands (command_uuid, request_type, command, created_at) VALUES (?, ?, '<?xml', ?)`,
|
||||
"OTHER-CMD-orphan", "ProfileList", oldTime)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Run cleanup.
|
||||
err = ds.CleanupOrphanedNanoRefetchCommands(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Verify: old orphaned REFETCH command should be gone.
|
||||
var count int
|
||||
err = sqlx.GetContext(ctx, ds.reader(ctx), &count,
|
||||
`SELECT COUNT(*) FROM nano_commands WHERE command_uuid = 'REFETCH-APPS-orphan'`)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 0, count, "old orphaned REFETCH command should be deleted")
|
||||
|
||||
// Verify: old REFETCH command with reference should still exist.
|
||||
err = sqlx.GetContext(ctx, ds.reader(ctx), &count,
|
||||
`SELECT COUNT(*) FROM nano_commands WHERE command_uuid = 'REFETCH-APPS-with-ref'`)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 1, count, "REFETCH command with neq reference should not be deleted")
|
||||
|
||||
// Verify: recent orphaned REFETCH command should still exist.
|
||||
err = sqlx.GetContext(ctx, ds.reader(ctx), &count,
|
||||
`SELECT COUNT(*) FROM nano_commands WHERE command_uuid = 'REFETCH-APPS-recent-orphan'`)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 1, count, "recent orphaned REFETCH command should not be deleted")
|
||||
|
||||
// Verify: non-REFETCH command should still exist.
|
||||
err = sqlx.GetContext(ctx, ds.reader(ctx), &count,
|
||||
`SELECT COUNT(*) FROM nano_commands WHERE command_uuid = 'OTHER-CMD-orphan'`)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 1, count, "non-REFETCH command should not be deleted")
|
||||
}
|
||||
|
||||
func testRecoveryLockAutoRotation(t *testing.T, ds *Datastore) {
|
||||
ctx := t.Context()
|
||||
|
||||
|
|
|
|||
|
|
@ -421,6 +421,16 @@ type Datastore interface {
|
|||
// CleanupHostMDMAppleProfiles removes abandoned host MDM Apple profiles entries.
|
||||
CleanupHostMDMAppleProfiles(ctx context.Context) error
|
||||
|
||||
// CleanupStaleNanoRefetchCommands deletes up to 3 nano_enrollment_queue and
|
||||
// their corresponding nano_command_results entries for the given enrollment ID
|
||||
// and REFETCH command prefix type that were sent and acknowledged/errored at
|
||||
// least 30 days ago. The current command UUID is excluded from deletion.
|
||||
CleanupStaleNanoRefetchCommands(ctx context.Context, enrollmentID string, commandUUIDPrefix string, currentCommandUUID string) error
|
||||
|
||||
// CleanupOrphanedNanoRefetchCommands deletes up to 100 REFETCH-prefixed nano_commands
|
||||
// older than 30 days that have no remaining references in nano_enrollment_queue.
|
||||
CleanupOrphanedNanoRefetchCommands(ctx context.Context) error
|
||||
|
||||
// IsHostConnectedToFleetMDM verifies if the host has an active Fleet MDM enrollment with this server
|
||||
IsHostConnectedToFleetMDM(ctx context.Context, host *Host) (bool, error)
|
||||
|
||||
|
|
|
|||
|
|
@ -325,6 +325,10 @@ type CleanupHostMDMCommandsFunc func(ctx context.Context) error
|
|||
|
||||
type CleanupHostMDMAppleProfilesFunc func(ctx context.Context) error
|
||||
|
||||
type CleanupStaleNanoRefetchCommandsFunc func(ctx context.Context, enrollmentID string, commandUUIDPrefix string, currentCommandUUID string) error
|
||||
|
||||
type CleanupOrphanedNanoRefetchCommandsFunc func(ctx context.Context) error
|
||||
|
||||
type IsHostConnectedToFleetMDMFunc func(ctx context.Context, host *fleet.Host) (bool, error)
|
||||
|
||||
type ListHostCertificatesFunc func(ctx context.Context, hostID uint, opts fleet.ListOptions) ([]*fleet.HostCertificateRecord, *fleet.PaginationMetadata, error)
|
||||
|
|
@ -2301,6 +2305,12 @@ type DataStore struct {
|
|||
CleanupHostMDMAppleProfilesFunc CleanupHostMDMAppleProfilesFunc
|
||||
CleanupHostMDMAppleProfilesFuncInvoked bool
|
||||
|
||||
CleanupStaleNanoRefetchCommandsFunc CleanupStaleNanoRefetchCommandsFunc
|
||||
CleanupStaleNanoRefetchCommandsFuncInvoked bool
|
||||
|
||||
CleanupOrphanedNanoRefetchCommandsFunc CleanupOrphanedNanoRefetchCommandsFunc
|
||||
CleanupOrphanedNanoRefetchCommandsFuncInvoked bool
|
||||
|
||||
IsHostConnectedToFleetMDMFunc IsHostConnectedToFleetMDMFunc
|
||||
IsHostConnectedToFleetMDMFuncInvoked bool
|
||||
|
||||
|
|
@ -5644,6 +5654,20 @@ func (s *DataStore) CleanupHostMDMAppleProfiles(ctx context.Context) error {
|
|||
return s.CleanupHostMDMAppleProfilesFunc(ctx)
|
||||
}
|
||||
|
||||
func (s *DataStore) CleanupStaleNanoRefetchCommands(ctx context.Context, enrollmentID string, commandUUIDPrefix string, currentCommandUUID string) error {
|
||||
s.mu.Lock()
|
||||
s.CleanupStaleNanoRefetchCommandsFuncInvoked = true
|
||||
s.mu.Unlock()
|
||||
return s.CleanupStaleNanoRefetchCommandsFunc(ctx, enrollmentID, commandUUIDPrefix, currentCommandUUID)
|
||||
}
|
||||
|
||||
func (s *DataStore) CleanupOrphanedNanoRefetchCommands(ctx context.Context) error {
|
||||
s.mu.Lock()
|
||||
s.CleanupOrphanedNanoRefetchCommandsFuncInvoked = true
|
||||
s.mu.Unlock()
|
||||
return s.CleanupOrphanedNanoRefetchCommandsFunc(ctx)
|
||||
}
|
||||
|
||||
func (s *DataStore) IsHostConnectedToFleetMDM(ctx context.Context, host *fleet.Host) (bool, error) {
|
||||
s.mu.Lock()
|
||||
s.IsHostConnectedToFleetMDMFuncInvoked = true
|
||||
|
|
|
|||
|
|
@ -4024,6 +4024,11 @@ func (svc *MDMAppleCheckinAndCommandService) handleRefetchAppsResults(ctx contex
|
|||
}
|
||||
}
|
||||
|
||||
// Best-effort cleanup of stale refetch commands of the same type.
|
||||
if err := svc.ds.CleanupStaleNanoRefetchCommands(ctx, host.UUID, fleet.RefetchAppsCommandUUIDPrefix, cmdResult.CommandUUID); err != nil {
|
||||
svc.logger.ErrorContext(ctx, "cleanup stale nano refetch apps commands", "err", err, "host_uuid", host.UUID, "command_prefix", fleet.RefetchAppsCommandUUIDPrefix)
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
|
|
@ -4552,6 +4557,11 @@ func (svc *MDMAppleCheckinAndCommandService) handleRefetchCertsResults(ctx conte
|
|||
return nil, ctxerr.Wrap(ctx, err, "refetch certs: update host certificates")
|
||||
}
|
||||
|
||||
// Best-effort cleanup of stale refetch commands of the same type.
|
||||
if err := svc.ds.CleanupStaleNanoRefetchCommands(ctx, host.UUID, fleet.RefetchCertsCommandUUIDPrefix, cmdResult.CommandUUID); err != nil {
|
||||
svc.logger.ErrorContext(ctx, "cleanup stale nano refetch certs commands", "err", err, "host_uuid", host.UUID, "command_prefix", fleet.RefetchCertsCommandUUIDPrefix)
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
|
|
@ -4660,6 +4670,11 @@ func (svc *MDMAppleCheckinAndCommandService) handleRefetchDeviceResults(ctx cont
|
|||
}
|
||||
}
|
||||
|
||||
// Best-effort cleanup of stale refetch commands of the same type.
|
||||
if err := svc.ds.CleanupStaleNanoRefetchCommands(ctx, host.UUID, fleet.RefetchDeviceCommandUUIDPrefix, cmdResult.CommandUUID); err != nil {
|
||||
svc.logger.ErrorContext(ctx, "cleanup stale nano refetch device commands", "err", err, "host_uuid", host.UUID, "command_prefix", fleet.RefetchDeviceCommandUUIDPrefix)
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4924,6 +4924,12 @@ func TestMDMCommandAndReportResultsIOSIPadOSRefetch(t *testing.T) {
|
|||
require.Equal(t, lostModeCommandUUID, commandUUID)
|
||||
return nil
|
||||
}
|
||||
ds.CleanupStaleNanoRefetchCommandsFunc = func(ctx context.Context, enrollmentID string, commandUUIDPrefix string, currentCommandUUID string) error {
|
||||
require.Equal(t, hostUUID, enrollmentID)
|
||||
require.Equal(t, fleet.RefetchDeviceCommandUUIDPrefix, commandUUIDPrefix)
|
||||
require.Equal(t, commandUUID, currentCommandUUID)
|
||||
return nil
|
||||
}
|
||||
|
||||
_, err := svc.CommandAndReportResults(
|
||||
&mdm.Request{Context: ctx},
|
||||
|
|
|
|||
Loading…
Reference in a new issue