mirror of
https://github.com/containers/podman.git
synced 2025-06-26 12:56:45 +08:00
Merge pull request #12435 from vrothberg/fix-12320
compat API: allow enforcing short-names resolution to Docker Hub
This commit is contained in:
6
Makefile
6
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:
|
||||
|
4
go.mod
4
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
|
||||
|
9
go.sum
9
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=
|
||||
|
@ -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 {
|
||||
|
@ -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"))
|
||||
|
@ -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)
|
||||
|
@ -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())
|
||||
|
@ -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()))
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
|
148
test/apiv2/70-short-names.at
Normal file
148
test/apiv2/70-short-names.at
Normal file
@ -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
|
8
test/apiv2/containers.conf
Normal file
8
test/apiv2/containers.conf
Normal file
@ -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
|
18
vendor/github.com/containers/common/libimage/search.go
generated
vendored
18
vendor/github.com/containers/common/libimage/search.go
generated
vendored
@ -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
|
||||
|
101
vendor/github.com/containers/image/v5/copy/copy.go
generated
vendored
101
vendor/github.com/containers/image/v5/copy/copy.go
generated
vendored
@ -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
|
||||
}
|
||||
|
6
vendor/github.com/containers/image/v5/copy/manifest.go
generated
vendored
6
vendor/github.com/containers/image/v5/copy/manifest.go
generated
vendored
@ -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?
|
||||
|
59
vendor/github.com/containers/image/v5/pkg/shortnames/shortnames.go
generated
vendored
59
vendor/github.com/containers/image/v5/pkg/shortnames/shortnames.go
generated
vendored
@ -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...))
|
||||
}
|
||||
|
5
vendor/github.com/containers/image/v5/types/types.go
generated
vendored
5
vendor/github.com/containers/image/v5/types/types.go
generated
vendored
@ -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;
|
||||
|
4
vendor/github.com/containers/image/v5/version/version.go
generated
vendored
4
vendor/github.com/containers/image/v5/version/version.go
generated
vendored
@ -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.
|
||||
|
4
vendor/modules.txt
vendored
4
vendor/modules.txt
vendored
@ -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
|
||||
|
Reference in New Issue
Block a user