mirror of
https://github.com/containers/podman.git
synced 2025-06-22 09:58:10 +08:00
Merge pull request #20328 from vrothberg/RUN-1936
api: add `compatMode` parameter to libpod's pull endpoint
This commit is contained in:
@ -15,7 +15,6 @@ import (
|
||||
"github.com/containers/common/pkg/config"
|
||||
"github.com/containers/common/pkg/filters"
|
||||
"github.com/containers/image/v5/manifest"
|
||||
"github.com/containers/image/v5/types"
|
||||
"github.com/containers/podman/v4/libpod"
|
||||
"github.com/containers/podman/v4/pkg/api/handlers"
|
||||
"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/util"
|
||||
"github.com/containers/storage"
|
||||
"github.com/docker/distribution/registry/api/errcode"
|
||||
docker "github.com/docker/docker/api/types"
|
||||
dockerContainer "github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/pkg/jsonmessage"
|
||||
"github.com/docker/go-connections/nat"
|
||||
"github.com/opencontainers/go-digest"
|
||||
"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) {
|
||||
// 200 no error
|
||||
// 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)
|
||||
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
|
||||
}
|
||||
}
|
||||
utils.CompatPull(r.Context(), w, runtime, possiblyNormalizedName, config.PullPolicyAlways, pullOptions)
|
||||
}
|
||||
|
||||
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)
|
||||
decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder)
|
||||
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"`
|
||||
CompatMode bool `schema:"compatMode"`
|
||||
PullPolicy string `schema:"policy"`
|
||||
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,
|
||||
PullPolicy: "always",
|
||||
@ -46,6 +48,11 @@ func ImagesPull(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
if query.Quiet && query.CompatMode {
|
||||
utils.InternalServerError(w, errors.New("'quiet' and 'compatMode' cannot be used simultaneously"))
|
||||
return
|
||||
}
|
||||
|
||||
if len(query.Reference) == 0 {
|
||||
utils.InternalServerError(w, errors.New("reference parameter cannot be empty"))
|
||||
return
|
||||
@ -104,6 +111,11 @@ func ImagesPull(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
if query.CompatMode {
|
||||
utils.CompatPull(r.Context(), w, runtime, query.Reference, pullPolicy, pullOptions)
|
||||
return
|
||||
}
|
||||
|
||||
writer := channel.NewWriter(make(chan []byte))
|
||||
defer writer.Close()
|
||||
pullOptions.Writer = writer
|
||||
|
@ -1,12 +1,14 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/containers/common/libimage"
|
||||
"github.com/containers/common/pkg/config"
|
||||
"github.com/containers/image/v5/docker"
|
||||
"github.com/containers/image/v5/docker/reference"
|
||||
storageTransport "github.com/containers/image/v5/storage"
|
||||
@ -16,6 +18,9 @@ import (
|
||||
api "github.com/containers/podman/v4/pkg/api/types"
|
||||
"github.com/containers/podman/v4/pkg/util"
|
||||
"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
|
||||
@ -102,3 +107,100 @@ func GetImage(r *http.Request, name string) (*libimage.Image, error) {
|
||||
}
|
||||
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
|
||||
// default: false
|
||||
// - in: query
|
||||
// name: compatMode
|
||||
// description: "Return the same JSON payload as the Docker-compat endpoint."
|
||||
// type: boolean
|
||||
// default: false
|
||||
// - in: query
|
||||
// name: Arch
|
||||
// description: Pull image for the specified architecture.
|
||||
// type: string
|
||||
|
@ -49,6 +49,7 @@ t GET images/$iid/json 200 \
|
||||
.RepoTags[0]=$IMAGE
|
||||
|
||||
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
|
||||
|
||||
|
Reference in New Issue
Block a user