github.com/cookieai-jar/moby@v17.12.1-ce-rc2+incompatible/api/server/router/image/image_routes.go (about) 1 package image 2 3 import ( 4 "encoding/base64" 5 "encoding/json" 6 "fmt" 7 "io" 8 "net/http" 9 "os" 10 "strconv" 11 "strings" 12 13 "github.com/docker/docker/api/server/httputils" 14 "github.com/docker/docker/api/types" 15 "github.com/docker/docker/api/types/backend" 16 "github.com/docker/docker/api/types/container" 17 "github.com/docker/docker/api/types/filters" 18 "github.com/docker/docker/api/types/versions" 19 "github.com/docker/docker/pkg/ioutils" 20 "github.com/docker/docker/pkg/streamformatter" 21 "github.com/docker/docker/pkg/system" 22 "github.com/docker/docker/registry" 23 specs "github.com/opencontainers/image-spec/specs-go/v1" 24 "github.com/pkg/errors" 25 "golang.org/x/net/context" 26 ) 27 28 func (s *imageRouter) postCommit(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 29 if err := httputils.ParseForm(r); err != nil { 30 return err 31 } 32 33 if err := httputils.CheckForJSON(r); err != nil { 34 return err 35 } 36 37 cname := r.Form.Get("container") 38 39 pause := httputils.BoolValue(r, "pause") 40 version := httputils.VersionFromContext(ctx) 41 if r.FormValue("pause") == "" && versions.GreaterThanOrEqualTo(version, "1.13") { 42 pause = true 43 } 44 45 c, _, _, err := s.decoder.DecodeConfig(r.Body) 46 if err != nil && err != io.EOF { //Do not fail if body is empty. 47 return err 48 } 49 if c == nil { 50 c = &container.Config{} 51 } 52 53 commitCfg := &backend.ContainerCommitConfig{ 54 ContainerCommitConfig: types.ContainerCommitConfig{ 55 Pause: pause, 56 Repo: r.Form.Get("repo"), 57 Tag: r.Form.Get("tag"), 58 Author: r.Form.Get("author"), 59 Comment: r.Form.Get("comment"), 60 Config: c, 61 MergeConfigs: true, 62 }, 63 Changes: r.Form["changes"], 64 } 65 66 imgID, err := s.backend.Commit(cname, commitCfg) 67 if err != nil { 68 return err 69 } 70 71 return httputils.WriteJSON(w, http.StatusCreated, &types.IDResponse{ID: imgID}) 72 } 73 74 // Creates an image from Pull or from Import 75 func (s *imageRouter) postImagesCreate(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 76 77 if err := httputils.ParseForm(r); err != nil { 78 return err 79 } 80 81 var ( 82 image = r.Form.Get("fromImage") 83 repo = r.Form.Get("repo") 84 tag = r.Form.Get("tag") 85 message = r.Form.Get("message") 86 err error 87 output = ioutils.NewWriteFlusher(w) 88 platform = &specs.Platform{} 89 ) 90 defer output.Close() 91 92 w.Header().Set("Content-Type", "application/json") 93 94 version := httputils.VersionFromContext(ctx) 95 if versions.GreaterThanOrEqualTo(version, "1.32") { 96 // TODO @jhowardmsft. The following environment variable is an interim 97 // measure to allow the daemon to have a default platform if omitted by 98 // the client. This allows LCOW and WCOW to work with a down-level CLI 99 // for a short period of time, as the CLI changes can't be merged 100 // until after the daemon changes have been merged. Once the CLI is 101 // updated, this can be removed. PR for CLI is currently in 102 // https://github.com/docker/cli/pull/474. 103 apiPlatform := r.FormValue("platform") 104 if system.LCOWSupported() && apiPlatform == "" { 105 apiPlatform = os.Getenv("LCOW_API_PLATFORM_IF_OMITTED") 106 } 107 platform = system.ParsePlatform(apiPlatform) 108 if err = system.ValidatePlatform(platform); err != nil { 109 err = fmt.Errorf("invalid platform: %s", err) 110 } 111 } 112 113 if err == nil { 114 if image != "" { //pull 115 metaHeaders := map[string][]string{} 116 for k, v := range r.Header { 117 if strings.HasPrefix(k, "X-Meta-") { 118 metaHeaders[k] = v 119 } 120 } 121 122 authEncoded := r.Header.Get("X-Registry-Auth") 123 authConfig := &types.AuthConfig{} 124 if authEncoded != "" { 125 authJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded)) 126 if err := json.NewDecoder(authJSON).Decode(authConfig); err != nil { 127 // for a pull it is not an error if no auth was given 128 // to increase compatibility with the existing api it is defaulting to be empty 129 authConfig = &types.AuthConfig{} 130 } 131 } 132 err = s.backend.PullImage(ctx, image, tag, platform.OS, metaHeaders, authConfig, output) 133 } else { //import 134 src := r.Form.Get("fromSrc") 135 // 'err' MUST NOT be defined within this block, we need any error 136 // generated from the download to be available to the output 137 // stream processing below 138 err = s.backend.ImportImage(src, repo, platform.OS, tag, message, r.Body, output, r.Form["changes"]) 139 } 140 } 141 if err != nil { 142 if !output.Flushed() { 143 return err 144 } 145 output.Write(streamformatter.FormatError(err)) 146 } 147 148 return nil 149 } 150 151 type validationError struct { 152 cause error 153 } 154 155 func (e validationError) Error() string { 156 return e.cause.Error() 157 } 158 159 func (e validationError) Cause() error { 160 return e.cause 161 } 162 163 func (validationError) InvalidParameter() {} 164 165 func (s *imageRouter) postImagesPush(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 166 metaHeaders := map[string][]string{} 167 for k, v := range r.Header { 168 if strings.HasPrefix(k, "X-Meta-") { 169 metaHeaders[k] = v 170 } 171 } 172 if err := httputils.ParseForm(r); err != nil { 173 return err 174 } 175 authConfig := &types.AuthConfig{} 176 177 authEncoded := r.Header.Get("X-Registry-Auth") 178 if authEncoded != "" { 179 // the new format is to handle the authConfig as a header 180 authJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded)) 181 if err := json.NewDecoder(authJSON).Decode(authConfig); err != nil { 182 // to increase compatibility to existing api it is defaulting to be empty 183 authConfig = &types.AuthConfig{} 184 } 185 } else { 186 // the old format is supported for compatibility if there was no authConfig header 187 if err := json.NewDecoder(r.Body).Decode(authConfig); err != nil { 188 return errors.Wrap(validationError{err}, "Bad parameters and missing X-Registry-Auth") 189 } 190 } 191 192 image := vars["name"] 193 tag := r.Form.Get("tag") 194 195 output := ioutils.NewWriteFlusher(w) 196 defer output.Close() 197 198 w.Header().Set("Content-Type", "application/json") 199 200 if err := s.backend.PushImage(ctx, image, tag, metaHeaders, authConfig, output); err != nil { 201 if !output.Flushed() { 202 return err 203 } 204 output.Write(streamformatter.FormatError(err)) 205 } 206 return nil 207 } 208 209 func (s *imageRouter) getImagesGet(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 210 if err := httputils.ParseForm(r); err != nil { 211 return err 212 } 213 214 w.Header().Set("Content-Type", "application/x-tar") 215 216 output := ioutils.NewWriteFlusher(w) 217 defer output.Close() 218 var names []string 219 if name, ok := vars["name"]; ok { 220 names = []string{name} 221 } else { 222 names = r.Form["names"] 223 } 224 225 if err := s.backend.ExportImage(names, output); err != nil { 226 if !output.Flushed() { 227 return err 228 } 229 output.Write(streamformatter.FormatError(err)) 230 } 231 return nil 232 } 233 234 func (s *imageRouter) postImagesLoad(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 235 if err := httputils.ParseForm(r); err != nil { 236 return err 237 } 238 quiet := httputils.BoolValueOrDefault(r, "quiet", true) 239 240 w.Header().Set("Content-Type", "application/json") 241 242 output := ioutils.NewWriteFlusher(w) 243 defer output.Close() 244 if err := s.backend.LoadImage(r.Body, output, quiet); err != nil { 245 output.Write(streamformatter.FormatError(err)) 246 } 247 return nil 248 } 249 250 type missingImageError struct{} 251 252 func (missingImageError) Error() string { 253 return "image name cannot be blank" 254 } 255 256 func (missingImageError) InvalidParameter() {} 257 258 func (s *imageRouter) deleteImages(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 259 if err := httputils.ParseForm(r); err != nil { 260 return err 261 } 262 263 name := vars["name"] 264 265 if strings.TrimSpace(name) == "" { 266 return missingImageError{} 267 } 268 269 force := httputils.BoolValue(r, "force") 270 prune := !httputils.BoolValue(r, "noprune") 271 272 list, err := s.backend.ImageDelete(name, force, prune) 273 if err != nil { 274 return err 275 } 276 277 return httputils.WriteJSON(w, http.StatusOK, list) 278 } 279 280 func (s *imageRouter) getImagesByName(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 281 imageInspect, err := s.backend.LookupImage(vars["name"]) 282 if err != nil { 283 return err 284 } 285 286 return httputils.WriteJSON(w, http.StatusOK, imageInspect) 287 } 288 289 func (s *imageRouter) getImagesJSON(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 290 if err := httputils.ParseForm(r); err != nil { 291 return err 292 } 293 294 imageFilters, err := filters.FromJSON(r.Form.Get("filters")) 295 if err != nil { 296 return err 297 } 298 299 filterParam := r.Form.Get("filter") 300 // FIXME(vdemeester) This has been deprecated in 1.13, and is target for removal for v17.12 301 if filterParam != "" { 302 imageFilters.Add("reference", filterParam) 303 } 304 305 images, err := s.backend.Images(imageFilters, httputils.BoolValue(r, "all"), false) 306 if err != nil { 307 return err 308 } 309 310 return httputils.WriteJSON(w, http.StatusOK, images) 311 } 312 313 func (s *imageRouter) getImagesHistory(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 314 name := vars["name"] 315 history, err := s.backend.ImageHistory(name) 316 if err != nil { 317 return err 318 } 319 320 return httputils.WriteJSON(w, http.StatusOK, history) 321 } 322 323 func (s *imageRouter) postImagesTag(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 324 if err := httputils.ParseForm(r); err != nil { 325 return err 326 } 327 if err := s.backend.TagImage(vars["name"], r.Form.Get("repo"), r.Form.Get("tag")); err != nil { 328 return err 329 } 330 w.WriteHeader(http.StatusCreated) 331 return nil 332 } 333 334 func (s *imageRouter) getImagesSearch(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 335 if err := httputils.ParseForm(r); err != nil { 336 return err 337 } 338 var ( 339 config *types.AuthConfig 340 authEncoded = r.Header.Get("X-Registry-Auth") 341 headers = map[string][]string{} 342 ) 343 344 if authEncoded != "" { 345 authJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded)) 346 if err := json.NewDecoder(authJSON).Decode(&config); err != nil { 347 // for a search it is not an error if no auth was given 348 // to increase compatibility with the existing api it is defaulting to be empty 349 config = &types.AuthConfig{} 350 } 351 } 352 for k, v := range r.Header { 353 if strings.HasPrefix(k, "X-Meta-") { 354 headers[k] = v 355 } 356 } 357 limit := registry.DefaultSearchLimit 358 if r.Form.Get("limit") != "" { 359 limitValue, err := strconv.Atoi(r.Form.Get("limit")) 360 if err != nil { 361 return err 362 } 363 limit = limitValue 364 } 365 query, err := s.backend.SearchRegistryForImages(ctx, r.Form.Get("filters"), r.Form.Get("term"), limit, config, headers) 366 if err != nil { 367 return err 368 } 369 return httputils.WriteJSON(w, http.StatusOK, query.Results) 370 } 371 372 func (s *imageRouter) postImagesPrune(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 373 if err := httputils.ParseForm(r); err != nil { 374 return err 375 } 376 377 pruneFilters, err := filters.FromJSON(r.Form.Get("filters")) 378 if err != nil { 379 return err 380 } 381 382 pruneReport, err := s.backend.ImagesPrune(ctx, pruneFilters) 383 if err != nil { 384 return err 385 } 386 return httputils.WriteJSON(w, http.StatusOK, pruneReport) 387 }