@@ -70,22 +70,11 @@ func LoadImage(ctx context.Context, inputPath string, tmpDir string) (*Standalon
7070 if err != nil {
7171 return nil , fmt .Errorf ("failed to read index.json from %s: %w" , inputPath , err )
7272 }
73- rootDesc , err := parseRootDescriptor ( indexData )
73+ rootDesc , err := resolveLayoutRoot ( tmpDir , indexData )
7474 if err != nil {
7575 return nil , err
7676 }
7777
78- // If the root descriptor is a manifest list (e.g. from nerdctl save),
79- // resolve it to available platform manifests. This handles partial exports
80- // where the manifest list references all platforms but only a subset of
81- // platform blobs were exported.
82- if images .IsIndexType (rootDesc .MediaType ) {
83- rootDesc , err = resolveManifestList (tmpDir , rootDesc )
84- if err != nil {
85- return nil , err
86- }
87- }
88-
8978 orasStore , err := oci .New (tmpDir )
9079 if err != nil {
9180 return nil , fmt .Errorf ("failed to create writable OCI store: %w" , err )
@@ -139,73 +128,81 @@ func SaveImageToDir(srcDir string, desc ocispec.Descriptor, outputPath string) e
139128 return os .WriteFile (filepath .Join (outputPath , "index.json" ), indexData , 0644 )
140129}
141130
142- // parseRootDescriptor unmarshals OCI index JSON and returns the manifest descriptor.
143- func parseRootDescriptor (indexData []byte ) (ocispec.Descriptor , error ) {
144- var index ocispec.Index
145- if err := json .Unmarshal (indexData , & index ); err != nil {
146- return ocispec.Descriptor {}, fmt .Errorf ("failed to unmarshal index.json: %w" , err )
147- }
148- if len (index .Manifests ) == 0 {
131+ // resolveLayoutRoot returns a descriptor for the OCI image layout's root manifest.
132+ // It accepts index.json shapes produced by common tools: a single image manifest,
133+ // a single descriptor pointing at a nested manifest list (e.g. nerdctl save), or
134+ // a flat list of per-platform manifests (e.g. go-containerregistry layout.Write).
135+ // Children whose blobs are missing are filtered out.
136+ func resolveLayoutRoot (layoutDir string , indexData []byte ) (ocispec.Descriptor , error ) {
137+ var top ocispec.Index
138+ if err := json .Unmarshal (indexData , & top ); err != nil {
139+ return ocispec.Descriptor {}, fmt .Errorf ("unmarshal index.json: %w" , err )
140+ }
141+ if len (top .Manifests ) == 0 {
149142 return ocispec.Descriptor {}, errors .New ("index.json contains no manifests" )
150143 }
151- return index .Manifests [0 ], nil
152- }
153-
154- // resolveManifestList reads a manifest list blob and resolves it based on which
155- // platform blobs are actually present in the layout. If all platforms are available,
156- // it returns the original manifest list. If only one is available, it returns that
157- // platform manifest directly. If multiple (but not all) are available, it writes a
158- // filtered manifest list containing only the available platforms. This handles tools
159- // like `nerdctl save` that export a manifest list referencing all platforms even when
160- // only a subset was pulled.
161- func resolveManifestList (layoutDir string , listDesc ocispec.Descriptor ) (ocispec.Descriptor , error ) {
162- blobPath := filepath .Join (layoutDir , "blobs" , listDesc .Digest .Algorithm ().String (), listDesc .Digest .Encoded ())
163- listData , err := os .ReadFile (blobPath )
164- if err != nil {
165- return ocispec.Descriptor {}, fmt .Errorf ("failed to read manifest list blob: %w" , err )
166- }
167-
168- var manifestList ocispec.Index
169- if err := json .Unmarshal (listData , & manifestList ); err != nil {
170- return ocispec.Descriptor {}, fmt .Errorf ("failed to unmarshal manifest list: %w" , err )
144+ // Single non-index entry: a plain single-platform image.
145+ if len (top .Manifests ) == 1 && ! images .IsIndexType (top .Manifests [0 ].MediaType ) {
146+ return top .Manifests [0 ], nil
171147 }
172148
173- // Find which platform manifests have their blobs present
174- var available []ocispec.Descriptor
175- for _ , desc := range manifestList .Manifests {
176- p := filepath .Join (layoutDir , "blobs" , desc .Digest .Algorithm ().String (), desc .Digest .Encoded ())
177- if _ , err := os .Stat (p ); err == nil {
178- available = append (available , desc )
149+ // Locate the manifest list to walk. Either index.json points at a nested list
150+ // blob, or index.json is itself the list.
151+ var (
152+ listDesc ocispec.Descriptor
153+ listBytes = indexData
154+ mediaType = top .MediaType
155+ )
156+ if len (top .Manifests ) == 1 {
157+ listDesc = top .Manifests [0 ]
158+ mediaType = listDesc .MediaType
159+ b , err := os .ReadFile (blobPath (layoutDir , listDesc .Digest ))
160+ if err != nil {
161+ return ocispec.Descriptor {}, fmt .Errorf ("read manifest list: %w" , err )
179162 }
163+ listBytes = b
180164 }
181- if len ( available ) == 0 {
182- return ocispec.Descriptor {}, errors . New ( "manifest list contains no manifests with available blobs" )
165+ if mediaType == "" {
166+ mediaType = ocispec .MediaTypeImageIndex
183167 }
184168
185- // If all platforms are available, keep the original manifest list
186- if len ( available ) == len ( manifestList . Manifests ) {
187- return listDesc , nil
169+ var list ocispec. Index
170+ if err := json . Unmarshal ( listBytes , & list ); err != nil {
171+ return ocispec. Descriptor {}, fmt . Errorf ( "unmarshal manifest list: %w" , err )
188172 }
189173
190- // If only one platform is available, return it directly as a single manifest
191- if len (available ) == 1 {
174+ available := make ([]ocispec.Descriptor , 0 , len (list .Manifests ))
175+ for _ , d := range list .Manifests {
176+ if _ , err := os .Stat (blobPath (layoutDir , d .Digest )); err == nil {
177+ available = append (available , d )
178+ }
179+ }
180+ switch {
181+ case len (available ) == 0 :
182+ return ocispec.Descriptor {}, errors .New ("manifest list contains no entries with available blobs" )
183+ case len (available ) == 1 && images .IsManifestType (available [0 ].MediaType ):
192184 return available [0 ], nil
185+ case listDesc .Digest != "" && len (available ) == len (list .Manifests ):
186+ // Nested list is fully available; keep its original digest.
187+ return listDesc , nil
193188 }
194189
195- // Multiple (but not all) platforms available: write a filtered manifest list
196- manifestList .Manifests = available
197- filteredData , err := json .Marshal (manifestList )
190+ list . MediaType = mediaType
191+ list .Manifests = available
192+ data , err := json .Marshal (list )
198193 if err != nil {
199- return ocispec.Descriptor {}, fmt .Errorf ("failed to marshal filtered manifest list: %w" , err )
194+ return ocispec.Descriptor {}, fmt .Errorf ("marshal manifest list: %w" , err )
200195 }
201- filteredDigest := digest .FromBytes (filteredData )
202- filteredPath := filepath .Join (layoutDir , "blobs" , filteredDigest .Algorithm ().String (), filteredDigest .Encoded ())
203- if err := os .WriteFile (filteredPath , filteredData , 0644 ); err != nil {
204- return ocispec.Descriptor {}, fmt .Errorf ("failed to write filtered manifest list: %w" , err )
196+ dgst := digest .FromBytes (data )
197+ if err := os .MkdirAll (filepath .Dir (blobPath (layoutDir , dgst )), 0755 ); err != nil {
198+ return ocispec.Descriptor {}, err
205199 }
206- return ocispec.Descriptor {
207- MediaType : listDesc .MediaType ,
208- Digest : filteredDigest ,
209- Size : int64 (len (filteredData )),
210- }, nil
200+ if err := os .WriteFile (blobPath (layoutDir , dgst ), data , 0644 ); err != nil {
201+ return ocispec.Descriptor {}, err
202+ }
203+ return ocispec.Descriptor {MediaType : mediaType , Digest : dgst , Size : int64 (len (data ))}, nil
204+ }
205+
206+ func blobPath (layoutDir string , dgst digest.Digest ) string {
207+ return filepath .Join (layoutDir , "blobs" , dgst .Algorithm ().String (), dgst .Encoded ())
211208}
0 commit comments