github.com/hanks177/podman/v4@v4.1.3-0.20220613032544-16d90015bc83/pkg/api/handlers/compat/images_push.go (about) 1 package compat 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "io/ioutil" 7 "net/http" 8 "strings" 9 10 "github.com/containers/image/v5/types" 11 "github.com/hanks177/podman/v4/libpod" 12 "github.com/hanks177/podman/v4/pkg/api/handlers/utils" 13 api "github.com/hanks177/podman/v4/pkg/api/types" 14 "github.com/hanks177/podman/v4/pkg/auth" 15 "github.com/hanks177/podman/v4/pkg/domain/entities" 16 "github.com/hanks177/podman/v4/pkg/domain/infra/abi" 17 "github.com/containers/storage" 18 "github.com/docker/docker/pkg/jsonmessage" 19 "github.com/gorilla/schema" 20 "github.com/pkg/errors" 21 "github.com/sirupsen/logrus" 22 ) 23 24 // PushImage is the handler for the compat http endpoint for pushing images. 25 func PushImage(w http.ResponseWriter, r *http.Request) { 26 decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder) 27 runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime) 28 29 digestFile, err := ioutil.TempFile("", "digest.txt") 30 if err != nil { 31 utils.Error(w, http.StatusInternalServerError, errors.Wrap(err, "unable to create tempfile")) 32 return 33 } 34 defer digestFile.Close() 35 36 // Now use the ABI implementation to prevent us from having duplicate 37 // code. 38 imageEngine := abi.ImageEngine{Libpod: runtime} 39 40 query := struct { 41 All bool `schema:"all"` 42 Compress bool `schema:"compress"` 43 Destination string `schema:"destination"` 44 Format string `schema:"format"` 45 TLSVerify bool `schema:"tlsVerify"` 46 Tag string `schema:"tag"` 47 }{ 48 // This is where you can override the golang default value for one of fields 49 TLSVerify: true, 50 } 51 52 if err := decoder.Decode(&query, r.URL.Query()); err != nil { 53 utils.Error(w, http.StatusBadRequest, errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) 54 return 55 } 56 57 // Note that Docker's docs state "Image name or ID" to be in the path 58 // parameter but it really must be a name as Docker does not allow for 59 // pushing an image by ID. 60 imageName := strings.TrimSuffix(utils.GetName(r), "/push") // GetName returns the entire path 61 if query.Tag != "" { 62 imageName += ":" + query.Tag 63 } 64 65 if _, err := utils.ParseStorageReference(imageName); err != nil { 66 utils.Error(w, http.StatusBadRequest, errors.Wrapf(err, "image source %q is not a containers-storage-transport reference", imageName)) 67 return 68 } 69 70 possiblyNormalizedName, err := utils.NormalizeToDockerHub(r, imageName) 71 if err != nil { 72 utils.Error(w, http.StatusInternalServerError, errors.Wrap(err, "error normalizing image")) 73 return 74 } 75 imageName = possiblyNormalizedName 76 localImage, _, err := runtime.LibimageRuntime().LookupImage(possiblyNormalizedName, nil) 77 if err != nil { 78 utils.ImageNotFound(w, imageName, errors.Wrapf(err, "failed to find image %s", imageName)) 79 return 80 } 81 rawManifest, _, err := localImage.Manifest(r.Context()) 82 if err != nil { 83 utils.Error(w, http.StatusBadRequest, err) 84 return 85 } 86 87 authconf, authfile, err := auth.GetCredentials(r) 88 if err != nil { 89 utils.Error(w, http.StatusBadRequest, err) 90 return 91 } 92 defer auth.RemoveAuthfile(authfile) 93 var username, password string 94 if authconf != nil { 95 username = authconf.Username 96 password = authconf.Password 97 } 98 options := entities.ImagePushOptions{ 99 All: query.All, 100 Authfile: authfile, 101 Compress: query.Compress, 102 Format: query.Format, 103 Password: password, 104 Username: username, 105 DigestFile: digestFile.Name(), 106 Quiet: true, 107 Progress: make(chan types.ProgressProperties), 108 } 109 if _, found := r.URL.Query()["tlsVerify"]; found { 110 options.SkipTLSVerify = types.NewOptionalBool(!query.TLSVerify) 111 } 112 113 var destination string 114 if _, found := r.URL.Query()["destination"]; found { 115 destination = query.Destination 116 } else { 117 destination = imageName 118 } 119 120 flush := func() {} 121 if flusher, ok := w.(http.Flusher); ok { 122 flush = flusher.Flush 123 } 124 125 w.WriteHeader(http.StatusOK) 126 w.Header().Set("Content-Type", "application/json") 127 flush() 128 129 var report jsonmessage.JSONMessage 130 enc := json.NewEncoder(w) 131 enc.SetEscapeHTML(true) 132 133 report.Status = fmt.Sprintf("The push refers to repository [%s]", imageName) 134 if err := enc.Encode(report); err != nil { 135 logrus.Warnf("Failed to json encode error %q", err.Error()) 136 } 137 flush() 138 139 pushErrChan := make(chan error) 140 go func() { 141 pushErrChan <- imageEngine.Push(r.Context(), imageName, destination, options) 142 }() 143 144 loop: // break out of for/select infinite loop 145 for { 146 report = jsonmessage.JSONMessage{} 147 148 select { 149 case e := <-options.Progress: 150 switch e.Event { 151 case types.ProgressEventNewArtifact: 152 report.Status = "Preparing" 153 case types.ProgressEventRead: 154 report.Status = "Pushing" 155 report.Progress = &jsonmessage.JSONProgress{ 156 Current: int64(e.Offset), 157 Total: e.Artifact.Size, 158 } 159 case types.ProgressEventSkipped: 160 report.Status = "Layer already exists" 161 case types.ProgressEventDone: 162 report.Status = "Pushed" 163 } 164 report.ID = e.Artifact.Digest.Encoded()[0:12] 165 if err := enc.Encode(report); err != nil { 166 logrus.Warnf("Failed to json encode error %q", err.Error()) 167 } 168 flush() 169 case err := <-pushErrChan: 170 if err != nil { 171 var msg string 172 if errors.Is(err, storage.ErrImageUnknown) { 173 msg = "An image does not exist locally with the tag: " + imageName 174 } else { 175 msg = err.Error() 176 } 177 report.Error = &jsonmessage.JSONError{ 178 Message: msg, 179 } 180 report.ErrorMessage = msg 181 if err := enc.Encode(report); err != nil { 182 logrus.Warnf("Failed to json encode error %q", err.Error()) 183 } 184 flush() 185 break loop 186 } 187 188 digestBytes, err := ioutil.ReadAll(digestFile) 189 if err != nil { 190 report.Error = &jsonmessage.JSONError{ 191 Message: err.Error(), 192 } 193 report.ErrorMessage = err.Error() 194 if err := enc.Encode(report); err != nil { 195 logrus.Warnf("Failed to json encode error %q", err.Error()) 196 } 197 flush() 198 break loop 199 } 200 tag := query.Tag 201 if tag == "" { 202 tag = "latest" 203 } 204 report.Status = fmt.Sprintf("%s: digest: %s size: %d", tag, string(digestBytes), len(rawManifest)) 205 if err := enc.Encode(report); err != nil { 206 logrus.Warnf("Failed to json encode error %q", err.Error()) 207 } 208 209 flush() 210 break loop // break out of for/select infinite loop 211 } 212 } 213 }