mirror of
https://github.com/containers/podman.git
synced 2025-05-31 15:42:48 +08:00

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>
147 lines
4.2 KiB
Go
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
|
|
}
|
|
}
|
|
}
|