Skip to content

Commit 68a8590

Browse files
Merge pull request #2059 from ardaguclu/revert-2037-remove-schema1
Revert "WRKLDS-1713: Remove schema1"
2 parents 9808979 + 0a1a2a8 commit 68a8590

File tree

5 files changed

+202
-12
lines changed

5 files changed

+202
-12
lines changed

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ require (
1818
github.com/distribution/distribution/v3 v3.0.0-20230519140516-983358f8e250
1919
github.com/docker/docker v25.0.6+incompatible
2020
github.com/docker/go-units v0.5.0
21+
github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7
2122
github.com/elazarl/goproxy v1.2.1
2223
github.com/fsnotify/fsnotify v1.7.0
2324
github.com/fsouza/go-dockerclient v1.10.0
@@ -85,7 +86,6 @@ require (
8586
github.com/docker/docker-credential-helpers v0.8.1 // indirect
8687
github.com/docker/go-connections v0.5.0 // indirect
8788
github.com/docker/go-metrics v0.0.1 // indirect
88-
github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 // indirect
8989
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
9090
github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect
9191
github.com/fatih/camelcase v1.0.0 // indirect

pkg/cli/image/append/append.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -350,7 +350,7 @@ func (o *AppendImageOptions) appendManifestList(ctx context.Context, createdAt *
350350
}
351351

352352
// push new manifestlist to registry
353-
toDigest, err := imagemanifest.PutManifestInCompatibleSchema(ctx, forPush, to.Ref.Tag, toManifests, toRepo.Named())
353+
toDigest, err := imagemanifest.PutManifestInCompatibleSchema(ctx, forPush, to.Ref.Tag, toManifests, toRepo.Named(), nil, nil)
354354
if err != nil {
355355
return fmt.Errorf("unable to push manifestlist: %#v", err)
356356
}
@@ -559,7 +559,7 @@ func (o *AppendImageOptions) append(ctx context.Context, createdAt *time.Time,
559559
if skipTagging {
560560
tag = ""
561561
}
562-
toDigest, err := imagemanifest.PutManifestInCompatibleSchema(ctx, manifest, tag, toManifests, toRepo.Named())
562+
toDigest, err := imagemanifest.PutManifestInCompatibleSchema(ctx, manifest, tag, toManifests, toRepo.Named(), fromRepo.Blobs(ctx), configJSON)
563563
if err != nil {
564564
return fmt.Errorf("unable to convert the image to a compatible schema version: %v", err)
565565
}

pkg/cli/image/manifest/manifest.go

Lines changed: 193 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,13 @@ import (
1515
"github.com/distribution/distribution/v3"
1616
"github.com/distribution/distribution/v3/manifest/manifestlist"
1717
"github.com/distribution/distribution/v3/manifest/ocischema"
18+
"github.com/distribution/distribution/v3/manifest/schema1"
1819
"github.com/distribution/distribution/v3/manifest/schema2"
1920
"github.com/distribution/distribution/v3/reference"
21+
"github.com/distribution/distribution/v3/registry/api/errcode"
22+
v2 "github.com/distribution/distribution/v3/registry/api/v2"
23+
24+
"github.com/docker/libtrust"
2025
"github.com/opencontainers/go-digest"
2126
"k8s.io/client-go/rest"
2227
"k8s.io/klog/v2"
@@ -26,6 +31,7 @@ import (
2631
imagereference "github.com/openshift/library-go/pkg/image/reference"
2732
"github.com/openshift/library-go/pkg/image/registryclient"
2833
"github.com/openshift/oc/pkg/cli/image/manifest/dockercredentials"
34+
"github.com/openshift/oc/pkg/helpers/image/dockerlayer/add"
2935
)
3036

3137
type ParallelOptions struct {
@@ -403,6 +409,40 @@ func ManifestToImageConfig(ctx context.Context, srcManifest distribution.Manifes
403409
}
404410

405411
return base, layers, nil
412+
413+
case *schema1.SignedManifest:
414+
if klog.V(4).Enabled() {
415+
_, configJSON, _ := srcManifest.Payload()
416+
klog.Infof("Raw image config json:\n%s", string(configJSON))
417+
}
418+
if len(t.History) == 0 {
419+
return nil, nil, fmt.Errorf("input image is in an unknown format: no v1Compatibility history")
420+
}
421+
config := &dockerv1client.DockerV1CompatibilityImage{}
422+
if err := json.Unmarshal([]byte(t.History[0].V1Compatibility), &config); err != nil {
423+
return nil, nil, err
424+
}
425+
426+
base := &dockerv1client.DockerImageConfig{}
427+
if err := dockerv1client.Convert_DockerV1CompatibilityImage_to_DockerImageConfig(config, base); err != nil {
428+
return nil, nil, err
429+
}
430+
431+
// schema1 layers are in reverse order
432+
layers := make([]distribution.Descriptor, 0, len(t.FSLayers))
433+
for i := len(t.FSLayers) - 1; i >= 0; i-- {
434+
layer := distribution.Descriptor{
435+
MediaType: schema2.MediaTypeLayer,
436+
Digest: t.FSLayers[i].BlobSum,
437+
// size must be reconstructed from the blobs
438+
}
439+
// we must reconstruct the tar sum from the blobs
440+
add.AddLayerToConfig(base, layer, "")
441+
layers = append(layers, layer)
442+
}
443+
444+
return base, layers, nil
445+
406446
case *manifestlist.DeserializedManifestList:
407447
return nil, nil, fmt.Errorf("use --keep-manifest-list option for image manifest type %T from %s", srcManifest, location)
408448
default:
@@ -501,17 +541,15 @@ func ManifestsFromList(ctx context.Context, srcDigest digest.Digest, srcManifest
501541
}
502542
}
503543

504-
// PutManifestInCompatibleSchema just calls ManifestService.Put right now.
505-
// No schema conversion is happening anymore. Instead of using this function,
506-
// call ManifestService.Put directly.
507-
//
508-
// Deprecated
544+
// TODO: Remove support for v2 schema in 4.9
509545
func PutManifestInCompatibleSchema(
510546
ctx context.Context,
511547
srcManifest distribution.Manifest,
512548
tag string,
513549
toManifests distribution.ManifestService,
514550
ref reference.Named,
551+
blobs distribution.BlobService, // support schema2 -> schema1 downconversion
552+
configJSON []byte, // optional, if not passed blobs will be used
515553
) (digest.Digest, error) {
516554
var options []distribution.ManifestServiceOption
517555
if len(tag) > 0 {
@@ -520,6 +558,155 @@ func PutManifestInCompatibleSchema(
520558
} else {
521559
klog.V(5).Infof("Put manifest %s", ref)
522560
}
561+
switch t := srcManifest.(type) {
562+
case *schema1.SignedManifest:
563+
manifest, err := convertToSchema2(ctx, blobs, t)
564+
if err != nil {
565+
klog.V(2).Infof("Unable to convert manifest to schema2: %v", err)
566+
return toManifests.Put(ctx, t, distribution.WithTag(tag))
567+
}
568+
klog.Infof("warning: Digests are not preserved with schema version 1 images. Support for schema version 1 images will be removed in a future release")
569+
return toManifests.Put(ctx, manifest, options...)
570+
}
571+
572+
toDigest, err := toManifests.Put(ctx, srcManifest, options...)
573+
if err == nil {
574+
return toDigest, nil
575+
}
576+
errs, ok := err.(errcode.Errors)
577+
if !ok || len(errs) == 0 {
578+
return toDigest, err
579+
}
580+
errCode, ok := errs[0].(errcode.Error)
581+
if !ok || errCode.ErrorCode() != v2.ErrorCodeManifestInvalid {
582+
return toDigest, err
583+
}
584+
// try downconverting to v2-schema1
585+
schema2Manifest, ok := srcManifest.(*schema2.DeserializedManifest)
586+
if !ok {
587+
return toDigest, err
588+
}
589+
tagRef, tagErr := reference.WithTag(ref, tag)
590+
if tagErr != nil {
591+
return toDigest, err
592+
}
593+
klog.V(5).Infof("Registry reported invalid manifest error, attempting to convert to v2schema1 as ref %s", tagRef)
594+
schema1Manifest, convertErr := convertToSchema1(ctx, blobs, configJSON, schema2Manifest, tagRef)
595+
if convertErr != nil {
596+
if klog.V(6).Enabled() {
597+
_, data, _ := schema2Manifest.Payload()
598+
klog.Infof("Input schema\n%s", string(data))
599+
}
600+
klog.V(2).Infof("Unable to convert manifest to schema1: %v", convertErr)
601+
return toDigest, err
602+
}
603+
if klog.V(6).Enabled() {
604+
_, data, _ := schema1Manifest.Payload()
605+
klog.Infof("Converted to v2schema1\n%s", string(data))
606+
}
607+
return toManifests.Put(ctx, schema1Manifest, distribution.WithTag(tag))
608+
}
609+
610+
// convertToSchema2 attempts to build a v2 manifest from a v1 manifest, which requires reading blobs to get layer sizes.
611+
// Requires the destination layers already exist in the target repository.
612+
func convertToSchema2(ctx context.Context, blobs distribution.BlobService, srcManifest *schema1.SignedManifest) (distribution.Manifest, error) {
613+
if klog.V(6).Enabled() {
614+
klog.Infof("Up converting v1 schema image:\n%#v", srcManifest.Manifest)
615+
}
616+
617+
config, layers, err := ManifestToImageConfig(ctx, srcManifest, blobs, ManifestLocation{})
618+
if err != nil {
619+
return nil, err
620+
}
621+
if klog.V(6).Enabled() {
622+
klog.Infof("Resulting schema: %#v", config)
623+
}
624+
// create synthetic history
625+
// TODO: create restored history?
626+
if len(config.History) == 0 {
627+
for i := len(config.History); i < len(layers); i++ {
628+
config.History = append(config.History, dockerv1client.DockerConfigHistory{
629+
Created: config.Created,
630+
})
631+
}
632+
}
633+
634+
configJSON, err := json.Marshal(config)
635+
if err != nil {
636+
return nil, err
637+
}
638+
if klog.V(6).Enabled() {
639+
klog.Infof("Resulting config.json:\n%s", string(configJSON))
640+
}
641+
configDescriptor := distribution.Descriptor{
642+
Digest: digest.FromBytes(configJSON),
643+
Size: int64(len(configJSON)),
644+
MediaType: schema2.MediaTypeImageConfig,
645+
}
646+
b := schema2.NewManifestBuilder(configDescriptor, configJSON)
647+
_, err = blobs.Put(ctx, schema2.MediaTypeImageConfig, configJSON)
648+
if err != nil {
649+
return nil, err
650+
}
651+
for _, layer := range layers {
652+
desc, err := blobs.Stat(ctx, layer.Digest)
653+
if err != nil {
654+
return nil, err
655+
}
656+
desc.MediaType = schema2.MediaTypeLayer
657+
if err := b.AppendReference(desc); err != nil {
658+
return nil, err
659+
}
660+
}
661+
return b.Build(ctx)
662+
}
523663

524-
return toManifests.Put(ctx, srcManifest, options...)
664+
// TODO: Remove support for v2 schema in 4.9
665+
func convertToSchema1(ctx context.Context, blobs distribution.BlobService, configJSON []byte, schema2Manifest *schema2.DeserializedManifest, ref reference.Named) (distribution.Manifest, error) {
666+
if configJSON == nil {
667+
targetDescriptor := schema2Manifest.Target()
668+
config, err := blobs.Get(ctx, targetDescriptor.Digest)
669+
if err != nil {
670+
return nil, err
671+
}
672+
configJSON = config
673+
}
674+
trustKey, err := loadPrivateKey()
675+
if err != nil {
676+
return nil, err
677+
}
678+
if klog.V(6).Enabled() {
679+
klog.Infof("Down converting v2 schema image:\n%#v\n%s", schema2Manifest.Layers, configJSON)
680+
}
681+
builder := schema1.NewConfigManifestBuilder(blobs, trustKey, ref, configJSON)
682+
for _, d := range schema2Manifest.Layers {
683+
if err := builder.AppendReference(d); err != nil {
684+
return nil, err
685+
}
686+
}
687+
manifest, err := builder.Build(ctx)
688+
if err != nil {
689+
return nil, err
690+
}
691+
return manifest, nil
692+
}
693+
694+
var (
695+
privateKeyLock sync.Mutex
696+
privateKey libtrust.PrivateKey
697+
)
698+
699+
// TODO: Remove support for v2 schema in 4.9
700+
func loadPrivateKey() (libtrust.PrivateKey, error) {
701+
privateKeyLock.Lock()
702+
defer privateKeyLock.Unlock()
703+
if privateKey != nil {
704+
return privateKey, nil
705+
}
706+
trustKey, err := libtrust.GenerateECP256PrivateKey()
707+
if err != nil {
708+
return nil, err
709+
}
710+
privateKey = trustKey
711+
return privateKey, nil
525712
}

pkg/cli/image/mirror/mirror.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"github.com/distribution/distribution/v3"
1313
"github.com/distribution/distribution/v3/manifest/manifestlist"
1414
"github.com/distribution/distribution/v3/manifest/ocischema"
15+
"github.com/distribution/distribution/v3/manifest/schema1"
1516
"github.com/distribution/distribution/v3/manifest/schema2"
1617
"github.com/distribution/distribution/v3/reference"
1718
"github.com/distribution/distribution/v3/registry/client"
@@ -608,6 +609,7 @@ func (o *MirrorImageOptions) plan() (*plan, error) {
608609
addBlobsForManifest := func(srcManifest distribution.Manifest) {
609610
switch srcManifest.(type) {
610611
case *schema2.DeserializedManifest:
612+
case *schema1.SignedManifest:
611613
case *ocischema.DeserializedManifest:
612614
case *manifestlist.DeserializedManifestList:
613615
// we do not need to upload layers in a manifestlist
@@ -880,7 +882,7 @@ func copyManifestToTags(
880882
panic(fmt.Sprintf("empty source manifest for %s", srcDigest))
881883
}
882884
for _, tag := range tags {
883-
toDigest, err := imagemanifest.PutManifestInCompatibleSchema(ctx, srcManifest, tag, plan.to, ref)
885+
toDigest, err := imagemanifest.PutManifestInCompatibleSchema(ctx, srcManifest, tag, plan.to, ref, plan.toBlobs, nil)
884886
if err != nil {
885887
errs = append(errs, fmt.Errorf("unable to push manifest to %s:%s: %v", plan.toRef, tag, err))
886888
continue
@@ -906,7 +908,7 @@ func copyManifest(
906908
if !ok {
907909
panic(fmt.Sprintf("empty source manifest for %s", srcDigest))
908910
}
909-
toDigest, err := imagemanifest.PutManifestInCompatibleSchema(ctx, srcManifest, "", plan.to, ref)
911+
toDigest, err := imagemanifest.PutManifestInCompatibleSchema(ctx, srcManifest, "", plan.to, ref, plan.toBlobs, nil)
910912
if err != nil {
911913
return fmt.Errorf("unable to push manifest to %s: %v", plan.toRef, err)
912914
}

pkg/helpers/image/test/util.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"fmt"
55
"time"
66

7+
"github.com/distribution/distribution/v3/manifest/schema1"
78
"github.com/distribution/distribution/v3/manifest/schema2"
89
imagespecv1 "github.com/opencontainers/image-spec/specs-go/v1"
910

@@ -107,7 +108,7 @@ func ImageWithLayers(id, ref string, configName *string, layers ...string) image
107108
},
108109
},
109110
DockerImageReference: ref,
110-
DockerImageManifestMediaType: schema2.MediaTypeManifest,
111+
DockerImageManifestMediaType: schema1.MediaTypeManifest,
111112
}
112113

113114
image.DockerImageMetadata = runtime.RawExtension{

0 commit comments

Comments
 (0)