diff options
author | OpenShift Merge Robot <openshift-merge-robot@users.noreply.github.com> | 2021-11-30 16:30:23 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-11-30 16:30:23 +0100 |
commit | 771f8c628bef17eb183efce8c7ee7a91cbf4087b (patch) | |
tree | adad2324e3c5f094785a411cb537d8f922a3fafc | |
parent | 8de68b170716dd1293c5a044f3e9cfd962fdbfb1 (diff) | |
parent | 5bdd571b1e46f26e23f030456efb009cbb765e4c (diff) | |
download | podman-771f8c628bef17eb183efce8c7ee7a91cbf4087b.tar.gz podman-771f8c628bef17eb183efce8c7ee7a91cbf4087b.tar.bz2 podman-771f8c628bef17eb183efce8c7ee7a91cbf4087b.zip |
Merge pull request #12435 from vrothberg/fix-12320
compat API: allow enforcing short-names resolution to Docker Hub
21 files changed, 486 insertions, 118 deletions
@@ -512,7 +512,7 @@ validate.completions: .PHONY: run-docker-py-tests run-docker-py-tests: touch test/__init__.py - pytest test/python/docker/ + env CONTAINERS_CONF=$(CURDIR)/test/apiv2/containers.conf pytest test/python/docker/ -rm test/__init__.py .PHONY: localunit @@ -594,8 +594,8 @@ remotesystem: .PHONY: localapiv2 localapiv2: env PODMAN=./bin/podman ./test/apiv2/test-apiv2 - env PODMAN=./bin/podman ${PYTHON} -m unittest discover -v ./test/apiv2/python - env PODMAN=./bin/podman ${PYTHON} -m unittest discover -v ./test/python/docker + env CONTAINERS_CONF=$(CURDIR)/test/apiv2/containers.conf PODMAN=./bin/podman ${PYTHON} -m unittest discover -v ./test/apiv2/python + env CONTAINERS_CONF=$(CURDIR)/test/apiv2/containers.conf PODMAN=./bin/podman ${PYTHON} -m unittest discover -v ./test/python/docker .PHONY: remoteapiv2 remoteapiv2: @@ -12,9 +12,9 @@ require ( github.com/containernetworking/cni v1.0.1 github.com/containernetworking/plugins v1.0.1 github.com/containers/buildah v1.23.1 - github.com/containers/common v0.46.1-0.20211122213330-d4e7724a0c58 + github.com/containers/common v0.46.1-0.20211125160015-ccf46abecd91 github.com/containers/conmon v2.0.20+incompatible - github.com/containers/image/v5 v5.17.0 + github.com/containers/image/v5 v5.17.1-0.20211129144953-4f6d0b45be6c github.com/containers/ocicrypt v1.1.2 github.com/containers/psgo v1.7.1 github.com/containers/storage v1.37.1-0.20211122214631-59ba58582415 @@ -199,7 +199,6 @@ github.com/containerd/containerd v1.5.0-beta.3/go.mod h1:/wr9AVtEM7x9c+n0+stptlo github.com/containerd/containerd v1.5.0-beta.4/go.mod h1:GmdgZd2zA2GYIBZ0w09ZvgqEq8EfBp/m3lcVZIvPHhI= github.com/containerd/containerd v1.5.0-rc.0/go.mod h1:V/IXoMqNGgBlabz3tHD2TWDoTJseu1FGOKuoA4nNb2s= github.com/containerd/containerd v1.5.1/go.mod h1:0DOxVqwDy2iZvrZp2JUx/E+hS0UNTVn7dJnIOwtYR4g= -github.com/containerd/containerd v1.5.4/go.mod h1:sx18RgvW6ABJ4iYUw7Q5x7bgFOAB9B6G7+yO0XBc4zw= github.com/containerd/containerd v1.5.5/go.mod h1:oSTh0QpT1w6jYcGmbiSbxv9OSQYaa88mPyWIuU79zyo= github.com/containerd/containerd v1.5.7 h1:rQyoYtj4KddB3bxG6SAqd4+08gePNyJjRqvOIfV3rkM= github.com/containerd/containerd v1.5.7/go.mod h1:gyvv6+ugqY25TiXxcZC3L5yOeYgEw0QMhscqVp1AR9c= @@ -263,14 +262,14 @@ github.com/containernetworking/plugins v1.0.1/go.mod h1:QHCfGpaTwYTbbH+nZXKVTxNB github.com/containers/buildah v1.23.1 h1:Tpc9DsRuU+0Oofewpxb6OJVNQjCu7yloN/obUqzfDTY= github.com/containers/buildah v1.23.1/go.mod h1:4WnrN0yrA7ab0ppgunixu2WM1rlD2rG8QLJAKbEkZlQ= github.com/containers/common v0.44.2/go.mod h1:7sdP4vmI5Bm6FPFxb3lvAh1Iktb6tiO1MzjUzhxdoGo= -github.com/containers/common v0.46.1-0.20211122213330-d4e7724a0c58 h1:d99ZfYePYt1gU5dPvtIdnORNtv/7mkAZUHhCJzR5D5k= -github.com/containers/common v0.46.1-0.20211122213330-d4e7724a0c58/go.mod h1:GrXYaGvQtdKA+fCQLudCTOSGRwZ06MVmRnC7KlI+syY= +github.com/containers/common v0.46.1-0.20211125160015-ccf46abecd91 h1:h9SrSLSQkvluH/sEJ8X1rlBqCoGJtLvSOu4OGK0Qtuw= +github.com/containers/common v0.46.1-0.20211125160015-ccf46abecd91/go.mod h1:PHwsa3UBgbvn2/MwpTQvyHXvVpuwfBrlDBx3GpIRPDQ= github.com/containers/conmon v2.0.20+incompatible h1:YbCVSFSCqFjjVwHTPINGdMX1F6JXHGTUje2ZYobNrkg= github.com/containers/conmon v2.0.20+incompatible/go.mod h1:hgwZ2mtuDrppv78a/cOBNiCm6O0UMWGx1mu7P00nu5I= github.com/containers/image/v5 v5.16.0/go.mod h1:XgTpfAPLRGOd1XYyCU5cISFr777bLmOerCSpt/v7+Q4= -github.com/containers/image/v5 v5.16.1/go.mod h1:mCvIFdzyyP1B0NBcZ80OIuaYqFn/OpFpaOMOMn1kU2M= -github.com/containers/image/v5 v5.17.0 h1:KS5pro80CCsSp5qDBTMmSAWQo+xcBX19zUPExmYX2OQ= github.com/containers/image/v5 v5.17.0/go.mod h1:GnYVusVRFPMMTAAUkrcS8NNSpBp8oyrjOUe04AAmRr4= +github.com/containers/image/v5 v5.17.1-0.20211129144953-4f6d0b45be6c h1:WfMOQlq3CDvVe5ONUGfj9/MajskqUHnbo24j24Xg2ZM= +github.com/containers/image/v5 v5.17.1-0.20211129144953-4f6d0b45be6c/go.mod h1:boW5ckkT0wu9obDEiOIxrtWQmz1znMuHiVMQPcpHnk0= github.com/containers/libtrust v0.0.0-20190913040956-14b96171aa3b h1:Q8ePgVfHDplZ7U33NwHZkrVELsZP5fYj9pM5WBZB2GE= github.com/containers/libtrust v0.0.0-20190913040956-14b96171aa3b/go.mod h1:9rfv8iPl1ZP7aqh9YA68wnZv2NUDbXdcdPHVz0pFbPY= github.com/containers/ocicrypt v1.0.1/go.mod h1:MeJDzk1RJHv89LjsH0Sp5KTY3ZYkjXO/C+bKAeWFIrc= diff --git a/pkg/api/handlers/compat/containers_create.go b/pkg/api/handlers/compat/containers_create.go index d5abb6e44..8837e08ca 100644 --- a/pkg/api/handlers/compat/containers_create.go +++ b/pkg/api/handlers/compat/containers_create.go @@ -52,6 +52,13 @@ func CreateContainer(w http.ResponseWriter, r *http.Request) { return } + imageName, err := utils.NormalizeToDockerHub(r, body.Config.Image) + if err != nil { + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "error normalizing image")) + return + } + body.Config.Image = imageName + newImage, resolvedName, err := runtime.LibimageRuntime().LookupImage(body.Config.Image, nil) if err != nil { if errors.Cause(err) == storage.ErrImageUnknown { diff --git a/pkg/api/handlers/compat/images.go b/pkg/api/handlers/compat/images.go index 0b7ba8bee..af8b6b63d 100644 --- a/pkg/api/handlers/compat/images.go +++ b/pkg/api/handlers/compat/images.go @@ -12,7 +12,6 @@ import ( "github.com/containers/common/libimage" "github.com/containers/common/pkg/config" "github.com/containers/image/v5/manifest" - "github.com/containers/image/v5/pkg/shortnames" "github.com/containers/image/v5/types" "github.com/containers/podman/v3/libpod" "github.com/containers/podman/v3/pkg/api/handlers" @@ -56,6 +55,12 @@ func ExportImage(w http.ResponseWriter, r *http.Request) { defer os.Remove(tmpfile.Name()) name := utils.GetName(r) + possiblyNormalizedName, err := utils.NormalizeToDockerHub(r, name) + if err != nil { + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "error normalizing image")) + return + } + imageEngine := abi.ImageEngine{Libpod: runtime} saveOptions := entities.ImageSaveOptions{ @@ -63,7 +68,7 @@ func ExportImage(w http.ResponseWriter, r *http.Request) { Output: tmpfile.Name(), } - if err := imageEngine.Save(r.Context(), name, nil, saveOptions); err != nil { + if err := imageEngine.Save(r.Context(), possiblyNormalizedName, nil, saveOptions); err != nil { if errors.Cause(err) == storage.ErrImageUnknown { utils.ImageNotFound(w, name, errors.Wrapf(err, "failed to find image %s", name)) return @@ -87,9 +92,6 @@ func ExportImage(w http.ResponseWriter, r *http.Request) { } func CommitContainer(w http.ResponseWriter, r *http.Request) { - var ( - destImage string - ) decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder) runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime) @@ -98,12 +100,12 @@ func CommitContainer(w http.ResponseWriter, r *http.Request) { Changes string `schema:"changes"` Comment string `schema:"comment"` Container string `schema:"container"` + Pause bool `schema:"pause"` + Repo string `schema:"repo"` + Tag string `schema:"tag"` // fromSrc string # fromSrc is currently unused - Pause bool `schema:"pause"` - Repo string `schema:"repo"` - Tag string `schema:"tag"` }{ - // This is where you can override the golang default value for one of fields + Tag: "latest", } if err := decoder.Decode(&query, r.URL.Query()); err != nil { @@ -116,7 +118,6 @@ func CommitContainer(w http.ResponseWriter, r *http.Request) { return } sc := runtime.SystemContext() - tag := "latest" options := libpod.ContainerCommitOptions{ Pause: true, } @@ -133,9 +134,6 @@ func CommitContainer(w http.ResponseWriter, r *http.Request) { return } - if len(query.Tag) > 0 { - tag = query.Tag - } options.Message = query.Comment options.Author = query.Author options.Pause = query.Pause @@ -146,9 +144,15 @@ func CommitContainer(w http.ResponseWriter, r *http.Request) { return } - // I know mitr hates this ... but doing for now + var destImage string if len(query.Repo) > 1 { - destImage = fmt.Sprintf("%s:%s", query.Repo, tag) + destImage = fmt.Sprintf("%s:%s", query.Repo, query.Tag) + possiblyNormalizedName, err := utils.NormalizeToDockerHub(r, destImage) + if err != nil { + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "error normalizing image")) + return + } + destImage = possiblyNormalizedName } commitImage, err := ctr.Commit(r.Context(), destImage, options) @@ -156,7 +160,7 @@ func CommitContainer(w http.ResponseWriter, r *http.Request) { utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrapf(err, "CommitFailure")) return } - utils.WriteResponse(w, http.StatusOK, handlers.IDResponse{ID: commitImage.ID()}) // nolint + utils.WriteResponse(w, http.StatusCreated, handlers.IDResponse{ID: commitImage.ID()}) // nolint } func CreateImageFromSrc(w http.ResponseWriter, r *http.Request) { @@ -195,12 +199,22 @@ func CreateImageFromSrc(w http.ResponseWriter, r *http.Request) { } } + reference := query.Repo + if query.Repo != "" { + possiblyNormalizedName, err := utils.NormalizeToDockerHub(r, reference) + if err != nil { + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "error normalizing image")) + return + } + reference = possiblyNormalizedName + } + platformSpecs := strings.Split(query.Platform, "/") opts := entities.ImageImportOptions{ Source: source, Changes: query.Changes, Message: query.Message, - Reference: query.Repo, + Reference: reference, OS: platformSpecs[0], } if len(platformSpecs) > 1 { @@ -250,13 +264,9 @@ func CreateImageFromImage(w http.ResponseWriter, r *http.Request) { return } - fromImage := mergeNameAndTagOrDigest(query.FromImage, query.Tag) - - // without this early check this function would return 200 but reported error via body stream soon after - // it's better to let caller know early via HTTP status code that request cannot be processed - _, err := shortnames.Resolve(runtime.SystemContext(), fromImage) + possiblyNormalizedName, err := utils.NormalizeToDockerHub(r, mergeNameAndTagOrDigest(query.FromImage, query.Tag)) if err != nil { - utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrap(err, "failed to resolve image name")) + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "error normalizing image")) return } @@ -291,7 +301,7 @@ func CreateImageFromImage(w http.ResponseWriter, r *http.Request) { pullResChan := make(chan pullResult) go func() { - pulledImages, err := runtime.LibimageRuntime().Pull(r.Context(), fromImage, config.PullPolicyAlways, pullOptions) + pulledImages, err := runtime.LibimageRuntime().Pull(r.Context(), possiblyNormalizedName, config.PullPolicyAlways, pullOptions) pullResChan <- pullResult{images: pulledImages, err: err} }() @@ -371,7 +381,13 @@ func GetImage(w http.ResponseWriter, r *http.Request) { // 404 no such // 500 internal name := utils.GetName(r) - newImage, err := utils.GetImage(r, name) + possiblyNormalizedName, err := utils.NormalizeToDockerHub(r, name) + if err != nil { + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "error normalizing image")) + return + } + + newImage, err := utils.GetImage(r, possiblyNormalizedName) if err != nil { // Here we need to fiddle with the error message because docker-py is looking for "No // such image" to determine on how to raise the correct exception. @@ -483,7 +499,16 @@ func ExportImages(w http.ResponseWriter, r *http.Request) { return } - images := query.Names + images := make([]string, len(query.Names)) + for i, img := range query.Names { + possiblyNormalizedName, err := utils.NormalizeToDockerHub(r, img) + if err != nil { + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "error normalizing image")) + return + } + images[i] = possiblyNormalizedName + } + tmpfile, err := ioutil.TempFile("", "api.tar") if err != nil { utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to create tempfile")) diff --git a/pkg/api/handlers/compat/images_build.go b/pkg/api/handlers/compat/images_build.go index ac5934c13..bc53e9312 100644 --- a/pkg/api/handlers/compat/images_build.go +++ b/pkg/api/handlers/compat/images_build.go @@ -118,7 +118,7 @@ func BuildImage(w http.ResponseWriter, r *http.Request) { SecurityOpt string `schema:"securityopt"` ShmSize int `schema:"shmsize"` Squash bool `schema:"squash"` - Tag []string `schema:"t"` + Tags []string `schema:"t"` Target string `schema:"target"` Timestamp int64 `schema:"timestamp"` Ulimits string `schema:"ulimits"` @@ -144,6 +144,9 @@ func BuildImage(w http.ResponseWriter, r *http.Request) { } } + // convert tag formats + tags := query.Tags + // convert addcaps formats var addCaps = []string{} if _, found := r.URL.Query()["addcaps"]; found { @@ -240,8 +243,13 @@ func BuildImage(w http.ResponseWriter, r *http.Request) { } var output string - if len(query.Tag) > 0 { - output = query.Tag[0] + if len(tags) > 0 { + possiblyNormalizedName, err := utils.NormalizeToDockerHub(r, tags[0]) + if err != nil { + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "error normalizing image")) + return + } + output = possiblyNormalizedName } format := buildah.Dockerv2ImageManifest registry := query.Registry @@ -257,9 +265,14 @@ func BuildImage(w http.ResponseWriter, r *http.Request) { } } } - var additionalTags []string - if len(query.Tag) > 1 { - additionalTags = query.Tag[1:] + var additionalTags []string // nolint + for i := 1; i < len(tags); i++ { + possiblyNormalizedTag, err := utils.NormalizeToDockerHub(r, tags[i]) + if err != nil { + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "error normalizing image")) + return + } + additionalTags = append(additionalTags, possiblyNormalizedTag) } var buildArgs = map[string]string{} @@ -404,6 +417,22 @@ func BuildImage(w http.ResponseWriter, r *http.Request) { } defer auth.RemoveAuthfile(authfile) + fromImage := query.From + if fromImage != "" { + possiblyNormalizedName, err := utils.NormalizeToDockerHub(r, fromImage) + if err != nil { + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "error normalizing image")) + return + } + fromImage = possiblyNormalizedName + } + + systemContext := &types.SystemContext{ + AuthFilePath: authfile, + DockerAuthConfig: creds, + } + utils.PossiblyEnforceDockerHub(r, systemContext) + // Channels all mux'ed in select{} below to follow API build protocol stdout := channel.NewWriter(make(chan []byte)) defer stdout.Close() @@ -458,7 +487,7 @@ func BuildImage(w http.ResponseWriter, r *http.Request) { Err: auxout, Excludes: excludes, ForceRmIntermediateCtrs: query.ForceRm, - From: query.From, + From: fromImage, IgnoreUnrecognizedInstructions: query.Ignore, Isolation: isolation, Jobs: &jobs, @@ -481,10 +510,7 @@ func BuildImage(w http.ResponseWriter, r *http.Request) { RusageLogFile: query.RusageLogFile, Squash: query.Squash, Target: query.Target, - SystemContext: &types.SystemContext{ - AuthFilePath: authfile, - DockerAuthConfig: creds, - }, + SystemContext: systemContext, } for _, platformSpec := range query.Platform { @@ -590,7 +616,7 @@ func BuildImage(w http.ResponseWriter, r *http.Request) { logrus.Warnf("Failed to json encode error %v", err) } flush() - for _, tag := range query.Tag { + for _, tag := range tags { m.Stream = fmt.Sprintf("Successfully tagged %s\n", tag) if err := enc.Encode(m); err != nil { logrus.Warnf("Failed to json encode error %v", err) diff --git a/pkg/api/handlers/compat/images_history.go b/pkg/api/handlers/compat/images_history.go index 0c6b9fa88..fb3c2ebd2 100644 --- a/pkg/api/handlers/compat/images_history.go +++ b/pkg/api/handlers/compat/images_history.go @@ -14,9 +14,15 @@ func HistoryImage(w http.ResponseWriter, r *http.Request) { runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime) name := utils.GetName(r) - newImage, _, err := runtime.LibimageRuntime().LookupImage(name, nil) + possiblyNormalizedName, err := utils.NormalizeToDockerHub(r, name) if err != nil { - utils.Error(w, "Something went wrong.", http.StatusNotFound, errors.Wrapf(err, "failed to find image %s", name)) + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "error normalizing image")) + return + } + + newImage, _, err := runtime.LibimageRuntime().LookupImage(possiblyNormalizedName, nil) + if err != nil { + utils.ImageNotFound(w, possiblyNormalizedName, errors.Wrapf(err, "failed to find image %s", possiblyNormalizedName)) return } history, err := newImage.History(r.Context()) diff --git a/pkg/api/handlers/compat/images_push.go b/pkg/api/handlers/compat/images_push.go index 8b6d3d56a..5ecb429ae 100644 --- a/pkg/api/handlers/compat/images_push.go +++ b/pkg/api/handlers/compat/images_push.go @@ -61,12 +61,24 @@ func PushImage(w http.ResponseWriter, r *http.Request) { if query.Tag != "" { imageName += ":" + query.Tag } + if _, err := utils.ParseStorageReference(imageName); err != nil { utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, errors.Wrapf(err, "image source %q is not a containers-storage-transport reference", imageName)) return } + possiblyNormalizedName, err := utils.NormalizeToDockerHub(r, imageName) + if err != nil { + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "error normalizing image")) + return + } + imageName = possiblyNormalizedName + if _, _, err := runtime.LibimageRuntime().LookupImage(possiblyNormalizedName, nil); err != nil { + utils.ImageNotFound(w, imageName, errors.Wrapf(err, "failed to find image %s", imageName)) + return + } + authconf, authfile, key, err := auth.GetCredentials(r) if err != nil { utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "failed to parse %q header for %s", key, r.URL.String())) diff --git a/pkg/api/handlers/compat/images_remove.go b/pkg/api/handlers/compat/images_remove.go index 2dc247c1f..5c06d8de0 100644 --- a/pkg/api/handlers/compat/images_remove.go +++ b/pkg/api/handlers/compat/images_remove.go @@ -34,12 +34,18 @@ func RemoveImage(w http.ResponseWriter, r *http.Request) { } } name := utils.GetName(r) + possiblyNormalizedName, err := utils.NormalizeToDockerHub(r, name) + if err != nil { + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "error normalizing image")) + return + } + imageEngine := abi.ImageEngine{Libpod: runtime} options := entities.ImageRemoveOptions{ Force: query.Force, } - report, rmerrors := imageEngine.Remove(r.Context(), []string{name}, options) + report, rmerrors := imageEngine.Remove(r.Context(), []string{possiblyNormalizedName}, options) if len(rmerrors) > 0 && rmerrors[0] != nil { err := rmerrors[0] if errors.Cause(err) == storage.ErrImageUnknown { diff --git a/pkg/api/handlers/compat/images_tag.go b/pkg/api/handlers/compat/images_tag.go index 5d413a821..3fe13e2f5 100644 --- a/pkg/api/handlers/compat/images_tag.go +++ b/pkg/api/handlers/compat/images_tag.go @@ -14,12 +14,16 @@ import ( func TagImage(w http.ResponseWriter, r *http.Request) { runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime) - // /v1.xx/images/(name)/tag name := utils.GetName(r) + possiblyNormalizedName, err := utils.NormalizeToDockerHub(r, name) + if err != nil { + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "error normalizing image")) + return + } // Allow tagging manifest list instead of resolving instances from manifest lookupOptions := &libimage.LookupImageOptions{ManifestList: true} - newImage, _, err := runtime.LibimageRuntime().LookupImage(name, lookupOptions) + newImage, _, err := runtime.LibimageRuntime().LookupImage(possiblyNormalizedName, lookupOptions) if err != nil { utils.ImageNotFound(w, name, errors.Wrapf(err, "failed to find image %s", name)) return @@ -35,7 +39,14 @@ func TagImage(w http.ResponseWriter, r *http.Request) { } repo := r.Form.Get("repo") tagName := fmt.Sprintf("%s:%s", repo, tag) - if err := newImage.Tag(tagName); err != nil { + + possiblyNormalizedTag, err := utils.NormalizeToDockerHub(r, tagName) + if err != nil { + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "error normalizing image")) + return + } + + if err := newImage.Tag(possiblyNormalizedTag); err != nil { utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err) return } diff --git a/pkg/api/handlers/utils/images.go b/pkg/api/handlers/utils/images.go index d5eb71aa1..d874165e3 100644 --- a/pkg/api/handlers/utils/images.go +++ b/pkg/api/handlers/utils/images.go @@ -3,19 +3,61 @@ package utils import ( "fmt" "net/http" + "strings" "github.com/containers/common/libimage" "github.com/containers/common/pkg/filters" "github.com/containers/image/v5/docker" - "github.com/containers/image/v5/storage" + storageTransport "github.com/containers/image/v5/storage" "github.com/containers/image/v5/transports/alltransports" "github.com/containers/image/v5/types" "github.com/containers/podman/v3/libpod" api "github.com/containers/podman/v3/pkg/api/types" + "github.com/containers/podman/v3/pkg/util" + "github.com/containers/storage" + "github.com/docker/distribution/reference" "github.com/gorilla/schema" "github.com/pkg/errors" ) +// NormalizeToDockerHub normalizes the specified nameOrID to Docker Hub if the +// request is for the compat API and if containers.conf set the specific mode. +// If nameOrID is a (short) ID for a local image, the full ID will be returned. +func NormalizeToDockerHub(r *http.Request, nameOrID string) (string, error) { + if IsLibpodRequest(r) || !util.DefaultContainerConfig().Engine.CompatAPIEnforceDockerHub { + return nameOrID, nil + } + + // Try to lookup the input to figure out if it was an ID or not. + runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime) + img, _, err := runtime.LibimageRuntime().LookupImage(nameOrID, nil) + if err != nil { + if errors.Cause(err) != storage.ErrImageUnknown { + return "", fmt.Errorf("normalizing name for compat API: %v", err) + } + } else if strings.HasPrefix(img.ID(), nameOrID) { + return img.ID(), nil + } + + // No ID, so we can normalize. + named, err := reference.ParseNormalizedNamed(nameOrID) + if err != nil { + return "", fmt.Errorf("normalizing name for compat API: %v", err) + } + + return named.String(), nil +} + +// PossiblyEnforceDockerHub sets fields in the system context to enforce +// resolving short names to Docker Hub if the request is for the compat API and +// if containers.conf set the specific mode. +func PossiblyEnforceDockerHub(r *http.Request, sys *types.SystemContext) { + if IsLibpodRequest(r) || !util.DefaultContainerConfig().Engine.CompatAPIEnforceDockerHub { + return + } + sys.PodmanOnlyShortNamesIgnoreRegistriesConfAndForceDockerHub = true +} + // IsRegistryReference checks if the specified name points to the "docker://" // transport. If it points to no supported transport, we'll assume a // non-transport reference pointing to an image (e.g., "fedora:latest"). @@ -35,13 +77,13 @@ func IsRegistryReference(name string) error { // `types.ImageReference` and enforces it to refer to a // containers-storage-transport reference. func ParseStorageReference(name string) (types.ImageReference, error) { - storagePrefix := fmt.Sprintf("%s:", storage.Transport.Name()) + storagePrefix := storageTransport.Transport.Name() imageRef, err := alltransports.ParseImageName(name) if err == nil && imageRef.Transport().Name() != docker.Transport.Name() { return nil, errors.Errorf("reference %q must be a storage reference", name) } else if err != nil { origErr := err - imageRef, err = alltransports.ParseImageName(fmt.Sprintf("%s%s", storagePrefix, name)) + imageRef, err = alltransports.ParseImageName(fmt.Sprintf("%s:%s", storagePrefix, name)) if err != nil { return nil, errors.Wrapf(origErr, "reference %q must be a storage reference", name) } diff --git a/test/apiv2/12-imagesMore.at b/test/apiv2/12-imagesMore.at index 144b83194..3a5d5c096 100644 --- a/test/apiv2/12-imagesMore.at +++ b/test/apiv2/12-imagesMore.at @@ -47,8 +47,7 @@ t POST "images/localhost:5000/myrepo/push?tlsVerify=false&tag=mytag" 200 t POST "libpod/images/$iid/untag?repo=localhost:5000/myrepo&tag=mytag" 201 # Try to push non-existing image -t POST "images/localhost:5000/idonotexist/push?tlsVerify=false" 200 -jq -re 'select(.errorDetail)' <<<"$output" &>/dev/null || echo -e "${red}not ok: error message not found in output${nc}" 1>&2 +t POST "images/localhost:5000/idonotexist/push?tlsVerify=false" 404 t GET libpod/images/$IMAGE/json 200 \ .RepoTags[-1]=$IMAGE diff --git a/test/apiv2/70-short-names.at b/test/apiv2/70-short-names.at new file mode 100644 index 000000000..a5087c115 --- /dev/null +++ b/test/apiv2/70-short-names.at @@ -0,0 +1,148 @@ +# -*- sh -*- +# +# Tests for exercising short-name resolution in the compat API. +# + +# Pull the libpod/quay image which is used in all tests below. +t POST "images/create?fromImage=quay.io/libpod/alpine:latest" 200 .error~null .status~".*Download complete.*" + + +########## TAG + +t POST "images/quay.io/libpod/alpine/tag?repo=foo" 201 +t DELETE "images/foo" 200 + + +########## BUILD + +function test_build { + from=$1 + tag=$2 + fqn=$3 + + TMPD=$(mktemp -d podman-apiv2-test.build.XXXXXXXX) + CONTAINERFILE_TAR="${TMPD}/containerfile.tar" + cat > $TMPD/containerfile << EOF +FROM $from +RUN touch /foo +EOF + tar --format=posix -C $TMPD -cvf ${CONTAINERFILE_TAR} containerfile &> /dev/null + + curl -XPOST --data-binary @<(cat $CONTAINERFILE_TAR) \ + -H "content-type: application/x-tar" \ + --dump-header "${TMPD}/headers.txt" \ + -o "${TMPD}/response.txt" \ + "http://$HOST:$PORT/build?dockerfile=containerfile&t=$tag" &> /dev/null + + if ! grep -q '200 OK' "${TMPD}/headers.txt"; then + cat "${TMPD}/headers.txt" + cat "${TMPD}/response.txt" + echo -e "${red}NOK: Image build from tar failed response was not 200 OK (application/x-tar)" + exit 1 + fi + + rm -rf $TMPD + t DELETE "images/$fqn" 200 +} + +t POST "images/quay.io/libpod/alpine/tag?repo=foo" 201 +test_build foo bar "docker.io/library/bar:latest" +t DELETE "images/foo" 200 + + +########## TAG + +# Looking up 'alpine' will fail as it gets normalized to docker.io. +t POST "images/alpine/tag?repo=foo" 404 .cause="image not known" + +# The libpod endpoint will resolve to it without issues. +t GET "libpod/images/alpine/exists" 204 + +# Now let's tag the image with 'foo'. Remember, it will be normalized to +# docker.io/library/foo. +t GET "libpod/images/docker.io/library/foo/exists" 404 +t POST "images/quay.io/libpod/alpine/tag?repo=foo" 201 +t GET "libpod/images/docker.io/library/foo/exists" 204 + + +########## REMOVE + +t DELETE "images/alpine" 404 .cause="image not known" # fails since docker.io/library/alpine does not exist +t DELETE "images/foo" 200 # removes the previously tagged image + + +########## GET + +# Same procedure as above but with the /get endpoint. +t GET "images/alpine/get" 404 .cause="image not known" +t POST "images/quay.io/libpod/alpine/tag?repo=foo" 201 +t GET "images/foo/get" 200 '[POSIX tar archive]' +t DELETE "images/foo" 200 + + +########## HISTORY + +t GET "images/alpine/history" 404 .cause="image not known" +t GET "images/quay.io/libpod/alpine/history" 200 +t POST "images/quay.io/libpod/alpine/tag?repo=foo" 201 +t GET "libpod/images/foo/history" 200 +t DELETE "images/foo" 200 + + +########## PUSH + +t POST "images/alpine/push?destination=localhost:9999/do/not:exist" 404 .cause="image not known" +t POST "images/quay.io/libpod/alpine/push?destination=localhost:9999/do/not:exist" 200 # Error is in the response +t POST "images/quay.io/libpod/alpine/tag?repo=foo" 201 +t POST "images/foo/push?destination=localhost:9999/do/not:exist" 200 # Error is in the response +t DELETE "images/foo" 200 + + +########## CREATE A CONTAINER + +t POST "containers/create" Image=alpine 404 .cause="image not known" +t POST "containers/create" Image=quay.io/libpod/alpine:latest 201 +cid=$(jq -r '.Id' <<<"$output") +t POST "images/quay.io/libpod/alpine/tag?repo=foo" 201 +t POST "containers/create" Image=foo 201 +cid=$(jq -r '.Id' <<<"$output") +t DELETE "images/foo" 200 +t DELETE "containers/$cid" 204 + +########## COMMIT CONTAINER + +t POST "containers/create" Image=quay.io/libpod/alpine:latest 201 +cid=$(jq -r '.Id' <<<"$output") +t GET "images/alpine/get" 404 .cause="image not known" +t POST "commit?container=$cid&repo=foo&tag=tag" 201 +t GET "images/foo/get" 404 .cause="image not known" +t GET "images/foo:tag/get" 200 +t DELETE "images/docker.io/library/foo:tag" 200 +t DELETE "containers/$cid" 204 + + +######### SMOKE TESTS WITHOUT DOCKER.IO ENFORCEMENT + +# Note that we need to restart the service with a custom containers.conf to +# disable the docker.io enforcement. + +stop_service +CONTAINERS_CONF=$(pwd)/test/apiv2/containers.conf start_service + +t POST "images/create?fromImage=quay.io/libpod/alpine:latest" 200 .error~null .status~".*Download complete.*" +t POST "images/alpine/tag?repo=foo" 201 +t GET "images/localhost/foo:latest/get" 200 +t DELETE "images/foo" 200 +t GET "images/alpine/history" 200 +t POST "images/alpine/push?destination=localhost:9999/do/not:exist" 200 # Error is in the response +t POST "containers/create" Image=alpine 201 +cid=$(jq -r '.Id' <<<"$output") +t POST "commit?container=$cid&repo=foo&tag=tag" 201 +t DELETE "images/localhost/foo:tag" 200 +t DELETE "containers/$cid" 204 + +test_build alpine bar "localhost/bar:latest" + + +stop_service +start_service diff --git a/test/apiv2/containers.conf b/test/apiv2/containers.conf new file mode 100644 index 000000000..24762192f --- /dev/null +++ b/test/apiv2/containers.conf @@ -0,0 +1,8 @@ +# This containers.conf file is used to test enforcing short-name resolution to +# docker.io for Podman's *compat* API. By default, the compat API defaults to +# resolving to docker.io only. The behavior can be altered by configuring the +# containers.conf as done below in which case short names are subject to aliases, +# "localhost/" and the unqualified-search registries. + +[engine] +compat_api_enforce_docker_hub=false diff --git a/vendor/github.com/containers/common/libimage/search.go b/vendor/github.com/containers/common/libimage/search.go index ece81531a..33a4776ce 100644 --- a/vendor/github.com/containers/common/libimage/search.go +++ b/vendor/github.com/containers/common/libimage/search.go @@ -58,6 +58,10 @@ type SearchOptions struct { InsecureSkipTLSVerify types.OptionalBool // ListTags returns the search result with available tags ListTags bool + // Registries to search if the specified term does not include a + // registry. If set, the unqualified-search registries in + // containers-registries.conf(5) are ignored. + Registries []string } // SearchFilter allows filtering images while searching. @@ -105,6 +109,10 @@ func ParseSearchFilter(filter []string) (*SearchFilter, error) { return sFilter, nil } +// Search searches term. If term includes a registry, only this registry will +// be used for searching. Otherwise, the unqualified-search registries in +// containers-registries.conf(5) or the ones specified in the options will be +// used. func (r *Runtime) Search(ctx context.Context, term string, options *SearchOptions) ([]SearchResult, error) { if options == nil { options = &SearchOptions{} @@ -117,10 +125,14 @@ func (r *Runtime) Search(ctx context.Context, term string, options *SearchOption // that we cannot use the reference parser from the containers/image // library as the search term may container arbitrary input such as // wildcards. See bugzilla.redhat.com/show_bug.cgi?id=1846629. - if spl := strings.SplitN(term, "/", 2); len(spl) > 1 { - searchRegistries = append(searchRegistries, spl[0]) + spl := strings.SplitN(term, "/", 2) + switch { + case len(spl) > 1: + searchRegistries = []string{spl[0]} term = spl[1] - } else { + case len(options.Registries) > 0: + searchRegistries = options.Registries + default: regs, err := sysregistriesv2.UnqualifiedSearchRegistries(r.systemContextCopy()) if err != nil { return nil, err diff --git a/vendor/github.com/containers/image/v5/copy/copy.go b/vendor/github.com/containers/image/v5/copy/copy.go index e1649ba8e..317f8922a 100644 --- a/vendor/github.com/containers/image/v5/copy/copy.go +++ b/vendor/github.com/containers/image/v5/copy/copy.go @@ -80,13 +80,13 @@ type copier struct { // imageCopier tracks state specific to a single image (possibly an item of a manifest list) type imageCopier struct { - c *copier - manifestUpdates *types.ManifestUpdateOptions - src types.Image - diffIDsAreNeeded bool - canModifyManifest bool - canSubstituteBlobs bool - ociEncryptLayers *[]int + c *copier + manifestUpdates *types.ManifestUpdateOptions + src types.Image + diffIDsAreNeeded bool + cannotModifyManifestReason string // The reason the manifest cannot be modified, or an empty string if it can + canSubstituteBlobs bool + ociEncryptLayers *[]int } const ( @@ -129,10 +129,14 @@ type Options struct { DestinationCtx *types.SystemContext ProgressInterval time.Duration // time to wait between reports to signal the progress channel Progress chan types.ProgressProperties // Reported to when ProgressInterval has arrived for a single artifact+offset. + + // Preserve digests, and fail if we cannot. + PreserveDigests bool // manifest MIME type of image set by user. "" is default and means use the autodetection to the the manifest MIME type ForceManifestMIMEType string ImageListSelection ImageListSelection // set to either CopySystemImage (the default), CopyAllImages, or CopySpecificImages to control which instances we copy when the source reference is a list; ignored if the source reference is not a list Instances []digest.Digest // if ImageListSelection is CopySpecificImages, copy only these instances and the list itself + // If OciEncryptConfig is non-nil, it indicates that an image should be encrypted. // The encryption options is derived from the construction of EncryptConfig object. // Note: During initial encryption process of a layer, the resultant digest is not known @@ -410,7 +414,36 @@ func (c *copier) copyMultipleImages(ctx context.Context, policyContext *signatur return nil, errors.Wrapf(err, "Can not copy signatures to %s", transports.ImageName(c.dest.Reference())) } } - canModifyManifestList := (len(sigs) == 0) + + // If the destination is a digested reference, make a note of that, determine what digest value we're + // expecting, and check that the source manifest matches it. + destIsDigestedReference := false + if named := c.dest.Reference().DockerReference(); named != nil { + if digested, ok := named.(reference.Digested); ok { + destIsDigestedReference = true + matches, err := manifest.MatchesDigest(manifestList, digested.Digest()) + if err != nil { + return nil, errors.Wrapf(err, "computing digest of source image's manifest") + } + if !matches { + return nil, errors.New("Digest of source image's manifest would not match destination reference") + } + } + } + + // Determine if we're allowed to modify the manifest list. + // If we can, set to the empty string. If we can't, set to the reason why. + // Compare, and perhaps keep in sync with, the version in copyOneImage. + cannotModifyManifestListReason := "" + if len(sigs) > 0 { + cannotModifyManifestListReason = "Would invalidate signatures" + } + if destIsDigestedReference { + cannotModifyManifestListReason = "Destination specifies a digest" + } + if options.PreserveDigests { + cannotModifyManifestListReason = "Instructed to preserve digests" + } // Determine if we'll need to convert the manifest list to a different format. forceListMIMEType := options.ForceManifestMIMEType @@ -425,8 +458,8 @@ func (c *copier) copyMultipleImages(ctx context.Context, policyContext *signatur return nil, errors.Wrapf(err, "determining manifest list type to write to destination") } if selectedListType != originalList.MIMEType() { - if !canModifyManifestList { - return nil, errors.Errorf("manifest list must be converted to type %q to be written to destination, but that would invalidate signatures", selectedListType) + if cannotModifyManifestListReason != "" { + return nil, errors.Errorf("Manifest list must be converted to type %q to be written to destination, but we cannot modify it: %q", selectedListType, cannotModifyManifestListReason) } } @@ -510,8 +543,8 @@ func (c *copier) copyMultipleImages(ctx context.Context, policyContext *signatur // If we can't just use the original value, but we have to change it, flag an error. if !bytes.Equal(attemptedManifestList, originalManifestList) { - if !canModifyManifestList { - return nil, errors.Errorf(" manifest list must be converted to type %q to be written to destination, but that would invalidate signatures", thisListType) + if cannotModifyManifestListReason != "" { + return nil, errors.Errorf("Manifest list must be converted to type %q to be written to destination, but we cannot modify it: %q", thisListType, cannotModifyManifestListReason) } logrus.Debugf("Manifest list has been updated") } else { @@ -629,13 +662,27 @@ func (c *copier) copyOneImage(ctx context.Context, policyContext *signature.Poli } } + // Determine if we're allowed to modify the manifest. + // If we can, set to the empty string. If we can't, set to the reason why. + // Compare, and perhaps keep in sync with, the version in copyMultipleImages. + cannotModifyManifestReason := "" + if len(sigs) > 0 { + cannotModifyManifestReason = "Would invalidate signatures" + } + if destIsDigestedReference { + cannotModifyManifestReason = "Destination specifies a digest" + } + if options.PreserveDigests { + cannotModifyManifestReason = "Instructed to preserve digests" + } + ic := imageCopier{ c: c, manifestUpdates: &types.ManifestUpdateOptions{InformationOnly: types.ManifestUpdateInformation{Destination: c.dest}}, src: src, // diffIDsAreNeeded is computed later - canModifyManifest: len(sigs) == 0 && !destIsDigestedReference, - ociEncryptLayers: options.OciEncryptLayers, + cannotModifyManifestReason: cannotModifyManifestReason, + ociEncryptLayers: options.OciEncryptLayers, } // Ensure _this_ copy sees exactly the intended data when either processing a signed image or signing it. // This may be too conservative, but for now, better safe than sorry, _especially_ on the SignBy path: @@ -643,7 +690,7 @@ func (c *copier) copyOneImage(ctx context.Context, policyContext *signature.Poli // We do intend the RecordDigestUncompressedPair calls to only work with reliable data, but at least there’s a risk // that the compressed version coming from a third party may be designed to attack some other decompressor implementation, // and we would reuse and sign it. - ic.canSubstituteBlobs = ic.canModifyManifest && options.SignBy == "" + ic.canSubstituteBlobs = ic.cannotModifyManifestReason == "" && options.SignBy == "" if err := ic.updateEmbeddedDockerReference(); err != nil { return nil, "", "", err @@ -710,10 +757,10 @@ func (c *copier) copyOneImage(ctx context.Context, policyContext *signature.Poli } // If the original MIME type is acceptable, determineManifestConversion always uses it as preferredManifestMIMEType. // So if we are here, we will definitely be trying to convert the manifest. - // With !ic.canModifyManifest, that would just be a string of repeated failures for the same reason, + // With ic.cannotModifyManifestReason != "", that would just be a string of repeated failures for the same reason, // so let’s bail out early and with a better error message. - if !ic.canModifyManifest { - return nil, "", "", errors.Wrap(err, "Writing manifest failed (and converting it is not possible, image is signed or the destination specifies a digest)") + if ic.cannotModifyManifestReason != "" { + return nil, "", "", errors.Wrapf(err, "Writing manifest failed and we cannot try conversions: %q", cannotModifyManifestReason) } // errs is a list of errors when trying various manifest types. Also serves as an "upload succeeded" flag when set to nil. @@ -813,9 +860,9 @@ func (ic *imageCopier) updateEmbeddedDockerReference() error { return nil // No reference embedded in the manifest, or it matches destRef already. } - if !ic.canModifyManifest { - return errors.Errorf("Copying a schema1 image with an embedded Docker reference to %s (Docker reference %s) would change the manifest, which is not possible (image is signed or the destination specifies a digest)", - transports.ImageName(ic.c.dest.Reference()), destRef.String()) + if ic.cannotModifyManifestReason != "" { + return errors.Errorf("Copying a schema1 image with an embedded Docker reference to %s (Docker reference %s) would change the manifest, which we cannot do: %q", + transports.ImageName(ic.c.dest.Reference()), destRef.String(), ic.cannotModifyManifestReason) } ic.manifestUpdates.EmbeddedDockerReference = destRef return nil @@ -833,7 +880,7 @@ func isTTY(w io.Writer) bool { return false } -// copyLayers copies layers from ic.src/ic.c.rawSource to dest, using and updating ic.manifestUpdates if necessary and ic.canModifyManifest. +// copyLayers copies layers from ic.src/ic.c.rawSource to dest, using and updating ic.manifestUpdates if necessary and ic.cannotModifyManifestReason == "". func (ic *imageCopier) copyLayers(ctx context.Context) error { srcInfos := ic.src.LayerInfos() numLayers := len(srcInfos) @@ -844,8 +891,8 @@ func (ic *imageCopier) copyLayers(ctx context.Context) error { srcInfosUpdated := false // If we only need to check authorization, no updates required. if updatedSrcInfos != nil && !reflect.DeepEqual(srcInfos, updatedSrcInfos) { - if !ic.canModifyManifest { - return errors.Errorf("Copying this image requires changing layer representation, which is not possible (image is signed or the destination specifies a digest)") + if ic.cannotModifyManifestReason != "" { + return errors.Errorf("Copying this image would require changing layer representation, which we cannot do: %q", ic.cannotModifyManifestReason) } srcInfos = updatedSrcInfos srcInfosUpdated = true @@ -975,8 +1022,8 @@ func layerDigestsDiffer(a, b []types.BlobInfo) bool { func (ic *imageCopier) copyUpdatedConfigAndManifest(ctx context.Context, instanceDigest *digest.Digest) ([]byte, digest.Digest, error) { pendingImage := ic.src if !ic.noPendingManifestUpdates() { - if !ic.canModifyManifest { - return nil, "", errors.Errorf("Internal error: copy needs an updated manifest but that was known to be forbidden") + if ic.cannotModifyManifestReason != "" { + return nil, "", errors.Errorf("Internal error: copy needs an updated manifest but that was known to be forbidden: %q", ic.cannotModifyManifestReason) } if !ic.diffIDsAreNeeded && ic.src.UpdatedImageNeedsLayerDiffIDs(*ic.manifestUpdates) { // We have set ic.diffIDsAreNeeded based on the preferred MIME type returned by determineManifestConversion. @@ -1359,7 +1406,7 @@ func (ic *imageCopier) copyLayerFromStream(ctx context.Context, srcStream io.Rea } } - blobInfo, err := ic.c.copyBlobFromStream(ctx, srcStream, srcInfo, getDiffIDRecorder, ic.canModifyManifest, false, toEncrypt, bar, layerIndex, emptyLayer) // Sets err to nil on success + blobInfo, err := ic.c.copyBlobFromStream(ctx, srcStream, srcInfo, getDiffIDRecorder, ic.cannotModifyManifestReason == "", false, toEncrypt, bar, layerIndex, emptyLayer) // Sets err to nil on success return blobInfo, diffIDChan, err // We need the defer … pipeWriter.CloseWithError() to happen HERE so that the caller can block on reading from diffIDChan } diff --git a/vendor/github.com/containers/image/v5/copy/manifest.go b/vendor/github.com/containers/image/v5/copy/manifest.go index b97edbf08..86ec8863a 100644 --- a/vendor/github.com/containers/image/v5/copy/manifest.go +++ b/vendor/github.com/containers/image/v5/copy/manifest.go @@ -79,10 +79,10 @@ func (ic *imageCopier) determineManifestConversion(ctx context.Context, destSupp if _, ok := supportedByDest[srcType]; ok { prioritizedTypes.append(srcType) } - if !ic.canModifyManifest { - // We could also drop the !ic.canModifyManifest check and have the caller + if ic.cannotModifyManifestReason != "" { + // We could also drop this check and have the caller // make the choice; it is already doing that to an extent, to improve error - // messages. But it is nice to hide the “if !ic.canModifyManifest, do no conversion” + // messages. But it is nice to hide the “if we can't modify, do no conversion” // special case in here; the caller can then worry (or not) only about a good UI. logrus.Debugf("We can't modify the manifest, hoping for the best...") return srcType, []string{}, nil // Take our chances - FIXME? Or should we fail without trying? diff --git a/vendor/github.com/containers/image/v5/pkg/shortnames/shortnames.go b/vendor/github.com/containers/image/v5/pkg/shortnames/shortnames.go index fb0a15b99..46c10ff63 100644 --- a/vendor/github.com/containers/image/v5/pkg/shortnames/shortnames.go +++ b/vendor/github.com/containers/image/v5/pkg/shortnames/shortnames.go @@ -118,6 +118,7 @@ type Resolved struct { } func (r *Resolved) addCandidate(named reference.Named) { + named = reference.TagNameOnly(named) // Make sure to add ":latest" if needed r.PullCandidates = append(r.PullCandidates, PullCandidate{named, false, r}) } @@ -138,6 +139,8 @@ const ( rationaleUSR // Resolved value has been selected by the user (via the prompt). rationaleUserSelection + // Resolved value has been enforced to use Docker Hub (via SystemContext). + rationaleEnforcedDockerHub ) // Description returns a human-readable description about the resolution @@ -152,6 +155,8 @@ func (r *Resolved) Description() string { return fmt.Sprintf("Resolved %q as an alias (%s)", r.userInput, r.originDescription) case rationaleUSR: return fmt.Sprintf("Resolving %q using unqualified-search registries (%s)", r.userInput, r.originDescription) + case rationaleEnforcedDockerHub: + return fmt.Sprintf("Resolving %q to docker.io (%s)", r.userInput, r.originDescription) case rationaleUserSelection, rationaleNone: fallthrough default: @@ -265,8 +270,20 @@ func Resolve(ctx *types.SystemContext, name string) (*Resolved, error) { return nil, err } if !isShort { // no short name - named := reference.TagNameOnly(shortRef) // Make sure to add ":latest" if needed + resolved.addCandidate(shortRef) + return resolved, nil + } + + // Resolve to docker.io only if enforced by the caller (e.g., Podman's + // Docker-compatible REST API). + if ctx != nil && ctx.PodmanOnlyShortNamesIgnoreRegistriesConfAndForceDockerHub { + named, err := reference.ParseNormalizedNamed(name) + if err != nil { + return nil, errors.Wrapf(err, "cannot normalize input: %q", name) + } resolved.addCandidate(named) + resolved.rationale = rationaleEnforcedDockerHub + resolved.originDescription = "enforced by caller" return resolved, nil } @@ -295,9 +312,6 @@ func Resolve(ctx *types.SystemContext, name string) (*Resolved, error) { return nil, err } } - // Make sure to add ":latest" if needed - namedAlias = reference.TagNameOnly(namedAlias) - resolved.addCandidate(namedAlias) resolved.rationale = rationaleAlias resolved.originDescription = aliasOriginDescription @@ -325,9 +339,6 @@ func Resolve(ctx *types.SystemContext, name string) (*Resolved, error) { if err != nil { return nil, errors.Wrapf(err, "creating reference with unqualified-search registry %q", reg) } - // Make sure to add ":latest" if needed - named = reference.TagNameOnly(named) - resolved.addCandidate(named) } @@ -412,6 +423,23 @@ func ResolveLocally(ctx *types.SystemContext, name string) ([]reference.Named, e var candidates []reference.Named + // Complete the candidates with the specified registries. + completeCandidates := func(registries []string) ([]reference.Named, error) { + for _, reg := range registries { + named, err := reference.ParseNormalizedNamed(fmt.Sprintf("%s/%s", reg, name)) + if err != nil { + return nil, errors.Wrapf(err, "creating reference with unqualified-search registry %q", reg) + } + named = reference.TagNameOnly(named) // Make sure to add ":latest" if needed + candidates = append(candidates, named) + } + return candidates, nil + } + + if ctx != nil && ctx.PodmanOnlyShortNamesIgnoreRegistriesConfAndForceDockerHub { + return completeCandidates([]string{"docker.io"}) + } + // Strip off the tag to normalize the short name for looking it up in // the config files. isTagged, isDigested, shortNameRepo, tag, digest := splitUserInput(shortRef) @@ -434,9 +462,7 @@ func ResolveLocally(ctx *types.SystemContext, name string) ([]reference.Named, e return nil, err } } - // Make sure to add ":latest" if needed - namedAlias = reference.TagNameOnly(namedAlias) - + namedAlias = reference.TagNameOnly(namedAlias) // Make sure to add ":latest" if needed candidates = append(candidates, namedAlias) } @@ -447,16 +473,5 @@ func ResolveLocally(ctx *types.SystemContext, name string) ([]reference.Named, e } // Note that "localhost" has precedence over the unqualified-search registries. - for _, reg := range append([]string{"localhost"}, unqualifiedSearchRegistries...) { - named, err := reference.ParseNormalizedNamed(fmt.Sprintf("%s/%s", reg, name)) - if err != nil { - return nil, errors.Wrapf(err, "creating reference with unqualified-search registry %q", reg) - } - // Make sure to add ":latest" if needed - named = reference.TagNameOnly(named) - - candidates = append(candidates, named) - } - - return candidates, nil + return completeCandidates(append([]string{"localhost"}, unqualifiedSearchRegistries...)) } diff --git a/vendor/github.com/containers/image/v5/types/types.go b/vendor/github.com/containers/image/v5/types/types.go index c98a6c6fd..dcff8caf7 100644 --- a/vendor/github.com/containers/image/v5/types/types.go +++ b/vendor/github.com/containers/image/v5/types/types.go @@ -561,6 +561,11 @@ type SystemContext struct { UserShortNameAliasConfPath string // If set, short-name resolution in pkg/shortnames must follow the specified mode ShortNameMode *ShortNameMode + // If set, short names will resolve in pkg/shortnames to docker.io only, and unqualified-search registries and + // short-name aliases in registries.conf are ignored. Note that this field is only intended to help enforce + // resolving to Docker Hub in the Docker-compatible REST API of Podman; it should never be used outside this + // specific context. + PodmanOnlyShortNamesIgnoreRegistriesConfAndForceDockerHub bool // If not "", overrides the default path for the authentication file, but only new format files AuthFilePath string // if not "", overrides the default path for the authentication file, but with the legacy format; diff --git a/vendor/github.com/containers/image/v5/version/version.go b/vendor/github.com/containers/image/v5/version/version.go index ffb2a4ce2..17639f0d4 100644 --- a/vendor/github.com/containers/image/v5/version/version.go +++ b/vendor/github.com/containers/image/v5/version/version.go @@ -8,10 +8,10 @@ const ( // VersionMinor is for functionality in a backwards-compatible manner VersionMinor = 17 // VersionPatch is for backwards-compatible bug fixes - VersionPatch = 0 + VersionPatch = 1 // VersionDev indicates development branch. Releases will be empty string. - VersionDev = "" + VersionDev = "-dev" ) // Version is the specification version that the package types support. diff --git a/vendor/modules.txt b/vendor/modules.txt index 701c3f73a..a104465c6 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -106,7 +106,7 @@ github.com/containers/buildah/pkg/rusage github.com/containers/buildah/pkg/sshagent github.com/containers/buildah/pkg/util github.com/containers/buildah/util -# github.com/containers/common v0.46.1-0.20211122213330-d4e7724a0c58 +# github.com/containers/common v0.46.1-0.20211125160015-ccf46abecd91 ## explicit github.com/containers/common/libimage github.com/containers/common/libimage/manifests @@ -142,7 +142,7 @@ github.com/containers/common/version # github.com/containers/conmon v2.0.20+incompatible ## explicit github.com/containers/conmon/runner/config -# github.com/containers/image/v5 v5.17.0 +# github.com/containers/image/v5 v5.17.1-0.20211129144953-4f6d0b45be6c ## explicit github.com/containers/image/v5/copy github.com/containers/image/v5/directory |