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