Files
podman/pkg/api/handlers/libpod/images_pull.go
Miloslav Trmač 2aeb690d37 Don't return a header name from auth.GetCredentials
Almost every caller is using it only to wrap an error
in exactly the same way, so move that error context into GetCredentials
and simplify the users.

(The one other caller, build, was even wrapping the error incorrectly
talking about query parameters; so let it use the same text as the others.)

Signed-off-by: Miloslav Trmač <mitr@redhat.com>
2021-12-10 18:16:18 +01:00

147 lines
4.2 KiB
Go

package libpod
import (
"context"
"encoding/json"
"net/http"
"github.com/containers/common/libimage"
"github.com/containers/common/pkg/config"
"github.com/containers/image/v5/types"
"github.com/containers/podman/v3/libpod"
"github.com/containers/podman/v3/pkg/api/handlers/utils"
api "github.com/containers/podman/v3/pkg/api/types"
"github.com/containers/podman/v3/pkg/auth"
"github.com/containers/podman/v3/pkg/channel"
"github.com/containers/podman/v3/pkg/domain/entities"
"github.com/gorilla/schema"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
// ImagesPull is the v2 libpod endpoint for pulling images. Note that the
// mandatory `reference` must be a reference to a registry (i.e., of docker
// transport or be normalized to one). Other transports are rejected as they
// do not make sense in a remote context.
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"`
PullPolicy string `schema:"policy"`
Quiet bool `schema:"quiet"`
}{
TLSVerify: true,
PullPolicy: "always",
}
if err := decoder.Decode(&query, r.URL.Query()); err != nil {
utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String()))
return
}
if len(query.Reference) == 0 {
utils.InternalServerError(w, errors.New("reference parameter cannot be empty"))
return
}
// Make sure that the reference has no transport or the docker one.
if err := utils.IsRegistryReference(query.Reference); err != nil {
utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, err)
return
}
pullOptions := &libimage.PullOptions{}
pullOptions.AllTags = query.AllTags
pullOptions.Architecture = query.Arch
pullOptions.OS = query.OS
pullOptions.Variant = query.Variant
if _, found := r.URL.Query()["tlsVerify"]; found {
pullOptions.InsecureSkipTLSVerify = types.NewOptionalBool(!query.TLSVerify)
}
// Do the auth dance.
authConf, authfile, err := auth.GetCredentials(r)
if err != nil {
utils.Error(w, "failed to retrieve repository credentials", http.StatusBadRequest, err)
return
}
defer auth.RemoveAuthfile(authfile)
pullOptions.AuthFilePath = authfile
if authConf != nil {
pullOptions.Username = authConf.Username
pullOptions.Password = authConf.Password
pullOptions.IdentityToken = authConf.IdentityToken
}
writer := channel.NewWriter(make(chan []byte))
defer writer.Close()
pullOptions.Writer = writer
pullPolicy, err := config.ParsePullPolicy(query.PullPolicy)
if err != nil {
utils.Error(w, "failed to parse pull policy", http.StatusBadRequest, err)
return
}
var pulledImages []*libimage.Image
var pullError error
runCtx, cancel := context.WithCancel(r.Context())
go func() {
defer cancel()
pulledImages, pullError = runtime.LibimageRuntime().Pull(runCtx, query.Reference, pullPolicy, pullOptions)
}()
flush := func() {
if flusher, ok := w.(http.Flusher); ok {
flusher.Flush()
}
}
w.WriteHeader(http.StatusOK)
w.Header().Set("Content-Type", "application/json")
flush()
enc := json.NewEncoder(w)
enc.SetEscapeHTML(true)
for {
var report entities.ImagePullReport
select {
case s := <-writer.Chan():
report.Stream = string(s)
if !query.Quiet {
if err := enc.Encode(report); err != nil {
logrus.Warnf("Failed to encode json: %v", err)
}
}
flush()
case <-runCtx.Done():
for _, image := range pulledImages {
report.Images = append(report.Images, image.ID())
// Pull last ID from list and publish in 'id' stanza. This maintains previous API contract
report.ID = image.ID()
}
if pullError != nil {
report.Error = pullError.Error()
}
if err := enc.Encode(report); err != nil {
logrus.Warnf("Failed to encode json: %v", err)
}
flush()
return
case <-r.Context().Done():
// Client has closed connection
return
}
}
}