Merge pull request #12435 from vrothberg/fix-12320

compat API: allow enforcing short-names resolution to Docker Hub
This commit is contained in:
OpenShift Merge Robot
2021-11-30 16:30:23 +01:00
committed by GitHub
21 changed files with 486 additions and 118 deletions

View File

@ -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
View File

@ -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
View File

@ -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=

View File

@ -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 {

View File

@ -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"`
// fromSrc string # fromSrc is currently unused
Pause bool `schema:"pause"`
Repo string `schema:"repo"`
Tag string `schema:"tag"`
// fromSrc string # fromSrc is currently unused
}{
// 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"))

View File

@ -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)

View File

@ -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())

View File

@ -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()))

View File

@ -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 {

View File

@ -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
}

View File

@ -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)
}

View File

@ -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

View 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

View 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

View File

@ -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

View File

@ -84,7 +84,7 @@ type imageCopier struct {
manifestUpdates *types.ManifestUpdateOptions
src types.Image
diffIDsAreNeeded bool
canModifyManifest bool
cannotModifyManifestReason string // The reason the manifest cannot be modified, or an empty string if it can
canSubstituteBlobs bool
ociEncryptLayers *[]int
}
@ -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,12 +662,26 @@ 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,
cannotModifyManifestReason: cannotModifyManifestReason,
ociEncryptLayers: options.OciEncryptLayers,
}
// Ensure _this_ copy sees exactly the intended data when either processing a signed image or signing it.
@ -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 theres 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 lets 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
}

View File

@ -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?

View File

@ -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...))
}

View File

@ -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;

View File

@ -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
View File

@ -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