// Copyright 2014 go-dockerclient authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package docker import ( "bytes" "encoding/json" "fmt" "io" "net/http" "net/url" "strconv" "strings" "sync" "time" ) // ListContainersOptions specify parameters to the ListContainers function. // // See http://goo.gl/QpCnDN for more details. type ListContainersOptions struct { All bool Size bool Limit int Since string Before string } type APIPort struct { PrivatePort int64 PublicPort int64 Type string IP string } // APIContainers represents a container. // // See http://goo.gl/QeFH7U for more details. type APIContainers struct { ID string `json:"Id"` Image string Command string Created int64 Status string Ports []APIPort SizeRw int64 SizeRootFs int64 Names []string } // ListContainers returns a slice of containers matching the given criteria. // // See http://goo.gl/QpCnDN for more details. func (c *Client) ListContainers(opts ListContainersOptions) ([]APIContainers, error) { path := "/containers/json?" + queryString(opts) body, _, err := c.do("GET", path, nil) if err != nil { return nil, err } var containers []APIContainers err = json.Unmarshal(body, &containers) if err != nil { return nil, err } return containers, nil } // Port represents the port number and the protocol, in the form // /. For example: 80/tcp. type Port string // Port returns the number of the port. func (p Port) Port() string { return strings.Split(string(p), "/")[0] } // Proto returns the name of the protocol. func (p Port) Proto() string { parts := strings.Split(string(p), "/") if len(parts) == 1 { return "tcp" } return parts[1] } // State represents the state of a container. type State struct { sync.RWMutex Running bool Pid int ExitCode int StartedAt time.Time FinishedAt time.Time Ghost bool } // String returns the string representation of a state. func (s *State) String() string { s.RLock() defer s.RUnlock() if s.Running { if s.Ghost { return "Ghost" } return fmt.Sprintf("Up %s", time.Now().UTC().Sub(s.StartedAt)) } return fmt.Sprintf("Exit %d", s.ExitCode) } type PortBinding struct { HostIp string HostPort string } type PortMapping map[string]string type NetworkSettings struct { IPAddress string IPPrefixLen int Gateway string Bridge string PortMapping map[string]PortMapping Ports map[Port][]PortBinding } func (settings *NetworkSettings) PortMappingAPI() []APIPort { var mapping []APIPort for port, bindings := range settings.Ports { p, _ := parsePort(port.Port()) if len(bindings) == 0 { mapping = append(mapping, APIPort{ PublicPort: int64(p), Type: port.Proto(), }) continue } for _, binding := range bindings { p, _ := parsePort(port.Port()) h, _ := parsePort(binding.HostPort) mapping = append(mapping, APIPort{ PrivatePort: int64(p), PublicPort: int64(h), Type: port.Proto(), IP: binding.HostIp, }) } } return mapping } func parsePort(rawPort string) (int, error) { port, err := strconv.ParseUint(rawPort, 10, 16) if err != nil { return 0, err } return int(port), nil } type Config struct { Hostname string Domainname string User string Memory int64 MemorySwap int64 CpuShares int64 AttachStdin bool AttachStdout bool AttachStderr bool PortSpecs []string ExposedPorts map[Port]struct{} Tty bool OpenStdin bool StdinOnce bool Env []string Cmd []string Dns []string // For Docker API v1.9 and below only Image string Volumes map[string]struct{} VolumesFrom string WorkingDir string Entrypoint []string NetworkDisabled bool } type Container struct { ID string Created time.Time Path string Args []string Config *Config State State Image string NetworkSettings *NetworkSettings SysInitPath string ResolvConfPath string HostnamePath string HostsPath string Name string Driver string Volumes map[string]string VolumesRW map[string]bool HostConfig *HostConfig } // InspectContainer returns information about a container by its ID. // // See http://goo.gl/2o52Sx for more details. func (c *Client) InspectContainer(id string) (*Container, error) { path := "/containers/" + id + "/json" body, status, err := c.do("GET", path, nil) if status == http.StatusNotFound { return nil, &NoSuchContainer{ID: id} } if err != nil { return nil, err } var container Container err = json.Unmarshal(body, &container) if err != nil { return nil, err } return &container, nil } // ContainerChanges returns changes in the filesystem of the given container. // // See http://goo.gl/DpGyzK for more details. func (c *Client) ContainerChanges(id string) ([]Change, error) { path := "/containers/" + id + "/changes" body, status, err := c.do("GET", path, nil) if status == http.StatusNotFound { return nil, &NoSuchContainer{ID: id} } if err != nil { return nil, err } var changes []Change err = json.Unmarshal(body, &changes) if err != nil { return nil, err } return changes, nil } // CreateContainerOptions specify parameters to the CreateContainer function. // // See http://goo.gl/WPPYtB for more details. type CreateContainerOptions struct { Name string Config *Config `qs:"-"` } // CreateContainer creates a new container, returning the container instance, // or an error in case of failure. // // See http://goo.gl/tjihUc for more details. func (c *Client) CreateContainer(opts CreateContainerOptions) (*Container, error) { path := "/containers/create?" + queryString(opts) body, status, err := c.do("POST", path, opts.Config) if status == http.StatusNotFound { return nil, ErrNoSuchImage } if err != nil { return nil, err } var container Container err = json.Unmarshal(body, &container) if err != nil { return nil, err } container.Name = opts.Name return &container, nil } type KeyValuePair struct { Key string Value string } type HostConfig struct { Binds []string ContainerIDFile string LxcConf []KeyValuePair Privileged bool PortBindings map[Port][]PortBinding Links []string PublishAllPorts bool Dns []string // For Docker API v1.10 and above only } // StartContainer starts a container, returning an errror in case of failure. // // See http://goo.gl/y5GZlE for more details. func (c *Client) StartContainer(id string, hostConfig *HostConfig) error { if hostConfig == nil { hostConfig = &HostConfig{} } path := "/containers/" + id + "/start" _, status, err := c.do("POST", path, hostConfig) if status == http.StatusNotFound { return &NoSuchContainer{ID: id} } if err != nil { return err } return nil } // StopContainer stops a container, killing it after the given timeout (in // seconds). // // See http://goo.gl/X2mj8t for more details. func (c *Client) StopContainer(id string, timeout uint) error { path := fmt.Sprintf("/containers/%s/stop?t=%d", id, timeout) _, status, err := c.do("POST", path, nil) if status == http.StatusNotFound { return &NoSuchContainer{ID: id} } if err != nil { return err } return nil } // RestartContainer stops a container, killing it after the given timeout (in // seconds), during the stop process. // // See http://goo.gl/zms73Z for more details. func (c *Client) RestartContainer(id string, timeout uint) error { path := fmt.Sprintf("/containers/%s/restart?t=%d", id, timeout) _, status, err := c.do("POST", path, nil) if status == http.StatusNotFound { return &NoSuchContainer{ID: id} } if err != nil { return err } return nil } // KillContainerOptions represents the set of options that can be used in a // call to KillContainer. type KillContainerOptions struct { // The ID of the container. ID string `qs:"-"` // The signal to send to the container. When omitted, Docker server // will assume SIGKILL. Signal Signal } // KillContainer kills a container, returning an error in case of failure. // // See http://goo.gl/DPbbBy for more details. func (c *Client) KillContainer(opts KillContainerOptions) error { path := "/containers/" + opts.ID + "/kill" + "?" + queryString(opts) _, status, err := c.do("POST", path, nil) if status == http.StatusNotFound { return &NoSuchContainer{ID: opts.ID} } if err != nil { return err } return nil } // RemoveContainerOptions encapsulates options to remove a container. type RemoveContainerOptions struct { // The ID of the container. ID string `qs:"-"` // A flag that indicates whether Docker should remove the volumes // associated to the container. RemoveVolumes bool `qs:"v"` // A flag that indicates whether Docker should remove the container // even if it is currently running. Force bool } // RemoveContainer removes a container, returning an error in case of failure. // // See http://goo.gl/PBvGdU for more details. func (c *Client) RemoveContainer(opts RemoveContainerOptions) error { path := "/containers/" + opts.ID + "?" + queryString(opts) _, status, err := c.do("DELETE", path, nil) if status == http.StatusNotFound { return &NoSuchContainer{ID: opts.ID} } if err != nil { return err } return nil } // CopyFromContainerOptions is the set of options that can be used when copying // files or folders from a container. // // See http://goo.gl/mnxRMl for more details. type CopyFromContainerOptions struct { OutputStream io.Writer `json:"-"` Container string `json:"-"` Resource string } // CopyFromContainer copy files or folders from a container, using a given // resource. // // See http://goo.gl/mnxRMl for more details. func (c *Client) CopyFromContainer(opts CopyFromContainerOptions) error { if opts.Container == "" { return &NoSuchContainer{ID: opts.Container} } url := fmt.Sprintf("/containers/%s/copy", opts.Container) body, status, err := c.do("POST", url, opts) if status == http.StatusNotFound { return &NoSuchContainer{ID: opts.Container} } if err != nil { return err } io.Copy(opts.OutputStream, bytes.NewBuffer(body)) return nil } // WaitContainer blocks until the given container stops, return the exit code // of the container status. // // See http://goo.gl/gnHJL2 for more details. func (c *Client) WaitContainer(id string) (int, error) { body, status, err := c.do("POST", "/containers/"+id+"/wait", nil) if status == http.StatusNotFound { return 0, &NoSuchContainer{ID: id} } if err != nil { return 0, err } var r struct{ StatusCode int } err = json.Unmarshal(body, &r) if err != nil { return 0, err } return r.StatusCode, nil } // CommitContainerOptions aggregates parameters to the CommitContainer method. // // See http://goo.gl/628gxm for more details. type CommitContainerOptions struct { Container string Repository string `qs:"repo"` Tag string Message string `qs:"m"` Author string Run *Config `qs:"-"` } type Image struct { ID string `json:"id"` Parent string `json:"parent,omitempty"` Comment string `json:"comment,omitempty"` Created time.Time `json:"created"` Container string `json:"container,omitempty"` ContainerConfig Config `json:"container_config,omitempty"` DockerVersion string `json:"docker_version,omitempty"` Author string `json:"author,omitempty"` Config *Config `json:"config,omitempty"` Architecture string `json:"architecture,omitempty"` Size int64 } // CommitContainer creates a new image from a container's changes. // // See http://goo.gl/628gxm for more details. func (c *Client) CommitContainer(opts CommitContainerOptions) (*Image, error) { path := "/commit?" + queryString(opts) body, status, err := c.do("POST", path, opts.Run) if status == http.StatusNotFound { return nil, &NoSuchContainer{ID: opts.Container} } if err != nil { return nil, err } var image Image err = json.Unmarshal(body, &image) if err != nil { return nil, err } return &image, nil } // AttachToContainerOptions is the set of options that can be used when // attaching to a container. // // See http://goo.gl/oPzcqH for more details. type AttachToContainerOptions struct { Container string `qs:"-"` InputStream io.Reader `qs:"-"` OutputStream io.Writer `qs:"-"` ErrorStream io.Writer `qs:"-"` // Get container logs, sending it to OutputStream. Logs bool // Stream the response? Stream bool // Attach to stdin, and use InputFile. Stdin bool // Attach to stdout, and use OutputStream. Stdout bool // Attach to stderr, and use ErrorStream. Stderr bool // If set, after a successful connect, a sentinel will be sent and then the // client will block on receive before continuing. // // It must be an unbuffered channel. Using a buffered channel can lead // to unexpected behavior. Success chan struct{} } // AttachToContainer attaches to a container, using the given options. // // See http://goo.gl/oPzcqH for more details. func (c *Client) AttachToContainer(opts AttachToContainerOptions) error { if opts.Container == "" { return &NoSuchContainer{ID: opts.Container} } path := "/containers/" + opts.Container + "/attach?" + queryString(opts) return c.hijack("POST", path, opts.Success, opts.InputStream, opts.ErrorStream, opts.OutputStream) } // ResizeContainerTTY resizes the terminal to the given height and width. func (c *Client) ResizeContainerTTY(id string, height, width int) error { params := make(url.Values) params.Set("h", strconv.Itoa(height)) params.Set("w", strconv.Itoa(width)) _, _, err := c.do("POST", "/containers/"+id+"/resize?"+params.Encode(), nil) return err } // ExportContainerOptions is the set of parameters to the ExportContainer // method. // // See http://goo.gl/Lqk0FZ for more details. type ExportContainerOptions struct { ID string OutputStream io.Writer } // ExportContainer export the contents of container id as tar archive // and prints the exported contents to stdout. // // See http://goo.gl/Lqk0FZ for more details. func (c *Client) ExportContainer(opts ExportContainerOptions) error { if opts.ID == "" { return NoSuchContainer{ID: opts.ID} } url := fmt.Sprintf("/containers/%s/export", opts.ID) return c.stream("GET", url, nil, nil, opts.OutputStream) } // NoSuchContainer is the error returned when a given container does not exist. type NoSuchContainer struct { ID string } func (err NoSuchContainer) Error() string { return "No such container: " + err.ID }