@@ -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
3137type 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
509545func 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}
0 commit comments