From 5c1618953c1da50850bfe2ca5068ffe47c19abad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Graber?= Date: Fri, 28 Nov 2025 15:02:49 -0500 Subject: [PATCH] client/oci: Use SHA256 of combined layers as digest MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The digest from the registry refers to the image itself rather than a particular build. As a result it would lead to the same digest on all architectures. On our end, for cache handling, we need each architecture to have its own stable digest. Relying on the particular layers should achieve that. Closes #2682 Signed-off-by: Stéphane Graber --- client/oci_images.go | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/client/oci_images.go b/client/oci_images.go index 3bd159458..8d2afd35e 100644 --- a/client/oci_images.go +++ b/client/oci_images.go @@ -3,6 +3,7 @@ package incus import ( "compress/gzip" "context" + "crypto/sha256" "encoding/base64" "encoding/json" "errors" @@ -30,6 +31,7 @@ type ociInfo struct { Digest string `json:"Digest"` Created time.Time `json:"Created"` Architecture string `json:"Architecture"` + Layers []string `json:"Layers"` LayersData []struct { Size int64 `json:"Size"` } `json:"LayersData"` @@ -417,7 +419,7 @@ func (r *ProtocolOCI) GetImageAlias(name string) (*api.ImageAliasesEntry, string } info.Alias = name - info.Digest = strings.Replace(info.Digest, "sha256:", "", 1) + info.Digest = r.computeFingerprint(info.Layers) archID, err := osarch.ArchitectureID(info.Architecture) if err != nil { @@ -478,3 +480,13 @@ func (r *ProtocolOCI) GetImageAliasArchitectures(imageType string, name string) func (r *ProtocolOCI) ExportImage(_ string, _ api.ImageExportPost) (Operation, error) { return nil, errors.New("Exporting images is not supported with OCI registry") } + +func (r *ProtocolOCI) computeFingerprint(layers []string) string { + h := sha256.New() + + for _, layer := range layers { + h.Write([]byte(layer)) + } + + return fmt.Sprintf("%x", h.Sum(nil)) +}