mirror of
https://github.com/containers/podman.git
synced 2025-07-25 09:05:00 +08:00
Merge pull request #20328 from vrothberg/RUN-1936
api: add `compatMode` parameter to libpod's pull endpoint
This commit is contained in:
pkg/api
test/apiv2
@ -15,7 +15,6 @@ import (
|
|||||||
"github.com/containers/common/pkg/config"
|
"github.com/containers/common/pkg/config"
|
||||||
"github.com/containers/common/pkg/filters"
|
"github.com/containers/common/pkg/filters"
|
||||||
"github.com/containers/image/v5/manifest"
|
"github.com/containers/image/v5/manifest"
|
||||||
"github.com/containers/image/v5/types"
|
|
||||||
"github.com/containers/podman/v4/libpod"
|
"github.com/containers/podman/v4/libpod"
|
||||||
"github.com/containers/podman/v4/pkg/api/handlers"
|
"github.com/containers/podman/v4/pkg/api/handlers"
|
||||||
"github.com/containers/podman/v4/pkg/api/handlers/utils"
|
"github.com/containers/podman/v4/pkg/api/handlers/utils"
|
||||||
@ -25,10 +24,8 @@ import (
|
|||||||
"github.com/containers/podman/v4/pkg/domain/infra/abi"
|
"github.com/containers/podman/v4/pkg/domain/infra/abi"
|
||||||
"github.com/containers/podman/v4/pkg/util"
|
"github.com/containers/podman/v4/pkg/util"
|
||||||
"github.com/containers/storage"
|
"github.com/containers/storage"
|
||||||
"github.com/docker/distribution/registry/api/errcode"
|
|
||||||
docker "github.com/docker/docker/api/types"
|
docker "github.com/docker/docker/api/types"
|
||||||
dockerContainer "github.com/docker/docker/api/types/container"
|
dockerContainer "github.com/docker/docker/api/types/container"
|
||||||
"github.com/docker/docker/pkg/jsonmessage"
|
|
||||||
"github.com/docker/go-connections/nat"
|
"github.com/docker/go-connections/nat"
|
||||||
"github.com/opencontainers/go-digest"
|
"github.com/opencontainers/go-digest"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
@ -253,11 +250,6 @@ func CreateImageFromSrc(w http.ResponseWriter, r *http.Request) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
type pullResult struct {
|
|
||||||
images []*libimage.Image
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
|
|
||||||
func CreateImageFromImage(w http.ResponseWriter, r *http.Request) {
|
func CreateImageFromImage(w http.ResponseWriter, r *http.Request) {
|
||||||
// 200 no error
|
// 200 no error
|
||||||
// 404 repo does not exist or no read access
|
// 404 repo does not exist or no read access
|
||||||
@ -309,99 +301,7 @@ func CreateImageFromImage(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
progress := make(chan types.ProgressProperties)
|
utils.CompatPull(r.Context(), w, runtime, possiblyNormalizedName, config.PullPolicyAlways, pullOptions)
|
||||||
pullOptions.Progress = progress
|
|
||||||
|
|
||||||
pullResChan := make(chan pullResult)
|
|
||||||
go func() {
|
|
||||||
pulledImages, err := runtime.LibimageRuntime().Pull(r.Context(), possiblyNormalizedName, config.PullPolicyAlways, pullOptions)
|
|
||||||
pullResChan <- pullResult{images: pulledImages, err: err}
|
|
||||||
}()
|
|
||||||
|
|
||||||
enc := json.NewEncoder(w)
|
|
||||||
enc.SetEscapeHTML(true)
|
|
||||||
|
|
||||||
flush := func() {
|
|
||||||
if flusher, ok := w.(http.Flusher); ok {
|
|
||||||
flusher.Flush()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
statusWritten := false
|
|
||||||
writeStatusCode := func(code int) {
|
|
||||||
if !statusWritten {
|
|
||||||
w.WriteHeader(code)
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
flush()
|
|
||||||
statusWritten = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
loop: // break out of for/select infinite loop
|
|
||||||
for {
|
|
||||||
report := jsonmessage.JSONMessage{}
|
|
||||||
report.Progress = &jsonmessage.JSONProgress{}
|
|
||||||
select {
|
|
||||||
case e := <-progress:
|
|
||||||
writeStatusCode(http.StatusOK)
|
|
||||||
switch e.Event {
|
|
||||||
case types.ProgressEventNewArtifact:
|
|
||||||
report.Status = "Pulling fs layer"
|
|
||||||
case types.ProgressEventRead:
|
|
||||||
report.Status = "Downloading"
|
|
||||||
report.Progress.Current = int64(e.Offset)
|
|
||||||
report.Progress.Total = e.Artifact.Size
|
|
||||||
report.ProgressMessage = report.Progress.String()
|
|
||||||
case types.ProgressEventSkipped:
|
|
||||||
report.Status = "Already exists"
|
|
||||||
case types.ProgressEventDone:
|
|
||||||
report.Status = "Download complete"
|
|
||||||
}
|
|
||||||
report.ID = e.Artifact.Digest.Encoded()[0:12]
|
|
||||||
if err := enc.Encode(report); err != nil {
|
|
||||||
logrus.Warnf("Failed to json encode error %q", err.Error())
|
|
||||||
}
|
|
||||||
flush()
|
|
||||||
case pullRes := <-pullResChan:
|
|
||||||
err := pullRes.err
|
|
||||||
if err != nil {
|
|
||||||
var errcd errcode.ErrorCoder
|
|
||||||
if errors.As(err, &errcd) {
|
|
||||||
writeStatusCode(errcd.ErrorCode().Descriptor().HTTPStatusCode)
|
|
||||||
} else {
|
|
||||||
writeStatusCode(http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
msg := err.Error()
|
|
||||||
report.Error = &jsonmessage.JSONError{
|
|
||||||
Message: msg,
|
|
||||||
}
|
|
||||||
report.ErrorMessage = msg
|
|
||||||
} else {
|
|
||||||
pulledImages := pullRes.images
|
|
||||||
if len(pulledImages) > 0 {
|
|
||||||
img := pulledImages[0].ID()
|
|
||||||
if utils.IsLibpodRequest(r) {
|
|
||||||
report.Status = "Pull complete"
|
|
||||||
} else {
|
|
||||||
report.Status = "Download complete"
|
|
||||||
}
|
|
||||||
report.ID = img[0:12]
|
|
||||||
} else {
|
|
||||||
msg := "internal error: no images pulled"
|
|
||||||
report.Error = &jsonmessage.JSONError{
|
|
||||||
Message: msg,
|
|
||||||
}
|
|
||||||
report.ErrorMessage = msg
|
|
||||||
writeStatusCode(http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err := enc.Encode(report); err != nil {
|
|
||||||
logrus.Warnf("Failed to json encode error %q", err.Error())
|
|
||||||
}
|
|
||||||
flush()
|
|
||||||
break loop // break out of for/select infinite loop
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetImage(w http.ResponseWriter, r *http.Request) {
|
func GetImage(w http.ResponseWriter, r *http.Request) {
|
||||||
|
@ -28,14 +28,16 @@ func ImagesPull(w http.ResponseWriter, r *http.Request) {
|
|||||||
runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
|
runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
|
||||||
decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder)
|
decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder)
|
||||||
query := struct {
|
query := struct {
|
||||||
Reference string `schema:"reference"`
|
|
||||||
OS string `schema:"OS"`
|
|
||||||
Arch string `schema:"Arch"`
|
|
||||||
Variant string `schema:"Variant"`
|
|
||||||
TLSVerify bool `schema:"tlsVerify"`
|
|
||||||
AllTags bool `schema:"allTags"`
|
AllTags bool `schema:"allTags"`
|
||||||
|
CompatMode bool `schema:"compatMode"`
|
||||||
PullPolicy string `schema:"policy"`
|
PullPolicy string `schema:"policy"`
|
||||||
Quiet bool `schema:"quiet"`
|
Quiet bool `schema:"quiet"`
|
||||||
|
Reference string `schema:"reference"`
|
||||||
|
TLSVerify bool `schema:"tlsVerify"`
|
||||||
|
// Platform fields below:
|
||||||
|
Arch string `schema:"Arch"`
|
||||||
|
OS string `schema:"OS"`
|
||||||
|
Variant string `schema:"Variant"`
|
||||||
}{
|
}{
|
||||||
TLSVerify: true,
|
TLSVerify: true,
|
||||||
PullPolicy: "always",
|
PullPolicy: "always",
|
||||||
@ -46,6 +48,11 @@ func ImagesPull(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if query.Quiet && query.CompatMode {
|
||||||
|
utils.InternalServerError(w, errors.New("'quiet' and 'compatMode' cannot be used simultaneously"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if len(query.Reference) == 0 {
|
if len(query.Reference) == 0 {
|
||||||
utils.InternalServerError(w, errors.New("reference parameter cannot be empty"))
|
utils.InternalServerError(w, errors.New("reference parameter cannot be empty"))
|
||||||
return
|
return
|
||||||
@ -104,6 +111,11 @@ func ImagesPull(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if query.CompatMode {
|
||||||
|
utils.CompatPull(r.Context(), w, runtime, query.Reference, pullPolicy, pullOptions)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
writer := channel.NewWriter(make(chan []byte))
|
writer := channel.NewWriter(make(chan []byte))
|
||||||
defer writer.Close()
|
defer writer.Close()
|
||||||
pullOptions.Writer = writer
|
pullOptions.Writer = writer
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
package utils
|
package utils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/containers/common/libimage"
|
"github.com/containers/common/libimage"
|
||||||
|
"github.com/containers/common/pkg/config"
|
||||||
"github.com/containers/image/v5/docker"
|
"github.com/containers/image/v5/docker"
|
||||||
"github.com/containers/image/v5/docker/reference"
|
"github.com/containers/image/v5/docker/reference"
|
||||||
storageTransport "github.com/containers/image/v5/storage"
|
storageTransport "github.com/containers/image/v5/storage"
|
||||||
@ -16,6 +18,9 @@ import (
|
|||||||
api "github.com/containers/podman/v4/pkg/api/types"
|
api "github.com/containers/podman/v4/pkg/api/types"
|
||||||
"github.com/containers/podman/v4/pkg/util"
|
"github.com/containers/podman/v4/pkg/util"
|
||||||
"github.com/containers/storage"
|
"github.com/containers/storage"
|
||||||
|
"github.com/docker/distribution/registry/api/errcode"
|
||||||
|
"github.com/docker/docker/pkg/jsonmessage"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NormalizeToDockerHub normalizes the specified nameOrID to Docker Hub if the
|
// NormalizeToDockerHub normalizes the specified nameOrID to Docker Hub if the
|
||||||
@ -102,3 +107,100 @@ func GetImage(r *http.Request, name string) (*libimage.Image, error) {
|
|||||||
}
|
}
|
||||||
return image, err
|
return image, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type pullResult struct {
|
||||||
|
images []*libimage.Image
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func CompatPull(ctx context.Context, w http.ResponseWriter, runtime *libpod.Runtime, reference string, pullPolicy config.PullPolicy, pullOptions *libimage.PullOptions) {
|
||||||
|
progress := make(chan types.ProgressProperties)
|
||||||
|
pullOptions.Progress = progress
|
||||||
|
|
||||||
|
pullResChan := make(chan pullResult)
|
||||||
|
go func() {
|
||||||
|
pulledImages, err := runtime.LibimageRuntime().Pull(ctx, reference, pullPolicy, pullOptions)
|
||||||
|
pullResChan <- pullResult{images: pulledImages, err: err}
|
||||||
|
}()
|
||||||
|
|
||||||
|
enc := json.NewEncoder(w)
|
||||||
|
enc.SetEscapeHTML(true)
|
||||||
|
|
||||||
|
flush := func() {
|
||||||
|
if flusher, ok := w.(http.Flusher); ok {
|
||||||
|
flusher.Flush()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
statusWritten := false
|
||||||
|
writeStatusCode := func(code int) {
|
||||||
|
if !statusWritten {
|
||||||
|
w.WriteHeader(code)
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
flush()
|
||||||
|
statusWritten = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loop: // break out of for/select infinite loop
|
||||||
|
for {
|
||||||
|
report := jsonmessage.JSONMessage{}
|
||||||
|
report.Progress = &jsonmessage.JSONProgress{}
|
||||||
|
select {
|
||||||
|
case e := <-progress:
|
||||||
|
writeStatusCode(http.StatusOK)
|
||||||
|
switch e.Event {
|
||||||
|
case types.ProgressEventNewArtifact:
|
||||||
|
report.Status = "Pulling fs layer"
|
||||||
|
case types.ProgressEventRead:
|
||||||
|
report.Status = "Downloading"
|
||||||
|
report.Progress.Current = int64(e.Offset)
|
||||||
|
report.Progress.Total = e.Artifact.Size
|
||||||
|
report.ProgressMessage = report.Progress.String()
|
||||||
|
case types.ProgressEventSkipped:
|
||||||
|
report.Status = "Already exists"
|
||||||
|
case types.ProgressEventDone:
|
||||||
|
report.Status = "Download complete"
|
||||||
|
}
|
||||||
|
report.ID = e.Artifact.Digest.Encoded()[0:12]
|
||||||
|
if err := enc.Encode(report); err != nil {
|
||||||
|
logrus.Warnf("Failed to json encode error %q", err.Error())
|
||||||
|
}
|
||||||
|
flush()
|
||||||
|
case pullRes := <-pullResChan:
|
||||||
|
err := pullRes.err
|
||||||
|
if err != nil {
|
||||||
|
var errcd errcode.ErrorCoder
|
||||||
|
if errors.As(err, &errcd) {
|
||||||
|
writeStatusCode(errcd.ErrorCode().Descriptor().HTTPStatusCode)
|
||||||
|
} else {
|
||||||
|
writeStatusCode(http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
msg := err.Error()
|
||||||
|
report.Error = &jsonmessage.JSONError{
|
||||||
|
Message: msg,
|
||||||
|
}
|
||||||
|
report.ErrorMessage = msg
|
||||||
|
} else {
|
||||||
|
pulledImages := pullRes.images
|
||||||
|
if len(pulledImages) > 0 {
|
||||||
|
img := pulledImages[0].ID()
|
||||||
|
report.Status = "Download complete"
|
||||||
|
report.ID = img[0:12]
|
||||||
|
} else {
|
||||||
|
msg := "internal error: no images pulled"
|
||||||
|
report.Error = &jsonmessage.JSONError{
|
||||||
|
Message: msg,
|
||||||
|
}
|
||||||
|
report.ErrorMessage = msg
|
||||||
|
writeStatusCode(http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := enc.Encode(report); err != nil {
|
||||||
|
logrus.Warnf("Failed to json encode error %q", err.Error())
|
||||||
|
}
|
||||||
|
flush()
|
||||||
|
break loop // break out of for/select infinite loop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1019,6 +1019,11 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error {
|
|||||||
// type: boolean
|
// type: boolean
|
||||||
// default: false
|
// default: false
|
||||||
// - in: query
|
// - in: query
|
||||||
|
// name: compatMode
|
||||||
|
// description: "Return the same JSON payload as the Docker-compat endpoint."
|
||||||
|
// type: boolean
|
||||||
|
// default: false
|
||||||
|
// - in: query
|
||||||
// name: Arch
|
// name: Arch
|
||||||
// description: Pull image for the specified architecture.
|
// description: Pull image for the specified architecture.
|
||||||
// type: string
|
// type: string
|
||||||
|
@ -49,6 +49,7 @@ t GET images/$iid/json 200 \
|
|||||||
.RepoTags[0]=$IMAGE
|
.RepoTags[0]=$IMAGE
|
||||||
|
|
||||||
t POST "images/create?fromImage=alpine" 200 .error~null .status~".*Download complete.*"
|
t POST "images/create?fromImage=alpine" 200 .error~null .status~".*Download complete.*"
|
||||||
|
t POST "libpod/images/pull?reference=alpine&compatMode=true" 200 .error~null .status~".*Download complete.*"
|
||||||
|
|
||||||
t POST "images/create?fromImage=alpine&tag=latest" 200
|
t POST "images/create?fromImage=alpine&tag=latest" 200
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user