Skip to content

Commit

Permalink
Merge pull request containers#20328 from vrothberg/RUN-1936
Browse files Browse the repository at this point in the history
api: add `compatMode` parameter to libpod's pull endpoint
  • Loading branch information
openshift-ci[bot] authored Oct 11, 2023
2 parents 3dcd6af + 8b46e85 commit d437ca8
Show file tree
Hide file tree
Showing 5 changed files with 126 additions and 106 deletions.
102 changes: 1 addition & 101 deletions pkg/api/handlers/compat/images.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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"
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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) {
Expand Down
22 changes: 17 additions & 5 deletions pkg/api/handlers/libpod/images_pull.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down
102 changes: 102 additions & 0 deletions pkg/api/handlers/utils/images.go
Original file line number Diff line number Diff line change
@@ -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"
Expand All @@ -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
Expand Down Expand Up @@ -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
}
}
}
5 changes: 5 additions & 0 deletions pkg/api/server/register_images.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions test/apiv2/10-images.at
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down

0 comments on commit d437ca8

Please sign in to comment.