diff --git a/pkg/api/handlers/libpod/images.go b/pkg/api/handlers/libpod/images.go index 31bd08267d..5fc3b21e1c 100644 --- a/pkg/api/handlers/libpod/images.go +++ b/pkg/api/handlers/libpod/images.go @@ -15,6 +15,8 @@ import ( "github.com/containers/common/libimage" "github.com/containers/common/pkg/ssh" "github.com/containers/image/v5/manifest" + "github.com/containers/image/v5/pkg/shortnames" + "github.com/containers/image/v5/types" "github.com/containers/podman/v4/libpod" "github.com/containers/podman/v4/libpod/define" "github.com/containers/podman/v4/pkg/api/handlers" @@ -720,3 +722,36 @@ func ImageScp(w http.ResponseWriter, r *http.Request) { utils.WriteResponse(w, http.StatusOK, &reports.ScpReport{Id: rep.Names[0]}) } + +// Resolve the passed (short) name to one more candidates it may resolve to. +// See https://www.redhat.com/sysadmin/container-image-short-names. +// +// One user of this endpoint is Podman Desktop which needs to figure out where +// an image may resolve to. +func ImageResolve(w http.ResponseWriter, r *http.Request) { + runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime) + name := utils.GetName(r) + + mode := types.ShortNameModeDisabled + sys := runtime.SystemContext() + sys.ShortNameMode = &mode + + resolved, err := shortnames.Resolve(sys, name) + if err != nil { + utils.Error(w, http.StatusBadRequest, fmt.Errorf("resolving %q: %w", name, err)) + return + } + + if len(resolved.PullCandidates) == 0 { // Should never happen but let's be defensive. + utils.Error(w, http.StatusInternalServerError, fmt.Errorf("name %q did not resolve to any candidate", name)) + return + } + + names := make([]string, 0, len(resolved.PullCandidates)) + for _, candidate := range resolved.PullCandidates { + names = append(names, candidate.Value.String()) + } + + report := handlers.LibpodImagesResolveReport{Names: names} + utils.WriteResponse(w, http.StatusOK, report) +} diff --git a/pkg/api/handlers/types.go b/pkg/api/handlers/types.go index e32b15a28a..2ea2fa516c 100644 --- a/pkg/api/handlers/types.go +++ b/pkg/api/handlers/types.go @@ -34,6 +34,12 @@ type LibpodImagesRemoveReport struct { Errors []string } +// LibpodImagesResolveReport includes a list of fully-qualified image references. +type LibpodImagesResolveReport struct { + // Fully-qualified image references. + Names []string +} + type ContainersPruneReport struct { docker.ContainersPruneReport } diff --git a/pkg/api/server/register_images.go b/pkg/api/server/register_images.go index 6264b192df..d541f5a972 100644 --- a/pkg/api/server/register_images.go +++ b/pkg/api/server/register_images.go @@ -1690,5 +1690,27 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // 500: // $ref: '#/responses/internalError' r.Handle(VersionedPath("/libpod/images/scp/{name:.*}"), s.APIHandler(libpod.ImageScp)).Methods(http.MethodPost) + // swagger:operation GET /libpod/images/{name}/resolve libpod ImageResolveLibpod + // --- + // tags: + // - images + // summary: Resolve an image (short) name + // description: Resolve the passed image name to a list of fully-qualified images referring to container registries. + // parameters: + // - in: path + // name: name + // type: string + // required: true + // description: the (short) name to resolve + // produces: + // - application/json + // responses: + // 204: + // description: resolved image names + // 400: + // $ref: "#/responses/badParamError" + // 500: + // $ref: '#/responses/internalError' + r.Handle(VersionedPath("/libpod/images/{name:.*}/resolve"), s.APIHandler(libpod.ImageResolve)).Methods(http.MethodGet) return nil } diff --git a/test/apiv2/10-images.at b/test/apiv2/10-images.at index dba0eb3210..2c1f734c9e 100644 --- a/test/apiv2/10-images.at +++ b/test/apiv2/10-images.at @@ -325,4 +325,22 @@ t DELETE images/$iid_test2?noprune=false 200 t GET libpod/images/$iid_test1/exists 404 t GET libpod/images/$iid_test2/exists 404 +# If the /resolve tests fail, make sure to use ../registries.conf for the +# podman-service. + +# With an alias, we only get one item back. +t GET libpod/images/podman-desktop-test123:this/resolve 200 \ + .Names[0]="florent.fr/will/like:this" + +# If no alias matches, we will get a candidate for each unqualified-search +# registry. +t GET libpod/images/no-alias-for-sure/resolve 200 \ + .Names[0]="docker.io/library/no-alias-for-sure:latest" \ + .Names[1]="quay.io/no-alias-for-sure:latest" \ + .Names[2]="registry.fedoraproject.org/no-alias-for-sure:latest" + +# Test invalid input. +t GET libpod/images/noCAPITALcharAllowed/resolve 400 \ + .cause="repository name must be lowercase" + # vim: filetype=sh diff --git a/test/apiv2/test-apiv2 b/test/apiv2/test-apiv2 index c1ec290df6..c4d04d6d4a 100755 --- a/test/apiv2/test-apiv2 +++ b/test/apiv2/test-apiv2 @@ -448,7 +448,8 @@ function start_service() { $PODMAN_BIN unshare true fi - $PODMAN_BIN \ + CONTAINERS_REGISTRIES_CONF=$TESTS_DIR/../registries.conf \ + $PODMAN_BIN \ --root $WORKDIR/server_root --syslog=true \ system service \ --time 0 \ diff --git a/test/registries.conf b/test/registries.conf index 8e46717603..6569be833f 100644 --- a/test/registries.conf +++ b/test/registries.conf @@ -21,3 +21,7 @@ location="quay.io/libpod" [[registry]] location="localhost:5000" insecure=true + +# Alias used in tests. +[aliases] + "podman-desktop-test123"="florent.fr/will/like"