Skip to content

Commit 6eb48d9

Browse files
committed
imagetools: fix oci-layout index update when blob exists
When pushing to an OCI layout where the top-level descriptor blob already existed, pushOCILayout returned early without updating index.json or writing pending referrers. Restructure the control flow so the blob-exists case skips only the write but still updates the index and flushes referrers. Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
1 parent 3e5c05c commit 6eb48d9

2 files changed

Lines changed: 92 additions & 7 deletions

File tree

tests/imagetools.go

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ var imagetoolsTests = []func(t *testing.T, sb integration.Sandbox){
3535
testImagetoolsOCILayoutInspect,
3636
testImagetoolsOCILayoutCreateSourceAndTarget,
3737
testImagetoolsOCILayoutReferrers,
38+
testImagetoolsOCILayoutExistingContent,
3839
testImagetoolsOCILayoutMergeSources,
3940
testImagetoolsOCILayoutTargetDigest,
4041
testImagetoolsAppend,
@@ -566,6 +567,90 @@ func testImagetoolsOCILayoutReferrers(t *testing.T, sb integration.Sandbox) {
566567
}
567568
}
568569

570+
// testImagetoolsOCILayoutExistingContent verifies importing into an existing OCI
571+
// layout still updates index.json state when the top-level descriptor blob is
572+
// already present.
573+
func testImagetoolsOCILayoutExistingContent(t *testing.T, sb integration.Sandbox) {
574+
if !isDockerContainerWorker(sb) {
575+
t.Skip("only testing with docker-container worker, imagetools only runs on docker-container")
576+
}
577+
578+
dir := createDockerfileWithArches(t, "amd64", "arm64")
579+
registry, err := sb.NewRegistry()
580+
if errors.Is(err, integration.ErrRequirements) {
581+
t.Skip(err.Error())
582+
}
583+
require.NoError(t, err)
584+
585+
source := registry + "/buildx/imtools-oci-layout-existing-content-src:latest"
586+
out, err := buildCmd(sb, withArgs(
587+
"--output", "type=image,name="+source+",push=true,oci-mediatypes=true,oci-artifact=true",
588+
"--platform=linux/amd64,linux/arm64",
589+
"--provenance=mode=min",
590+
dir,
591+
))
592+
require.NoError(t, err, string(out))
593+
594+
layoutPath := filepath.Join(dir, "layout-existing-content")
595+
initialRef := "oci-layout://" + layoutPath + ":latest"
596+
cmd := buildxCmd(sb, withArgs("imagetools", "create", "-t", initialRef, source))
597+
dt, err := cmd.CombinedOutput()
598+
require.NoError(t, err, string(dt))
599+
600+
cmd = buildxCmd(sb, withArgs("imagetools", "inspect", source, "--raw"))
601+
dt, err = cmd.CombinedOutput()
602+
require.NoError(t, err, string(dt))
603+
604+
var srcIdx ocispecs.Index
605+
err = json.Unmarshal(dt, &srcIdx)
606+
require.NoError(t, err)
607+
608+
var attestations []ocispecs.Descriptor
609+
for _, mfst := range srcIdx.Manifests {
610+
if mfst.Annotations["vnd.docker.reference.type"] == "attestation-manifest" {
611+
attestations = append(attestations, mfst)
612+
}
613+
}
614+
require.Len(t, attestations, 2)
615+
616+
signatures := make([]ocispecs.Descriptor, 0, len(attestations))
617+
for _, attestation := range attestations {
618+
signatures = append(signatures, pushFakeSignatureReferrer(t, source, attestation))
619+
}
620+
621+
secondRef := "oci-layout://" + layoutPath + ":second"
622+
cmd = buildxCmd(sb, withArgs("imagetools", "create", "-t", secondRef, source))
623+
dt, err = cmd.CombinedOutput()
624+
require.NoError(t, err, string(dt))
625+
626+
cmd = buildxCmd(sb, withArgs("imagetools", "inspect", secondRef, "--raw"))
627+
dt, err = cmd.CombinedOutput()
628+
require.NoError(t, err, string(dt))
629+
630+
idxBytes, err := os.ReadFile(filepath.Join(layoutPath, "index.json"))
631+
require.NoError(t, err)
632+
633+
var layoutIdx ocispecs.Index
634+
err = json.Unmarshal(idxBytes, &layoutIdx)
635+
require.NoError(t, err)
636+
637+
directReferrers := map[digest.Digest]ocispecs.Descriptor{}
638+
directReferrerCount := 0
639+
for _, desc := range layoutIdx.Manifests {
640+
if desc.Annotations["io.containerd.manifest.subject"] != "" {
641+
directReferrerCount++
642+
directReferrers[desc.Digest] = desc
643+
}
644+
}
645+
require.Len(t, directReferrers, directReferrerCount)
646+
require.Len(t, directReferrers, len(signatures))
647+
for i, sig := range signatures {
648+
desc, ok := directReferrers[sig.Digest]
649+
require.True(t, ok)
650+
require.Equal(t, attestations[i].Digest.String(), desc.Annotations["io.containerd.manifest.subject"])
651+
}
652+
}
653+
569654
// testImagetoolsOCILayoutMergeSources verifies create merges registry and local OCI layout sources.
570655
func testImagetoolsOCILayoutMergeSources(t *testing.T, sb integration.Sandbox) {
571656
if !isDockerContainerWorker(sb) {

util/imagetools/create.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -558,14 +558,14 @@ func (r *Resolver) pushOCILayout(ctx context.Context, ref *Location, desc ocispe
558558
}
559559
w, err := store.Writer(ctx, content.WithRef(desc.Digest.String()), content.WithDescriptor(desc))
560560
if err != nil {
561-
if errdefs.IsAlreadyExists(err) {
562-
return nil
561+
if !errdefs.IsAlreadyExists(err) {
562+
return err
563+
}
564+
} else {
565+
err = content.Copy(ctx, w, bytes.NewReader(dt), desc.Size, desc.Digest)
566+
if err != nil && !errdefs.IsAlreadyExists(err) {
567+
return err
563568
}
564-
return err
565-
}
566-
err = content.Copy(ctx, w, bytes.NewReader(dt), desc.Size, desc.Digest)
567-
if err != nil && !errdefs.IsAlreadyExists(err) {
568-
return err
569569
}
570570

571571
idx := ociindex.NewStoreIndex(ref.OCILayout().Path)

0 commit comments

Comments
 (0)