From 76fec12274ebae33fee7180321545a79297bb823 Mon Sep 17 00:00:00 2001 From: Valentin Rothberg Date: Wed, 11 Oct 2023 10:14:14 +0200 Subject: [PATCH] api: break out compat image pull Break out the code for pulling images via the compat API. The goal is to make this code shareable between the compat and libpod API to allow for a "compat mode" in the libpod pull endpoint. [NO NEW TESTS NEEDED] as it should not change behavior. Signed-off-by: Valentin Rothberg --- pkg/api/handlers/compat/images.go | 102 +----------------------------- pkg/api/handlers/utils/images.go | 102 ++++++++++++++++++++++++++++++ 2 files changed, 103 insertions(+), 101 deletions(-) diff --git a/pkg/api/handlers/compat/images.go b/pkg/api/handlers/compat/images.go index 04e59d58ff..0dc0d2bdad 100644 --- a/pkg/api/handlers/compat/images.go +++ b/pkg/api/handlers/compat/images.go @@ -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) { diff --git a/pkg/api/handlers/utils/images.go b/pkg/api/handlers/utils/images.go index 7831718cdb..58cda0e01f 100644 --- a/pkg/api/handlers/utils/images.go +++ b/pkg/api/handlers/utils/images.go @@ -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 + } + } +}