github.com/containers/podman/v2@v2.2.2-0.20210501105131-c1e07d070c4c/pkg/api/handlers/libpod/images_pull.go (about) 1 package libpod 2 3 import ( 4 "context" 5 "encoding/json" 6 "fmt" 7 "net/http" 8 "strings" 9 10 "github.com/containers/image/v5/docker" 11 "github.com/containers/image/v5/docker/reference" 12 "github.com/containers/image/v5/types" 13 "github.com/containers/podman/v2/libpod" 14 "github.com/containers/podman/v2/libpod/image" 15 "github.com/containers/podman/v2/pkg/api/handlers/utils" 16 "github.com/containers/podman/v2/pkg/auth" 17 "github.com/containers/podman/v2/pkg/channel" 18 "github.com/containers/podman/v2/pkg/domain/entities" 19 "github.com/containers/podman/v2/pkg/util" 20 "github.com/gorilla/schema" 21 "github.com/pkg/errors" 22 "github.com/sirupsen/logrus" 23 ) 24 25 // ImagesPull is the v2 libpod endpoint for pulling images. Note that the 26 // mandatory `reference` must be a reference to a registry (i.e., of docker 27 // transport or be normalized to one). Other transports are rejected as they 28 // do not make sense in a remote context. 29 func ImagesPull(w http.ResponseWriter, r *http.Request) { 30 runtime := r.Context().Value("runtime").(*libpod.Runtime) 31 decoder := r.Context().Value("decoder").(*schema.Decoder) 32 query := struct { 33 Reference string `schema:"reference"` 34 OverrideOS string `schema:"overrideOS"` 35 OverrideArch string `schema:"overrideArch"` 36 OverrideVariant string `schema:"overrideVariant"` 37 TLSVerify bool `schema:"tlsVerify"` 38 AllTags bool `schema:"allTags"` 39 }{ 40 TLSVerify: true, 41 } 42 43 if err := decoder.Decode(&query, r.URL.Query()); err != nil { 44 utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, 45 errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) 46 return 47 } 48 49 if len(query.Reference) == 0 { 50 utils.InternalServerError(w, errors.New("reference parameter cannot be empty")) 51 return 52 } 53 54 imageRef, err := utils.ParseDockerReference(query.Reference) 55 if err != nil { 56 utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, err) 57 return 58 } 59 60 // Trim the docker-transport prefix. 61 rawImage := strings.TrimPrefix(query.Reference, fmt.Sprintf("%s://", docker.Transport.Name())) 62 63 // all-tags doesn't work with a tagged reference, so let's check early 64 namedRef, err := reference.Parse(rawImage) 65 if err != nil { 66 utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, 67 errors.Wrapf(err, "error parsing reference %q", rawImage)) 68 return 69 } 70 if _, isTagged := namedRef.(reference.Tagged); isTagged && query.AllTags { 71 utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, 72 errors.Errorf("reference %q must not have a tag for all-tags", rawImage)) 73 return 74 } 75 76 authConf, authfile, key, err := auth.GetCredentials(r) 77 if err != nil { 78 utils.Error(w, "failed to retrieve repository credentials", http.StatusBadRequest, errors.Wrapf(err, "failed to parse %q header for %s", key, r.URL.String())) 79 return 80 } 81 defer auth.RemoveAuthfile(authfile) 82 83 // Setup the registry options 84 dockerRegistryOptions := image.DockerRegistryOptions{ 85 DockerRegistryCreds: authConf, 86 OSChoice: query.OverrideOS, 87 ArchitectureChoice: query.OverrideArch, 88 VariantChoice: query.OverrideVariant, 89 } 90 if _, found := r.URL.Query()["tlsVerify"]; found { 91 dockerRegistryOptions.DockerInsecureSkipTLSVerify = types.NewOptionalBool(!query.TLSVerify) 92 } 93 94 sys := runtime.SystemContext() 95 if sys == nil { 96 sys = image.GetSystemContext("", authfile, false) 97 } 98 dockerRegistryOptions.DockerCertPath = sys.DockerCertPath 99 sys.DockerAuthConfig = authConf 100 101 // Prepare the images we want to pull 102 imagesToPull := []string{} 103 imageName := namedRef.String() 104 105 if !query.AllTags { 106 imagesToPull = append(imagesToPull, imageName) 107 } else { 108 tags, err := docker.GetRepositoryTags(context.Background(), sys, imageRef) 109 if err != nil { 110 utils.InternalServerError(w, errors.Wrap(err, "error getting repository tags")) 111 return 112 } 113 for _, tag := range tags { 114 imagesToPull = append(imagesToPull, fmt.Sprintf("%s:%s", imageName, tag)) 115 } 116 } 117 118 writer := channel.NewWriter(make(chan []byte, 1)) 119 defer writer.Close() 120 121 stderr := channel.NewWriter(make(chan []byte, 1)) 122 defer stderr.Close() 123 124 images := make([]string, 0, len(imagesToPull)) 125 runCtx, cancel := context.WithCancel(context.Background()) 126 go func(imgs []string) { 127 defer cancel() 128 // Finally pull the images 129 for _, img := range imgs { 130 newImage, err := runtime.ImageRuntime().New( 131 runCtx, 132 img, 133 "", 134 authfile, 135 writer, 136 &dockerRegistryOptions, 137 image.SigningOptions{}, 138 nil, 139 util.PullImageAlways) 140 if err != nil { 141 stderr.Write([]byte(err.Error() + "\n")) 142 } else { 143 images = append(images, newImage.ID()) 144 } 145 } 146 }(imagesToPull) 147 148 flush := func() { 149 if flusher, ok := w.(http.Flusher); ok { 150 flusher.Flush() 151 } 152 } 153 154 w.WriteHeader(http.StatusOK) 155 w.Header().Add("Content-Type", "application/json") 156 flush() 157 158 enc := json.NewEncoder(w) 159 enc.SetEscapeHTML(true) 160 var failed bool 161 loop: // break out of for/select infinite loop 162 for { 163 var report entities.ImagePullReport 164 select { 165 case e := <-writer.Chan(): 166 report.Stream = string(e) 167 if err := enc.Encode(report); err != nil { 168 stderr.Write([]byte(err.Error())) 169 } 170 flush() 171 case e := <-stderr.Chan(): 172 failed = true 173 report.Error = string(e) 174 if err := enc.Encode(report); err != nil { 175 logrus.Warnf("Failed to json encode error %q", err.Error()) 176 } 177 flush() 178 case <-runCtx.Done(): 179 if !failed { 180 // Send all image id's pulled in 'images' stanza 181 report.Images = images 182 if err := enc.Encode(report); err != nil { 183 logrus.Warnf("Failed to json encode error %q", err.Error()) 184 } 185 186 report.Images = nil 187 // Pull last ID from list and publish in 'id' stanza. This maintains previous API contract 188 report.ID = images[len(images)-1] 189 if err := enc.Encode(report); err != nil { 190 logrus.Warnf("Failed to json encode error %q", err.Error()) 191 } 192 193 flush() 194 } 195 break loop // break out of for/select infinite loop 196 case <-r.Context().Done(): 197 // Client has closed connection 198 break loop // break out of for/select infinite loop 199 } 200 } 201 }