Skip to content

Commit 6677e0b

Browse files
committed
fix cache on deb multirepo
1 parent 4b630ae commit 6677e0b

4 files changed

Lines changed: 192 additions & 25 deletions

File tree

internal/ospackage/debutils/download.go

Lines changed: 112 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ type pkgChecksum struct {
5959
var (
6060
RepoCfg RepoConfig
6161
RepoCfgs []RepoConfig // Support for multiple repositories
62+
UserRepoCfgs []RepoConfig // Populated from UserRepo packageRepositories
6263
PkgChecksum []pkgChecksum
6364
GzHref string
6465
Architecture string
@@ -68,14 +69,32 @@ var (
6869
urlExistenceCacheMu sync.Mutex
6970
urlExistenceCache map[string]bool
7071
urlExistenceCacheLoaded bool
72+
73+
packageListURLCacheMu sync.Mutex
74+
packageListURLCache map[string]string
75+
packageListURLCacheLoaded bool
7176
)
7277

7378
const urlExistenceCacheFileName = "url_exists_cache.json"
79+
const packageListURLCacheFileName = "package_list_url_cache.json"
7480

7581
func urlExistenceCacheFilePath() string {
7682
return filepath.Join(config.TempDir(), "builds", urlExistenceCacheFileName)
7783
}
7884

85+
func packageListURLCacheFilePath() string {
86+
return filepath.Join(config.TempDir(), "builds", packageListURLCacheFileName)
87+
}
88+
89+
func packageListURLCacheKey(baseURL, codename, arch, component string) string {
90+
return strings.Join([]string{
91+
strings.TrimSpace(baseURL),
92+
strings.TrimSpace(codename),
93+
strings.TrimSpace(arch),
94+
strings.TrimSpace(component),
95+
}, "|")
96+
}
97+
7998
func loadURLExistenceCacheLocked() {
8099
if urlExistenceCacheLoaded {
81100
return
@@ -129,6 +148,63 @@ func saveURLExistenceToCache(url string, exists bool) {
129148
}
130149
}
131150

151+
func loadPackageListURLCacheLocked() {
152+
if packageListURLCacheLoaded {
153+
return
154+
}
155+
packageListURLCacheLoaded = true
156+
packageListURLCache = make(map[string]string)
157+
158+
cacheFile := packageListURLCacheFilePath()
159+
data, err := os.ReadFile(cacheFile)
160+
if err != nil {
161+
return
162+
}
163+
164+
if err := json.Unmarshal(data, &packageListURLCache); err != nil {
165+
packageListURLCache = make(map[string]string)
166+
}
167+
}
168+
169+
func getPackageListURLFromCache(baseURL, codename, arch, component string) (string, bool) {
170+
packageListURLCacheMu.Lock()
171+
defer packageListURLCacheMu.Unlock()
172+
173+
loadPackageListURLCacheLocked()
174+
url, ok := packageListURLCache[packageListURLCacheKey(baseURL, codename, arch, component)]
175+
if !ok || strings.TrimSpace(url) == "" {
176+
return "", false
177+
}
178+
return url, true
179+
}
180+
181+
func savePackageListURLToCache(baseURL, codename, arch, component, packageListURL string) {
182+
log := logger.Logger()
183+
184+
packageListURLCacheMu.Lock()
185+
defer packageListURLCacheMu.Unlock()
186+
187+
loadPackageListURLCacheLocked()
188+
key := packageListURLCacheKey(baseURL, codename, arch, component)
189+
packageListURLCache[key] = packageListURL
190+
191+
cacheFile := packageListURLCacheFilePath()
192+
if err := os.MkdirAll(filepath.Dir(cacheFile), 0755); err != nil {
193+
log.Warnf("failed to create package-list URL cache directory: %v", err)
194+
return
195+
}
196+
197+
data, err := json.Marshal(packageListURLCache)
198+
if err != nil {
199+
log.Warnf("failed to marshal package-list URL cache: %v", err)
200+
return
201+
}
202+
203+
if err := os.WriteFile(cacheFile, data, 0644); err != nil {
204+
log.Warnf("failed to persist package-list URL cache: %v", err)
205+
}
206+
}
207+
132208
func extractDebPackageNameFromFile(fileName string) string {
133209
base := strings.TrimSuffix(fileName, ".deb")
134210
if idx := strings.Index(base, "_"); idx > 0 {
@@ -244,7 +320,7 @@ func isDebRequirementInCache(
244320
}
245321

246322
func debMetadataBuildPaths() []string {
247-
buildPaths := make([]string, 0, 1+len(RepoCfgs))
323+
buildPaths := make([]string, 0, 1+len(RepoCfgs)+len(UserRepoCfgs))
248324
seen := make(map[string]struct{})
249325
add := func(path string) {
250326
path = strings.TrimSpace(path)
@@ -262,6 +338,9 @@ func debMetadataBuildPaths() []string {
262338
for _, rc := range RepoCfgs {
263339
add(rc.BuildPath)
264340
}
341+
for _, rc := range UserRepoCfgs {
342+
add(rc.BuildPath)
343+
}
265344

266345
return buildPaths
267346
}
@@ -504,10 +583,13 @@ func BuildRepoConfigs(userRepoList []Repository, arch string) ([]RepoConfig, err
504583
return userRepo, nil
505584
}
506585

507-
func UserPackages() ([]ospackage.PackageInfo, error) {
508-
509-
log := logger.Logger()
510-
log.Infof("fetching packages from %s", "user package list")
586+
// initializeUserRepoCfgs builds the UserRepoCfgs from UserRepo without fetching package metadata.
587+
// This is called early to ensure UserRepoCfgs is available before cache checks.
588+
func initializeUserRepoCfgs() error {
589+
// UserRepoCfgs is already initialized, skip
590+
if len(UserRepoCfgs) > 0 {
591+
return nil
592+
}
511593

512594
var repoList []Repository
513595
repoGroup := "custrepo"
@@ -528,18 +610,32 @@ func UserPackages() ([]ospackage.PackageInfo, error) {
528610
})
529611
}
530612

531-
// If no valid repositories were found (all were placeholders), return empty package list
613+
// If no valid repositories were found (all were placeholders), nothing to do
532614
if len(repoList) == 0 {
533-
return []ospackage.PackageInfo{}, nil
615+
return nil
534616
}
535617

536618
userRepo, err := BuildRepoConfigs(repoList, Architecture)
537619
if err != nil {
538-
return nil, fmt.Errorf("building user repo configs failed: %w", err)
620+
return fmt.Errorf("building user repo configs failed: %w", err)
621+
}
622+
UserRepoCfgs = userRepo
623+
624+
return nil
625+
}
626+
627+
func UserPackages() ([]ospackage.PackageInfo, error) {
628+
629+
log := logger.Logger()
630+
log.Infof("fetching packages from %s", "user package list")
631+
632+
// Initialize UserRepoCfgs early if not already done
633+
if err := initializeUserRepoCfgs(); err != nil {
634+
return nil, err
539635
}
540636

541637
var allUserPackages []ospackage.PackageInfo
542-
for _, rpItx := range userRepo {
638+
for _, rpItx := range UserRepoCfgs {
543639

544640
userPkgs, err := ParseRepositoryMetadata(rpItx.PkgPrefix, rpItx.PkgList, rpItx.ReleaseFile, rpItx.ReleaseSign, rpItx.PbGPGKey, rpItx.BuildPath, rpItx.Arch, rpItx.AllowPackages)
545641
if err != nil {
@@ -777,6 +873,13 @@ func DownloadPackagesComplete(pkgList []string, destDir, dotFile string, pkgSour
777873
var downloadPkgList []string
778874

779875
log := logger.Logger()
876+
877+
// Initialize user repo configs early so they are available for cache metadata lookup
878+
if err := initializeUserRepoCfgs(); err != nil {
879+
log.Warnf("Failed to initialize user repo configs: %v", err)
880+
// Continue anyway; user repos are optional
881+
}
882+
780883
absDestDir, err := filepath.Abs(destDir)
781884
if err != nil {
782885
return downloadPkgList, nil, fmt.Errorf("resolving cache directory: %w", err)

internal/ospackage/debutils/download_test.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,13 @@ func resetURLExistenceCacheForTest(t *testing.T) {
2222
urlExistenceCacheMu.Unlock()
2323

2424
_ = os.Remove(urlExistenceCacheFilePath())
25+
26+
packageListURLCacheMu.Lock()
27+
packageListURLCache = nil
28+
packageListURLCacheLoaded = false
29+
packageListURLCacheMu.Unlock()
30+
31+
_ = os.Remove(packageListURLCacheFilePath())
2532
}
2633

2734
// TestPackages tests the Packages function
@@ -658,6 +665,40 @@ func TestCheckFileExists_UsesCacheOffline(t *testing.T) {
658665
}
659666
}
660667

668+
func TestGetPackagesNames_UsesCachedURLOffline(t *testing.T) {
669+
resetURLExistenceCacheForTest(t)
670+
671+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
672+
if r.URL.Path == "/dists/stable/main/binary-amd64/Packages.gz" {
673+
w.WriteHeader(http.StatusOK)
674+
return
675+
}
676+
w.WriteHeader(http.StatusNotFound)
677+
}))
678+
679+
baseURL := server.URL
680+
first, err := GetPackagesNames(baseURL, "stable", "amd64", "main")
681+
if err != nil {
682+
t.Fatalf("first GetPackagesNames failed: %v", err)
683+
}
684+
if first == "" {
685+
t.Fatalf("expected first GetPackagesNames to return URL")
686+
}
687+
688+
server.Close()
689+
690+
second, err := GetPackagesNames(baseURL, "stable", "amd64", "main")
691+
if err != nil {
692+
t.Fatalf("second GetPackagesNames should use cache and work offline: %v", err)
693+
}
694+
if second == "" {
695+
t.Fatalf("expected cached GetPackagesNames to return URL")
696+
}
697+
if second != first {
698+
t.Fatalf("expected cached URL %q, got %q", first, second)
699+
}
700+
}
701+
661702
// TestValidate tests the Validate function
662703
func TestValidate(t *testing.T) {
663704
// Save original PkgChecksum

internal/ospackage/debutils/resolver.go

Lines changed: 34 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -195,27 +195,45 @@ func ParseRepositoryMetadata(baseURL string, pkggz string, releaseFile string, r
195195
metaURLList = []string{releaseFile, releaseSign}
196196
}
197197

198-
// Always refresh metadata files (Release, signature, key) to detect upstream changes.
198+
// Only skip Release file refresh if local files exist; allow network check for freshness.
199+
// This balances offline resilience with metadata staleness detection.
200+
refreshNeeded := false
199201
for _, f := range metaLocalFiles {
200-
if _, err := os.Stat(f); err == nil {
201-
if remErr := os.Remove(f); remErr != nil {
202-
return nil, fmt.Errorf("failed to remove old file %s: %w", f, remErr)
203-
}
202+
if _, err := os.Stat(f); err != nil {
203+
refreshNeeded = true
204+
break
204205
}
205206
}
206207

207-
// Download the metadata files
208-
err := pkgfetcher.FetchPackages(metaURLList, pkgMetaDir, 1)
209-
if err != nil {
210-
return nil, fmt.Errorf("failed to fetch critical repo config packages: %w", err)
211-
}
212-
// Verify the release file
213-
relVryResult, err := VerifyRelease(localReleaseFile, localReleaseSign, localPBGPGKey)
214-
if err != nil {
215-
return nil, fmt.Errorf("failed to verify release file: %w", err)
208+
// Download the metadata files only if missing
209+
if refreshNeeded {
210+
log.Infof("Refreshing metadata files for %s", baseURL)
211+
for _, f := range metaLocalFiles {
212+
if _, err := os.Stat(f); err == nil {
213+
if remErr := os.Remove(f); remErr != nil {
214+
return nil, fmt.Errorf("failed to remove old file %s: %w", f, remErr)
215+
}
216+
}
217+
}
218+
err := pkgfetcher.FetchPackages(metaURLList, pkgMetaDir, 1)
219+
if err != nil {
220+
return nil, fmt.Errorf("failed to fetch critical repo config packages: %w", err)
221+
}
222+
} else {
223+
log.Debugf("Using existing metadata files for %s (offline mode)", baseURL)
216224
}
217-
if !relVryResult {
218-
return nil, fmt.Errorf("release file verification failed")
225+
226+
// Verify the release file if it was refreshed online; offline cached files are trusted.
227+
if refreshNeeded {
228+
relVryResult, err := VerifyRelease(localReleaseFile, localReleaseSign, localPBGPGKey)
229+
if err != nil {
230+
return nil, fmt.Errorf("failed to verify release file: %w", err)
231+
}
232+
if !relVryResult {
233+
return nil, fmt.Errorf("release file verification failed")
234+
}
235+
} else {
236+
log.Debugf("Skipping release file verification (using cached offline files)")
219237
}
220238

221239
// verify the sham256 checksum of the Packages.gz file

internal/ospackage/debutils/zip.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,10 @@ func GetPackagesNames(baseURL string, codename string, arch string, component st
8484
if baseURL == "<URL>" || baseURL == "" {
8585
return "", nil
8686
}
87+
if cachedURL, ok := getPackageListURLFromCache(baseURL, codename, arch, component); ok {
88+
logger.Logger().Debugf("Using cached package list URL for %s/%s/%s/%s: %s", baseURL, codename, arch, component, cachedURL)
89+
return cachedURL, nil
90+
}
8791
possibleFiles := []string{"Packages.gz", "Packages.xz"}
8892
var foundFile string
8993
for _, fname := range possibleFiles {
@@ -94,6 +98,7 @@ func GetPackagesNames(baseURL string, codename string, arch string, component st
9498
}
9599
if fileExist {
96100
foundFile = packageListURL
101+
savePackageListURLToCache(baseURL, codename, arch, component, packageListURL)
97102
break
98103
} else {
99104
logger.Logger().Debugf("Searching package list at: %s", packageListURL)

0 commit comments

Comments
 (0)