Fixing issue with OSV artifact cleanup on date boundaries (#43408)

Unreleased bug fix for https://github.com/fleetdm/fleet/pull/42063
**Related issue:** Resolves #39900

## Testing

- [x] Added/updated automated tests
- [x] QA'd all new/changed functionality manually

For unreleased bug fixes in a release candidate, one of:

- [x] Confirmed that the fix is not expected to adversely impact load
test results
- [x] Alerted the release DRI if additional load testing is needed
We shouldn't need any additional load testing. This change will not have
a large impact on load.
This commit is contained in:
Konstantin Sykulev 2026-04-10 12:38:11 -04:00 committed by GitHub
parent e7470fabae
commit c8e9610dd2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 39 additions and 9 deletions

View file

@ -174,9 +174,15 @@ func computeFileSHA256(path string) (string, error) {
}
type SyncResult struct {
// Downloaded versions were fetched from the release and saved to disk.
Downloaded []string
Skipped []string
Failed []string
// Skipped versions already had a local file with a matching checksum.
Skipped []string
// NotInRelease versions had no matching asset in the release (likely caused by a date-boundary).
NotInRelease []string
// Failed versions had an asset in the release but the download or
// checksum verification failed.
Failed []string
}
// downloadFunc is a function that downloads an asset to a destination path
@ -190,9 +196,10 @@ func SyncOSV(ctx context.Context, dstDir string, ubuntuVersions []string, date t
// syncOSVWithDownloader is the internal implementation that accepts a custom download function for testing
func syncOSVWithDownloader(ctx context.Context, dstDir string, ubuntuVersions []string, date time.Time, release *ReleaseInfo, download downloadFunc) (*SyncResult, error) {
result := &SyncResult{
Downloaded: make([]string, 0),
Skipped: make([]string, 0),
Failed: make([]string, 0),
Downloaded: make([]string, 0),
Skipped: make([]string, 0),
NotInRelease: make([]string, 0),
Failed: make([]string, 0),
}
for _, ubuntuVersion := range ubuntuVersions {
@ -201,8 +208,7 @@ func syncOSVWithDownloader(ctx context.Context, dstDir string, ubuntuVersions []
assetInfo, ok := release.Assets[filename]
if !ok {
// Artifact not available, skip
result.Skipped = append(result.Skipped, ubuntuVersion)
result.NotInRelease = append(result.NotInRelease, ubuntuVersion)
continue
}

View file

@ -119,6 +119,30 @@ func TestRemoveOldOSVArtifactsWithSkippedVersions(t *testing.T) {
require.True(t, os.IsNotExist(err), "old artifact from day before should be removed even when version was skipped")
}
func TestRemoveOldOSVArtifactsDateBoundaryRace(t *testing.T) {
tmpDir := t.TempDir()
// now is April 10 but the release only created April 9 artifacts.
today := time.Date(2026, 4, 10, 0, 5, 0, 0, time.UTC)
files := []string{
"osv-ubuntu-2404-2026-04-09.json.gz", // Yesterday's artifact (only one available)
}
for _, file := range files {
err := os.WriteFile(filepath.Join(tmpDir, file), []byte("test"), 0o644)
require.NoError(t, err)
}
// 2404 is in NotInRelease, not Skipped
// so removeOldOSVArtifacts should not touch it
err := removeOldOSVArtifacts(today, tmpDir, []string{})
require.NoError(t, err)
// Yesterday's artifact must still exist since the version wasn't in the successful set
_, err = os.Stat(filepath.Join(tmpDir, "osv-ubuntu-2404-2026-04-09.json.gz"))
require.NoError(t, err, "old artifact must be preserved when version is not in release")
}
func TestGetNeededUbuntuVersions(t *testing.T) {
tests := []struct {
name string
@ -244,10 +268,10 @@ func TestSyncOSVFaultTolerance(t *testing.T) {
}
result, err := syncOSVWithDownloader(context.Background(), tmpDir, versions, date, release, mockDownload)
require.NoError(t, err)
require.Error(t, err)
require.NotNil(t, result)
require.Contains(t, result.Skipped, "2504", "2504 artifact not in release, should be skipped")
require.Contains(t, result.NotInRelease, "2504", "2504 artifact not in release")
require.Contains(t, result.Failed, "2204", "2204 download failed, should be in Failed")
}