1// Copyright 2015 go-dockerclient authors. All rights reserved. 2// Use of this source code is governed by a BSD-style 3// license that can be found in the LICENSE file. 4 5package docker 6 7import ( 8 "bytes" 9 "encoding/base64" 10 "encoding/json" 11 "errors" 12 "fmt" 13 "io" 14 "net/http" 15 "net/url" 16 "os" 17 "time" 18) 19 20// APIImages represent an image returned in the ListImages call. 21type APIImages struct { 22 ID string `json:"Id" yaml:"Id"` 23 RepoTags []string `json:"RepoTags,omitempty" yaml:"RepoTags,omitempty"` 24 Created int64 `json:"Created,omitempty" yaml:"Created,omitempty"` 25 Size int64 `json:"Size,omitempty" yaml:"Size,omitempty"` 26 VirtualSize int64 `json:"VirtualSize,omitempty" yaml:"VirtualSize,omitempty"` 27 ParentID string `json:"ParentId,omitempty" yaml:"ParentId,omitempty"` 28 RepoDigests []string `json:"RepoDigests,omitempty" yaml:"RepoDigests,omitempty"` 29 Labels map[string]string `json:"Labels,omitempty" yaml:"Labels,omitempty"` 30} 31 32// Image is the type representing a docker image and its various properties 33type Image struct { 34 ID string `json:"Id" yaml:"Id"` 35 Parent string `json:"Parent,omitempty" yaml:"Parent,omitempty"` 36 Comment string `json:"Comment,omitempty" yaml:"Comment,omitempty"` 37 Created time.Time `json:"Created,omitempty" yaml:"Created,omitempty"` 38 Container string `json:"Container,omitempty" yaml:"Container,omitempty"` 39 ContainerConfig Config `json:"ContainerConfig,omitempty" yaml:"ContainerConfig,omitempty"` 40 DockerVersion string `json:"DockerVersion,omitempty" yaml:"DockerVersion,omitempty"` 41 Author string `json:"Author,omitempty" yaml:"Author,omitempty"` 42 Config *Config `json:"Config,omitempty" yaml:"Config,omitempty"` 43 Architecture string `json:"Architecture,omitempty" yaml:"Architecture,omitempty"` 44 Size int64 `json:"Size,omitempty" yaml:"Size,omitempty"` 45 VirtualSize int64 `json:"VirtualSize,omitempty" yaml:"VirtualSize,omitempty"` 46} 47 48// ImagePre012 serves the same purpose as the Image type except that it is for 49// earlier versions of the Docker API (pre-012 to be specific) 50type ImagePre012 struct { 51 ID string `json:"id"` 52 Parent string `json:"parent,omitempty"` 53 Comment string `json:"comment,omitempty"` 54 Created time.Time `json:"created"` 55 Container string `json:"container,omitempty"` 56 ContainerConfig Config `json:"container_config,omitempty"` 57 DockerVersion string `json:"docker_version,omitempty"` 58 Author string `json:"author,omitempty"` 59 Config *Config `json:"config,omitempty"` 60 Architecture string `json:"architecture,omitempty"` 61 Size int64 `json:"size,omitempty"` 62} 63 64var ( 65 // ErrNoSuchImage is the error returned when the image does not exist. 66 ErrNoSuchImage = errors.New("no such image") 67 68 // ErrMissingRepo is the error returned when the remote repository is 69 // missing. 70 ErrMissingRepo = errors.New("missing remote repository e.g. 'github.com/user/repo'") 71 72 // ErrMissingOutputStream is the error returned when no output stream 73 // is provided to some calls, like BuildImage. 74 ErrMissingOutputStream = errors.New("missing output stream") 75 76 // ErrMultipleContexts is the error returned when both a ContextDir and 77 // InputStream are provided in BuildImageOptions 78 ErrMultipleContexts = errors.New("image build may not be provided BOTH context dir and input stream") 79 80 // ErrMustSpecifyNames is the error rreturned when the Names field on 81 // ExportImagesOptions is nil or empty 82 ErrMustSpecifyNames = errors.New("must specify at least one name to export") 83) 84 85// ListImagesOptions specify parameters to the ListImages function. 86// 87// See https://goo.gl/xBe1u3 for more details. 88type ListImagesOptions struct { 89 All bool 90 Filters map[string][]string 91 Digests bool 92} 93 94// ListImages returns the list of available images in the server. 95// 96// See https://goo.gl/xBe1u3 for more details. 97func (c *Client) ListImages(opts ListImagesOptions) ([]APIImages, error) { 98 path := "/images/json?" + queryString(opts) 99 resp, err := c.do("GET", path, doOptions{}) 100 if err != nil { 101 return nil, err 102 } 103 defer resp.Body.Close() 104 var images []APIImages 105 if err := json.NewDecoder(resp.Body).Decode(&images); err != nil { 106 return nil, err 107 } 108 return images, nil 109} 110 111// ImageHistory represent a layer in an image's history returned by the 112// ImageHistory call. 113type ImageHistory struct { 114 ID string `json:"Id" yaml:"Id"` 115 Tags []string `json:"Tags,omitempty" yaml:"Tags,omitempty"` 116 Created int64 `json:"Created,omitempty" yaml:"Created,omitempty"` 117 CreatedBy string `json:"CreatedBy,omitempty" yaml:"CreatedBy,omitempty"` 118 Size int64 `json:"Size,omitempty" yaml:"Size,omitempty"` 119} 120 121// ImageHistory returns the history of the image by its name or ID. 122// 123// See https://goo.gl/8bnTId for more details. 124func (c *Client) ImageHistory(name string) ([]ImageHistory, error) { 125 resp, err := c.do("GET", "/images/"+name+"/history", doOptions{}) 126 if err != nil { 127 if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound { 128 return nil, ErrNoSuchImage 129 } 130 return nil, err 131 } 132 defer resp.Body.Close() 133 var history []ImageHistory 134 if err := json.NewDecoder(resp.Body).Decode(&history); err != nil { 135 return nil, err 136 } 137 return history, nil 138} 139 140// RemoveImage removes an image by its name or ID. 141// 142// See https://goo.gl/V3ZWnK for more details. 143func (c *Client) RemoveImage(name string) error { 144 resp, err := c.do("DELETE", "/images/"+name, doOptions{}) 145 if err != nil { 146 if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound { 147 return ErrNoSuchImage 148 } 149 return err 150 } 151 resp.Body.Close() 152 return nil 153} 154 155// RemoveImageOptions present the set of options available for removing an image 156// from a registry. 157// 158// See https://goo.gl/V3ZWnK for more details. 159type RemoveImageOptions struct { 160 Force bool `qs:"force"` 161 NoPrune bool `qs:"noprune"` 162} 163 164// RemoveImageExtended removes an image by its name or ID. 165// Extra params can be passed, see RemoveImageOptions 166// 167// See https://goo.gl/V3ZWnK for more details. 168func (c *Client) RemoveImageExtended(name string, opts RemoveImageOptions) error { 169 uri := fmt.Sprintf("/images/%s?%s", name, queryString(&opts)) 170 resp, err := c.do("DELETE", uri, doOptions{}) 171 if err != nil { 172 if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound { 173 return ErrNoSuchImage 174 } 175 return err 176 } 177 resp.Body.Close() 178 return nil 179} 180 181// InspectImage returns an image by its name or ID. 182// 183// See https://goo.gl/jHPcg6 for more details. 184func (c *Client) InspectImage(name string) (*Image, error) { 185 resp, err := c.do("GET", "/images/"+name+"/json", doOptions{}) 186 if err != nil { 187 if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound { 188 return nil, ErrNoSuchImage 189 } 190 return nil, err 191 } 192 defer resp.Body.Close() 193 194 var image Image 195 196 // if the caller elected to skip checking the server's version, assume it's the latest 197 if c.SkipServerVersionCheck || c.expectedAPIVersion.GreaterThanOrEqualTo(apiVersion112) { 198 if err := json.NewDecoder(resp.Body).Decode(&image); err != nil { 199 return nil, err 200 } 201 } else { 202 var imagePre012 ImagePre012 203 if err := json.NewDecoder(resp.Body).Decode(&imagePre012); err != nil { 204 return nil, err 205 } 206 207 image.ID = imagePre012.ID 208 image.Parent = imagePre012.Parent 209 image.Comment = imagePre012.Comment 210 image.Created = imagePre012.Created 211 image.Container = imagePre012.Container 212 image.ContainerConfig = imagePre012.ContainerConfig 213 image.DockerVersion = imagePre012.DockerVersion 214 image.Author = imagePre012.Author 215 image.Config = imagePre012.Config 216 image.Architecture = imagePre012.Architecture 217 image.Size = imagePre012.Size 218 } 219 220 return &image, nil 221} 222 223// PushImageOptions represents options to use in the PushImage method. 224// 225// See https://goo.gl/zPtZaT for more details. 226type PushImageOptions struct { 227 // Name of the image 228 Name string 229 230 // Tag of the image 231 Tag string 232 233 // Registry server to push the image 234 Registry string 235 236 OutputStream io.Writer `qs:"-"` 237 RawJSONStream bool `qs:"-"` 238} 239 240// PushImage pushes an image to a remote registry, logging progress to w. 241// 242// An empty instance of AuthConfiguration may be used for unauthenticated 243// pushes. 244// 245// See https://goo.gl/zPtZaT for more details. 246func (c *Client) PushImage(opts PushImageOptions, auth AuthConfiguration) error { 247 if opts.Name == "" { 248 return ErrNoSuchImage 249 } 250 headers, err := headersWithAuth(auth) 251 if err != nil { 252 return err 253 } 254 name := opts.Name 255 opts.Name = "" 256 path := "/images/" + name + "/push?" + queryString(&opts) 257 return c.stream("POST", path, streamOptions{ 258 setRawTerminal: true, 259 rawJSONStream: opts.RawJSONStream, 260 headers: headers, 261 stdout: opts.OutputStream, 262 }) 263} 264 265// PullImageOptions present the set of options available for pulling an image 266// from a registry. 267// 268// See https://goo.gl/iJkZjD for more details. 269type PullImageOptions struct { 270 Repository string `qs:"fromImage"` 271 Registry string 272 Tag string 273 OutputStream io.Writer `qs:"-"` 274 RawJSONStream bool `qs:"-"` 275} 276 277// PullImage pulls an image from a remote registry, logging progress to 278// opts.OutputStream. 279// 280// See https://goo.gl/iJkZjD for more details. 281func (c *Client) PullImage(opts PullImageOptions, auth AuthConfiguration) error { 282 if opts.Repository == "" { 283 return ErrNoSuchImage 284 } 285 286 headers, err := headersWithAuth(auth) 287 if err != nil { 288 return err 289 } 290 return c.createImage(queryString(&opts), headers, nil, opts.OutputStream, opts.RawJSONStream) 291} 292 293func (c *Client) createImage(qs string, headers map[string]string, in io.Reader, w io.Writer, rawJSONStream bool) error { 294 path := "/images/create?" + qs 295 return c.stream("POST", path, streamOptions{ 296 setRawTerminal: true, 297 rawJSONStream: rawJSONStream, 298 headers: headers, 299 in: in, 300 stdout: w, 301 }) 302} 303 304// LoadImageOptions represents the options for LoadImage Docker API Call 305// 306// See https://goo.gl/JyClMX for more details. 307type LoadImageOptions struct { 308 InputStream io.Reader 309} 310 311// LoadImage imports a tarball docker image 312// 313// See https://goo.gl/JyClMX for more details. 314func (c *Client) LoadImage(opts LoadImageOptions) error { 315 return c.stream("POST", "/images/load", streamOptions{ 316 setRawTerminal: true, 317 in: opts.InputStream, 318 }) 319} 320 321// ExportImageOptions represent the options for ExportImage Docker API call. 322// 323// See https://goo.gl/le7vK8 for more details. 324type ExportImageOptions struct { 325 Name string 326 OutputStream io.Writer 327} 328 329// ExportImage exports an image (as a tar file) into the stream. 330// 331// See https://goo.gl/le7vK8 for more details. 332func (c *Client) ExportImage(opts ExportImageOptions) error { 333 return c.stream("GET", fmt.Sprintf("/images/%s/get", opts.Name), streamOptions{ 334 setRawTerminal: true, 335 stdout: opts.OutputStream, 336 }) 337} 338 339// ExportImagesOptions represent the options for ExportImages Docker API call 340// 341// See https://goo.gl/huC7HA for more details. 342type ExportImagesOptions struct { 343 Names []string 344 OutputStream io.Writer `qs:"-"` 345} 346 347// ExportImages exports one or more images (as a tar file) into the stream 348// 349// See https://goo.gl/huC7HA for more details. 350func (c *Client) ExportImages(opts ExportImagesOptions) error { 351 if opts.Names == nil || len(opts.Names) == 0 { 352 return ErrMustSpecifyNames 353 } 354 return c.stream("GET", "/images/get?"+queryString(&opts), streamOptions{ 355 setRawTerminal: true, 356 stdout: opts.OutputStream, 357 }) 358} 359 360// ImportImageOptions present the set of informations available for importing 361// an image from a source file or the stdin. 362// 363// See https://goo.gl/iJkZjD for more details. 364type ImportImageOptions struct { 365 Repository string `qs:"repo"` 366 Source string `qs:"fromSrc"` 367 Tag string `qs:"tag"` 368 369 InputStream io.Reader `qs:"-"` 370 OutputStream io.Writer `qs:"-"` 371 RawJSONStream bool `qs:"-"` 372} 373 374// ImportImage imports an image from a url, a file or stdin 375// 376// See https://goo.gl/iJkZjD for more details. 377func (c *Client) ImportImage(opts ImportImageOptions) error { 378 if opts.Repository == "" { 379 return ErrNoSuchImage 380 } 381 if opts.Source != "-" { 382 opts.InputStream = nil 383 } 384 if opts.Source != "-" && !isURL(opts.Source) { 385 f, err := os.Open(opts.Source) 386 if err != nil { 387 return err 388 } 389 opts.InputStream = f 390 opts.Source = "-" 391 } 392 return c.createImage(queryString(&opts), nil, opts.InputStream, opts.OutputStream, opts.RawJSONStream) 393} 394 395// BuildImageOptions present the set of informations available for building an 396// image from a tarfile with a Dockerfile in it. 397// 398// For more details about the Docker building process, see 399// http://goo.gl/tlPXPu. 400type BuildImageOptions struct { 401 Name string `qs:"t"` 402 Dockerfile string `qs:"dockerfile"` 403 NoCache bool `qs:"nocache"` 404 SuppressOutput bool `qs:"q"` 405 Pull bool `qs:"pull"` 406 RmTmpContainer bool `qs:"rm"` 407 ForceRmTmpContainer bool `qs:"forcerm"` 408 Memory int64 `qs:"memory"` 409 Memswap int64 `qs:"memswap"` 410 CPUShares int64 `qs:"cpushares"` 411 CPUSetCPUs string `qs:"cpusetcpus"` 412 InputStream io.Reader `qs:"-"` 413 OutputStream io.Writer `qs:"-"` 414 RawJSONStream bool `qs:"-"` 415 Remote string `qs:"remote"` 416 Auth AuthConfiguration `qs:"-"` // for older docker X-Registry-Auth header 417 AuthConfigs AuthConfigurations `qs:"-"` // for newer docker X-Registry-Config header 418 ContextDir string `qs:"-"` 419 Ulimits []ULimit `qs:"-"` 420} 421 422// BuildImage builds an image from a tarball's url or a Dockerfile in the input 423// stream. 424// 425// See https://goo.gl/xySxCe for more details. 426func (c *Client) BuildImage(opts BuildImageOptions) error { 427 if opts.OutputStream == nil { 428 return ErrMissingOutputStream 429 } 430 headers, err := headersWithAuth(opts.Auth, c.versionedAuthConfigs(opts.AuthConfigs)) 431 if err != nil { 432 return err 433 } 434 435 if opts.Remote != "" && opts.Name == "" { 436 opts.Name = opts.Remote 437 } 438 if opts.InputStream != nil || opts.ContextDir != "" { 439 headers["Content-Type"] = "application/tar" 440 } else if opts.Remote == "" { 441 return ErrMissingRepo 442 } 443 if opts.ContextDir != "" { 444 if opts.InputStream != nil { 445 return ErrMultipleContexts 446 } 447 var err error 448 if opts.InputStream, err = createTarStream(opts.ContextDir, opts.Dockerfile); err != nil { 449 return err 450 } 451 } 452 453 qs := queryString(&opts) 454 if len(opts.Ulimits) > 0 { 455 if b, err := json.Marshal(opts.Ulimits); err == nil { 456 item := url.Values(map[string][]string{}) 457 item.Add("ulimits", string(b)) 458 qs = fmt.Sprintf("%s&%s", qs, item.Encode()) 459 } 460 } 461 462 return c.stream("POST", fmt.Sprintf("/build?%s", qs), streamOptions{ 463 setRawTerminal: true, 464 rawJSONStream: opts.RawJSONStream, 465 headers: headers, 466 in: opts.InputStream, 467 stdout: opts.OutputStream, 468 }) 469} 470 471func (c *Client) versionedAuthConfigs(authConfigs AuthConfigurations) interface{} { 472 if c.serverAPIVersion == nil { 473 c.checkAPIVersion() 474 } 475 if c.serverAPIVersion != nil && c.serverAPIVersion.GreaterThanOrEqualTo(apiVersion119) { 476 return AuthConfigurations119(authConfigs.Configs) 477 } 478 return authConfigs 479} 480 481// TagImageOptions present the set of options to tag an image. 482// 483// See https://goo.gl/98ZzkU for more details. 484type TagImageOptions struct { 485 Repo string 486 Tag string 487 Force bool 488} 489 490// TagImage adds a tag to the image identified by the given name. 491// 492// See https://goo.gl/98ZzkU for more details. 493func (c *Client) TagImage(name string, opts TagImageOptions) error { 494 if name == "" { 495 return ErrNoSuchImage 496 } 497 resp, err := c.do("POST", fmt.Sprintf("/images/"+name+"/tag?%s", 498 queryString(&opts)), doOptions{}) 499 500 if err != nil { 501 return err 502 } 503 504 defer resp.Body.Close() 505 506 if resp.StatusCode == http.StatusNotFound { 507 return ErrNoSuchImage 508 } 509 510 return err 511} 512 513func isURL(u string) bool { 514 p, err := url.Parse(u) 515 if err != nil { 516 return false 517 } 518 return p.Scheme == "http" || p.Scheme == "https" 519} 520 521func headersWithAuth(auths ...interface{}) (map[string]string, error) { 522 var headers = make(map[string]string) 523 524 for _, auth := range auths { 525 switch auth.(type) { 526 case AuthConfiguration: 527 var buf bytes.Buffer 528 if err := json.NewEncoder(&buf).Encode(auth); err != nil { 529 return nil, err 530 } 531 headers["X-Registry-Auth"] = base64.URLEncoding.EncodeToString(buf.Bytes()) 532 case AuthConfigurations, AuthConfigurations119: 533 var buf bytes.Buffer 534 if err := json.NewEncoder(&buf).Encode(auth); err != nil { 535 return nil, err 536 } 537 headers["X-Registry-Config"] = base64.URLEncoding.EncodeToString(buf.Bytes()) 538 } 539 } 540 541 return headers, nil 542} 543 544// APIImageSearch reflect the result of a search on the Docker Hub. 545// 546// See https://goo.gl/AYjyrF for more details. 547type APIImageSearch struct { 548 Description string `json:"description,omitempty" yaml:"description,omitempty"` 549 IsOfficial bool `json:"is_official,omitempty" yaml:"is_official,omitempty"` 550 IsAutomated bool `json:"is_automated,omitempty" yaml:"is_automated,omitempty"` 551 Name string `json:"name,omitempty" yaml:"name,omitempty"` 552 StarCount int `json:"star_count,omitempty" yaml:"star_count,omitempty"` 553} 554 555// SearchImages search the docker hub with a specific given term. 556// 557// See https://goo.gl/AYjyrF for more details. 558func (c *Client) SearchImages(term string) ([]APIImageSearch, error) { 559 resp, err := c.do("GET", "/images/search?term="+term, doOptions{}) 560 defer resp.Body.Close() 561 if err != nil { 562 return nil, err 563 } 564 var searchResult []APIImageSearch 565 if err := json.NewDecoder(resp.Body).Decode(&searchResult); err != nil { 566 return nil, err 567 } 568 return searchResult, nil 569} 570 571// SearchImagesEx search the docker hub with a specific given term and authentication. 572// 573// See https://goo.gl/AYjyrF for more details. 574func (c *Client) SearchImagesEx(term string, auth AuthConfiguration) ([]APIImageSearch, error) { 575 headers, err := headersWithAuth(auth) 576 if err != nil { 577 return nil, err 578 } 579 580 resp, err := c.do("GET", "/images/search?term="+term, doOptions{ 581 headers: headers, 582 }) 583 if err != nil { 584 return nil, err 585 } 586 587 defer resp.Body.Close() 588 589 var searchResult []APIImageSearch 590 if err := json.NewDecoder(resp.Body).Decode(&searchResult); err != nil { 591 return nil, err 592 } 593 594 return searchResult, nil 595} 596