@@ -727,7 +727,34 @@ func (rh *RouteHandler) UpdateManifest(response http.ResponseWriter, request *ht
727727 return
728728 }
729729
730- digest , subjectDigest , err := imgStore .PutImageManifest (name , reference , mediaType , body )
730+ rawTagQuery , tagQueryPresent := request .URL .Query ()["tag" ]
731+ digestQueryTags , normErr := normalizeManifestExtraTags (tagQueryPresent , rawTagQuery )
732+ if normErr != nil {
733+ err := apiErr .NewError (apiErr .MANIFEST_INVALID ).AddDetail (map [string ]string {"reason" : normErr .Error ()})
734+ zcommon .WriteJSON (response , http .StatusBadRequest , apiErr .NewErrorList (err ))
735+
736+ return
737+ }
738+
739+ if len (digestQueryTags ) > 0 && ! zcommon .IsDigest (reference ) {
740+ err := apiErr .NewError (apiErr .MANIFEST_INVALID ).AddDetail (map [string ]string {
741+ "reason" : "tag query parameters are only valid when pushing a manifest by digest" ,
742+ })
743+ zcommon .WriteJSON (response , http .StatusBadRequest , apiErr .NewErrorList (err ))
744+
745+ return
746+ }
747+
748+ if len (digestQueryTags ) > constants .MaxManifestDigestQueryTags {
749+ e := apiErr .NewError (apiErr .MANIFEST_INVALID ).AddDetail (map [string ]string {
750+ "reason" : fmt .Sprintf ("too many tag query parameters (max %d)" , constants .MaxManifestDigestQueryTags ),
751+ })
752+ zcommon .WriteJSON (response , http .StatusRequestURITooLong , apiErr .NewErrorList (e ))
753+
754+ return
755+ }
756+
757+ digest , subjectDigest , err := imgStore .PutImageManifest (name , reference , mediaType , body , digestQueryTags )
731758 if err != nil {
732759 details := zerr .GetDetails (err )
733760 if errors .Is (err , zerr .ErrRepoNotFound ) { //nolint:gocritic // errorslint conflicts with gocritic:IfElseChain
@@ -769,12 +796,33 @@ func (rh *RouteHandler) UpdateManifest(response http.ResponseWriter, request *ht
769796 }
770797
771798 if rh .c .MetaDB != nil {
772- err := meta .OnUpdateManifest (request .Context (), name , reference , mediaType ,
773- digest , body , rh .c .StoreController , rh .c .MetaDB , rh .c .Log )
774- if err != nil {
775- response .WriteHeader (http .StatusInternalServerError )
799+ if len (digestQueryTags ) > 0 {
800+ metaUpdateFailed := false
776801
777- return
802+ for _ , tag := range digestQueryTags {
803+ mErr := meta .SetImageMetaFromInput (request .Context (), name , tag , mediaType ,
804+ digest , body , imgStore , rh .c .MetaDB , rh .c .Log )
805+ if mErr != nil {
806+ rh .c .Log .Error ().Err (mErr ).Str ("repository" , name ).Str ("tag" , tag ).
807+ Msg ("multi-tag digest push: failed to update meta for tag" )
808+
809+ metaUpdateFailed = true
810+ }
811+ }
812+
813+ if metaUpdateFailed {
814+ response .WriteHeader (http .StatusInternalServerError )
815+
816+ return
817+ }
818+ } else {
819+ err := meta .OnUpdateManifest (request .Context (), name , reference , mediaType ,
820+ digest , body , rh .c .StoreController , rh .c .MetaDB , rh .c .Log )
821+ if err != nil {
822+ response .WriteHeader (http .StatusInternalServerError )
823+
824+ return
825+ }
778826 }
779827 }
780828
@@ -784,9 +832,45 @@ func (rh *RouteHandler) UpdateManifest(response http.ResponseWriter, request *ht
784832
785833 response .Header ().Set ("Location" , fmt .Sprintf ("/v2/%s/manifests/%s" , name , digest ))
786834 response .Header ().Set (constants .DistContentDigestKey , digest .String ())
835+
836+ for _ , tag := range digestQueryTags {
837+ response .Header ().Add (constants .OCITagResponseKey , tag ) //nolint:canonicalheader
838+ }
839+
787840 response .WriteHeader (http .StatusCreated )
788841}
789842
843+ // normalizeManifestExtraTags deduplicates tag query values in order and rejects empty tag components.
844+ func normalizeManifestExtraTags (tagQueryPresent bool , raw []string ) ([]string , error ) {
845+ if ! tagQueryPresent {
846+ return nil , nil
847+ }
848+
849+ seen := map [string ]struct {}{}
850+
851+ out := make ([]string , 0 , len (raw ))
852+
853+ for _ , rawTag := range raw {
854+ rawTag = strings .TrimSpace (rawTag )
855+ if rawTag == "" {
856+ return nil , zerr .ErrEmptyManifestTagQuery
857+ }
858+
859+ if _ , ok := seen [rawTag ]; ok {
860+ continue
861+ }
862+
863+ seen [rawTag ] = struct {}{}
864+ out = append (out , rawTag )
865+ }
866+
867+ if len (out ) == 0 {
868+ return nil , zerr .ErrEmptyManifestTagQuery
869+ }
870+
871+ return out , nil
872+ }
873+
790874// DeleteManifest godoc
791875// @Summary Delete image manifest
792876// @Description Delete an image's manifest given a reference or a digest
0 commit comments