diff --git a/client/simplestreams_images.go b/client/simplestreams_images.go index 32260698293..a8fc4dfbeb2 100644 --- a/client/simplestreams_images.go +++ b/client/simplestreams_images.go @@ -3,6 +3,7 @@ package incus import ( "context" "crypto/sha256" + "errors" "fmt" "io" "net/http" @@ -13,6 +14,7 @@ import ( "time" "github.com/lxc/incus/v6/shared/api" + "github.com/lxc/incus/v6/shared/logger" "github.com/lxc/incus/v6/shared/subprocess" "github.com/lxc/incus/v6/shared/util" ) @@ -121,6 +123,11 @@ func (r *ProtocolSimpleStreams) GetImageFile(fingerprint string, req ImageFileRe size, err = util.DownloadFileHash(context.TODO(), &httpClient, r.httpUserAgent, req.ProgressHandler, req.Canceler, filename, uri, hash, sha256.New(), target) if err != nil { + if errors.Is(err, util.ErrNotFound) { + logger.Info("Unable to download file by hash, invalidate potentially outdated cache", logger.Ctx{"filename": filename, "uri": uri, "hash": hash}) + r.ssClient.InvalidateCache() + } + return -1, err } } diff --git a/shared/simplestreams/simplestreams.go b/shared/simplestreams/simplestreams.go index d91c5fe5fda..b628faa6cd1 100644 --- a/shared/simplestreams/simplestreams.go +++ b/shared/simplestreams/simplestreams.go @@ -92,6 +92,11 @@ func (s *SimpleStreams) readCache(path string) ([]byte, bool) { return body, expired } +// InvalidateCache removes the on-disk cache for the SimpleStreams remote. +func (s *SimpleStreams) InvalidateCache() { + _ = os.RemoveAll(s.cachePath) +} + func (s *SimpleStreams) cachedDownload(path string) ([]byte, error) { fields := strings.Split(path, "/") fileName := fields[len(fields)-1] @@ -165,7 +170,7 @@ func (s *SimpleStreams) cachedDownload(path string) ([]byte, error) { if s.cachePath != "" { cacheName := filepath.Join(s.cachePath, fileName) _ = os.Remove(cacheName) - _ = os.WriteFile(cacheName, body, 0644) + _ = os.WriteFile(cacheName, body, 0o644) } return body, nil @@ -375,7 +380,8 @@ func (s *SimpleStreams) GetFiles(fingerprint string) (map[string]DownloadableFil files[path[2]] = DownloadableFile{ Path: path[0], Sha256: path[1], - Size: size} + Size: size, + } } return files, nil diff --git a/shared/util/net.go b/shared/util/net.go index 74bbef1c1df..21607b24ac3 100644 --- a/shared/util/net.go +++ b/shared/util/net.go @@ -2,6 +2,7 @@ package util import ( "context" + "errors" "fmt" "hash" "io" @@ -12,6 +13,10 @@ import ( "github.com/lxc/incus/v6/shared/units" ) +// ErrNotFound is used to explicitly signal error cases, where a resource +// can not be found (404 HTTP status code). +var ErrNotFound = errors.New("resource not found") + func DownloadFileHash(ctx context.Context, httpClient *http.Client, useragent string, progress func(progress ioprogress.ProgressData), canceler *cancel.HTTPRequestCanceller, filename string, url string, hash string, hashFunc hash.Hash, target io.WriteSeeker) (int64, error) { // Always seek to the beginning _, _ = target.Seek(0, io.SeekStart) @@ -44,6 +49,10 @@ func DownloadFileHash(ctx context.Context, httpClient *http.Client, useragent st defer close(doneCh) if r.StatusCode != http.StatusOK { + if r.StatusCode == http.StatusNotFound { + return -1, fmt.Errorf("Unable to fetch %s: %w", url, ErrNotFound) + } + return -1, fmt.Errorf("Unable to fetch %s: %s", url, r.Status) }