1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189
|
//go:build !remote
package libpod
import (
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"time"
"github.com/containers/common/libimage"
"github.com/containers/common/pkg/config"
"github.com/containers/image/v5/types"
"github.com/containers/podman/v5/libpod"
"github.com/containers/podman/v5/pkg/api/handlers/utils"
api "github.com/containers/podman/v5/pkg/api/types"
"github.com/containers/podman/v5/pkg/auth"
"github.com/containers/podman/v5/pkg/channel"
"github.com/containers/podman/v5/pkg/domain/entities"
"github.com/gorilla/schema"
"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 {
AllTags bool `schema:"allTags"`
CompatMode bool `schema:"compatMode"`
PullPolicy string `schema:"policy"`
Quiet bool `schema:"quiet"`
Reference string `schema:"reference"`
Retry uint `schema:"retry"`
RetryDelay string `schema:"retrydelay"`
TLSVerify bool `schema:"tlsVerify"`
// Platform fields below:
Arch string `schema:"Arch"`
OS string `schema:"OS"`
Variant string `schema:"Variant"`
}{
TLSVerify: true,
PullPolicy: "always",
}
if err := decoder.Decode(&query, r.URL.Query()); err != nil {
utils.Error(w, http.StatusBadRequest, fmt.Errorf("failed to parse parameters for %s: %w", r.URL.String(), err))
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
}
// Make sure that the reference has no transport or the docker one.
if err := utils.IsRegistryReference(query.Reference); err != nil {
utils.Error(w, 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, 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
}
pullPolicy, err := config.ParsePullPolicy(query.PullPolicy)
if err != nil {
utils.Error(w, http.StatusBadRequest, err)
return
}
if _, found := r.URL.Query()["retry"]; found {
pullOptions.MaxRetries = &query.Retry
}
if _, found := r.URL.Query()["retrydelay"]; found {
duration, err := time.ParseDuration(query.RetryDelay)
if err != nil {
utils.Error(w, http.StatusBadRequest, err)
return
}
pullOptions.RetryDelay = &duration
}
// Let's keep thing simple when running in quiet mode and pull directly.
if query.Quiet {
images, err := runtime.LibimageRuntime().Pull(r.Context(), query.Reference, pullPolicy, pullOptions)
var report entities.ImagePullReport
if err != nil {
report.Error = err.Error()
}
for _, image := range images {
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()
}
utils.WriteResponse(w, http.StatusOK, report)
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
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 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
}
}
}
|