aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorValentin Rothberg <rothberg@redhat.com>2021-11-23 11:08:31 +0100
committerValentin Rothberg <rothberg@redhat.com>2021-11-30 14:22:52 +0100
commit5bdd571b1e46f26e23f030456efb009cbb765e4c (patch)
treeadad2324e3c5f094785a411cb537d8f922a3fafc
parent8de68b170716dd1293c5a044f3e9cfd962fdbfb1 (diff)
downloadpodman-5bdd571b1e46f26e23f030456efb009cbb765e4c.tar.gz
podman-5bdd571b1e46f26e23f030456efb009cbb765e4c.tar.bz2
podman-5bdd571b1e46f26e23f030456efb009cbb765e4c.zip
compat API: allow enforcing short-names resolution to Docker Hub
The Docker-compatible REST API has historically behaved just as the rest of Podman and Buildah (and the atomic Docker in older RHEL/Fedora) where `containers-registries.conf` is centrally controlling which registries a short name may resolve to during pull or local image lookups. Please refer to a blog for more details [1]. Docker, however, is only resolving short names to docker.io which has been reported (see #12320) to break certain clients who rely on this behavior. In order to support this scenario, `containers.conf(5)` received a new option to control whether Podman's compat API resolves to docker.io only or behaves as before. Most endpoints allow for directly normalizing parameters that represent an image. If set in containers.conf, Podman will then normalize the references directly to docker.io. The build endpoint is an outlier since images are also referenced in Dockerfiles. The Buildah API, however, supports specifying a custom `types.SystemContext` in which we can set a field that enforces short-name resolution to docker.io in `c/image/pkg/shortnames`. Notice that this a "hybrid" approach of doing the normalization directly in the compat endpoints *and* in `pkg/shortnames` by passing a system context. Doing such a hybrid approach is neccessary since the compat and the libpod endpoints share the same `libimage.Runtime` which makes a global enforcement via the `libimage.Runtime.systemContext` impossible. Having two separate runtimes for the compat and the libpod endpoints seems risky and not generally applicable to all endpoints. [1] https://www.redhat.com/sysadmin/container-image-short-names Fixes: #12320 Signed-off-by: Valentin Rothberg <rothberg@redhat.com>
-rw-r--r--Makefile6
-rw-r--r--go.mod4
-rw-r--r--go.sum9
-rw-r--r--pkg/api/handlers/compat/containers_create.go7
-rw-r--r--pkg/api/handlers/compat/images.go77
-rw-r--r--pkg/api/handlers/compat/images_build.go50
-rw-r--r--pkg/api/handlers/compat/images_history.go10
-rw-r--r--pkg/api/handlers/compat/images_push.go12
-rw-r--r--pkg/api/handlers/compat/images_remove.go8
-rw-r--r--pkg/api/handlers/compat/images_tag.go17
-rw-r--r--pkg/api/handlers/utils/images.go48
-rw-r--r--test/apiv2/12-imagesMore.at3
-rw-r--r--test/apiv2/70-short-names.at148
-rw-r--r--test/apiv2/containers.conf8
-rw-r--r--vendor/github.com/containers/common/libimage/search.go18
-rw-r--r--vendor/github.com/containers/image/v5/copy/copy.go101
-rw-r--r--vendor/github.com/containers/image/v5/copy/manifest.go6
-rw-r--r--vendor/github.com/containers/image/v5/pkg/shortnames/shortnames.go59
-rw-r--r--vendor/github.com/containers/image/v5/types/types.go5
-rw-r--r--vendor/github.com/containers/image/v5/version/version.go4
-rw-r--r--vendor/modules.txt4
21 files changed, 486 insertions, 118 deletions
diff --git a/Makefile b/Makefile
index ceecda274..3009f618d 100644
--- a/Makefile
+++ b/Makefile
@@ -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:
diff --git a/go.mod b/go.mod
index 0e1ea3a7e..82dc3dfc3 100644
--- a/go.mod
+++ b/go.mod
@@ -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
diff --git a/go.sum b/go.sum
index 67eb41af1..09a30d2a7 100644
--- a/go.sum
+++ b/go.sum
@@ -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