Merge pull request #18472 from calavera/api_client_lib

Api client lib
This commit is contained in:
Tibor Vass 2015-12-09 19:17:11 +01:00
commit 375f754f49
98 changed files with 3074 additions and 1795 deletions

View file

@ -1,10 +1,8 @@
package client
import (
"encoding/json"
"fmt"
"io"
"net/url"
"github.com/Sirupsen/logrus"
"github.com/docker/docker/api/types"
@ -25,18 +23,11 @@ func (cli *DockerCli) CmdAttach(args ...string) error {
cmd.ParseFlags(args, true)
serverResp, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/json", nil, nil)
c, err := cli.client.ContainerInspect(cmd.Arg(0))
if err != nil {
return err
}
defer serverResp.body.Close()
var c types.ContainerJSON
if err := json.NewDecoder(serverResp.body).Decode(&c); err != nil {
return err
}
if !c.State.Running {
return fmt.Errorf("You cannot attach to a stopped container, start it first")
}
@ -55,28 +46,35 @@ func (cli *DockerCli) CmdAttach(args ...string) error {
}
}
var in io.ReadCloser
options := types.ContainerAttachOptions{
ContainerID: cmd.Arg(0),
Stream: true,
Stdin: !*noStdin && c.Config.OpenStdin,
Stdout: true,
Stderr: true,
}
v := url.Values{}
v.Set("stream", "1")
if !*noStdin && c.Config.OpenStdin {
v.Set("stdin", "1")
var in io.ReadCloser
if options.Stdin {
in = cli.in
}
v.Set("stdout", "1")
v.Set("stderr", "1")
if *proxy && !c.Config.Tty {
sigc := cli.forwardAllSignals(cmd.Arg(0))
sigc := cli.forwardAllSignals(options.ContainerID)
defer signal.StopCatch(sigc)
}
if err := cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), c.Config.Tty, in, cli.out, cli.err, nil, nil); err != nil {
resp, err := cli.client.ContainerAttach(options)
if err != nil {
return err
}
defer resp.Close()
if err := cli.holdHijackedConnection(c.Config.Tty, in, cli.out, cli.err, resp); err != nil {
return err
}
_, status, err := getExitCode(cli, cmd.Arg(0))
_, status, err := getExitCode(cli, options.ContainerID)
if err != nil {
return err
}

View file

@ -3,23 +3,19 @@ package client
import (
"archive/tar"
"bufio"
"encoding/base64"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"os"
"os/exec"
"path/filepath"
"regexp"
"runtime"
"strconv"
"strings"
"github.com/docker/distribution/reference"
"github.com/docker/docker/api"
"github.com/docker/docker/api/types"
Cli "github.com/docker/docker/cli"
"github.com/docker/docker/opts"
"github.com/docker/docker/pkg/archive"
@ -33,7 +29,6 @@ import (
"github.com/docker/docker/pkg/units"
"github.com/docker/docker/pkg/urlutil"
"github.com/docker/docker/registry"
"github.com/docker/docker/runconfig"
tagpkg "github.com/docker/docker/tag"
"github.com/docker/docker/utils"
)
@ -207,108 +202,55 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
}
}
// Send the build context
v := url.Values{
"t": flTags.GetAll(),
}
if *suppressOutput {
v.Set("q", "1")
}
var remoteContext string
if isRemote {
v.Set("remote", cmd.Arg(0))
}
if *noCache {
v.Set("nocache", "1")
}
if *rm {
v.Set("rm", "1")
} else {
v.Set("rm", "0")
remoteContext = cmd.Arg(0)
}
if *forceRm {
v.Set("forcerm", "1")
options := types.ImageBuildOptions{
Context: body,
Memory: memory,
MemorySwap: memorySwap,
Tags: flTags.GetAll(),
SuppressOutput: *suppressOutput,
RemoteContext: remoteContext,
NoCache: *noCache,
Remove: *rm,
ForceRemove: *forceRm,
PullParent: *pull,
Isolation: *isolation,
CPUSetCPUs: *flCPUSetCpus,
CPUSetMems: *flCPUSetMems,
CPUShares: *flCPUShares,
CPUQuota: *flCPUQuota,
CPUPeriod: *flCPUPeriod,
CgroupParent: *flCgroupParent,
ShmSize: *flShmSize,
Dockerfile: relDockerfile,
Ulimits: flUlimits.GetList(),
BuildArgs: flBuildArg.GetAll(),
AuthConfigs: cli.configFile.AuthConfigs,
}
if *pull {
v.Set("pull", "1")
response, err := cli.client.ImageBuild(options)
if err != nil {
return err
}
if !runconfig.IsolationLevel.IsDefault(runconfig.IsolationLevel(*isolation)) {
v.Set("isolation", *isolation)
}
v.Set("cpusetcpus", *flCPUSetCpus)
v.Set("cpusetmems", *flCPUSetMems)
v.Set("cpushares", strconv.FormatInt(*flCPUShares, 10))
v.Set("cpuquota", strconv.FormatInt(*flCPUQuota, 10))
v.Set("cpuperiod", strconv.FormatInt(*flCPUPeriod, 10))
v.Set("memory", strconv.FormatInt(memory, 10))
v.Set("memswap", strconv.FormatInt(memorySwap, 10))
v.Set("cgroupparent", *flCgroupParent)
if *flShmSize != "" {
parsedShmSize, err := units.RAMInBytes(*flShmSize)
if err != nil {
return err
err = jsonmessage.DisplayJSONMessagesStream(response.Body, cli.out, cli.outFd, cli.isTerminalOut)
if err != nil {
if jerr, ok := err.(*jsonmessage.JSONError); ok {
// If no error code is set, default to 1
if jerr.Code == 0 {
jerr.Code = 1
}
return Cli.StatusError{Status: jerr.Message, StatusCode: jerr.Code}
}
v.Set("shmsize", strconv.FormatInt(parsedShmSize, 10))
}
v.Set("dockerfile", relDockerfile)
ulimitsVar := flUlimits.GetList()
ulimitsJSON, err := json.Marshal(ulimitsVar)
if err != nil {
return err
}
v.Set("ulimits", string(ulimitsJSON))
// collect all the build-time environment variables for the container
buildArgs := runconfig.ConvertKVStringsToMap(flBuildArg.GetAll())
buildArgsJSON, err := json.Marshal(buildArgs)
if err != nil {
return err
}
v.Set("buildargs", string(buildArgsJSON))
headers := http.Header(make(map[string][]string))
buf, err := json.Marshal(cli.configFile.AuthConfigs)
if err != nil {
return err
}
headers.Add("X-Registry-Config", base64.URLEncoding.EncodeToString(buf))
headers.Set("Content-Type", "application/tar")
sopts := &streamOpts{
rawTerminal: true,
in: body,
out: cli.out,
headers: headers,
}
serverResp, err := cli.stream("POST", fmt.Sprintf("/build?%s", v.Encode()), sopts)
// Windows: show error message about modified file permissions.
if runtime.GOOS == "windows" {
h, err := httputils.ParseServerHeader(serverResp.header.Get("Server"))
if err == nil {
if h.OS != "windows" {
fmt.Fprintln(cli.err, `SECURITY WARNING: You are building a Docker image from Windows against a non-Windows Docker host. All files and directories added to build context will have '-rwxr-xr-x' permissions. It is recommended to double check and reset permissions for sensitive files and directories.`)
}
}
}
if jerr, ok := err.(*jsonmessage.JSONError); ok {
// If no error code is set, default to 1
if jerr.Code == 0 {
jerr.Code = 1
}
return Cli.StatusError{Status: jerr.Message, StatusCode: jerr.Code}
}
if err != nil {
return err
if response.OSType == "windows" {
fmt.Fprintln(cli.err, `SECURITY WARNING: You are building a Docker image from Windows against a non-Windows Docker host. All files and directories added to build context will have '-rwxr-xr-x' permissions. It is recommended to double check and reset permissions for sensitive files and directories.`)
}
// Since the build was successful, now we must tag any of the resolved

View file

@ -1,19 +1,17 @@
package client
import (
"crypto/tls"
"errors"
"fmt"
"io"
"net/http"
"net/url"
"os"
"strings"
"runtime"
"github.com/docker/docker/api/client/lib"
"github.com/docker/docker/cli"
"github.com/docker/docker/cliconfig"
"github.com/docker/docker/dockerversion"
"github.com/docker/docker/opts"
"github.com/docker/docker/pkg/sockets"
"github.com/docker/docker/pkg/term"
"github.com/docker/docker/pkg/tlsconfig"
)
@ -24,13 +22,6 @@ type DockerCli struct {
// initializing closure
init func() error
// proto holds the client protocol i.e. unix.
proto string
// addr holds the client address.
addr string
// basePath holds the path to prepend to the requests
basePath string
// configFile has the client configuration file
configFile *cliconfig.ConfigFile
// in holds the input stream and closer (io.ReadCloser) for the client.
@ -41,11 +32,6 @@ type DockerCli struct {
err io.Writer
// keyFile holds the key file as a string.
keyFile string
// tlsConfig holds the TLS configuration for the client, and will
// set the scheme to https in NewDockerCli if present.
tlsConfig *tls.Config
// scheme holds the scheme of the client i.e. https.
scheme string
// inFd holds the file descriptor of the client's STDIN (if valid).
inFd uintptr
// outFd holds file descriptor of the client's STDOUT (if valid).
@ -54,8 +40,8 @@ type DockerCli struct {
isTerminalIn bool
// isTerminalOut indicates whether the client's STDOUT is a TTY
isTerminalOut bool
// transport holds the client transport instance.
transport *http.Transport
// client is the http client that performs all API operations
client apiClient
}
// Initialize calls the init function that will setup the configuration for the client
@ -98,50 +84,29 @@ func NewDockerCli(in io.ReadCloser, out, err io.Writer, clientFlags *cli.ClientF
}
cli.init = func() error {
clientFlags.PostParse()
configFile, e := cliconfig.Load(cliconfig.ConfigDir())
if e != nil {
fmt.Fprintf(cli.err, "WARNING: Error loading config file:%v\n", e)
}
cli.configFile = configFile
hosts := clientFlags.Common.Hosts
switch len(hosts) {
case 0:
hosts = []string{os.Getenv("DOCKER_HOST")}
case 1:
// only accept one host to talk to
default:
return errors.New("Please specify only one -H")
host, err := getServerHost(clientFlags.Common.Hosts, clientFlags.Common.TLSOptions)
if err != nil {
return err
}
defaultHost := opts.DefaultTCPHost
if clientFlags.Common.TLSOptions != nil {
defaultHost = opts.DefaultTLSHost
customHeaders := cli.configFile.HTTPHeaders
if customHeaders == nil {
customHeaders = map[string]string{}
}
customHeaders["User-Agent"] = "Docker-Client/" + dockerversion.Version + " (" + runtime.GOOS + ")"
var e error
if hosts[0], e = opts.ParseHost(defaultHost, hosts[0]); e != nil {
return e
}
protoAddrParts := strings.SplitN(hosts[0], "://", 2)
cli.proto, cli.addr = protoAddrParts[0], protoAddrParts[1]
if cli.proto == "tcp" {
// error is checked in pkg/parsers already
parsed, _ := url.Parse("tcp://" + cli.addr)
cli.addr = parsed.Host
cli.basePath = parsed.Path
}
if clientFlags.Common.TLSOptions != nil {
cli.scheme = "https"
var e error
cli.tlsConfig, e = tlsconfig.Client(*clientFlags.Common.TLSOptions)
if e != nil {
return e
}
} else {
cli.scheme = "http"
client, err := lib.NewClient(host, clientFlags.Common.TLSOptions, customHeaders)
if err != nil {
return err
}
cli.client = client
if cli.in != nil {
cli.inFd, cli.isTerminalIn = term.GetFdInfo(cli.in)
@ -150,20 +115,27 @@ func NewDockerCli(in io.ReadCloser, out, err io.Writer, clientFlags *cli.ClientF
cli.outFd, cli.isTerminalOut = term.GetFdInfo(cli.out)
}
// The transport is created here for reuse during the client session.
cli.transport = &http.Transport{
TLSClientConfig: cli.tlsConfig,
}
sockets.ConfigureTCPTransport(cli.transport, cli.proto, cli.addr)
configFile, e := cliconfig.Load(cliconfig.ConfigDir())
if e != nil {
fmt.Fprintf(cli.err, "WARNING: Error loading config file:%v\n", e)
}
cli.configFile = configFile
return nil
}
return cli
}
func getServerHost(hosts []string, tlsOptions *tlsconfig.Options) (host string, err error) {
switch len(hosts) {
case 0:
host = os.Getenv("DOCKER_HOST")
case 1:
host = hosts[0]
default:
return "", errors.New("Please specify only one -H")
}
defaultHost := opts.DefaultTCPHost
if tlsOptions != nil {
defaultHost = opts.DefaultTLSHost
}
host, err = opts.ParseHost(defaultHost, host)
return
}

View file

@ -3,3 +3,74 @@
// Run "docker help SUBCOMMAND" or "docker SUBCOMMAND --help" to see more information on any Docker subcommand, including the full list of options supported for the subcommand.
// See https://docs.docker.com/installation/ for instructions on installing Docker.
package client
import (
"io"
"github.com/docker/docker/api/client/lib"
"github.com/docker/docker/api/types"
"github.com/docker/docker/cliconfig"
"github.com/docker/docker/pkg/parsers/filters"
"github.com/docker/docker/registry"
"github.com/docker/docker/runconfig"
)
// apiClient is an interface that clients that talk with a docker server must implement.
type apiClient interface {
ContainerAttach(options types.ContainerAttachOptions) (types.HijackedResponse, error)
ContainerCommit(options types.ContainerCommitOptions) (types.ContainerCommitResponse, error)
ContainerCreate(config *runconfig.ContainerConfigWrapper, containerName string) (types.ContainerCreateResponse, error)
ContainerDiff(containerID string) ([]types.ContainerChange, error)
ContainerExecAttach(execID string, config runconfig.ExecConfig) (types.HijackedResponse, error)
ContainerExecCreate(config runconfig.ExecConfig) (types.ContainerExecCreateResponse, error)
ContainerExecInspect(execID string) (types.ContainerExecInspect, error)
ContainerExecResize(options types.ResizeOptions) error
ContainerExecStart(execID string, config types.ExecStartCheck) error
ContainerExport(containerID string) (io.ReadCloser, error)
ContainerInspect(containerID string) (types.ContainerJSON, error)
ContainerInspectWithRaw(containerID string, getSize bool) (types.ContainerJSON, []byte, error)
ContainerKill(containerID, signal string) error
ContainerList(options types.ContainerListOptions) ([]types.Container, error)
ContainerLogs(options types.ContainerLogsOptions) (io.ReadCloser, error)
ContainerPause(containerID string) error
ContainerRemove(options types.ContainerRemoveOptions) error
ContainerRename(containerID, newContainerName string) error
ContainerResize(options types.ResizeOptions) error
ContainerRestart(containerID string, timeout int) error
ContainerStatPath(containerID, path string) (types.ContainerPathStat, error)
ContainerStats(containerID string, stream bool) (io.ReadCloser, error)
ContainerStart(containerID string) error
ContainerStop(containerID string, timeout int) error
ContainerTop(containerID string, arguments []string) (types.ContainerProcessList, error)
ContainerUnpause(containerID string) error
ContainerWait(containerID string) (int, error)
CopyFromContainer(containerID, srcPath string) (io.ReadCloser, types.ContainerPathStat, error)
CopyToContainer(options types.CopyToContainerOptions) error
Events(options types.EventsOptions) (io.ReadCloser, error)
ImageBuild(options types.ImageBuildOptions) (types.ImageBuildResponse, error)
ImageCreate(options types.ImageCreateOptions) (io.ReadCloser, error)
ImageHistory(imageID string) ([]types.ImageHistory, error)
ImageImport(options types.ImageImportOptions) (io.ReadCloser, error)
ImageInspectWithRaw(imageID string, getSize bool) (types.ImageInspect, []byte, error)
ImageList(options types.ImageListOptions) ([]types.Image, error)
ImageLoad(input io.Reader) (io.ReadCloser, error)
ImagePull(options types.ImagePullOptions, privilegeFunc lib.RequestPrivilegeFunc) (io.ReadCloser, error)
ImagePush(options types.ImagePushOptions, privilegeFunc lib.RequestPrivilegeFunc) (io.ReadCloser, error)
ImageRemove(options types.ImageRemoveOptions) ([]types.ImageDelete, error)
ImageSearch(options types.ImageSearchOptions, privilegeFunc lib.RequestPrivilegeFunc) ([]registry.SearchResult, error)
ImageSave(imageIDs []string) (io.ReadCloser, error)
ImageTag(options types.ImageTagOptions) error
Info() (types.Info, error)
NetworkConnect(networkID, containerID string) error
NetworkCreate(options types.NetworkCreate) (types.NetworkCreateResponse, error)
NetworkDisconnect(networkID, containerID string) error
NetworkInspect(networkID string) (types.NetworkResource, error)
NetworkList() ([]types.NetworkResource, error)
NetworkRemove(networkID string) error
RegistryLogin(auth cliconfig.AuthConfig) (types.AuthResponse, error)
SystemVersion() (types.VersionResponse, error)
VolumeCreate(options types.VolumeCreateRequest) (types.Volume, error)
VolumeInspect(volumeID string) (types.Volume, error)
VolumeList(filter filters.Args) (types.VolumesListResponse, error)
VolumeRemove(volumeID string) error
}

View file

@ -4,7 +4,6 @@ import (
"encoding/json"
"errors"
"fmt"
"net/url"
"github.com/docker/distribution/reference"
"github.com/docker/docker/api/types"
@ -59,39 +58,27 @@ func (cli *DockerCli) CmdCommit(args ...string) error {
}
}
v := url.Values{}
v.Set("container", name)
v.Set("repo", repositoryName)
v.Set("tag", tag)
v.Set("comment", *flComment)
v.Set("author", *flAuthor)
for _, change := range flChanges.GetAll() {
v.Add("changes", change)
}
if *flPause != true {
v.Set("pause", "0")
}
var (
config *runconfig.Config
response types.ContainerCommitResponse
)
var config *runconfig.Config
if *flConfig != "" {
config = &runconfig.Config{}
if err := json.Unmarshal([]byte(*flConfig), config); err != nil {
return err
}
}
serverResp, err := cli.call("POST", "/commit?"+v.Encode(), config, nil)
if err != nil {
return err
options := types.ContainerCommitOptions{
ContainerID: name,
RepositoryName: repositoryName,
Tag: tag,
Comment: *flComment,
Author: *flAuthor,
Changes: flChanges.GetAll(),
Pause: *flPause,
Config: config,
}
defer serverResp.body.Close()
if err := json.NewDecoder(serverResp.body).Decode(&response); err != nil {
response, err := cli.client.ContainerCommit(options)
if err != nil {
return err
}

View file

@ -1,12 +1,8 @@
package client
import (
"encoding/base64"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"os"
"path/filepath"
"strings"
@ -129,38 +125,7 @@ func splitCpArg(arg string) (container, path string) {
}
func (cli *DockerCli) statContainerPath(containerName, path string) (types.ContainerPathStat, error) {
var stat types.ContainerPathStat
query := make(url.Values, 1)
query.Set("path", filepath.ToSlash(path)) // Normalize the paths used in the API.
urlStr := fmt.Sprintf("/containers/%s/archive?%s", containerName, query.Encode())
response, err := cli.call("HEAD", urlStr, nil, nil)
if err != nil {
return stat, err
}
defer response.body.Close()
if response.statusCode != http.StatusOK {
return stat, fmt.Errorf("unexpected status code from daemon: %d", response.statusCode)
}
return getContainerPathStatFromHeader(response.header)
}
func getContainerPathStatFromHeader(header http.Header) (types.ContainerPathStat, error) {
var stat types.ContainerPathStat
encodedStat := header.Get("X-Docker-Container-Path-Stat")
statDecoder := base64.NewDecoder(base64.StdEncoding, strings.NewReader(encodedStat))
err := json.NewDecoder(statDecoder).Decode(&stat)
if err != nil {
err = fmt.Errorf("unable to decode container path stat header: %s", err)
}
return stat, err
return cli.client.ContainerStatPath(containerName, path)
}
func resolveLocalPath(localPath string) (absPath string, err error) {
@ -200,39 +165,19 @@ func (cli *DockerCli) copyFromContainer(srcContainer, srcPath, dstPath string, c
}
query := make(url.Values, 1)
query.Set("path", filepath.ToSlash(srcPath)) // Normalize the paths used in the API.
urlStr := fmt.Sprintf("/containers/%s/archive?%s", srcContainer, query.Encode())
response, err := cli.call("GET", urlStr, nil, nil)
content, stat, err := cli.client.CopyFromContainer(srcContainer, srcPath)
if err != nil {
return err
}
defer response.body.Close()
if response.statusCode != http.StatusOK {
return fmt.Errorf("unexpected status code from daemon: %d", response.statusCode)
}
defer content.Close()
if dstPath == "-" {
// Send the response to STDOUT.
_, err = io.Copy(os.Stdout, response.body)
_, err = io.Copy(os.Stdout, content)
return err
}
// In order to get the copy behavior right, we need to know information
// about both the source and the destination. The response headers include
// stat info about the source that we can use in deciding exactly how to
// copy it locally. Along with the stat info about the local destination,
// we have everything we need to handle the multiple possibilities there
// can be when copying a file/dir from one location to another file/dir.
stat, err := getContainerPathStatFromHeader(response.header)
if err != nil {
return fmt.Errorf("unable to get resource stat from response: %s", err)
}
// Prepare source copy info.
srcInfo := archive.CopyInfo{
Path: srcPath,
@ -241,10 +186,10 @@ func (cli *DockerCli) copyFromContainer(srcContainer, srcPath, dstPath string, c
RebaseName: rebaseName,
}
preArchive := response.body
preArchive := content
if len(srcInfo.RebaseName) != 0 {
_, srcBase := archive.SplitPathDirEntry(srcInfo.Path)
preArchive = archive.RebaseArchiveEntries(response.body, srcBase, srcInfo.RebaseName)
preArchive = archive.RebaseArchiveEntries(content, srcBase, srcInfo.RebaseName)
}
// See comments in the implementation of `archive.CopyTo` for exactly what
// goes into deciding how and whether the source archive needs to be
@ -340,22 +285,12 @@ func (cli *DockerCli) copyToContainer(srcPath, dstContainer, dstPath string, cpP
content = preparedArchive
}
query := make(url.Values, 2)
query.Set("path", filepath.ToSlash(resolvedDstPath)) // Normalize the paths used in the API.
// Do not allow for an existing directory to be overwritten by a non-directory and vice versa.
query.Set("noOverwriteDirNonDir", "true")
urlStr := fmt.Sprintf("/containers/%s/archive?%s", dstContainer, query.Encode())
response, err := cli.stream("PUT", urlStr, &streamOpts{in: content})
if err != nil {
return err
}
defer response.body.Close()
if response.statusCode != http.StatusOK {
return fmt.Errorf("unexpected status code from daemon: %d", response.statusCode)
options := types.CopyToContainerOptions{
ContainerID: dstContainer,
Path: resolvedDstPath,
Content: content,
AllowOverwriteDirWithFile: false,
}
return nil
return cli.client.CopyToContainer(options)
}

View file

@ -1,17 +1,15 @@
package client
import (
"encoding/base64"
"encoding/json"
"fmt"
"io"
"net/url"
"os"
"strings"
"github.com/docker/distribution/reference"
"github.com/docker/docker/api/client/lib"
"github.com/docker/docker/api/types"
Cli "github.com/docker/docker/cli"
"github.com/docker/docker/pkg/jsonmessage"
"github.com/docker/docker/registry"
"github.com/docker/docker/runconfig"
tagpkg "github.com/docker/docker/tag"
@ -22,8 +20,6 @@ func (cli *DockerCli) pullImage(image string) error {
}
func (cli *DockerCli) pullImageCustomOut(image string, out io.Writer) error {
v := url.Values{}
ref, err := reference.ParseNamed(image)
if err != nil {
return err
@ -40,9 +36,6 @@ func (cli *DockerCli) pullImageCustomOut(image string, out io.Writer) error {
tag = tagpkg.DefaultTag
}
v.Set("fromImage", ref.Name())
v.Set("tag", tag)
// Resolve the Repository name from fqn to RepositoryInfo
repoInfo, err := registry.ParseRepositoryInfo(ref)
if err != nil {
@ -50,24 +43,24 @@ func (cli *DockerCli) pullImageCustomOut(image string, out io.Writer) error {
}
// Resolve the Auth config relevant for this server
authConfig := registry.ResolveAuthConfig(cli.configFile, repoInfo.Index)
buf, err := json.Marshal(authConfig)
encodedAuth, err := cli.encodeRegistryAuth(repoInfo.Index)
if err != nil {
return err
}
registryAuthHeader := []string{
base64.URLEncoding.EncodeToString(buf),
options := types.ImageCreateOptions{
Parent: ref.Name(),
Tag: tag,
RegistryAuth: encodedAuth,
}
sopts := &streamOpts{
rawTerminal: true,
out: out,
headers: map[string][]string{"X-Registry-Auth": registryAuthHeader},
}
if _, err := cli.stream("POST", "/images/create?"+v.Encode(), sopts); err != nil {
responseBody, err := cli.client.ImageCreate(options)
if err != nil {
return err
}
return nil
defer responseBody.Close()
return jsonmessage.DisplayJSONMessagesStream(responseBody, out, cli.outFd, cli.isTerminalOut)
}
type cidFile struct {
@ -90,11 +83,6 @@ func newCIDFile(path string) (*cidFile, error) {
}
func (cli *DockerCli) createContainer(config *runconfig.Config, hostConfig *runconfig.HostConfig, cidfile, name string) (*types.ContainerCreateResponse, error) {
containerValues := url.Values{}
if name != "" {
containerValues.Set("name", name)
}
mergedConfig := runconfig.MergeConfigs(config, hostConfig)
var containerIDFile *cidFile
@ -135,34 +123,32 @@ func (cli *DockerCli) createContainer(config *runconfig.Config, hostConfig *runc
}
//create the container
serverResp, err := cli.call("POST", "/containers/create?"+containerValues.Encode(), mergedConfig, nil)
response, err := cli.client.ContainerCreate(mergedConfig, name)
//if image not found try to pull it
if serverResp.statusCode == 404 && strings.Contains(err.Error(), config.Image) {
fmt.Fprintf(cli.err, "Unable to find image '%s' locally\n", ref.String())
if err != nil {
if lib.IsErrImageNotFound(err) {
fmt.Fprintf(cli.err, "Unable to find image '%s' locally\n", ref.String())
// we don't want to write to stdout anything apart from container.ID
if err = cli.pullImageCustomOut(config.Image, cli.err); err != nil {
return nil, err
}
if trustedRef != nil && !isDigested {
if err := cli.tagTrusted(trustedRef, ref.(reference.NamedTagged)); err != nil {
// we don't want to write to stdout anything apart from container.ID
if err = cli.pullImageCustomOut(config.Image, cli.err); err != nil {
return nil, err
}
}
// Retry
if serverResp, err = cli.call("POST", "/containers/create?"+containerValues.Encode(), mergedConfig, nil); err != nil {
if trustedRef != nil && !isDigested {
if err := cli.tagTrusted(trustedRef, ref.(reference.NamedTagged)); err != nil {
return nil, err
}
}
// Retry
var retryErr error
response, retryErr = cli.client.ContainerCreate(mergedConfig, name)
if retryErr != nil {
return nil, retryErr
}
} else {
return nil, err
}
} else if err != nil {
return nil, err
}
defer serverResp.body.Close()
var response types.ContainerCreateResponse
if err := json.NewDecoder(serverResp.body).Decode(&response); err != nil {
return nil, err
}
for _, warning := range response.Warnings {
fmt.Fprintf(cli.err, "WARNING: %s\n", warning)
}

View file

@ -1,10 +1,8 @@
package client
import (
"encoding/json"
"fmt"
"github.com/docker/docker/api/types"
Cli "github.com/docker/docker/cli"
"github.com/docker/docker/pkg/archive"
flag "github.com/docker/docker/pkg/mflag"
@ -27,18 +25,11 @@ func (cli *DockerCli) CmdDiff(args ...string) error {
return fmt.Errorf("Container name cannot be empty")
}
serverResp, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/changes", nil, nil)
changes, err := cli.client.ContainerDiff(cmd.Arg(0))
if err != nil {
return err
}
defer serverResp.body.Close()
changes := []types.ContainerChange{}
if err := json.NewDecoder(serverResp.body).Decode(&changes); err != nil {
return err
}
for _, change := range changes {
var kind string
switch change.Kind {

View file

@ -1,14 +1,12 @@
package client
import (
"net/url"
"time"
"github.com/docker/docker/api/types"
Cli "github.com/docker/docker/cli"
"github.com/docker/docker/opts"
"github.com/docker/docker/pkg/jsonmessage"
flag "github.com/docker/docker/pkg/mflag"
"github.com/docker/docker/pkg/parsers/filters"
"github.com/docker/docker/pkg/timeutils"
)
// CmdEvents prints a live stream of real time events from the server.
@ -24,10 +22,7 @@ func (cli *DockerCli) CmdEvents(args ...string) error {
cmd.ParseFlags(args, true)
var (
v = url.Values{}
eventFilterArgs = filters.NewArgs()
)
eventFilterArgs := filters.NewArgs()
// Consolidate all filter flags, and sanity check them early.
// They'll get process in the daemon/server.
@ -38,34 +33,18 @@ func (cli *DockerCli) CmdEvents(args ...string) error {
return err
}
}
ref := time.Now()
if *since != "" {
ts, err := timeutils.GetTimestamp(*since, ref)
if err != nil {
return err
}
v.Set("since", ts)
options := types.EventsOptions{
Since: *since,
Until: *until,
Filters: eventFilterArgs,
}
if *until != "" {
ts, err := timeutils.GetTimestamp(*until, ref)
if err != nil {
return err
}
v.Set("until", ts)
}
if eventFilterArgs.Len() > 0 {
filterJSON, err := filters.ToParam(eventFilterArgs)
if err != nil {
return err
}
v.Set("filters", filterJSON)
}
sopts := &streamOpts{
rawTerminal: true,
out: cli.out,
}
if _, err := cli.stream("GET", "/events?"+v.Encode(), sopts); err != nil {
responseBody, err := cli.client.Events(options)
if err != nil {
return err
}
return nil
defer responseBody.Close()
return jsonmessage.DisplayJSONMessagesStream(responseBody, cli.out, cli.outFd, cli.isTerminalOut)
}

View file

@ -1,7 +1,6 @@
package client
import (
"encoding/json"
"fmt"
"io"
@ -24,37 +23,29 @@ func (cli *DockerCli) CmdExec(args ...string) error {
return Cli.StatusError{StatusCode: 1}
}
serverResp, err := cli.call("POST", "/containers/"+execConfig.Container+"/exec", execConfig, nil)
response, err := cli.client.ContainerExecCreate(*execConfig)
if err != nil {
return err
}
defer serverResp.body.Close()
var response types.ContainerExecCreateResponse
if err := json.NewDecoder(serverResp.body).Decode(&response); err != nil {
return err
}
execID := response.ID
if execID == "" {
fmt.Fprintf(cli.out, "exec ID empty")
return nil
}
//Temp struct for execStart so that we don't need to transfer all the execConfig
execStartCheck := &types.ExecStartCheck{
Detach: execConfig.Detach,
Tty: execConfig.Tty,
}
if !execConfig.Detach {
if err := cli.CheckTtyInput(execConfig.AttachStdin, execConfig.Tty); err != nil {
return err
}
} else {
if _, _, err := readBody(cli.call("POST", "/exec/"+execID+"/start", execStartCheck, nil)); err != nil {
execStartCheck := types.ExecStartCheck{
Detach: execConfig.Detach,
Tty: execConfig.Tty,
}
if err := cli.client.ContainerExecStart(execID, execStartCheck); err != nil {
return err
}
// For now don't print this - wait for when we support exec wait()
@ -66,18 +57,9 @@ func (cli *DockerCli) CmdExec(args ...string) error {
var (
out, stderr io.Writer
in io.ReadCloser
hijacked = make(chan io.Closer)
errCh chan error
)
// Block the return until the chan gets closed
defer func() {
logrus.Debugf("End of CmdExec(), Waiting for hijack to finish.")
if _, ok := <-hijacked; ok {
fmt.Fprintln(cli.err, "Hijack did not finish (chan still open)")
}
}()
if execConfig.AttachStdin {
in = cli.in
}
@ -91,24 +73,15 @@ func (cli *DockerCli) CmdExec(args ...string) error {
stderr = cli.err
}
}
errCh = promise.Go(func() error {
return cli.hijackWithContentType("POST", "/exec/"+execID+"/start", "application/json", execConfig.Tty, in, out, stderr, hijacked, execConfig)
})
// Acknowledge the hijack before starting
select {
case closer := <-hijacked:
// Make sure that hijack gets closed when returning. (result
// in closing hijack chan and freeing server's goroutines.
if closer != nil {
defer closer.Close()
}
case err := <-errCh:
if err != nil {
logrus.Debugf("Error hijack: %s", err)
return err
}
resp, err := cli.client.ContainerExecAttach(execID, *execConfig)
if err != nil {
return err
}
defer resp.Close()
errCh = promise.Go(func() error {
return cli.holdHijackedConnection(execConfig.Tty, in, out, stderr, resp)
})
if execConfig.Tty && cli.isTerminalIn {
if err := cli.monitorTtySize(execID, true); err != nil {

View file

@ -2,6 +2,7 @@ package client
import (
"errors"
"io"
"os"
Cli "github.com/docker/docker/cli"
@ -33,14 +34,12 @@ func (cli *DockerCli) CmdExport(args ...string) error {
return errors.New("Cowardly refusing to save to a terminal. Use the -o flag or redirect.")
}
image := cmd.Arg(0)
sopts := &streamOpts{
rawTerminal: true,
out: output,
}
if _, err := cli.stream("GET", "/containers/"+image+"/export", sopts); err != nil {
responseBody, err := cli.client.ContainerExport(cmd.Arg(0))
if err != nil {
return err
}
defer responseBody.Close()
return nil
_, err = io.Copy(output, responseBody)
return err
}

View file

@ -1,197 +1,21 @@
package client
import (
"crypto/tls"
"errors"
"fmt"
"io"
"net"
"net/http"
"net/http/httputil"
"os"
"runtime"
"strings"
"time"
"github.com/Sirupsen/logrus"
"github.com/docker/docker/api"
"github.com/docker/docker/dockerversion"
"github.com/docker/docker/api/types"
"github.com/docker/docker/pkg/stdcopy"
"github.com/docker/docker/pkg/term"
)
type tlsClientCon struct {
*tls.Conn
rawConn net.Conn
}
func (c *tlsClientCon) CloseWrite() error {
// Go standard tls.Conn doesn't provide the CloseWrite() method so we do it
// on its underlying connection.
if cwc, ok := c.rawConn.(interface {
CloseWrite() error
}); ok {
return cwc.CloseWrite()
}
return nil
}
func tlsDial(network, addr string, config *tls.Config) (net.Conn, error) {
return tlsDialWithDialer(new(net.Dialer), network, addr, config)
}
// We need to copy Go's implementation of tls.Dial (pkg/cryptor/tls/tls.go) in
// order to return our custom tlsClientCon struct which holds both the tls.Conn
// object _and_ its underlying raw connection. The rationale for this is that
// we need to be able to close the write end of the connection when attaching,
// which tls.Conn does not provide.
func tlsDialWithDialer(dialer *net.Dialer, network, addr string, config *tls.Config) (net.Conn, error) {
// We want the Timeout and Deadline values from dialer to cover the
// whole process: TCP connection and TLS handshake. This means that we
// also need to start our own timers now.
timeout := dialer.Timeout
if !dialer.Deadline.IsZero() {
deadlineTimeout := dialer.Deadline.Sub(time.Now())
if timeout == 0 || deadlineTimeout < timeout {
timeout = deadlineTimeout
}
}
var errChannel chan error
if timeout != 0 {
errChannel = make(chan error, 2)
time.AfterFunc(timeout, func() {
errChannel <- errors.New("")
})
}
rawConn, err := dialer.Dial(network, addr)
if err != nil {
return nil, err
}
// When we set up a TCP connection for hijack, there could be long periods
// of inactivity (a long running command with no output) that in certain
// network setups may cause ECONNTIMEOUT, leaving the client in an unknown
// state. Setting TCP KeepAlive on the socket connection will prohibit
// ECONNTIMEOUT unless the socket connection truly is broken
if tcpConn, ok := rawConn.(*net.TCPConn); ok {
tcpConn.SetKeepAlive(true)
tcpConn.SetKeepAlivePeriod(30 * time.Second)
}
colonPos := strings.LastIndex(addr, ":")
if colonPos == -1 {
colonPos = len(addr)
}
hostname := addr[:colonPos]
// If no ServerName is set, infer the ServerName
// from the hostname we're connecting to.
if config.ServerName == "" {
// Make a copy to avoid polluting argument or default.
c := *config
c.ServerName = hostname
config = &c
}
conn := tls.Client(rawConn, config)
if timeout == 0 {
err = conn.Handshake()
} else {
go func() {
errChannel <- conn.Handshake()
}()
err = <-errChannel
}
if err != nil {
rawConn.Close()
return nil, err
}
// This is Docker difference with standard's crypto/tls package: returned a
// wrapper which holds both the TLS and raw connections.
return &tlsClientCon{conn, rawConn}, nil
}
func (cli *DockerCli) dial() (net.Conn, error) {
if cli.tlsConfig != nil && cli.proto != "unix" {
// Notice this isn't Go standard's tls.Dial function
return tlsDial(cli.proto, cli.addr, cli.tlsConfig)
}
return net.Dial(cli.proto, cli.addr)
}
func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.ReadCloser, stdout, stderr io.Writer, started chan io.Closer, data interface{}) error {
return cli.hijackWithContentType(method, path, "text/plain", setRawTerminal, in, stdout, stderr, started, data)
}
func (cli *DockerCli) hijackWithContentType(method, path, contentType string, setRawTerminal bool, in io.ReadCloser, stdout, stderr io.Writer, started chan io.Closer, data interface{}) error {
defer func() {
if started != nil {
close(started)
}
}()
params, err := cli.encodeData(data)
if err != nil {
return err
}
req, err := http.NewRequest(method, fmt.Sprintf("%s/v%s%s", cli.basePath, api.Version, path), params)
if err != nil {
return err
}
// Add CLI Config's HTTP Headers BEFORE we set the Docker headers
// then the user can't change OUR headers
for k, v := range cli.configFile.HTTPHeaders {
req.Header.Set(k, v)
}
req.Header.Set("User-Agent", "Docker-Client/"+dockerversion.Version+" ("+runtime.GOOS+")")
req.Header.Set("Content-Type", contentType)
req.Header.Set("Connection", "Upgrade")
req.Header.Set("Upgrade", "tcp")
req.Host = cli.addr
dial, err := cli.dial()
if err != nil {
if strings.Contains(err.Error(), "connection refused") {
return fmt.Errorf("Cannot connect to the Docker daemon. Is 'docker daemon' running on this host?")
}
return err
}
// When we set up a TCP connection for hijack, there could be long periods
// of inactivity (a long running command with no output) that in certain
// network setups may cause ECONNTIMEOUT, leaving the client in an unknown
// state. Setting TCP KeepAlive on the socket connection will prohibit
// ECONNTIMEOUT unless the socket connection truly is broken
if tcpConn, ok := dial.(*net.TCPConn); ok {
tcpConn.SetKeepAlive(true)
tcpConn.SetKeepAlivePeriod(30 * time.Second)
}
clientconn := httputil.NewClientConn(dial, nil)
defer clientconn.Close()
// Server hijacks the connection, error 'connection closed' expected
clientconn.Do(req)
rwc, br := clientconn.Hijack()
defer rwc.Close()
if started != nil {
started <- rwc
}
var oldState *term.State
if in != nil && setRawTerminal && cli.isTerminalIn && os.Getenv("NORAW") == "" {
func (cli *DockerCli) holdHijackedConnection(setRawTerminal bool, inputStream io.ReadCloser, outputStream, errorStream io.Writer, resp types.HijackedResponse) error {
var (
err error
oldState *term.State
)
if inputStream != nil && setRawTerminal && cli.isTerminalIn && os.Getenv("NORAW") == "" {
oldState, err = term.SetRawTerminal(cli.inFd)
if err != nil {
return err
@ -200,22 +24,22 @@ func (cli *DockerCli) hijackWithContentType(method, path, contentType string, se
}
receiveStdout := make(chan error, 1)
if stdout != nil || stderr != nil {
if outputStream != nil || errorStream != nil {
go func() {
defer func() {
if in != nil {
if inputStream != nil {
if setRawTerminal && cli.isTerminalIn {
term.RestoreTerminal(cli.inFd, oldState)
}
in.Close()
inputStream.Close()
}
}()
// When TTY is ON, use regular copy
if setRawTerminal && stdout != nil {
_, err = io.Copy(stdout, br)
if setRawTerminal && outputStream != nil {
_, err = io.Copy(outputStream, resp.Reader)
} else {
_, err = stdcopy.StdCopy(stdout, stderr, br)
_, err = stdcopy.StdCopy(outputStream, errorStream, resp.Reader)
}
logrus.Debugf("[hijack] End of stdout")
receiveStdout <- err
@ -224,17 +48,13 @@ func (cli *DockerCli) hijackWithContentType(method, path, contentType string, se
stdinDone := make(chan struct{})
go func() {
if in != nil {
io.Copy(rwc, in)
if inputStream != nil {
io.Copy(resp.Conn, inputStream)
logrus.Debugf("[hijack] End of stdin")
}
if conn, ok := rwc.(interface {
CloseWrite() error
}); ok {
if err := conn.CloseWrite(); err != nil {
logrus.Debugf("Couldn't send EOF: %s", err)
}
if err := resp.CloseWrite(); err != nil {
logrus.Debugf("Couldn't send EOF: %s", err)
}
close(stdinDone)
}()
@ -246,7 +66,7 @@ func (cli *DockerCli) hijackWithContentType(method, path, contentType string, se
return err
}
case <-stdinDone:
if stdout != nil || stderr != nil {
if outputStream != nil || errorStream != nil {
if err := <-receiveStdout; err != nil {
logrus.Debugf("Error receiveStdout: %s", err)
return err

View file

@ -1,14 +1,12 @@
package client
import (
"encoding/json"
"fmt"
"strconv"
"strings"
"text/tabwriter"
"time"
"github.com/docker/docker/api/types"
Cli "github.com/docker/docker/cli"
flag "github.com/docker/docker/pkg/mflag"
"github.com/docker/docker/pkg/stringid"
@ -28,18 +26,11 @@ func (cli *DockerCli) CmdHistory(args ...string) error {
cmd.ParseFlags(args, true)
serverResp, err := cli.call("GET", "/images/"+cmd.Arg(0)+"/history", nil, nil)
history, err := cli.client.ImageHistory(cmd.Arg(0))
if err != nil {
return err
}
defer serverResp.body.Close()
history := []types.ImageHistory{}
if err := json.NewDecoder(serverResp.body).Decode(&history); err != nil {
return err
}
w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0)
if *quiet {

View file

@ -1,9 +1,7 @@
package client
import (
"encoding/json"
"fmt"
"net/url"
"strings"
"text/tabwriter"
"time"
@ -45,36 +43,22 @@ func (cli *DockerCli) CmdImages(args ...string) error {
}
}
matchName := cmd.Arg(0)
v := url.Values{}
if imageFilterArgs.Len() > 0 {
filterJSON, err := filters.ToParam(imageFilterArgs)
if err != nil {
return err
}
v.Set("filters", filterJSON)
}
var matchName string
if cmd.NArg() == 1 {
// FIXME rename this parameter, to not be confused with the filters flag
v.Set("filter", matchName)
}
if *all {
v.Set("all", "1")
matchName = cmd.Arg(0)
}
serverResp, err := cli.call("GET", "/images/json?"+v.Encode(), nil, nil)
options := types.ImageListOptions{
MatchName: matchName,
All: *all,
Filters: imageFilterArgs,
}
images, err := cli.client.ImageList(options)
if err != nil {
return err
}
defer serverResp.body.Close()
images := []types.Image{}
if err := json.NewDecoder(serverResp.body).Decode(&images); err != nil {
return err
}
w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0)
if !*quiet {
if *showDigests {

View file

@ -3,12 +3,13 @@ package client
import (
"fmt"
"io"
"net/url"
"os"
"github.com/docker/distribution/reference"
"github.com/docker/docker/api/types"
Cli "github.com/docker/docker/cli"
"github.com/docker/docker/opts"
"github.com/docker/docker/pkg/jsonmessage"
flag "github.com/docker/docker/pkg/mflag"
"github.com/docker/docker/pkg/urlutil"
"github.com/docker/docker/registry"
@ -29,20 +30,17 @@ func (cli *DockerCli) CmdImport(args ...string) error {
cmd.ParseFlags(args, true)
var (
v = url.Values{}
in io.Reader
tag string
src = cmd.Arg(0)
srcName = src
repository = cmd.Arg(1)
changes = flChanges.GetAll()
)
v.Set("fromSrc", src)
v.Set("repo", repository)
v.Set("message", *message)
for _, change := range flChanges.GetAll() {
v.Add("changes", change)
}
if cmd.NArg() == 3 {
fmt.Fprintf(cli.err, "[DEPRECATED] The format 'file|URL|- [REPOSITORY [TAG]]' has been deprecated. Please use file|URL|- [REPOSITORY[:TAG]]\n")
v.Set("tag", cmd.Arg(2))
tag = cmd.Arg(2)
}
if repository != "" {
@ -56,12 +54,10 @@ func (cli *DockerCli) CmdImport(args ...string) error {
}
}
var in io.Reader
if src == "-" {
in = cli.in
} else if !urlutil.IsURL(src) {
v.Set("fromSrc", "-")
srcName = "-"
file, err := os.Open(src)
if err != nil {
return err
@ -71,12 +67,20 @@ func (cli *DockerCli) CmdImport(args ...string) error {
}
sopts := &streamOpts{
rawTerminal: true,
in: in,
out: cli.out,
options := types.ImageImportOptions{
Source: in,
SourceName: srcName,
RepositoryName: repository,
Message: *message,
Tag: tag,
Changes: changes,
}
_, err := cli.stream("POST", "/images/create?"+v.Encode(), sopts)
return err
responseBody, err := cli.client.ImageImport(options)
if err != nil {
return err
}
defer responseBody.Close()
return jsonmessage.DisplayJSONMessagesStream(responseBody, cli.out, cli.outFd, cli.isTerminalOut)
}

View file

@ -1,12 +1,9 @@
package client
import (
"encoding/json"
"fmt"
"github.com/docker/docker/api/types"
Cli "github.com/docker/docker/cli"
"github.com/docker/docker/pkg/httputils"
"github.com/docker/docker/pkg/ioutils"
flag "github.com/docker/docker/pkg/mflag"
"github.com/docker/docker/pkg/units"
@ -21,18 +18,11 @@ func (cli *DockerCli) CmdInfo(args ...string) error {
cmd.ParseFlags(args, true)
serverResp, err := cli.call("GET", "/info", nil, nil)
info, err := cli.client.Info()
if err != nil {
return err
}
defer serverResp.body.Close()
info := &types.Info{}
if err := json.NewDecoder(serverResp.body).Decode(info); err != nil {
return fmt.Errorf("Error reading remote info: %v", err)
}
fmt.Fprintf(cli.out, "Containers: %d\n", info.Containers)
fmt.Fprintf(cli.out, "Images: %d\n", info.Images)
ioutils.FprintfIfNotEmpty(cli.out, "Server Version: %s\n", info.ServerVersion)
@ -90,38 +80,36 @@ func (cli *DockerCli) CmdInfo(args ...string) error {
}
// Only output these warnings if the server does not support these features
if h, err := httputils.ParseServerHeader(serverResp.header.Get("Server")); err == nil {
if h.OS != "windows" {
if !info.MemoryLimit {
fmt.Fprintf(cli.err, "WARNING: No memory limit support\n")
}
if !info.SwapLimit {
fmt.Fprintf(cli.err, "WARNING: No swap limit support\n")
}
if !info.OomKillDisable {
fmt.Fprintf(cli.err, "WARNING: No oom kill disable support\n")
}
if !info.CPUCfsQuota {
fmt.Fprintf(cli.err, "WARNING: No cpu cfs quota support\n")
}
if !info.CPUCfsPeriod {
fmt.Fprintf(cli.err, "WARNING: No cpu cfs period support\n")
}
if !info.CPUShares {
fmt.Fprintf(cli.err, "WARNING: No cpu shares support\n")
}
if !info.CPUSet {
fmt.Fprintf(cli.err, "WARNING: No cpuset support\n")
}
if !info.IPv4Forwarding {
fmt.Fprintf(cli.err, "WARNING: IPv4 forwarding is disabled\n")
}
if !info.BridgeNfIptables {
fmt.Fprintf(cli.err, "WARNING: bridge-nf-call-iptables is disabled\n")
}
if !info.BridgeNfIP6tables {
fmt.Fprintf(cli.err, "WARNING: bridge-nf-call-ip6tables is disabled\n")
}
if info.OSType != "windows" {
if !info.MemoryLimit {
fmt.Fprintf(cli.err, "WARNING: No memory limit support\n")
}
if !info.SwapLimit {
fmt.Fprintf(cli.err, "WARNING: No swap limit support\n")
}
if !info.OomKillDisable {
fmt.Fprintf(cli.err, "WARNING: No oom kill disable support\n")
}
if !info.CPUCfsQuota {
fmt.Fprintf(cli.err, "WARNING: No cpu cfs quota support\n")
}
if !info.CPUCfsPeriod {
fmt.Fprintf(cli.err, "WARNING: No cpu cfs period support\n")
}
if !info.CPUShares {
fmt.Fprintf(cli.err, "WARNING: No cpu shares support\n")
}
if !info.CPUSet {
fmt.Fprintf(cli.err, "WARNING: No cpuset support\n")
}
if !info.IPv4Forwarding {
fmt.Fprintf(cli.err, "WARNING: IPv4 forwarding is disabled\n")
}
if !info.BridgeNfIptables {
fmt.Fprintf(cli.err, "WARNING: bridge-nf-call-iptables is disabled\n")
}
if !info.BridgeNfIP6tables {
fmt.Fprintf(cli.err, "WARNING: bridge-nf-call-ip6tables is disabled\n")
}
}

View file

@ -1,16 +1,12 @@
package client
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"strings"
"text/template"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/client/inspect"
"github.com/docker/docker/api/client/lib"
Cli "github.com/docker/docker/cli"
flag "github.com/docker/docker/pkg/mflag"
)
@ -34,172 +30,104 @@ func (cli *DockerCli) CmdInspect(args ...string) error {
cmd.ParseFlags(args, true)
var tmpl *template.Template
var err error
var obj []byte
var statusCode int
if *tmplStr != "" {
if tmpl, err = template.New("").Funcs(funcMap).Parse(*tmplStr); err != nil {
return Cli.StatusError{StatusCode: 64,
Status: "Template parsing error: " + err.Error()}
}
}
if *inspectType != "" && *inspectType != "container" && *inspectType != "image" {
return fmt.Errorf("%q is not a valid value for --type", *inspectType)
}
indented := new(bytes.Buffer)
indented.WriteString("[\n")
status := 0
isImage := false
v := url.Values{}
if *size {
v.Set("size", "1")
var elementSearcher inspectSearcher
switch *inspectType {
case "container":
elementSearcher = cli.inspectContainers(*size)
case "image":
elementSearcher = cli.inspectImages(*size)
default:
elementSearcher = cli.inspectAll(*size)
}
for _, name := range cmd.Args() {
if *inspectType == "" || *inspectType == "container" {
obj, statusCode, err = readBody(cli.call("GET", "/containers/"+name+"/json?"+v.Encode(), nil, nil))
if err != nil {
if err == errConnectionFailed {
return err
}
if *inspectType == "container" {
if statusCode == http.StatusNotFound {
fmt.Fprintf(cli.err, "Error: No such container: %s\n", name)
} else {
fmt.Fprintf(cli.err, "%s", err)
return cli.inspectElements(*tmplStr, cmd.Args(), elementSearcher)
}
func (cli *DockerCli) inspectContainers(getSize bool) inspectSearcher {
return func(ref string) (interface{}, []byte, error) {
return cli.client.ContainerInspectWithRaw(ref, getSize)
}
}
func (cli *DockerCli) inspectImages(getSize bool) inspectSearcher {
return func(ref string) (interface{}, []byte, error) {
return cli.client.ImageInspectWithRaw(ref, getSize)
}
}
func (cli *DockerCli) inspectAll(getSize bool) inspectSearcher {
return func(ref string) (interface{}, []byte, error) {
c, rawContainer, err := cli.client.ContainerInspectWithRaw(ref, getSize)
if err != nil {
// Search for image with that id if a container doesn't exist.
if lib.IsErrContainerNotFound(err) {
i, rawImage, err := cli.client.ImageInspectWithRaw(ref, getSize)
if err != nil {
if lib.IsErrImageNotFound(err) {
return nil, nil, fmt.Errorf("Error: No such image or container: %s", ref)
}
status = 1
continue
return nil, nil, err
}
return i, rawImage, err
}
return nil, nil, err
}
return c, rawContainer, err
}
}
if obj == nil && (*inspectType == "" || *inspectType == "image") {
obj, statusCode, err = readBody(cli.call("GET", "/images/"+name+"/json", nil, nil))
isImage = true
if err != nil {
if err == errConnectionFailed {
return err
}
if statusCode == http.StatusNotFound {
if *inspectType == "" {
fmt.Fprintf(cli.err, "Error: No such image or container: %s\n", name)
} else {
fmt.Fprintf(cli.err, "Error: No such image: %s\n", name)
}
} else {
fmt.Fprintf(cli.err, "%s", err)
}
status = 1
continue
}
}
type inspectSearcher func(ref string) (interface{}, []byte, error)
if tmpl == nil {
if err := json.Indent(indented, obj, "", " "); err != nil {
fmt.Fprintf(cli.err, "%s\n", err)
status = 1
continue
}
} else {
rdr := bytes.NewReader(obj)
dec := json.NewDecoder(rdr)
buf := bytes.NewBufferString("")
if isImage {
inspPtr := types.ImageInspect{}
if err := dec.Decode(&inspPtr); err != nil {
fmt.Fprintf(cli.err, "Unable to read inspect data: %v\n", err)
status = 1
break
}
if err := tmpl.Execute(buf, inspPtr); err != nil {
rdr.Seek(0, 0)
var ok bool
if buf, ok = cli.decodeRawInspect(tmpl, dec); !ok {
fmt.Fprintf(cli.err, "Template parsing error: %v\n", err)
status = 1
break
}
}
} else {
inspPtr := types.ContainerJSON{}
if err := dec.Decode(&inspPtr); err != nil {
fmt.Fprintf(cli.err, "Unable to read inspect data: %v\n", err)
status = 1
break
}
if err := tmpl.Execute(buf, inspPtr); err != nil {
rdr.Seek(0, 0)
var ok bool
if buf, ok = cli.decodeRawInspect(tmpl, dec); !ok {
fmt.Fprintf(cli.err, "Template parsing error: %v\n", err)
status = 1
break
}
}
}
cli.out.Write(buf.Bytes())
cli.out.Write([]byte{'\n'})
}
indented.WriteString(",")
func (cli *DockerCli) inspectElements(tmplStr string, references []string, searchByReference inspectSearcher) error {
elementInspector, err := cli.newInspectorWithTemplate(tmplStr)
if err != nil {
return Cli.StatusError{StatusCode: 64, Status: err.Error()}
}
if indented.Len() > 1 {
// Remove trailing ','
indented.Truncate(indented.Len() - 1)
}
indented.WriteString("]\n")
var inspectErr error
for _, ref := range references {
element, raw, err := searchByReference(ref)
if err != nil {
inspectErr = err
break
}
if tmpl == nil {
// Note that we will always write "[]" when "-f" isn't specified,
// to make sure the output would always be array, see
// https://github.com/docker/docker/pull/9500#issuecomment-65846734
if _, err := io.Copy(cli.out, indented); err != nil {
return err
if err := elementInspector.Inspect(element, raw); err != nil {
inspectErr = err
break
}
}
if status != 0 {
if err := elementInspector.Flush(); err != nil {
cli.inspectErrorStatus(err)
}
if status := cli.inspectErrorStatus(inspectErr); status != 0 {
return Cli.StatusError{StatusCode: status}
}
return nil
}
// decodeRawInspect executes the inspect template with a raw interface.
// This allows docker cli to parse inspect structs injected with Swarm fields.
// Unfortunately, go 1.4 doesn't fail executing invalid templates when the input is an interface.
// It doesn't allow to modify this behavior either, sending <no value> messages to the output.
// We assume that the template is invalid when there is a <no value>, if the template was valid
// we'd get <nil> or "" values. In that case we fail with the original error raised executing the
// template with the typed input.
//
// TODO: Go 1.5 allows to customize the error behavior, we can probably get rid of this as soon as
// we build Docker with that version:
// https://golang.org/pkg/text/template/#Template.Option
func (cli *DockerCli) decodeRawInspect(tmpl *template.Template, dec *json.Decoder) (*bytes.Buffer, bool) {
var raw interface{}
buf := bytes.NewBufferString("")
if rawErr := dec.Decode(&raw); rawErr != nil {
fmt.Fprintf(cli.err, "Unable to read inspect data: %v\n", rawErr)
return buf, false
func (cli *DockerCli) inspectErrorStatus(err error) (status int) {
if err != nil {
fmt.Fprintf(cli.err, "%s\n", err)
status = 1
}
if rawErr := tmpl.Execute(buf, raw); rawErr != nil {
return buf, false
}
if strings.Contains(buf.String(), "<no value>") {
return buf, false
}
return buf, true
return
}
func (cli *DockerCli) newInspectorWithTemplate(tmplStr string) (inspect.Inspector, error) {
elementInspector := inspect.NewIndentedInspector(cli.out)
if tmplStr != "" {
tmpl, err := template.New("").Funcs(funcMap).Parse(tmplStr)
if err != nil {
return nil, fmt.Errorf("Template parsing error: %s", err)
}
elementInspector = inspect.NewTemplateInspector(cli.out, tmpl)
}
return elementInspector, nil
}

View file

@ -0,0 +1,111 @@
package inspect
import (
"bytes"
"encoding/json"
"fmt"
"io"
"text/template"
)
// Inspector defines an interface to implement to process elements
type Inspector interface {
Inspect(typedElement interface{}, rawElement []byte) error
Flush() error
}
// TemplateInspector uses a text template to inspect elements.
type TemplateInspector struct {
outputStream io.Writer
buffer *bytes.Buffer
tmpl *template.Template
}
// NewTemplateInspector creates a new inspector with a template.
func NewTemplateInspector(outputStream io.Writer, tmpl *template.Template) Inspector {
return &TemplateInspector{
outputStream: outputStream,
buffer: new(bytes.Buffer),
tmpl: tmpl,
}
}
// Inspect executes the inspect template.
// It decodes the raw element into a map if the initial execution fails.
// This allows docker cli to parse inspect structs injected with Swarm fields.
func (i *TemplateInspector) Inspect(typedElement interface{}, rawElement []byte) error {
buffer := new(bytes.Buffer)
if err := i.tmpl.Execute(buffer, typedElement); err != nil {
if rawElement == nil {
return fmt.Errorf("Template parsing error: %v", err)
}
return i.tryRawInspectFallback(rawElement)
}
i.buffer.Write(buffer.Bytes())
i.buffer.WriteByte('\n')
return nil
}
func (i *TemplateInspector) tryRawInspectFallback(rawElement []byte) error {
var raw interface{}
buffer := new(bytes.Buffer)
rdr := bytes.NewReader(rawElement)
dec := json.NewDecoder(rdr)
if rawErr := dec.Decode(&raw); rawErr != nil {
return fmt.Errorf("unable to read inspect data: %v", rawErr)
}
tmplMissingKey := i.tmpl.Option("missingkey=error")
if rawErr := tmplMissingKey.Execute(buffer, raw); rawErr != nil {
return fmt.Errorf("Template parsing error: %v", rawErr)
}
i.buffer.Write(buffer.Bytes())
i.buffer.WriteByte('\n')
return nil
}
// Flush write the result of inspecting all elements into the output stream.
func (i *TemplateInspector) Flush() error {
_, err := io.Copy(i.outputStream, i.buffer)
return err
}
// IndentedInspector uses a buffer to stop the indented representation of an element.
type IndentedInspector struct {
outputStream io.Writer
elements []interface{}
}
// NewIndentedInspector generates a new IndentedInspector.
func NewIndentedInspector(outputStream io.Writer) Inspector {
return &IndentedInspector{
outputStream: outputStream,
}
}
// Inspect writes the raw element with an indented json format.
func (i *IndentedInspector) Inspect(typedElement interface{}, _ []byte) error {
i.elements = append(i.elements, typedElement)
return nil
}
// Flush write the result of inspecting all elements into the output stream.
func (i *IndentedInspector) Flush() error {
if len(i.elements) == 0 {
_, err := io.WriteString(i.outputStream, "[]\n")
return err
}
buffer, err := json.MarshalIndent(i.elements, "", " ")
if err != nil {
return err
}
if _, err := io.Copy(i.outputStream, bytes.NewReader(buffer)); err != nil {
return err
}
_, err = io.WriteString(i.outputStream, "\n")
return err
}

View file

@ -19,7 +19,7 @@ func (cli *DockerCli) CmdKill(args ...string) error {
var errNames []string
for _, name := range cmd.Args() {
if _, _, err := readBody(cli.call("POST", fmt.Sprintf("/containers/%s/kill?signal=%s", name, *signal), nil, nil)); err != nil {
if err := cli.client.ContainerKill(name, *signal); err != nil {
fmt.Fprintf(cli.err, "%s\n", err)
errNames = append(errNames, name)
} else {

102
api/client/lib/client.go Normal file
View file

@ -0,0 +1,102 @@
package lib
import (
"crypto/tls"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/docker/docker/api"
"github.com/docker/docker/pkg/sockets"
"github.com/docker/docker/pkg/tlsconfig"
"github.com/docker/docker/pkg/version"
)
// Client is the API client that performs all operations
// against a docker server.
type Client struct {
// proto holds the client protocol i.e. unix.
proto string
// addr holds the client address.
addr string
// basePath holds the path to prepend to the requests
basePath string
// scheme holds the scheme of the client i.e. https.
scheme string
// tlsConfig holds the tls configuration to use in hijacked requests.
tlsConfig *tls.Config
// httpClient holds the client transport instance. Exported to keep the old code running.
httpClient *http.Client
// version of the server to talk to.
version version.Version
// custom http headers configured by users
customHTTPHeaders map[string]string
}
// NewClient initializes a new API client
// for the given host. It uses the tlsOptions
// to decide whether to use a secure connection or not.
// It also initializes the custom http headers to add to each request.
func NewClient(host string, tlsOptions *tlsconfig.Options, httpHeaders map[string]string) (*Client, error) {
return NewClientWithVersion(host, api.Version, tlsOptions, httpHeaders)
}
// NewClientWithVersion initializes a new API client
// for the given host and API version. It uses the tlsOptions
// to decide whether to use a secure connection or not.
// It also initializes the custom http headers to add to each request.
func NewClientWithVersion(host string, version version.Version, tlsOptions *tlsconfig.Options, httpHeaders map[string]string) (*Client, error) {
var (
basePath string
tlsConfig *tls.Config
scheme = "http"
protoAddrParts = strings.SplitN(host, "://", 2)
proto, addr = protoAddrParts[0], protoAddrParts[1]
)
if proto == "tcp" {
parsed, err := url.Parse("tcp://" + addr)
if err != nil {
return nil, err
}
addr = parsed.Host
basePath = parsed.Path
}
if tlsOptions != nil {
scheme = "https"
var err error
tlsConfig, err = tlsconfig.Client(*tlsOptions)
if err != nil {
return nil, err
}
}
// The transport is created here for reuse during the client session.
transport := &http.Transport{
TLSClientConfig: tlsConfig,
}
sockets.ConfigureTCPTransport(transport, proto, addr)
return &Client{
proto: proto,
addr: addr,
basePath: basePath,
scheme: scheme,
tlsConfig: tlsConfig,
httpClient: &http.Client{Transport: transport},
version: version,
customHTTPHeaders: httpHeaders,
}, nil
}
// getAPIPath returns the versioned request path to call the api.
// It appends the query parameters to the path if they are not empty.
func (cli *Client) getAPIPath(p string, query url.Values) string {
apiPath := fmt.Sprintf("%s/v%s%s", cli.basePath, cli.version, p)
if len(query) > 0 {
apiPath += "?" + query.Encode()
}
return apiPath
}

View file

@ -0,0 +1,30 @@
package lib
import (
"net/url"
"github.com/docker/docker/api/types"
)
// ContainerAttach attaches a connection to a container in the server.
// It returns a types.HijackedConnection with the hijacked connection
// and the a reader to get output. It's up to the called to close
// the hijacked connection by calling types.HijackedResponse.Close.
func (cli *Client) ContainerAttach(options types.ContainerAttachOptions) (types.HijackedResponse, error) {
query := url.Values{}
if options.Stream {
query.Set("stream", "1")
}
if options.Stdin {
query.Set("stdin", "1")
}
if options.Stdout {
query.Set("stdout", "1")
}
if options.Stderr {
query.Set("stderr", "1")
}
headers := map[string][]string{"Content-Type": {"text/plain"}}
return cli.postHijacked("/containers/"+options.ContainerID+"/attach", query, nil, headers)
}

View file

@ -0,0 +1,37 @@
package lib
import (
"encoding/json"
"net/url"
"github.com/docker/docker/api/types"
)
// ContainerCommit applies changes into a container and creates a new tagged image.
func (cli *Client) ContainerCommit(options types.ContainerCommitOptions) (types.ContainerCommitResponse, error) {
query := url.Values{}
query.Set("container", options.ContainerID)
query.Set("repo", options.RepositoryName)
query.Set("tag", options.Tag)
query.Set("comment", options.Comment)
query.Set("author", options.Author)
for _, change := range options.Changes {
query.Add("changes", change)
}
if options.Pause != true {
query.Set("pause", "0")
}
var response types.ContainerCommitResponse
resp, err := cli.post("/commit", query, options.Config, nil)
if err != nil {
return response, err
}
defer ensureReaderClosed(resp)
if err := json.NewDecoder(resp.body).Decode(&response); err != nil {
return response, err
}
return response, nil
}

View file

@ -0,0 +1,43 @@
package lib
import (
"encoding/json"
"net/url"
"strings"
"github.com/docker/docker/api/types"
"github.com/docker/docker/runconfig"
)
// ContainerCreate creates a new container based in the given configuration.
// It can be associated with a name, but it's not mandatory.
func (cli *Client) ContainerCreate(config *runconfig.ContainerConfigWrapper, containerName string) (types.ContainerCreateResponse, error) {
var response types.ContainerCreateResponse
query := url.Values{}
if containerName != "" {
query.Set("name", containerName)
}
serverResp, err := cli.post("/containers/create", query, config, nil)
if err != nil {
if serverResp != nil && serverResp.statusCode == 404 && strings.Contains(err.Error(), config.Image) {
return response, imageNotFoundError{config.Image}
}
return response, err
}
if serverResp.statusCode == 404 && strings.Contains(err.Error(), config.Image) {
return response, imageNotFoundError{config.Image}
}
if err != nil {
return response, err
}
defer ensureReaderClosed(serverResp)
if err := json.NewDecoder(serverResp.body).Decode(&response); err != nil {
return response, err
}
return response, nil
}

View file

@ -0,0 +1,64 @@
package lib
import (
"bytes"
"encoding/json"
"io/ioutil"
"net/http"
"net/url"
"github.com/docker/docker/api/types"
)
// ContainerInspect returns the container information.
func (cli *Client) ContainerInspect(containerID string) (types.ContainerJSON, error) {
serverResp, err := cli.get("/containers/"+containerID+"/json", nil, nil)
if err != nil {
if serverResp.statusCode == http.StatusNotFound {
return types.ContainerJSON{}, containerNotFoundError{containerID}
}
return types.ContainerJSON{}, err
}
defer ensureReaderClosed(serverResp)
var response types.ContainerJSON
err = json.NewDecoder(serverResp.body).Decode(&response)
return response, err
}
// ContainerInspectWithRaw returns the container information and it's raw representation.
func (cli *Client) ContainerInspectWithRaw(containerID string, getSize bool) (types.ContainerJSON, []byte, error) {
query := url.Values{}
if getSize {
query.Set("size", "1")
}
serverResp, err := cli.get("/containers/"+containerID+"/json", query, nil)
if err != nil {
if serverResp.statusCode == http.StatusNotFound {
return types.ContainerJSON{}, nil, containerNotFoundError{containerID}
}
return types.ContainerJSON{}, nil, err
}
defer ensureReaderClosed(serverResp)
body, err := ioutil.ReadAll(serverResp.body)
if err != nil {
return types.ContainerJSON{}, nil, err
}
var response types.ContainerJSON
rdr := bytes.NewReader(body)
err = json.NewDecoder(rdr).Decode(&response)
return response, body, err
}
func (cli *Client) containerInspectWithResponse(containerID string, query url.Values) (types.ContainerJSON, *serverResponse, error) {
serverResp, err := cli.get("/containers/"+containerID+"/json", nil, nil)
if err != nil {
return types.ContainerJSON{}, serverResp, err
}
var response types.ContainerJSON
err = json.NewDecoder(serverResp.body).Decode(&response)
return response, serverResp, err
}

View file

@ -0,0 +1,54 @@
package lib
import (
"encoding/json"
"net/url"
"strconv"
"github.com/docker/docker/api/types"
"github.com/docker/docker/pkg/parsers/filters"
)
// ContainerList returns the list of containers in the docker host.
func (cli *Client) ContainerList(options types.ContainerListOptions) ([]types.Container, error) {
query := url.Values{}
if options.All {
query.Set("all", "1")
}
if options.Limit != -1 {
query.Set("limit", strconv.Itoa(options.Limit))
}
if options.Since != "" {
query.Set("since", options.Since)
}
if options.Before != "" {
query.Set("before", options.Before)
}
if options.Size {
query.Set("size", "1")
}
if options.Filter.Len() > 0 {
filterJSON, err := filters.ToParam(options.Filter)
if err != nil {
return nil, err
}
query.Set("filters", filterJSON)
}
resp, err := cli.get("/containers/json", query, nil)
if err != nil {
return nil, err
}
defer ensureReaderClosed(resp)
var containers []types.Container
err = json.NewDecoder(resp.body).Decode(&containers)
return containers, err
}

View file

@ -0,0 +1,26 @@
package lib
import (
"net/url"
"github.com/docker/docker/api/types"
)
// ContainerRemove kills and removes a container from the docker host.
func (cli *Client) ContainerRemove(options types.ContainerRemoveOptions) error {
query := url.Values{}
if options.RemoveVolumes {
query.Set("v", "1")
}
if options.RemoveLinks {
query.Set("link", "1")
}
if options.Force {
query.Set("force", "1")
}
resp, err := cli.delete("/containers/"+options.ContainerID, query, nil)
ensureReaderClosed(resp)
return err
}

View file

@ -0,0 +1,12 @@
package lib
import "net/url"
// ContainerRename changes the name of a given container.
func (cli *Client) ContainerRename(containerID, newContainerName string) error {
query := url.Values{}
query.Set("name", newContainerName)
resp, err := cli.post("/containers/"+containerID+"/rename", query, nil, nil)
ensureReaderClosed(resp)
return err
}

View file

@ -0,0 +1,17 @@
package lib
import (
"net/url"
"strconv"
)
// ContainerRestart stops and starts a container again.
// It makes the daemon to wait for the container to be up again for
// a specific amount of time, given the timeout.
func (cli *Client) ContainerRestart(containerID string, timeout int) error {
query := url.Values{}
query.Set("t", strconv.Itoa(timeout))
resp, err := cli.post("/containers/"+containerID+"/restart", query, nil, nil)
ensureReaderClosed(resp)
return err
}

View file

@ -0,0 +1,8 @@
package lib
// ContainerStart sends a request to the docker daemon to start a container.
func (cli *Client) ContainerStart(containerID string) error {
resp, err := cli.post("/containers/"+containerID+"/start", nil, nil, nil)
ensureReaderClosed(resp)
return err
}

View file

@ -0,0 +1,22 @@
package lib
import (
"io"
"net/url"
)
// ContainerStats returns near realtime stats for a given container.
// It's up to the caller to close the io.ReadCloser returned.
func (cli *Client) ContainerStats(containerID string, stream bool) (io.ReadCloser, error) {
query := url.Values{}
query.Set("stream", "0")
if stream {
query.Set("stream", "1")
}
resp, err := cli.get("/containers/"+containerID+"/stats", query, nil)
if err != nil {
return nil, err
}
return resp.body, err
}

View file

@ -0,0 +1,16 @@
package lib
import (
"net/url"
"strconv"
)
// ContainerStop stops a container without terminating the process.
// The process is blocked until the container stops or the timeout expires.
func (cli *Client) ContainerStop(containerID string, timeout int) error {
query := url.Values{}
query.Set("t", strconv.Itoa(timeout))
resp, err := cli.post("/containers/"+containerID+"/stop", query, nil, nil)
ensureReaderClosed(resp)
return err
}

View file

@ -0,0 +1,27 @@
package lib
import (
"encoding/json"
"net/url"
"strings"
"github.com/docker/docker/api/types"
)
// ContainerTop shows process information from within a container.
func (cli *Client) ContainerTop(containerID string, arguments []string) (types.ContainerProcessList, error) {
var response types.ContainerProcessList
query := url.Values{}
if len(arguments) > 0 {
query.Set("ps_args", strings.Join(arguments, " "))
}
resp, err := cli.get("/containers/"+containerID+"/top", query, nil)
if err != nil {
return response, err
}
defer ensureReaderClosed(resp)
err = json.NewDecoder(resp.body).Decode(&response)
return response, err
}

View file

@ -0,0 +1,8 @@
package lib
// ContainerUnpause resumes the process execution within a container
func (cli *Client) ContainerUnpause(containerID string) error {
resp, err := cli.post("/containers/"+containerID+"/unpause", nil, nil, nil)
ensureReaderClosed(resp)
return err
}

95
api/client/lib/copy.go Normal file
View file

@ -0,0 +1,95 @@
package lib
import (
"encoding/base64"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"path/filepath"
"strings"
"github.com/docker/docker/api/types"
)
// ContainerStatPath returns Stat information about a path inside the container filesystem.
func (cli *Client) ContainerStatPath(containerID, path string) (types.ContainerPathStat, error) {
query := url.Values{}
query.Set("path", filepath.ToSlash(path)) // Normalize the paths used in the API.
urlStr := fmt.Sprintf("/containers/%s/archive", containerID)
response, err := cli.head(urlStr, query, nil)
if err != nil {
return types.ContainerPathStat{}, err
}
defer ensureReaderClosed(response)
return getContainerPathStatFromHeader(response.header)
}
// CopyToContainer copies content into the container filesystem.
func (cli *Client) CopyToContainer(options types.CopyToContainerOptions) error {
query := url.Values{}
query.Set("path", filepath.ToSlash(options.Path)) // Normalize the paths used in the API.
// Do not allow for an existing directory to be overwritten by a non-directory and vice versa.
if !options.AllowOverwriteDirWithFile {
query.Set("noOverwriteDirNonDir", "true")
}
path := fmt.Sprintf("/containers/%s/archive", options.ContainerID)
response, err := cli.putRaw(path, query, options.Content, nil)
if err != nil {
return err
}
defer ensureReaderClosed(response)
if response.statusCode != http.StatusOK {
return fmt.Errorf("unexpected status code from daemon: %d", response.statusCode)
}
return nil
}
// CopyFromContainer get the content from the container and return it as a Reader
// to manipulate it in the host. It's up to the caller to close the reader.
func (cli *Client) CopyFromContainer(containerID, srcPath string) (io.ReadCloser, types.ContainerPathStat, error) {
query := make(url.Values, 1)
query.Set("path", filepath.ToSlash(srcPath)) // Normalize the paths used in the API.
apiPath := fmt.Sprintf("/containers/%s/archive", containerID)
response, err := cli.get(apiPath, query, nil)
if err != nil {
return nil, types.ContainerPathStat{}, err
}
if response.statusCode != http.StatusOK {
return nil, types.ContainerPathStat{}, fmt.Errorf("unexpected status code from daemon: %d", response.statusCode)
}
// In order to get the copy behavior right, we need to know information
// about both the source and the destination. The response headers include
// stat info about the source that we can use in deciding exactly how to
// copy it locally. Along with the stat info about the local destination,
// we have everything we need to handle the multiple possibilities there
// can be when copying a file/dir from one location to another file/dir.
stat, err := getContainerPathStatFromHeader(response.header)
if err != nil {
return nil, stat, fmt.Errorf("unable to get resource stat from response: %s", err)
}
return response.body, stat, err
}
func getContainerPathStatFromHeader(header http.Header) (types.ContainerPathStat, error) {
var stat types.ContainerPathStat
encodedStat := header.Get("X-Docker-Container-Path-Stat")
statDecoder := base64.NewDecoder(base64.StdEncoding, strings.NewReader(encodedStat))
err := json.NewDecoder(statDecoder).Decode(&stat)
if err != nil {
err = fmt.Errorf("unable to decode container path stat header: %s", err)
}
return stat, err
}

25
api/client/lib/diff.go Normal file
View file

@ -0,0 +1,25 @@
package lib
import (
"encoding/json"
"net/url"
"github.com/docker/docker/api/types"
)
// ContainerDiff shows differences in a container filesystem since it was started.
func (cli *Client) ContainerDiff(containerID string) ([]types.ContainerChange, error) {
var changes []types.ContainerChange
serverResp, err := cli.get("/containers/"+containerID+"/changes", url.Values{}, nil)
if err != nil {
return changes, err
}
defer ensureReaderClosed(serverResp)
if err := json.NewDecoder(serverResp.body).Decode(&changes); err != nil {
return changes, err
}
return changes, nil
}

94
api/client/lib/errors.go Normal file
View file

@ -0,0 +1,94 @@
package lib
import (
"errors"
"fmt"
)
// ErrConnectionFailed is a error raised when the connection between the client and the server failed.
var ErrConnectionFailed = errors.New("Cannot connect to the Docker daemon. Is the docker daemon running on this host?")
// imageNotFoundError implements an error returned when an image is not in the docker host.
type imageNotFoundError struct {
imageID string
}
// Error returns a string representation of an imageNotFoundError
func (i imageNotFoundError) Error() string {
return fmt.Sprintf("Error: No such image: %s", i.imageID)
}
// IsErrImageNotFound returns true if the error is caused
// when an image is not found in the docker host.
func IsErrImageNotFound(err error) bool {
_, ok := err.(imageNotFoundError)
return ok
}
// containerNotFoundError implements an error returned when a container is not in the docker host.
type containerNotFoundError struct {
containerID string
}
// Error returns a string representation of an containerNotFoundError
func (e containerNotFoundError) Error() string {
return fmt.Sprintf("Error: No such container: %s", e.containerID)
}
// IsErrContainerNotFound returns true if the error is caused
// when a container is not found in the docker host.
func IsErrContainerNotFound(err error) bool {
_, ok := err.(containerNotFoundError)
return ok
}
// networkNotFoundError implements an error returned when a network is not in the docker host.
type networkNotFoundError struct {
networkID string
}
// Error returns a string representation of an networkNotFoundError
func (e networkNotFoundError) Error() string {
return fmt.Sprintf("Error: No such network: %s", e.networkID)
}
// IsErrNetworkNotFound returns true if the error is caused
// when a network is not found in the docker host.
func IsErrNetworkNotFound(err error) bool {
_, ok := err.(networkNotFoundError)
return ok
}
// volumeNotFoundError implements an error returned when a volume is not in the docker host.
type volumeNotFoundError struct {
volumeID string
}
// Error returns a string representation of an networkNotFoundError
func (e volumeNotFoundError) Error() string {
return fmt.Sprintf("Error: No such volume: %s", e.volumeID)
}
// IsErrVolumeNotFound returns true if the error is caused
// when a volume is not found in the docker host.
func IsErrVolumeNotFound(err error) bool {
_, ok := err.(networkNotFoundError)
return ok
}
// unauthorizedError represents an authorization error in a remote registry.
type unauthorizedError struct {
cause error
}
// Error returns a string representation of an unauthorizedError
func (u unauthorizedError) Error() string {
return u.cause.Error()
}
// IsErrUnauthorized returns true if the error is caused
// when an the remote registry authentication fails
func IsErrUnauthorized(err error) bool {
_, ok := err.(unauthorizedError)
return ok
}

46
api/client/lib/events.go Normal file
View file

@ -0,0 +1,46 @@
package lib
import (
"io"
"net/url"
"time"
"github.com/docker/docker/api/types"
"github.com/docker/docker/pkg/parsers/filters"
"github.com/docker/docker/pkg/timeutils"
)
// Events returns a stream of events in the daemon in a ReadCloser.
// It's up to the caller to close the stream.
func (cli *Client) Events(options types.EventsOptions) (io.ReadCloser, error) {
query := url.Values{}
ref := time.Now()
if options.Since != "" {
ts, err := timeutils.GetTimestamp(options.Since, ref)
if err != nil {
return nil, err
}
query.Set("since", ts)
}
if options.Until != "" {
ts, err := timeutils.GetTimestamp(options.Until, ref)
if err != nil {
return nil, err
}
query.Set("until", ts)
}
if options.Filters.Len() > 0 {
filterJSON, err := filters.ToParam(options.Filters)
if err != nil {
return nil, err
}
query.Set("filters", filterJSON)
}
serverResponse, err := cli.get("/events", query, nil)
if err != nil {
return nil, err
}
return serverResponse.body, nil
}

49
api/client/lib/exec.go Normal file
View file

@ -0,0 +1,49 @@
package lib
import (
"encoding/json"
"github.com/docker/docker/api/types"
"github.com/docker/docker/runconfig"
)
// ContainerExecCreate creates a new exec configuration to run an exec process.
func (cli *Client) ContainerExecCreate(config runconfig.ExecConfig) (types.ContainerExecCreateResponse, error) {
var response types.ContainerExecCreateResponse
resp, err := cli.post("/containers/"+config.Container+"/exec", nil, config, nil)
if err != nil {
return response, err
}
defer ensureReaderClosed(resp)
err = json.NewDecoder(resp.body).Decode(&response)
return response, err
}
// ContainerExecStart starts an exec process already create in the docker host.
func (cli *Client) ContainerExecStart(execID string, config types.ExecStartCheck) error {
resp, err := cli.post("/exec/"+execID+"/start", nil, config, nil)
ensureReaderClosed(resp)
return err
}
// ContainerExecAttach attaches a connection to an exec process in the server.
// It returns a types.HijackedConnection with the hijacked connection
// and the a reader to get output. It's up to the called to close
// the hijacked connection by calling types.HijackedResponse.Close.
func (cli *Client) ContainerExecAttach(execID string, config runconfig.ExecConfig) (types.HijackedResponse, error) {
headers := map[string][]string{"Content-Type": {"application/json"}}
return cli.postHijacked("/exec/"+execID+"/start", nil, config, headers)
}
// ContainerExecInspect returns information about a specific exec process on the docker host.
func (cli *Client) ContainerExecInspect(execID string) (types.ContainerExecInspect, error) {
var response types.ContainerExecInspect
resp, err := cli.get("/exec/"+execID+"/json", nil, nil)
if err != nil {
return response, err
}
defer ensureReaderClosed(resp)
err = json.NewDecoder(resp.body).Decode(&response)
return response, err
}

18
api/client/lib/export.go Normal file
View file

@ -0,0 +1,18 @@
package lib
import (
"io"
"net/url"
)
// ContainerExport retrieves the raw contents of a container
// and returns them as a io.ReadCloser. It's up to the caller
// to close the stream.
func (cli *Client) ContainerExport(containerID string) (io.ReadCloser, error) {
serverResp, err := cli.get("/containers/"+containerID+"/export", url.Values{}, nil)
if err != nil {
return nil, err
}
return serverResp.body, nil
}

164
api/client/lib/hijack.go Normal file
View file

@ -0,0 +1,164 @@
package lib
import (
"crypto/tls"
"errors"
"fmt"
"net"
"net/http/httputil"
"net/url"
"strings"
"time"
"github.com/docker/docker/api/types"
)
// tlsClientCon holds tls information and a dialed connection.
type tlsClientCon struct {
*tls.Conn
rawConn net.Conn
}
func (c *tlsClientCon) CloseWrite() error {
// Go standard tls.Conn doesn't provide the CloseWrite() method so we do it
// on its underlying connection.
if conn, ok := c.rawConn.(types.CloseWriter); ok {
return conn.CloseWrite()
}
return nil
}
// postHijacked sends a POST request and hijacks the connection.
func (cli *Client) postHijacked(path string, query url.Values, body interface{}, headers map[string][]string) (types.HijackedResponse, error) {
bodyEncoded, err := encodeData(body)
if err != nil {
return types.HijackedResponse{}, err
}
req, err := cli.newRequest("POST", path, query, bodyEncoded, headers)
if err != nil {
return types.HijackedResponse{}, err
}
req.Host = cli.addr
req.Header.Set("Connection", "Upgrade")
req.Header.Set("Upgrade", "tcp")
conn, err := dial(cli.proto, cli.addr, cli.tlsConfig)
if err != nil {
if strings.Contains(err.Error(), "connection refused") {
return types.HijackedResponse{}, fmt.Errorf("Cannot connect to the Docker daemon. Is 'docker daemon' running on this host?")
}
return types.HijackedResponse{}, err
}
// When we set up a TCP connection for hijack, there could be long periods
// of inactivity (a long running command with no output) that in certain
// network setups may cause ECONNTIMEOUT, leaving the client in an unknown
// state. Setting TCP KeepAlive on the socket connection will prohibit
// ECONNTIMEOUT unless the socket connection truly is broken
if tcpConn, ok := conn.(*net.TCPConn); ok {
tcpConn.SetKeepAlive(true)
tcpConn.SetKeepAlivePeriod(30 * time.Second)
}
clientconn := httputil.NewClientConn(conn, nil)
defer clientconn.Close()
// Server hijacks the connection, error 'connection closed' expected
clientconn.Do(req)
rwc, br := clientconn.Hijack()
return types.HijackedResponse{rwc, br}, nil
}
func tlsDial(network, addr string, config *tls.Config) (net.Conn, error) {
return tlsDialWithDialer(new(net.Dialer), network, addr, config)
}
// We need to copy Go's implementation of tls.Dial (pkg/cryptor/tls/tls.go) in
// order to return our custom tlsClientCon struct which holds both the tls.Conn
// object _and_ its underlying raw connection. The rationale for this is that
// we need to be able to close the write end of the connection when attaching,
// which tls.Conn does not provide.
func tlsDialWithDialer(dialer *net.Dialer, network, addr string, config *tls.Config) (net.Conn, error) {
// We want the Timeout and Deadline values from dialer to cover the
// whole process: TCP connection and TLS handshake. This means that we
// also need to start our own timers now.
timeout := dialer.Timeout
if !dialer.Deadline.IsZero() {
deadlineTimeout := dialer.Deadline.Sub(time.Now())
if timeout == 0 || deadlineTimeout < timeout {
timeout = deadlineTimeout
}
}
var errChannel chan error
if timeout != 0 {
errChannel = make(chan error, 2)
time.AfterFunc(timeout, func() {
errChannel <- errors.New("")
})
}
rawConn, err := dialer.Dial(network, addr)
if err != nil {
return nil, err
}
// When we set up a TCP connection for hijack, there could be long periods
// of inactivity (a long running command with no output) that in certain
// network setups may cause ECONNTIMEOUT, leaving the client in an unknown
// state. Setting TCP KeepAlive on the socket connection will prohibit
// ECONNTIMEOUT unless the socket connection truly is broken
if tcpConn, ok := rawConn.(*net.TCPConn); ok {
tcpConn.SetKeepAlive(true)
tcpConn.SetKeepAlivePeriod(30 * time.Second)
}
colonPos := strings.LastIndex(addr, ":")
if colonPos == -1 {
colonPos = len(addr)
}
hostname := addr[:colonPos]
// If no ServerName is set, infer the ServerName
// from the hostname we're connecting to.
if config.ServerName == "" {
// Make a copy to avoid polluting argument or default.
c := *config
c.ServerName = hostname
config = &c
}
conn := tls.Client(rawConn, config)
if timeout == 0 {
err = conn.Handshake()
} else {
go func() {
errChannel <- conn.Handshake()
}()
err = <-errChannel
}
if err != nil {
rawConn.Close()
return nil, err
}
// This is Docker difference with standard's crypto/tls package: returned a
// wrapper which holds both the TLS and raw connections.
return &tlsClientCon{conn, rawConn}, nil
}
func dial(proto, addr string, tlsConfig *tls.Config) (net.Conn, error) {
if tlsConfig != nil && proto != "unix" {
// Notice this isn't Go standard's tls.Dial function
return tlsDial(proto, addr, tlsConfig)
}
return net.Dial(proto, addr)
}

23
api/client/lib/history.go Normal file
View file

@ -0,0 +1,23 @@
package lib
import (
"encoding/json"
"net/url"
"github.com/docker/docker/api/types"
)
// ImageHistory returns the changes in an image in history format.
func (cli *Client) ImageHistory(imageID string) ([]types.ImageHistory, error) {
var history []types.ImageHistory
serverResp, err := cli.get("/images/"+imageID+"/history", url.Values{}, nil)
if err != nil {
return history, err
}
defer ensureReaderClosed(serverResp)
if err := json.NewDecoder(serverResp.body).Decode(&history); err != nil {
return history, err
}
return history, nil
}

View file

@ -0,0 +1,113 @@
package lib
import (
"encoding/base64"
"encoding/json"
"net/http"
"net/url"
"strconv"
"github.com/docker/docker/api/types"
"github.com/docker/docker/pkg/httputils"
"github.com/docker/docker/pkg/units"
"github.com/docker/docker/runconfig"
)
// ImageBuild sends request to the daemon to build images.
// The Body in the response implement an io.ReadCloser and it's up to the caller to
// close it.
func (cli *Client) ImageBuild(options types.ImageBuildOptions) (types.ImageBuildResponse, error) {
query, err := imageBuildOptionsToQuery(options)
if err != nil {
return types.ImageBuildResponse{}, err
}
headers := http.Header(make(map[string][]string))
buf, err := json.Marshal(options.AuthConfigs)
if err != nil {
return types.ImageBuildResponse{}, err
}
headers.Add("X-Registry-Config", base64.URLEncoding.EncodeToString(buf))
headers.Set("Content-Type", "application/tar")
serverResp, err := cli.postRaw("/build", query, options.Context, headers)
if err != nil {
return types.ImageBuildResponse{}, err
}
var osType string
if h, err := httputils.ParseServerHeader(serverResp.header.Get("Server")); err == nil {
osType = h.OS
}
return types.ImageBuildResponse{
Body: serverResp.body,
OSType: osType,
}, nil
}
func imageBuildOptionsToQuery(options types.ImageBuildOptions) (url.Values, error) {
query := url.Values{
"t": options.Tags,
}
if options.SuppressOutput {
query.Set("q", "1")
}
if options.RemoteContext != "" {
query.Set("remote", options.RemoteContext)
}
if options.NoCache {
query.Set("nocache", "1")
}
if options.Remove {
query.Set("rm", "1")
} else {
query.Set("rm", "0")
}
if options.ForceRemove {
query.Set("forcerm", "1")
}
if options.PullParent {
query.Set("pull", "1")
}
if !runconfig.IsolationLevel.IsDefault(runconfig.IsolationLevel(options.Isolation)) {
query.Set("isolation", options.Isolation)
}
query.Set("cpusetcpus", options.CPUSetCPUs)
query.Set("cpusetmems", options.CPUSetMems)
query.Set("cpushares", strconv.FormatInt(options.CPUShares, 10))
query.Set("cpuquota", strconv.FormatInt(options.CPUQuota, 10))
query.Set("cpuperiod", strconv.FormatInt(options.CPUPeriod, 10))
query.Set("memory", strconv.FormatInt(options.Memory, 10))
query.Set("memswap", strconv.FormatInt(options.MemorySwap, 10))
query.Set("cgroupparent", options.CgroupParent)
if options.ShmSize != "" {
parsedShmSize, err := units.RAMInBytes(options.ShmSize)
if err != nil {
return query, err
}
query.Set("shmsize", strconv.FormatInt(parsedShmSize, 10))
}
query.Set("dockerfile", options.Dockerfile)
ulimitsJSON, err := json.Marshal(options.Ulimits)
if err != nil {
return query, err
}
query.Set("ulimits", string(ulimitsJSON))
buildArgs := runconfig.ConvertKVStringsToMap(options.BuildArgs)
buildArgsJSON, err := json.Marshal(buildArgs)
if err != nil {
return query, err
}
query.Set("buildargs", string(buildArgsJSON))
return query, nil
}

View file

@ -0,0 +1,26 @@
package lib
import (
"io"
"net/url"
"github.com/docker/docker/api/types"
)
// ImageCreate creates a new image based in the parent options.
// It returns the JSON content in the response body.
func (cli *Client) ImageCreate(options types.ImageCreateOptions) (io.ReadCloser, error) {
query := url.Values{}
query.Set("fromImage", options.Parent)
query.Set("tag", options.Tag)
resp, err := cli.tryImageCreate(query, options.RegistryAuth)
if err != nil {
return nil, err
}
return resp.body, nil
}
func (cli *Client) tryImageCreate(query url.Values, registryAuth string) (*serverResponse, error) {
headers := map[string][]string{"X-Registry-Auth": {registryAuth}}
return cli.post("/images/create", query, nil, headers)
}

View file

@ -0,0 +1,27 @@
package lib
import (
"io"
"net/url"
"github.com/docker/docker/api/types"
)
// ImageImport creates a new image based in the source options.
// It returns the JSON content in the response body.
func (cli *Client) ImageImport(options types.ImageImportOptions) (io.ReadCloser, error) {
query := url.Values{}
query.Set("fromSrc", options.SourceName)
query.Set("repo", options.RepositoryName)
query.Set("tag", options.Tag)
query.Set("message", options.Message)
for _, change := range options.Changes {
query.Add("changes", change)
}
resp, err := cli.postRaw("/images/create", query, options.Source, nil)
if err != nil {
return nil, err
}
return resp.body, nil
}

View file

@ -0,0 +1,37 @@
package lib
import (
"bytes"
"encoding/json"
"io/ioutil"
"net/http"
"net/url"
"github.com/docker/docker/api/types"
)
// ImageInspectWithRaw returns the image information and it's raw representation.
func (cli *Client) ImageInspectWithRaw(imageID string, getSize bool) (types.ImageInspect, []byte, error) {
query := url.Values{}
if getSize {
query.Set("size", "1")
}
serverResp, err := cli.get("/images/"+imageID+"/json", query, nil)
if err != nil {
if serverResp.statusCode == http.StatusNotFound {
return types.ImageInspect{}, nil, imageNotFoundError{imageID}
}
return types.ImageInspect{}, nil, err
}
defer ensureReaderClosed(serverResp)
body, err := ioutil.ReadAll(serverResp.body)
if err != nil {
return types.ImageInspect{}, nil, err
}
var response types.ImageInspect
rdr := bytes.NewReader(body)
err = json.NewDecoder(rdr).Decode(&response)
return response, body, err
}

View file

@ -0,0 +1,39 @@
package lib
import (
"encoding/json"
"net/url"
"github.com/docker/docker/api/types"
"github.com/docker/docker/pkg/parsers/filters"
)
// ImageList returns a list of images in the docker host.
func (cli *Client) ImageList(options types.ImageListOptions) ([]types.Image, error) {
var images []types.Image
query := url.Values{}
if options.Filters.Len() > 0 {
filterJSON, err := filters.ToParam(options.Filters)
if err != nil {
return images, err
}
query.Set("filters", filterJSON)
}
if options.MatchName != "" {
// FIXME rename this parameter, to not be confused with the filters flag
query.Set("filter", options.MatchName)
}
if options.All {
query.Set("all", "1")
}
serverResp, err := cli.get("/images/json", query, nil)
if err != nil {
return images, err
}
defer ensureReaderClosed(serverResp)
err = json.NewDecoder(serverResp.body).Decode(&images)
return images, err
}

View file

@ -0,0 +1,17 @@
package lib
import (
"io"
"net/url"
)
// ImageLoad loads an image in the docker host from the client host.
// It's up to the caller to close the io.ReadCloser returned by
// this function.
func (cli *Client) ImageLoad(input io.Reader) (io.ReadCloser, error) {
resp, err := cli.postRaw("/images/load", url.Values{}, input, nil)
if err != nil {
return nil, err
}
return resp.body, nil
}

View file

@ -0,0 +1,34 @@
package lib
import (
"io"
"net/http"
"net/url"
"github.com/docker/docker/api/types"
)
// ImagePull request the docker host to pull an image from a remote registry.
// It executes the privileged function if the operation is unauthorized
// and it tries one more time.
// It's up to the caller to handle the io.ReadCloser and close it properly.
func (cli *Client) ImagePull(options types.ImagePullOptions, privilegeFunc RequestPrivilegeFunc) (io.ReadCloser, error) {
query := url.Values{}
query.Set("fromImage", options.ImageID)
if options.Tag != "" {
query.Set("tag", options.Tag)
}
resp, err := cli.tryImageCreate(query, options.RegistryAuth)
if resp.statusCode == http.StatusUnauthorized {
newAuthHeader, privilegeErr := privilegeFunc()
if privilegeErr != nil {
return nil, privilegeErr
}
resp, err = cli.tryImageCreate(query, newAuthHeader)
}
if err != nil {
return nil, err
}
return resp.body, nil
}

View file

@ -0,0 +1,36 @@
package lib
import (
"io"
"net/http"
"net/url"
"github.com/docker/docker/api/types"
)
// ImagePush request the docker host to push an image to a remote registry.
// It executes the privileged function if the operation is unauthorized
// and it tries one more time.
// It's up to the caller to handle the io.ReadCloser and close it properly.
func (cli *Client) ImagePush(options types.ImagePushOptions, privilegeFunc RequestPrivilegeFunc) (io.ReadCloser, error) {
query := url.Values{}
query.Set("tag", options.Tag)
resp, err := cli.tryImagePush(options.ImageID, query, options.RegistryAuth)
if resp.statusCode == http.StatusUnauthorized {
newAuthHeader, privilegeErr := privilegeFunc()
if privilegeErr != nil {
return nil, privilegeErr
}
resp, err = cli.tryImagePush(options.ImageID, query, newAuthHeader)
}
if err != nil {
return nil, err
}
return resp.body, nil
}
func (cli *Client) tryImagePush(imageID string, query url.Values, registryAuth string) (*serverResponse, error) {
headers := map[string][]string{"X-Registry-Auth": {registryAuth}}
return cli.post("/images/"+imageID+"/push", query, nil, headers)
}

View file

@ -0,0 +1,30 @@
package lib
import (
"encoding/json"
"net/url"
"github.com/docker/docker/api/types"
)
// ImageRemove removes an image from the docker host.
func (cli *Client) ImageRemove(options types.ImageRemoveOptions) ([]types.ImageDelete, error) {
query := url.Values{}
if options.Force {
query.Set("force", "1")
}
if !options.PruneChildren {
query.Set("noprune", "1")
}
resp, err := cli.delete("/images/"+options.ImageID, query, nil)
if err != nil {
return nil, err
}
defer ensureReaderClosed(resp)
var dels []types.ImageDelete
err = json.NewDecoder(resp.body).Decode(&dels)
return dels, err
}

View file

@ -0,0 +1,20 @@
package lib
import (
"io"
"net/url"
)
// ImageSave retrieves one or more images from the docker host as a io.ReadCloser.
// It's up to the caller to store the images and close the stream.
func (cli *Client) ImageSave(imageIDs []string) (io.ReadCloser, error) {
query := url.Values{
"names": imageIDs,
}
resp, err := cli.get("/images/get", query, nil)
if err != nil {
return nil, err
}
return resp.body, nil
}

View file

@ -0,0 +1,39 @@
package lib
import (
"encoding/json"
"net/http"
"net/url"
"github.com/docker/docker/api/types"
"github.com/docker/docker/registry"
)
// ImageSearch makes the docker host to search by a term in a remote registry.
// The list of results is not sorted in any fashion.
func (cli *Client) ImageSearch(options types.ImageSearchOptions, privilegeFunc RequestPrivilegeFunc) ([]registry.SearchResult, error) {
var results []registry.SearchResult
query := url.Values{}
query.Set("term", options.Term)
resp, err := cli.tryImageSearch(query, options.RegistryAuth)
if resp.statusCode == http.StatusUnauthorized {
newAuthHeader, privilegeErr := privilegeFunc()
if privilegeErr != nil {
return results, privilegeErr
}
resp, err = cli.tryImageSearch(query, newAuthHeader)
}
if err != nil {
return results, err
}
defer ensureReaderClosed(resp)
err = json.NewDecoder(resp.body).Decode(&results)
return results, err
}
func (cli *Client) tryImageSearch(query url.Values, registryAuth string) (*serverResponse, error) {
headers := map[string][]string{"X-Registry-Auth": {registryAuth}}
return cli.get("/images/search", query, headers)
}

View file

@ -0,0 +1,21 @@
package lib
import (
"net/url"
"github.com/docker/docker/api/types"
)
// ImageTag tags an image in the docker host
func (cli *Client) ImageTag(options types.ImageTagOptions) error {
query := url.Values{}
query.Set("repo", options.RepositoryName)
query.Set("tag", options.Tag)
if options.Force {
query.Set("force", "1")
}
resp, err := cli.post("/images/"+options.ImageID+"/tag", query, nil, nil)
ensureReaderClosed(resp)
return err
}

25
api/client/lib/info.go Normal file
View file

@ -0,0 +1,25 @@
package lib
import (
"encoding/json"
"fmt"
"net/url"
"github.com/docker/docker/api/types"
)
// Info returns information about the docker server.
func (cli *Client) Info() (types.Info, error) {
var info types.Info
serverResp, err := cli.get("/info", url.Values{}, nil)
if err != nil {
return info, err
}
defer ensureReaderClosed(serverResp)
if err := json.NewDecoder(serverResp.body).Decode(&info); err != nil {
return info, fmt.Errorf("Error reading remote info: %v", err)
}
return info, nil
}

13
api/client/lib/kill.go Normal file
View file

@ -0,0 +1,13 @@
package lib
import "net/url"
// ContainerKill terminates the container process but does not remove the container from the docker host.
func (cli *Client) ContainerKill(containerID, signal string) error {
query := url.Values{}
query.Set("signal", signal)
resp, err := cli.post("/containers/"+containerID+"/kill", query, nil, nil)
ensureReaderClosed(resp)
return err
}

28
api/client/lib/login.go Normal file
View file

@ -0,0 +1,28 @@
package lib
import (
"encoding/json"
"net/http"
"net/url"
"github.com/docker/docker/api/types"
"github.com/docker/docker/cliconfig"
)
// RegistryLogin authenticates the docker server with a given docker registry.
// It returns UnauthorizerError when the authentication fails.
func (cli *Client) RegistryLogin(auth cliconfig.AuthConfig) (types.AuthResponse, error) {
resp, err := cli.post("/auth", url.Values{}, auth, nil)
if resp != nil && resp.statusCode == http.StatusUnauthorized {
return types.AuthResponse{}, unauthorizedError{err}
}
if err != nil {
return types.AuthResponse{}, err
}
defer ensureReaderClosed(resp)
var response types.AuthResponse
err = json.NewDecoder(resp.body).Decode(&response)
return response, err
}

46
api/client/lib/logs.go Normal file
View file

@ -0,0 +1,46 @@
package lib
import (
"io"
"net/url"
"time"
"github.com/docker/docker/api/types"
"github.com/docker/docker/pkg/timeutils"
)
// ContainerLogs returns the logs generated by a container in an io.ReadCloser.
// It's up to the caller to close the stream.
func (cli *Client) ContainerLogs(options types.ContainerLogsOptions) (io.ReadCloser, error) {
query := url.Values{}
if options.ShowStdout {
query.Set("stdout", "1")
}
if options.ShowStderr {
query.Set("stderr", "1")
}
if options.Since != "" {
ts, err := timeutils.GetTimestamp(options.Since, time.Now())
if err != nil {
return nil, err
}
query.Set("since", ts)
}
if options.Timestamps {
query.Set("timestamps", "1")
}
if options.Follow {
query.Set("follow", "1")
}
query.Set("tail", options.Tail)
resp, err := cli.get("/containers/"+options.ContainerID+"/logs", query, nil)
if err != nil {
return nil, err
}
return resp.body, nil
}

71
api/client/lib/network.go Normal file
View file

@ -0,0 +1,71 @@
package lib
import (
"encoding/json"
"net/http"
"github.com/docker/docker/api/types"
)
// NetworkCreate creates a new network in the docker host.
func (cli *Client) NetworkCreate(options types.NetworkCreate) (types.NetworkCreateResponse, error) {
var response types.NetworkCreateResponse
serverResp, err := cli.post("/networks/create", nil, options, nil)
if err != nil {
return response, err
}
json.NewDecoder(serverResp.body).Decode(&response)
ensureReaderClosed(serverResp)
return response, err
}
// NetworkRemove removes an existent network from the docker host.
func (cli *Client) NetworkRemove(networkID string) error {
resp, err := cli.delete("/networks/"+networkID, nil, nil)
ensureReaderClosed(resp)
return err
}
// NetworkConnect connects a container to an existent network in the docker host.
func (cli *Client) NetworkConnect(networkID, containerID string) error {
nc := types.NetworkConnect{containerID}
resp, err := cli.post("/networks/"+networkID+"/connect", nil, nc, nil)
ensureReaderClosed(resp)
return err
}
// NetworkDisconnect disconnects a container from an existent network in the docker host.
func (cli *Client) NetworkDisconnect(networkID, containerID string) error {
nc := types.NetworkConnect{containerID}
resp, err := cli.post("/networks/"+networkID+"/disconnect", nil, nc, nil)
ensureReaderClosed(resp)
return err
}
// NetworkList returns the list of networks configured in the docker host.
func (cli *Client) NetworkList() ([]types.NetworkResource, error) {
var networkResources []types.NetworkResource
resp, err := cli.get("/networks", nil, nil)
if err != nil {
return networkResources, err
}
defer ensureReaderClosed(resp)
err = json.NewDecoder(resp.body).Decode(&networkResources)
return networkResources, err
}
// NetworkInspect returns the information for a specific network configured in the docker host.
func (cli *Client) NetworkInspect(networkID string) (types.NetworkResource, error) {
var networkResource types.NetworkResource
resp, err := cli.get("/networks/"+networkID, nil, nil)
if err != nil {
if resp.statusCode == http.StatusNotFound {
return networkResource, networkNotFoundError{networkID}
}
return networkResource, err
}
defer ensureReaderClosed(resp)
err = json.NewDecoder(resp.body).Decode(&networkResource)
return networkResource, err
}

8
api/client/lib/pause.go Normal file
View file

@ -0,0 +1,8 @@
package lib
// ContainerPause pauses the main process of a given container without terminating it.
func (cli *Client) ContainerPause(containerID string) error {
resp, err := cli.post("/containers/"+containerID+"/pause", nil, nil, nil)
ensureReaderClosed(resp)
return err
}

View file

@ -0,0 +1,9 @@
package lib
// RequestPrivilegeFunc is a function interface that
// clients can supply to retry operations after
// getting an authorization error.
// This function returns the registry authentication
// header value in base 64 format, or an error
// if the privilege request fails.
type RequestPrivilegeFunc func() (string, error)

165
api/client/lib/request.go Normal file
View file

@ -0,0 +1,165 @@
package lib
import (
"bytes"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"strings"
"github.com/docker/docker/utils"
)
// serverResponse is a wrapper for http API responses.
type serverResponse struct {
body io.ReadCloser
header http.Header
statusCode int
}
// head sends an http request to the docker API using the method HEAD.
func (cli *Client) head(path string, query url.Values, headers map[string][]string) (*serverResponse, error) {
return cli.sendRequest("HEAD", path, query, nil, headers)
}
// get sends an http request to the docker API using the method GET.
func (cli *Client) get(path string, query url.Values, headers map[string][]string) (*serverResponse, error) {
return cli.sendRequest("GET", path, query, nil, headers)
}
// post sends an http request to the docker API using the method POST.
func (cli *Client) post(path string, query url.Values, body interface{}, headers map[string][]string) (*serverResponse, error) {
return cli.sendRequest("POST", path, query, body, headers)
}
// postRaw sends the raw input to the docker API using the method POST.
func (cli *Client) postRaw(path string, query url.Values, body io.Reader, headers map[string][]string) (*serverResponse, error) {
return cli.sendClientRequest("POST", path, query, body, headers)
}
// put sends an http request to the docker API using the method PUT.
func (cli *Client) put(path string, query url.Values, body interface{}, headers map[string][]string) (*serverResponse, error) {
return cli.sendRequest("PUT", path, query, body, headers)
}
// putRaw sends the raw input to the docker API using the method PUT.
func (cli *Client) putRaw(path string, query url.Values, body io.Reader, headers map[string][]string) (*serverResponse, error) {
return cli.sendClientRequest("PUT", path, query, body, headers)
}
// delete sends an http request to the docker API using the method DELETE.
func (cli *Client) delete(path string, query url.Values, headers map[string][]string) (*serverResponse, error) {
return cli.sendRequest("DELETE", path, query, nil, headers)
}
func (cli *Client) sendRequest(method, path string, query url.Values, body interface{}, headers map[string][]string) (*serverResponse, error) {
params, err := encodeData(body)
if err != nil {
return nil, err
}
if body != nil {
if headers == nil {
headers = make(map[string][]string)
}
headers["Content-Type"] = []string{"application/json"}
}
return cli.sendClientRequest(method, path, query, params, headers)
}
func (cli *Client) sendClientRequest(method, path string, query url.Values, body io.Reader, headers map[string][]string) (*serverResponse, error) {
serverResp := &serverResponse{
body: nil,
statusCode: -1,
}
expectedPayload := (method == "POST" || method == "PUT")
if expectedPayload && body == nil {
body = bytes.NewReader([]byte{})
}
req, err := cli.newRequest(method, path, query, body, headers)
req.URL.Host = cli.addr
req.URL.Scheme = cli.scheme
if expectedPayload && req.Header.Get("Content-Type") == "" {
req.Header.Set("Content-Type", "text/plain")
}
resp, err := cli.httpClient.Do(req)
if resp != nil {
serverResp.statusCode = resp.StatusCode
}
if err != nil {
if utils.IsTimeout(err) || strings.Contains(err.Error(), "connection refused") || strings.Contains(err.Error(), "dial unix") {
return serverResp, ErrConnectionFailed
}
if cli.scheme == "http" && strings.Contains(err.Error(), "malformed HTTP response") {
return serverResp, fmt.Errorf("%v.\n* Are you trying to connect to a TLS-enabled daemon without TLS?", err)
}
if cli.scheme == "https" && strings.Contains(err.Error(), "remote error: bad certificate") {
return serverResp, fmt.Errorf("The server probably has client authentication (--tlsverify) enabled. Please check your TLS client certification settings: %v", err)
}
return serverResp, fmt.Errorf("An error occurred trying to connect: %v", err)
}
if serverResp.statusCode < 200 || serverResp.statusCode >= 400 {
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return serverResp, err
}
if len(body) == 0 {
return serverResp, fmt.Errorf("Error: request returned %s for API route and version %s, check if the server supports the requested API version", http.StatusText(serverResp.statusCode), req.URL)
}
return serverResp, fmt.Errorf("Error response from daemon: %s", bytes.TrimSpace(body))
}
serverResp.body = resp.Body
serverResp.header = resp.Header
return serverResp, nil
}
func (cli *Client) newRequest(method, path string, query url.Values, body io.Reader, headers map[string][]string) (*http.Request, error) {
apiPath := cli.getAPIPath(path, query)
req, err := http.NewRequest(method, apiPath, body)
if err != nil {
return nil, err
}
// Add CLI Config's HTTP Headers BEFORE we set the Docker headers
// then the user can't change OUR headers
for k, v := range cli.customHTTPHeaders {
req.Header.Set(k, v)
}
if headers != nil {
for k, v := range headers {
req.Header[k] = v
}
}
return req, nil
}
func encodeData(data interface{}) (*bytes.Buffer, error) {
params := bytes.NewBuffer(nil)
if data != nil {
if err := json.NewEncoder(params).Encode(data); err != nil {
return nil, err
}
}
return params, nil
}
func ensureReaderClosed(response *serverResponse) {
if response != nil && response.body != nil {
response.body.Close()
}
}

28
api/client/lib/resize.go Normal file
View file

@ -0,0 +1,28 @@
package lib
import (
"net/url"
"strconv"
"github.com/docker/docker/api/types"
)
// ContainerResize changes the size of the tty for a container.
func (cli *Client) ContainerResize(options types.ResizeOptions) error {
return cli.resize("/containers/"+options.ID, options.Height, options.Width)
}
// ContainerExecResize changes the size of the tty for an exec process running inside a container.
func (cli *Client) ContainerExecResize(options types.ResizeOptions) error {
return cli.resize("/exec/"+options.ID, options.Height, options.Width)
}
func (cli *Client) resize(basePath string, height, width int) error {
query := url.Values{}
query.Set("h", strconv.Itoa(height))
query.Set("w", strconv.Itoa(width))
resp, err := cli.post(basePath+"/resize", query, nil, nil)
ensureReaderClosed(resp)
return err
}

38
api/client/lib/version.go Normal file
View file

@ -0,0 +1,38 @@
package lib
import (
"encoding/json"
"runtime"
"github.com/docker/docker/api"
"github.com/docker/docker/api/types"
"github.com/docker/docker/dockerversion"
"github.com/docker/docker/utils"
)
// SystemVersion returns information of the docker client and server host.
func (cli *Client) SystemVersion() (types.VersionResponse, error) {
client := &types.Version{
Version: dockerversion.Version,
APIVersion: api.Version,
GoVersion: runtime.Version(),
GitCommit: dockerversion.GitCommit,
BuildTime: dockerversion.BuildTime,
Os: runtime.GOOS,
Arch: runtime.GOARCH,
Experimental: utils.ExperimentalBuild(),
}
resp, err := cli.get("/version", nil, nil)
if err != nil {
return types.VersionResponse{Client: client}, err
}
defer ensureReaderClosed(resp)
var server types.Version
err = json.NewDecoder(resp.body).Decode(&server)
if err != nil {
return types.VersionResponse{Client: client}, err
}
return types.VersionResponse{Client: client, Server: &server}, nil
}

66
api/client/lib/volume.go Normal file
View file

@ -0,0 +1,66 @@
package lib
import (
"encoding/json"
"net/http"
"net/url"
"github.com/docker/docker/api/types"
"github.com/docker/docker/pkg/parsers/filters"
)
// VolumeList returns the volumes configured in the docker host.
func (cli *Client) VolumeList(filter filters.Args) (types.VolumesListResponse, error) {
var volumes types.VolumesListResponse
query := url.Values{}
if filter.Len() > 0 {
filterJSON, err := filters.ToParam(filter)
if err != nil {
return volumes, err
}
query.Set("filters", filterJSON)
}
resp, err := cli.get("/volumes", query, nil)
if err != nil {
return volumes, err
}
defer ensureReaderClosed(resp)
err = json.NewDecoder(resp.body).Decode(&volumes)
return volumes, err
}
// VolumeInspect returns the information about a specific volume in the docker host.
func (cli *Client) VolumeInspect(volumeID string) (types.Volume, error) {
var volume types.Volume
resp, err := cli.get("/volumes/"+volumeID, nil, nil)
if err != nil {
if resp.statusCode == http.StatusNotFound {
return volume, volumeNotFoundError{volumeID}
}
return volume, err
}
defer ensureReaderClosed(resp)
err = json.NewDecoder(resp.body).Decode(&volume)
return volume, err
}
// VolumeCreate creates a volume in the docker host.
func (cli *Client) VolumeCreate(options types.VolumeCreateRequest) (types.Volume, error) {
var volume types.Volume
resp, err := cli.post("/volumes/create", nil, options, nil)
if err != nil {
return volume, err
}
defer ensureReaderClosed(resp)
err = json.NewDecoder(resp.body).Decode(&volume)
return volume, err
}
// VolumeRemove removes a volume from the docker host.
func (cli *Client) VolumeRemove(volumeID string) error {
resp, err := cli.delete("/volumes/"+volumeID, nil, nil)
ensureReaderClosed(resp)
return err
}

24
api/client/lib/wait.go Normal file
View file

@ -0,0 +1,24 @@
package lib
import (
"encoding/json"
"github.com/docker/docker/api/types"
)
// ContainerWait pauses execution util a container is exits.
// It returns the API status code as response of its readiness.
func (cli *Client) ContainerWait(containerID string) (int, error) {
resp, err := cli.post("/containers/"+containerID+"/wait", nil, nil, nil)
if err != nil {
return -1, err
}
defer ensureReaderClosed(resp)
var res types.ContainerWaitResponse
if err := json.NewDecoder(resp.body).Decode(&res); err != nil {
return -1, err
}
return res.StatusCode, nil
}

View file

@ -17,26 +17,24 @@ func (cli *DockerCli) CmdLoad(args ...string) error {
cmd := Cli.Subcmd("load", nil, Cli.DockerCommands["load"].Description, true)
infile := cmd.String([]string{"i", "-input"}, "", "Read from a tar archive file, instead of STDIN")
cmd.Require(flag.Exact, 0)
cmd.ParseFlags(args, true)
var (
input io.Reader = cli.in
err error
)
var input io.Reader = cli.in
if *infile != "" {
input, err = os.Open(*infile)
file, err := os.Open(*infile)
if err != nil {
return err
}
defer file.Close()
input = file
}
sopts := &streamOpts{
rawTerminal: true,
in: input,
out: cli.out,
}
if _, err := cli.stream("POST", "/images/load", sopts); err != nil {
responseBody, err := cli.client.ImageLoad(input)
if err != nil {
return err
}
return nil
defer responseBody.Close()
_, err = io.Copy(cli.out, responseBody)
return err
}

View file

@ -2,14 +2,13 @@ package client
import (
"bufio"
"encoding/json"
"fmt"
"io"
"os"
"runtime"
"strings"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/client/lib"
Cli "github.com/docker/docker/cli"
"github.com/docker/docker/cliconfig"
flag "github.com/docker/docker/pkg/mflag"
@ -120,24 +119,15 @@ func (cli *DockerCli) CmdLogin(args ...string) error {
authconfig.ServerAddress = serverAddress
cli.configFile.AuthConfigs[serverAddress] = authconfig
serverResp, err := cli.call("POST", "/auth", cli.configFile.AuthConfigs[serverAddress], nil)
if serverResp.statusCode == 401 {
delete(cli.configFile.AuthConfigs, serverAddress)
if err2 := cli.configFile.Save(); err2 != nil {
fmt.Fprintf(cli.out, "WARNING: could not save config file: %v\n", err2)
}
return err
}
auth := cli.configFile.AuthConfigs[serverAddress]
response, err := cli.client.RegistryLogin(auth)
if err != nil {
return err
}
defer serverResp.body.Close()
var response types.AuthResponse
if err := json.NewDecoder(serverResp.body).Decode(&response); err != nil {
// Upon error, remove entry
delete(cli.configFile.AuthConfigs, serverAddress)
if lib.IsErrUnauthorized(err) {
delete(cli.configFile.AuthConfigs, serverAddress)
if err2 := cli.configFile.Save(); err2 != nil {
fmt.Fprintf(cli.out, "WARNING: could not save config file: %v\n", err2)
}
}
return err
}

View file

@ -1,15 +1,13 @@
package client
import (
"encoding/json"
"fmt"
"net/url"
"time"
"io"
"github.com/docker/docker/api/types"
Cli "github.com/docker/docker/cli"
flag "github.com/docker/docker/pkg/mflag"
"github.com/docker/docker/pkg/timeutils"
"github.com/docker/docker/pkg/stdcopy"
)
var validDrivers = map[string]bool{
@ -32,47 +30,34 @@ func (cli *DockerCli) CmdLogs(args ...string) error {
name := cmd.Arg(0)
serverResp, err := cli.call("GET", "/containers/"+name+"/json", nil, nil)
c, err := cli.client.ContainerInspect(name)
if err != nil {
return err
}
var c types.ContainerJSON
if err := json.NewDecoder(serverResp.body).Decode(&c); err != nil {
return err
}
if !validDrivers[c.HostConfig.LogConfig.Type] {
return fmt.Errorf("\"logs\" command is supported only for \"json-file\" and \"journald\" logging drivers (got: %s)", c.HostConfig.LogConfig.Type)
}
v := url.Values{}
v.Set("stdout", "1")
v.Set("stderr", "1")
if *since != "" {
ts, err := timeutils.GetTimestamp(*since, time.Now())
if err != nil {
return err
}
v.Set("since", ts)
options := types.ContainerLogsOptions{
ContainerID: name,
ShowStdout: true,
ShowStderr: true,
Since: *since,
Timestamps: *times,
Follow: *follow,
Tail: *tail,
}
if *times {
v.Set("timestamps", "1")
responseBody, err := cli.client.ContainerLogs(options)
if err != nil {
return err
}
defer responseBody.Close()
if *follow {
v.Set("follow", "1")
if c.Config.Tty {
_, err = io.Copy(cli.out, responseBody)
} else {
_, err = stdcopy.StdCopy(cli.out, cli.err, responseBody)
}
v.Set("tail", *tail)
sopts := &streamOpts{
rawTerminal: c.Config.Tty,
out: cli.out,
err: cli.err,
}
_, err = cli.stream("GET", "/containers/"+name+"/logs?"+v.Encode(), sopts)
return err
}

View file

@ -1,15 +1,10 @@
package client
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net"
"net/http"
"strings"
"text/tabwriter"
"text/template"
"github.com/docker/docker/api/types"
Cli "github.com/docker/docker/cli"
@ -76,12 +71,8 @@ func (cli *DockerCli) CmdNetworkCreate(args ...string) error {
Options: flOpts.GetAll(),
CheckDuplicate: true,
}
obj, _, err := readBody(cli.call("POST", "/networks/create", nc, nil))
if err != nil {
return err
}
var resp types.NetworkCreateResponse
err = json.Unmarshal(obj, &resp)
resp, err := cli.client.NetworkCreate(nc)
if err != nil {
return err
}
@ -95,15 +86,13 @@ func (cli *DockerCli) CmdNetworkCreate(args ...string) error {
func (cli *DockerCli) CmdNetworkRm(args ...string) error {
cmd := Cli.Subcmd("network rm", []string{"NETWORK [NETWORK...]"}, "Deletes one or more networks", false)
cmd.Require(flag.Min, 1)
err := cmd.ParseFlags(args, true)
if err != nil {
if err := cmd.ParseFlags(args, true); err != nil {
return err
}
status := 0
for _, net := range cmd.Args() {
_, _, err = readBody(cli.call("DELETE", "/networks/"+net, nil, nil))
if err != nil {
if err := cli.client.NetworkRemove(net); err != nil {
fmt.Fprintf(cli.err, "%s\n", err)
status = 1
continue
@ -121,14 +110,11 @@ func (cli *DockerCli) CmdNetworkRm(args ...string) error {
func (cli *DockerCli) CmdNetworkConnect(args ...string) error {
cmd := Cli.Subcmd("network connect", []string{"NETWORK CONTAINER"}, "Connects a container to a network", false)
cmd.Require(flag.Exact, 2)
err := cmd.ParseFlags(args, true)
if err != nil {
if err := cmd.ParseFlags(args, true); err != nil {
return err
}
nc := types.NetworkConnect{Container: cmd.Arg(1)}
_, _, err = readBody(cli.call("POST", "/networks/"+cmd.Arg(0)+"/connect", nc, nil))
return err
return cli.client.NetworkConnect(cmd.Arg(0), cmd.Arg(1))
}
// CmdNetworkDisconnect disconnects a container from a network
@ -137,14 +123,11 @@ func (cli *DockerCli) CmdNetworkConnect(args ...string) error {
func (cli *DockerCli) CmdNetworkDisconnect(args ...string) error {
cmd := Cli.Subcmd("network disconnect", []string{"NETWORK CONTAINER"}, "Disconnects container from a network", false)
cmd.Require(flag.Exact, 2)
err := cmd.ParseFlags(args, true)
if err != nil {
if err := cmd.ParseFlags(args, true); err != nil {
return err
}
nc := types.NetworkConnect{Container: cmd.Arg(1)}
_, _, err = readBody(cli.call("POST", "/networks/"+cmd.Arg(0)+"/disconnect", nc, nil))
return err
return cli.client.NetworkDisconnect(cmd.Arg(0), cmd.Arg(1))
}
// CmdNetworkLs lists all the netorks managed by docker daemon
@ -156,18 +139,11 @@ func (cli *DockerCli) CmdNetworkLs(args ...string) error {
noTrunc := cmd.Bool([]string{"-no-trunc"}, false, "Do not truncate the output")
cmd.Require(flag.Exact, 0)
err := cmd.ParseFlags(args, true)
if err != nil {
return err
}
obj, _, err := readBody(cli.call("GET", "/networks", nil, nil))
if err != nil {
if err := cmd.ParseFlags(args, true); err != nil {
return err
}
var networkResources []types.NetworkResource
err = json.Unmarshal(obj, &networkResources)
networkResources, err := cli.client.NetworkList()
if err != nil {
return err
}
@ -212,73 +188,12 @@ func (cli *DockerCli) CmdNetworkInspect(args ...string) error {
return err
}
var tmpl *template.Template
if *tmplStr != "" {
var err error
tmpl, err = template.New("").Funcs(funcMap).Parse(*tmplStr)
if err != nil {
return err
}
inspectSearcher := func(name string) (interface{}, []byte, error) {
i, err := cli.client.NetworkInspect(name)
return i, nil, err
}
status := 0
var networks []types.NetworkResource
buf := new(bytes.Buffer)
for _, name := range cmd.Args() {
obj, statusCode, err := readBody(cli.call("GET", "/networks/"+name, nil, nil))
if err != nil {
if statusCode == http.StatusNotFound {
fmt.Fprintf(cli.err, "Error: No such network: %s\n", name)
} else {
fmt.Fprintf(cli.err, "%s\n", err)
}
status = 1
continue
}
var networkResource types.NetworkResource
if err := json.NewDecoder(bytes.NewReader(obj)).Decode(&networkResource); err != nil {
return err
}
if tmpl == nil {
networks = append(networks, networkResource)
continue
}
if err := tmpl.Execute(buf, &networkResource); err != nil {
if err := tmpl.Execute(buf, &networkResource); err != nil {
fmt.Fprintf(cli.err, "%s\n", err)
return Cli.StatusError{StatusCode: 1}
}
}
buf.WriteString("\n")
}
if tmpl != nil {
if _, err := io.Copy(cli.out, buf); err != nil {
return err
}
return nil
}
if len(networks) == 0 {
io.WriteString(cli.out, "[]")
}
b, err := json.MarshalIndent(networks, "", " ")
if err != nil {
return err
}
if _, err := io.Copy(cli.out, bytes.NewReader(b)); err != nil {
return err
}
io.WriteString(cli.out, "\n")
if status != 0 {
return Cli.StatusError{StatusCode: status}
}
return nil
return cli.inspectElements(*tmplStr, cmd.Args(), inspectSearcher)
}
// Consolidates the ipam configuration as a group from different related configurations

View file

@ -18,7 +18,7 @@ func (cli *DockerCli) CmdPause(args ...string) error {
var errNames []string
for _, name := range cmd.Args() {
if _, _, err := readBody(cli.call("POST", fmt.Sprintf("/containers/%s/pause", name), nil, nil)); err != nil {
if err := cli.client.ContainerPause(name); err != nil {
fmt.Fprintf(cli.err, "%s\n", err)
errNames = append(errNames, name)
} else {

View file

@ -1,7 +1,6 @@
package client
import (
"encoding/json"
"fmt"
"strings"
@ -20,23 +19,11 @@ func (cli *DockerCli) CmdPort(args ...string) error {
cmd.ParseFlags(args, true)
serverResp, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/json", nil, nil)
c, err := cli.client.ContainerInspect(cmd.Arg(0))
if err != nil {
return err
}
defer serverResp.body.Close()
var c struct {
NetworkSettings struct {
Ports nat.PortMap
}
}
if err := json.NewDecoder(serverResp.body).Decode(&c); err != nil {
return err
}
if cmd.NArg() == 2 {
var (
port = cmd.Arg(1)

View file

@ -1,10 +1,6 @@
package client
import (
"encoding/json"
"net/url"
"strconv"
"github.com/docker/docker/api/client/ps"
"github.com/docker/docker/api/types"
Cli "github.com/docker/docker/cli"
@ -21,7 +17,6 @@ func (cli *DockerCli) CmdPs(args ...string) error {
err error
psFilterArgs = filters.NewArgs()
v = url.Values{}
cmd = Cli.Subcmd("ps", nil, Cli.DockerCommands["ps"].Description, true)
quiet = cmd.Bool([]string{"q", "-quiet"}, false, "Only display numeric IDs")
@ -44,26 +39,6 @@ func (cli *DockerCli) CmdPs(args ...string) error {
*last = 1
}
if *all {
v.Set("all", "1")
}
if *last != -1 {
v.Set("limit", strconv.Itoa(*last))
}
if *since != "" {
v.Set("since", *since)
}
if *before != "" {
v.Set("before", *before)
}
if *size {
v.Set("size", "1")
}
// Consolidate all filter flags, and sanity check them.
// They'll get processed in the daemon/server.
for _, f := range flFilter.GetAll() {
@ -72,27 +47,20 @@ func (cli *DockerCli) CmdPs(args ...string) error {
}
}
if psFilterArgs.Len() > 0 {
filterJSON, err := filters.ToParam(psFilterArgs)
if err != nil {
return err
}
v.Set("filters", filterJSON)
options := types.ContainerListOptions{
All: *all,
Limit: *last,
Since: *since,
Before: *before,
Size: *size,
Filter: psFilterArgs,
}
serverResp, err := cli.call("GET", "/containers/json?"+v.Encode(), nil, nil)
containers, err := cli.client.ContainerList(options)
if err != nil {
return err
}
defer serverResp.body.Close()
containers := []types.Container{}
if err := json.NewDecoder(serverResp.body).Decode(&containers); err != nil {
return err
}
f := *format
if len(f) == 0 {
if len(cli.PsFormat()) > 0 && !*quiet {

View file

@ -3,10 +3,13 @@ package client
import (
"errors"
"fmt"
"net/url"
"github.com/docker/distribution/reference"
"github.com/docker/docker/api/client/lib"
"github.com/docker/docker/api/types"
Cli "github.com/docker/docker/cli"
"github.com/docker/docker/cliconfig"
"github.com/docker/docker/pkg/jsonmessage"
flag "github.com/docker/docker/pkg/mflag"
"github.com/docker/docker/registry"
tagpkg "github.com/docker/docker/tag"
@ -62,15 +65,34 @@ func (cli *DockerCli) CmdPull(args ...string) error {
return err
}
authConfig := registry.ResolveAuthConfig(cli.configFile, repoInfo.Index)
requestPrivilege := cli.registryAuthenticationPrivilegedFunc(repoInfo.Index, "pull")
if isTrusted() && !ref.HasDigest() {
// Check if tag is digest
authConfig := registry.ResolveAuthConfig(cli.configFile, repoInfo.Index)
return cli.trustedPull(repoInfo, ref, authConfig)
return cli.trustedPull(repoInfo, ref, authConfig, requestPrivilege)
}
v := url.Values{}
v.Set("fromImage", distributionRef.String())
_, _, err = cli.clientRequestAttemptLogin("POST", "/images/create?"+v.Encode(), nil, cli.out, repoInfo.Index, "pull")
return err
return cli.imagePullPrivileged(authConfig, distributionRef.String(), "", requestPrivilege)
}
func (cli *DockerCli) imagePullPrivileged(authConfig cliconfig.AuthConfig, imageID, tag string, requestPrivilege lib.RequestPrivilegeFunc) error {
encodedAuth, err := authConfig.EncodeToBase64()
if err != nil {
return err
}
options := types.ImagePullOptions{
ImageID: imageID,
Tag: tag,
RegistryAuth: encodedAuth,
}
responseBody, err := cli.client.ImagePull(options, requestPrivilege)
if err != nil {
return err
}
defer responseBody.Close()
return jsonmessage.DisplayJSONMessagesStream(responseBody, cli.out, cli.outFd, cli.isTerminalOut)
}

View file

@ -3,10 +3,14 @@ package client
import (
"errors"
"fmt"
"net/url"
"io"
"github.com/docker/distribution/reference"
"github.com/docker/docker/api/client/lib"
"github.com/docker/docker/api/types"
Cli "github.com/docker/docker/cli"
"github.com/docker/docker/cliconfig"
"github.com/docker/docker/pkg/jsonmessage"
flag "github.com/docker/docker/pkg/mflag"
"github.com/docker/docker/registry"
)
@ -53,13 +57,30 @@ func (cli *DockerCli) CmdPush(args ...string) error {
return fmt.Errorf("You cannot push a \"root\" repository. Please rename your repository to <user>/<repo> (ex: %s/%s)", username, repoInfo.LocalName)
}
requestPrivilege := cli.registryAuthenticationPrivilegedFunc(repoInfo.Index, "push")
if isTrusted() {
return cli.trustedPush(repoInfo, tag, authConfig)
return cli.trustedPush(repoInfo, tag, authConfig, requestPrivilege)
}
v := url.Values{}
v.Set("tag", tag)
_, _, err = cli.clientRequestAttemptLogin("POST", "/images/"+ref.Name()+"/push?"+v.Encode(), nil, cli.out, repoInfo.Index, "push")
return err
return cli.imagePushPrivileged(authConfig, ref.Name(), tag, cli.out, requestPrivilege)
}
func (cli *DockerCli) imagePushPrivileged(authConfig cliconfig.AuthConfig, imageID, tag string, outputStream io.Writer, requestPrivilege lib.RequestPrivilegeFunc) error {
encodedAuth, err := authConfig.EncodeToBase64()
if err != nil {
return err
}
options := types.ImagePushOptions{
ImageID: imageID,
Tag: tag,
RegistryAuth: encodedAuth,
}
responseBody, err := cli.client.ImagePush(options, requestPrivilege)
if err != nil {
return err
}
defer responseBody.Close()
return jsonmessage.DisplayJSONMessagesStream(responseBody, outputStream, cli.outFd, cli.isTerminalOut)
}

View file

@ -24,7 +24,7 @@ func (cli *DockerCli) CmdRename(args ...string) error {
return fmt.Errorf("Error: Neither old nor new names may be empty")
}
if _, _, err := readBody(cli.call("POST", fmt.Sprintf("/containers/%s/rename?name=%s", oldName, newName), nil, nil)); err != nil {
if err := cli.client.ContainerRename(oldName, newName); err != nil {
fmt.Fprintf(cli.err, "%s\n", err)
return fmt.Errorf("Error: failed to rename container named %s", oldName)
}

View file

@ -2,8 +2,6 @@ package client
import (
"fmt"
"net/url"
"strconv"
Cli "github.com/docker/docker/cli"
flag "github.com/docker/docker/pkg/mflag"
@ -19,13 +17,9 @@ func (cli *DockerCli) CmdRestart(args ...string) error {
cmd.ParseFlags(args, true)
v := url.Values{}
v.Set("t", strconv.Itoa(*nSeconds))
var errNames []string
for _, name := range cmd.Args() {
_, _, err := readBody(cli.call("POST", "/containers/"+name+"/restart?"+v.Encode(), nil, nil))
if err != nil {
if err := cli.client.ContainerRestart(name, *nSeconds); err != nil {
fmt.Fprintf(cli.err, "%s\n", err)
errNames = append(errNames, name)
} else {

View file

@ -2,9 +2,9 @@ package client
import (
"fmt"
"net/url"
"strings"
"github.com/docker/docker/api/types"
Cli "github.com/docker/docker/cli"
flag "github.com/docker/docker/pkg/mflag"
)
@ -21,18 +21,6 @@ func (cli *DockerCli) CmdRm(args ...string) error {
cmd.ParseFlags(args, true)
val := url.Values{}
if *v {
val.Set("v", "1")
}
if *link {
val.Set("link", "1")
}
if *force {
val.Set("force", "1")
}
var errNames []string
for _, name := range cmd.Args() {
if name == "" {
@ -40,8 +28,14 @@ func (cli *DockerCli) CmdRm(args ...string) error {
}
name = strings.Trim(name, "/")
_, _, err := readBody(cli.call("DELETE", "/containers/"+name+"?"+val.Encode(), nil, nil))
if err != nil {
options := types.ContainerRemoveOptions{
ContainerID: name,
RemoveVolumes: *v,
RemoveLinks: *link,
Force: *force,
}
if err := cli.client.ContainerRemove(options); err != nil {
fmt.Fprintf(cli.err, "%s\n", err)
errNames = append(errNames, name)
} else {

View file

@ -1,7 +1,6 @@
package client
import (
"encoding/json"
"fmt"
"net/url"
@ -31,20 +30,17 @@ func (cli *DockerCli) CmdRmi(args ...string) error {
var errNames []string
for _, name := range cmd.Args() {
serverResp, err := cli.call("DELETE", "/images/"+name+"?"+v.Encode(), nil, nil)
options := types.ImageRemoveOptions{
ImageID: name,
Force: *force,
PruneChildren: !*noprune,
}
dels, err := cli.client.ImageRemove(options)
if err != nil {
fmt.Fprintf(cli.err, "%s\n", err)
errNames = append(errNames, name)
} else {
defer serverResp.body.Close()
dels := []types.ImageDelete{}
if err := json.NewDecoder(serverResp.body).Decode(&dels); err != nil {
fmt.Fprintf(cli.err, "%s\n", err)
errNames = append(errNames, name)
continue
}
for _, del := range dels {
if del.Deleted != "" {
fmt.Fprintf(cli.out, "Deleted: %s\n", del.Deleted)

View file

@ -3,12 +3,12 @@ package client
import (
"fmt"
"io"
"net/url"
"os"
"runtime"
"strings"
"github.com/Sirupsen/logrus"
"github.com/docker/docker/api/types"
Cli "github.com/docker/docker/cli"
derr "github.com/docker/docker/errors"
"github.com/docker/docker/opts"
@ -168,70 +168,57 @@ func (cli *DockerCli) CmdRun(args ...string) error {
if *flAutoRemove && (hostConfig.RestartPolicy.IsAlways() || hostConfig.RestartPolicy.IsOnFailure()) {
return ErrConflictRestartPolicyAndAutoRemove
}
// We need to instantiate the chan because the select needs it. It can
// be closed but can't be uninitialized.
hijacked := make(chan io.Closer)
// Block the return until the chan gets closed
defer func() {
logrus.Debugf("End of CmdRun(), Waiting for hijack to finish.")
if _, ok := <-hijacked; ok {
fmt.Fprintln(cli.err, "Hijack did not finish (chan still open)")
}
}()
if config.AttachStdin || config.AttachStdout || config.AttachStderr {
var (
out, stderr io.Writer
in io.ReadCloser
v = url.Values{}
)
v.Set("stream", "1")
if config.AttachStdin {
v.Set("stdin", "1")
in = cli.in
}
if config.AttachStdout {
v.Set("stdout", "1")
out = cli.out
}
if config.AttachStderr {
v.Set("stderr", "1")
if config.Tty {
stderr = cli.out
} else {
stderr = cli.err
}
}
errCh = promise.Go(func() error {
return cli.hijack("POST", "/containers/"+createResponse.ID+"/attach?"+v.Encode(), config.Tty, in, out, stderr, hijacked, nil)
})
} else {
close(hijacked)
}
// Acknowledge the hijack before starting
select {
case closer := <-hijacked:
// Make sure that the hijack gets closed when returning (results
// in closing the hijack chan and freeing server's goroutines)
if closer != nil {
defer closer.Close()
options := types.ContainerAttachOptions{
ContainerID: createResponse.ID,
Stream: true,
Stdin: config.AttachStdin,
Stdout: config.AttachStdout,
Stderr: config.AttachStderr,
}
case err := <-errCh:
resp, err := cli.client.ContainerAttach(options)
if err != nil {
logrus.Debugf("Error hijack: %s", err)
return err
}
errCh = promise.Go(func() error {
return cli.holdHijackedConnection(config.Tty, in, out, stderr, resp)
})
}
defer func() {
if *flAutoRemove {
if _, _, err = readBody(cli.call("DELETE", "/containers/"+createResponse.ID+"?v=1", nil, nil)); err != nil {
options := types.ContainerRemoveOptions{
ContainerID: createResponse.ID,
RemoveVolumes: true,
}
if err := cli.client.ContainerRemove(options); err != nil {
fmt.Fprintf(cli.err, "Error deleting container: %s\n", err)
}
}
}()
//start the container
if _, _, err := readBody(cli.call("POST", "/containers/"+createResponse.ID+"/start", nil, nil)); err != nil {
if err := cli.client.ContainerStart(createResponse.ID); err != nil {
cmd.ReportError(err.Error(), false)
return runStartContainerErr(err)
}
@ -262,7 +249,7 @@ func (cli *DockerCli) CmdRun(args ...string) error {
if *flAutoRemove {
// Autoremove: wait for the container to finish, retrieve
// the exit code and remove the container
if _, _, err := readBody(cli.call("POST", "/containers/"+createResponse.ID+"/wait", nil, nil)); err != nil {
if status, err = cli.client.ContainerWait(createResponse.ID); err != nil {
return runStartContainerErr(err)
}
if _, status, err = getExitCode(cli, createResponse.ID); err != nil {
@ -272,7 +259,7 @@ func (cli *DockerCli) CmdRun(args ...string) error {
// No Autoremove: Simply retrieve the exit code
if !config.Tty {
// In non-TTY mode, we can't detach, so we must wait for container exit
if status, err = waitForExit(cli, createResponse.ID); err != nil {
if status, err = cli.client.ContainerWait(createResponse.ID); err != nil {
return err
}
} else {

View file

@ -2,7 +2,7 @@ package client
import (
"errors"
"net/url"
"io"
"os"
Cli "github.com/docker/docker/cli"
@ -35,18 +35,12 @@ func (cli *DockerCli) CmdSave(args ...string) error {
}
}
sopts := &streamOpts{
rawTerminal: true,
out: output,
}
v := url.Values{}
for _, arg := range cmd.Args() {
v.Add("names", arg)
}
if _, err := cli.stream("GET", "/images/get?"+v.Encode(), sopts); err != nil {
responseBody, err := cli.client.ImageSave(cmd.Args())
if err != nil {
return err
}
defer responseBody.Close()
return nil
_, err = io.Copy(output, responseBody)
return err
}

View file

@ -1,26 +1,19 @@
package client
import (
"encoding/json"
"fmt"
"net/url"
"sort"
"strings"
"text/tabwriter"
"github.com/docker/docker/api/types"
Cli "github.com/docker/docker/cli"
flag "github.com/docker/docker/pkg/mflag"
"github.com/docker/docker/pkg/stringutils"
"github.com/docker/docker/registry"
)
// ByStars sorts search results in ascending order by number of stars.
type ByStars []registry.SearchResult
func (r ByStars) Len() int { return len(r) }
func (r ByStars) Swap(i, j int) { r[i], r[j] = r[j], r[i] }
func (r ByStars) Less(i, j int) bool { return r[i].StarCount < r[j].StarCount }
// CmdSearch searches the Docker Hub for images.
//
// Usage: docker search [OPTIONS] TERM
@ -42,19 +35,26 @@ func (cli *DockerCli) CmdSearch(args ...string) error {
return err
}
rdr, _, err := cli.clientRequestAttemptLogin("GET", "/images/search?"+v.Encode(), nil, nil, indexInfo, "search")
authConfig := registry.ResolveAuthConfig(cli.configFile, indexInfo)
requestPrivilege := cli.registryAuthenticationPrivilegedFunc(indexInfo, "search")
encodedAuth, err := authConfig.EncodeToBase64()
if err != nil {
return err
}
defer rdr.Close()
options := types.ImageSearchOptions{
Term: name,
RegistryAuth: encodedAuth,
}
results := ByStars{}
if err := json.NewDecoder(rdr).Decode(&results); err != nil {
unorderedResults, err := cli.client.ImageSearch(options, requestPrivilege)
if err != nil {
return err
}
sort.Sort(sort.Reverse(results))
results := searchResultsByStars(unorderedResults)
sort.Sort(results)
w := tabwriter.NewWriter(cli.out, 10, 1, 3, ' ', 0)
fmt.Fprintf(w, "NAME\tDESCRIPTION\tSTARS\tOFFICIAL\tAUTOMATED\n")
@ -81,3 +81,10 @@ func (cli *DockerCli) CmdSearch(args ...string) error {
w.Flush()
return nil
}
// SearchResultsByStars sorts search results in descending order by number of stars.
type searchResultsByStars []registry.SearchResult
func (r searchResultsByStars) Len() int { return len(r) }
func (r searchResultsByStars) Swap(i, j int) { r[i], r[j] = r[j], r[i] }
func (r searchResultsByStars) Less(i, j int) bool { return r[j].StarCount < r[i].StarCount }

View file

@ -1,11 +1,10 @@
package client
import (
"encoding/json"
"fmt"
"io"
"net/url"
"os"
"strings"
"github.com/Sirupsen/logrus"
"github.com/docker/docker/api/types"
@ -34,7 +33,8 @@ func (cli *DockerCli) forwardAllSignals(cid string) chan os.Signal {
fmt.Fprintf(cli.err, "Unsupported signal: %v. Discarding.\n", s)
continue
}
if _, _, err := readBody(cli.call("POST", fmt.Sprintf("/containers/%s/kill?signal=%s", cid, sig), nil, nil)); err != nil {
if err := cli.client.ContainerKill(cid, sig); err != nil {
logrus.Debugf("Error sending signal: %s", err)
}
}
@ -53,119 +53,91 @@ func (cli *DockerCli) CmdStart(args ...string) error {
cmd.ParseFlags(args, true)
var (
cErr chan error
tty bool
)
if *attach || *openStdin {
// We're going to attach to a container.
// 1. Ensure we only have one container.
if cmd.NArg() > 1 {
return fmt.Errorf("You cannot start and attach multiple containers at once.")
}
serverResp, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/json", nil, nil)
// 2. Attach to the container.
containerID := cmd.Arg(0)
c, err := cli.client.ContainerInspect(containerID)
if err != nil {
return err
}
defer serverResp.body.Close()
var c types.ContainerJSON
if err := json.NewDecoder(serverResp.body).Decode(&c); err != nil {
return err
}
tty = c.Config.Tty
if !tty {
sigc := cli.forwardAllSignals(cmd.Arg(0))
if !c.Config.Tty {
sigc := cli.forwardAllSignals(containerID)
defer signal.StopCatch(sigc)
}
options := types.ContainerAttachOptions{
ContainerID: containerID,
Stream: true,
Stdin: *openStdin && c.Config.OpenStdin,
Stdout: true,
Stderr: true,
}
var in io.ReadCloser
v := url.Values{}
v.Set("stream", "1")
if *openStdin && c.Config.OpenStdin {
v.Set("stdin", "1")
if options.Stdin {
in = cli.in
}
v.Set("stdout", "1")
v.Set("stderr", "1")
resp, err := cli.client.ContainerAttach(options)
if err != nil {
return err
}
defer resp.Close()
hijacked := make(chan io.Closer)
// Block the return until the chan gets closed
defer func() {
logrus.Debugf("CmdStart() returned, defer waiting for hijack to finish.")
if _, ok := <-hijacked; ok {
fmt.Fprintln(cli.err, "Hijack did not finish (chan still open)")
}
cli.in.Close()
}()
cErr = promise.Go(func() error {
return cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), tty, in, cli.out, cli.err, hijacked, nil)
cErr := promise.Go(func() error {
return cli.holdHijackedConnection(c.Config.Tty, in, cli.out, cli.err, resp)
})
// Acknowledge the hijack before starting
select {
case closer := <-hijacked:
// Make sure that the hijack gets closed when returning (results
// in closing the hijack chan and freeing server's goroutines)
if closer != nil {
defer closer.Close()
}
case err := <-cErr:
if err != nil {
return err
}
// 3. Start the container.
if err := cli.client.ContainerStart(containerID); err != nil {
return err
}
}
var encounteredError error
var errNames []string
for _, name := range cmd.Args() {
_, _, err := readBody(cli.call("POST", "/containers/"+name+"/start", nil, nil))
if err != nil {
if !*attach && !*openStdin {
// attach and openStdin is false means it could be starting multiple containers
// when a container start failed, show the error message and start next
fmt.Fprintf(cli.err, "%s\n", err)
errNames = append(errNames, name)
} else {
encounteredError = err
}
} else {
if !*attach && !*openStdin {
fmt.Fprintf(cli.out, "%s\n", name)
}
}
}
if len(errNames) > 0 {
encounteredError = fmt.Errorf("Error: failed to start containers: %v", errNames)
}
if encounteredError != nil {
return encounteredError
}
if *openStdin || *attach {
if tty && cli.isTerminalOut {
if err := cli.monitorTtySize(cmd.Arg(0), false); err != nil {
// 4. Wait for attachement to break.
if c.Config.Tty && cli.isTerminalOut {
if err := cli.monitorTtySize(containerID, false); err != nil {
fmt.Fprintf(cli.err, "Error monitoring TTY size: %s\n", err)
}
}
if attchErr := <-cErr; attchErr != nil {
return attchErr
}
_, status, err := getExitCode(cli, cmd.Arg(0))
_, status, err := getExitCode(cli, containerID)
if err != nil {
return err
}
if status != 0 {
return Cli.StatusError{StatusCode: status}
}
} else {
// We're not going to attach to anything.
// Start as many containers as we want.
return cli.startContainersWithoutAttachments(cmd.Args())
}
return nil
}
func (cli *DockerCli) startContainersWithoutAttachments(containerIDs []string) error {
var failedContainers []string
for _, containerID := range containerIDs {
if err := cli.client.ContainerStart(containerID); err != nil {
fmt.Fprintf(cli.err, "%s\n", err)
failedContainers = append(failedContainers, containerID)
} else {
fmt.Fprintf(cli.out, "%s\n", containerID)
}
}
if len(failedContainers) > 0 {
return fmt.Errorf("Error: failed to start containers: %v", strings.Join(failedContainers, ", "))
}
return nil
}

View file

@ -4,7 +4,6 @@ import (
"encoding/json"
"fmt"
"io"
"net/url"
"sort"
"strings"
"sync"
@ -37,26 +36,19 @@ type stats struct {
}
func (s *containerStats) Collect(cli *DockerCli, streamStats bool) {
v := url.Values{}
if streamStats {
v.Set("stream", "1")
} else {
v.Set("stream", "0")
}
serverResp, err := cli.call("GET", "/containers/"+s.Name+"/stats?"+v.Encode(), nil, nil)
responseBody, err := cli.client.ContainerStats(s.Name, streamStats)
if err != nil {
s.mu.Lock()
s.err = err
s.mu.Unlock()
return
}
defer serverResp.body.Close()
defer responseBody.Close()
var (
previousCPU uint64
previousSystem uint64
dec = json.NewDecoder(serverResp.body)
dec = json.NewDecoder(responseBody)
u = make(chan error, 1)
)
go func() {
@ -156,18 +148,13 @@ func (cli *DockerCli) CmdStats(args ...string) error {
showAll := len(names) == 0
if showAll {
v := url.Values{}
if *all {
v.Set("all", "1")
options := types.ContainerListOptions{
All: *all,
}
body, _, err := readBody(cli.call("GET", "/containers/json?"+v.Encode(), nil, nil))
cs, err := cli.client.ContainerList(options)
if err != nil {
return err
}
var cs []types.Container
if err := json.Unmarshal(body, &cs); err != nil {
return err
}
for _, c := range cs {
names = append(names, c.ID[:12])
}
@ -202,14 +189,15 @@ func (cli *DockerCli) CmdStats(args ...string) error {
err error
}
getNewContainers := func(c chan<- watch) {
res, err := cli.call("GET", "/events", nil, nil)
options := types.EventsOptions{}
resBody, err := cli.client.Events(options)
if err != nil {
c <- watch{err: err}
return
}
defer res.body.Close()
defer resBody.Close()
dec := json.NewDecoder(res.body)
dec := json.NewDecoder(resBody)
for {
var j *jsonmessage.JSONMessage
if err := dec.Decode(&j); err != nil {

View file

@ -2,8 +2,6 @@ package client
import (
"fmt"
"net/url"
"strconv"
Cli "github.com/docker/docker/cli"
flag "github.com/docker/docker/pkg/mflag"
@ -21,13 +19,9 @@ func (cli *DockerCli) CmdStop(args ...string) error {
cmd.ParseFlags(args, true)
v := url.Values{}
v.Set("t", strconv.Itoa(*nSeconds))
var errNames []string
for _, name := range cmd.Args() {
_, _, err := readBody(cli.call("POST", "/containers/"+name+"/stop?"+v.Encode(), nil, nil))
if err != nil {
if err := cli.client.ContainerStop(name, *nSeconds); err != nil {
fmt.Fprintf(cli.err, "%s\n", err)
errNames = append(errNames, name)
} else {

View file

@ -2,9 +2,9 @@ package client
import (
"errors"
"net/url"
"github.com/docker/distribution/reference"
"github.com/docker/docker/api/types"
Cli "github.com/docker/docker/cli"
flag "github.com/docker/docker/pkg/mflag"
"github.com/docker/docker/registry"
@ -20,7 +20,6 @@ func (cli *DockerCli) CmdTag(args ...string) error {
cmd.ParseFlags(args, true)
v := url.Values{}
ref, err := reference.ParseNamed(cmd.Arg(1))
if err != nil {
return err
@ -41,15 +40,13 @@ func (cli *DockerCli) CmdTag(args ...string) error {
if err := registry.ValidateRepositoryName(ref); err != nil {
return err
}
v.Set("repo", ref.Name())
v.Set("tag", tag)
if *force {
v.Set("force", "1")
options := types.ImageTagOptions{
ImageID: cmd.Arg(0),
RepositoryName: ref.Name(),
Tag: tag,
Force: *force,
}
if _, _, err := readBody(cli.call("POST", "/images/"+cmd.Arg(0)+"/tag?"+v.Encode(), nil, nil)); err != nil {
return err
}
return nil
return cli.client.ImageTag(options)
}

View file

@ -1,13 +1,10 @@
package client
import (
"encoding/json"
"fmt"
"net/url"
"strings"
"text/tabwriter"
"github.com/docker/docker/api/types"
Cli "github.com/docker/docker/cli"
flag "github.com/docker/docker/pkg/mflag"
)
@ -21,23 +18,16 @@ func (cli *DockerCli) CmdTop(args ...string) error {
cmd.ParseFlags(args, true)
val := url.Values{}
var arguments []string
if cmd.NArg() > 1 {
val.Set("ps_args", strings.Join(cmd.Args()[1:], " "))
arguments = cmd.Args()[1:]
}
serverResp, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/top?"+val.Encode(), nil, nil)
procList, err := cli.client.ContainerTop(cmd.Arg(0), arguments)
if err != nil {
return err
}
defer serverResp.body.Close()
procList := types.ContainerProcessList{}
if err := json.NewDecoder(serverResp.body).Decode(&procList); err != nil {
return err
}
w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0)
fmt.Fprintln(w, strings.Join(procList.Titles, "\t"))

View file

@ -22,6 +22,8 @@ import (
"github.com/docker/distribution/reference"
"github.com/docker/distribution/registry/client/auth"
"github.com/docker/distribution/registry/client/transport"
"github.com/docker/docker/api/client/lib"
"github.com/docker/docker/api/types"
"github.com/docker/docker/cliconfig"
"github.com/docker/docker/pkg/ansiescape"
"github.com/docker/docker/pkg/ioutils"
@ -250,16 +252,15 @@ func (cli *DockerCli) trustedReference(ref reference.NamedTagged) (reference.Can
func (cli *DockerCli) tagTrusted(trustedRef reference.Canonical, ref reference.NamedTagged) error {
fmt.Fprintf(cli.out, "Tagging %s as %s\n", trustedRef.String(), ref.String())
tv := url.Values{}
tv.Set("repo", trustedRef.Name())
tv.Set("tag", ref.Tag())
tv.Set("force", "1")
if _, _, err := readBody(cli.call("POST", "/images/"+trustedRef.String()+"/tag?"+tv.Encode(), nil, nil)); err != nil {
return err
options := types.ImageTagOptions{
ImageID: trustedRef.String(),
RepositoryName: trustedRef.Name(),
Tag: ref.Tag(),
Force: true,
}
return nil
return cli.client.ImageTag(options)
}
func notaryError(err error) error {
@ -278,11 +279,8 @@ func notaryError(err error) error {
return err
}
func (cli *DockerCli) trustedPull(repoInfo *registry.RepositoryInfo, ref registry.Reference, authConfig cliconfig.AuthConfig) error {
var (
v = url.Values{}
refs = []target{}
)
func (cli *DockerCli) trustedPull(repoInfo *registry.RepositoryInfo, ref registry.Reference, authConfig cliconfig.AuthConfig, requestPrivilege lib.RequestPrivilegeFunc) error {
var refs []target
notaryRepo, err := cli.getNotaryRepository(repoInfo, authConfig)
if err != nil {
@ -317,17 +315,14 @@ func (cli *DockerCli) trustedPull(repoInfo *registry.RepositoryInfo, ref registr
refs = append(refs, r)
}
v.Set("fromImage", repoInfo.LocalName.Name())
for i, r := range refs {
displayTag := r.reference.String()
if displayTag != "" {
displayTag = ":" + displayTag
}
fmt.Fprintf(cli.out, "Pull (%d of %d): %s%s@%s\n", i+1, len(refs), repoInfo.LocalName, displayTag, r.digest)
v.Set("tag", r.digest.String())
_, _, err = cli.clientRequestAttemptLogin("POST", "/images/create?"+v.Encode(), nil, cli.out, repoInfo.Index, "pull")
if err != nil {
if err := cli.imagePullPrivileged(authConfig, repoInfo.LocalName.Name(), r.digest.String(), requestPrivilege); err != nil {
return err
}
@ -385,20 +380,18 @@ func targetStream(in io.Writer) (io.WriteCloser, <-chan []target) {
return ioutils.NewWriteCloserWrapper(out, w.Close), targetChan
}
func (cli *DockerCli) trustedPush(repoInfo *registry.RepositoryInfo, tag string, authConfig cliconfig.AuthConfig) error {
func (cli *DockerCli) trustedPush(repoInfo *registry.RepositoryInfo, tag string, authConfig cliconfig.AuthConfig, requestPrivilege lib.RequestPrivilegeFunc) error {
streamOut, targetChan := targetStream(cli.out)
v := url.Values{}
v.Set("tag", tag)
reqError := cli.imagePushPrivileged(authConfig, repoInfo.LocalName.Name(), tag, streamOut, requestPrivilege)
_, _, err := cli.clientRequestAttemptLogin("POST", "/images/"+repoInfo.LocalName.Name()+"/push?"+v.Encode(), nil, streamOut, repoInfo.Index, "push")
// Close stream channel to finish target parsing
if err := streamOut.Close(); err != nil {
return err
}
// Check error from request
if err != nil {
return err
if reqError != nil {
return reqError
}
// Get target results

View file

@ -18,7 +18,7 @@ func (cli *DockerCli) CmdUnpause(args ...string) error {
var errNames []string
for _, name := range cmd.Args() {
if _, _, err := readBody(cli.call("POST", fmt.Sprintf("/containers/%s/unpause", name), nil, nil)); err != nil {
if err := cli.client.ContainerUnpause(name); err != nil {
fmt.Fprintf(cli.err, "%s\n", err)
errNames = append(errNames, name)
} else {

View file

@ -1,242 +1,33 @@
package client
import (
"bytes"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"os"
gosignal "os/signal"
"runtime"
"strconv"
"strings"
"time"
"github.com/Sirupsen/logrus"
"github.com/docker/docker/api"
"github.com/docker/docker/api/client/lib"
"github.com/docker/docker/api/types"
"github.com/docker/docker/cliconfig"
"github.com/docker/docker/dockerversion"
"github.com/docker/docker/pkg/jsonmessage"
"github.com/docker/docker/pkg/signal"
"github.com/docker/docker/pkg/stdcopy"
"github.com/docker/docker/pkg/term"
"github.com/docker/docker/registry"
"github.com/docker/docker/utils"
)
var (
errConnectionFailed = errors.New("Cannot connect to the Docker daemon. Is the docker daemon running on this host?")
)
type serverResponse struct {
body io.ReadCloser
header http.Header
statusCode int
}
// HTTPClient creates a new HTTP client with the cli's client transport instance.
func (cli *DockerCli) HTTPClient() *http.Client {
return &http.Client{Transport: cli.transport}
}
func (cli *DockerCli) encodeData(data interface{}) (*bytes.Buffer, error) {
params := bytes.NewBuffer(nil)
if data != nil {
if err := json.NewEncoder(params).Encode(data); err != nil {
return nil, err
}
}
return params, nil
}
func (cli *DockerCli) clientRequest(method, path string, in io.Reader, headers map[string][]string) (*serverResponse, error) {
serverResp := &serverResponse{
body: nil,
statusCode: -1,
}
expectedPayload := (method == "POST" || method == "PUT")
if expectedPayload && in == nil {
in = bytes.NewReader([]byte{})
}
req, err := http.NewRequest(method, fmt.Sprintf("%s/v%s%s", cli.basePath, api.Version, path), in)
if err != nil {
return serverResp, err
}
// Add CLI Config's HTTP Headers BEFORE we set the Docker headers
// then the user can't change OUR headers
for k, v := range cli.configFile.HTTPHeaders {
req.Header.Set(k, v)
}
req.Header.Set("User-Agent", "Docker-Client/"+dockerversion.Version+" ("+runtime.GOOS+")")
req.URL.Host = cli.addr
req.URL.Scheme = cli.scheme
if headers != nil {
for k, v := range headers {
req.Header[k] = v
}
}
if expectedPayload && req.Header.Get("Content-Type") == "" {
req.Header.Set("Content-Type", "text/plain")
}
resp, err := cli.HTTPClient().Do(req)
if resp != nil {
serverResp.statusCode = resp.StatusCode
}
if err != nil {
if utils.IsTimeout(err) || strings.Contains(err.Error(), "connection refused") || strings.Contains(err.Error(), "dial unix") {
return serverResp, errConnectionFailed
}
if cli.tlsConfig == nil && strings.Contains(err.Error(), "malformed HTTP response") {
return serverResp, fmt.Errorf("%v.\n* Are you trying to connect to a TLS-enabled daemon without TLS?", err)
}
if cli.tlsConfig != nil && strings.Contains(err.Error(), "remote error: bad certificate") {
return serverResp, fmt.Errorf("The server probably has client authentication (--tlsverify) enabled. Please check your TLS client certification settings: %v", err)
}
return serverResp, fmt.Errorf("An error occurred trying to connect: %v", err)
}
if serverResp.statusCode < 200 || serverResp.statusCode >= 400 {
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return serverResp, err
}
if len(body) == 0 {
return serverResp, fmt.Errorf("Error: request returned %s for API route and version %s, check if the server supports the requested API version", http.StatusText(serverResp.statusCode), req.URL)
}
return serverResp, fmt.Errorf("Error response from daemon: %s", bytes.TrimSpace(body))
}
serverResp.body = resp.Body
serverResp.header = resp.Header
return serverResp, nil
}
// cmdAttempt builds the corresponding registry Auth Header from the given
// authConfig. It returns the servers body, status, error response
func (cli *DockerCli) cmdAttempt(authConfig cliconfig.AuthConfig, method, path string, in io.Reader, out io.Writer) (io.ReadCloser, int, error) {
buf, err := json.Marshal(authConfig)
if err != nil {
return nil, -1, err
}
registryAuthHeader := []string{
base64.URLEncoding.EncodeToString(buf),
}
// begin the request
serverResp, err := cli.clientRequest(method, path, in, map[string][]string{
"X-Registry-Auth": registryAuthHeader,
})
if err == nil && out != nil {
// If we are streaming output, complete the stream since
// errors may not appear until later.
err = cli.streamBody(serverResp.body, serverResp.header.Get("Content-Type"), true, out, nil)
}
if err != nil {
// Since errors in a stream appear after status 200 has been written,
// we may need to change the status code.
if strings.Contains(err.Error(), "Authentication is required") ||
strings.Contains(err.Error(), "Status 401") ||
strings.Contains(err.Error(), "401 Unauthorized") ||
strings.Contains(err.Error(), "status code 401") {
serverResp.statusCode = http.StatusUnauthorized
}
}
return serverResp.body, serverResp.statusCode, err
}
func (cli *DockerCli) clientRequestAttemptLogin(method, path string, in io.Reader, out io.Writer, index *registry.IndexInfo, cmdName string) (io.ReadCloser, int, error) {
// Resolve the Auth config relevant for this server
func (cli *DockerCli) encodeRegistryAuth(index *registry.IndexInfo) (string, error) {
authConfig := registry.ResolveAuthConfig(cli.configFile, index)
body, statusCode, err := cli.cmdAttempt(authConfig, method, path, in, out)
if statusCode == http.StatusUnauthorized {
return authConfig.EncodeToBase64()
}
func (cli *DockerCli) registryAuthenticationPrivilegedFunc(index *registry.IndexInfo, cmdName string) lib.RequestPrivilegeFunc {
return func() (string, error) {
fmt.Fprintf(cli.out, "\nPlease login prior to %s:\n", cmdName)
if err = cli.CmdLogin(index.GetAuthConfigKey()); err != nil {
return nil, -1, err
if err := cli.CmdLogin(index.GetAuthConfigKey()); err != nil {
return "", err
}
authConfig = registry.ResolveAuthConfig(cli.configFile, index)
return cli.cmdAttempt(authConfig, method, path, in, out)
return cli.encodeRegistryAuth(index)
}
return body, statusCode, err
}
func (cli *DockerCli) callWrapper(method, path string, data interface{}, headers map[string][]string) (io.ReadCloser, http.Header, int, error) {
sr, err := cli.call(method, path, data, headers)
return sr.body, sr.header, sr.statusCode, err
}
func (cli *DockerCli) call(method, path string, data interface{}, headers map[string][]string) (*serverResponse, error) {
params, err := cli.encodeData(data)
if err != nil {
sr := &serverResponse{
body: nil,
header: nil,
statusCode: -1,
}
return sr, nil
}
if data != nil {
if headers == nil {
headers = make(map[string][]string)
}
headers["Content-Type"] = []string{"application/json"}
}
serverResp, err := cli.clientRequest(method, path, params, headers)
return serverResp, err
}
type streamOpts struct {
rawTerminal bool
in io.Reader
out io.Writer
err io.Writer
headers map[string][]string
}
func (cli *DockerCli) stream(method, path string, opts *streamOpts) (*serverResponse, error) {
serverResp, err := cli.clientRequest(method, path, opts.in, opts.headers)
if err != nil {
return serverResp, err
}
return serverResp, cli.streamBody(serverResp.body, serverResp.header.Get("Content-Type"), opts.rawTerminal, opts.out, opts.err)
}
func (cli *DockerCli) streamBody(body io.ReadCloser, contentType string, rawTerminal bool, stdout, stderr io.Writer) error {
defer body.Close()
if api.MatchesContentType(contentType, "application/json") {
return jsonmessage.DisplayJSONMessagesStream(body, stdout, cli.outFd, cli.isTerminalOut)
}
if stdout != nil || stderr != nil {
// When TTY is ON, use regular copy
var err error
if rawTerminal {
_, err = io.Copy(stdout, body)
} else {
_, err = stdcopy.StdCopy(stdout, stderr, body)
}
logrus.Debugf("[stream] End of stdout")
return err
}
return nil
}
func (cli *DockerCli) resizeTty(id string, isExec bool) {
@ -244,86 +35,53 @@ func (cli *DockerCli) resizeTty(id string, isExec bool) {
if height == 0 && width == 0 {
return
}
v := url.Values{}
v.Set("h", strconv.Itoa(height))
v.Set("w", strconv.Itoa(width))
path := ""
if !isExec {
path = "/containers/" + id + "/resize?"
} else {
path = "/exec/" + id + "/resize?"
options := types.ResizeOptions{
ID: id,
Height: height,
Width: width,
}
if _, _, err := readBody(cli.call("POST", path+v.Encode(), nil, nil)); err != nil {
var err error
if !isExec {
err = cli.client.ContainerExecResize(options)
} else {
err = cli.client.ContainerResize(options)
}
if err != nil {
logrus.Debugf("Error resize: %s", err)
}
}
func waitForExit(cli *DockerCli, containerID string) (int, error) {
serverResp, err := cli.call("POST", "/containers/"+containerID+"/wait", nil, nil)
if err != nil {
return -1, err
}
defer serverResp.body.Close()
var res types.ContainerWaitResponse
if err := json.NewDecoder(serverResp.body).Decode(&res); err != nil {
return -1, err
}
return res.StatusCode, nil
}
// getExitCode perform an inspect on the container. It returns
// the running state and the exit code.
func getExitCode(cli *DockerCli, containerID string) (bool, int, error) {
serverResp, err := cli.call("GET", "/containers/"+containerID+"/json", nil, nil)
c, err := cli.client.ContainerInspect(containerID)
if err != nil {
// If we can't connect, then the daemon probably died.
if err != errConnectionFailed {
if err != lib.ErrConnectionFailed {
return false, -1, err
}
return false, -1, nil
}
defer serverResp.body.Close()
var c types.ContainerJSON
if err := json.NewDecoder(serverResp.body).Decode(&c); err != nil {
return false, -1, err
}
return c.State.Running, c.State.ExitCode, nil
}
// getExecExitCode perform an inspect on the exec command. It returns
// the running state and the exit code.
func getExecExitCode(cli *DockerCli, execID string) (bool, int, error) {
serverResp, err := cli.call("GET", "/exec/"+execID+"/json", nil, nil)
resp, err := cli.client.ContainerExecInspect(execID)
if err != nil {
// If we can't connect, then the daemon probably died.
if err != errConnectionFailed {
if err != lib.ErrConnectionFailed {
return false, -1, err
}
return false, -1, nil
}
defer serverResp.body.Close()
//TODO: Should we reconsider having a type in api/types?
//this is a response to exex/id/json not container
var c struct {
Running bool
ExitCode int
}
if err := json.NewDecoder(serverResp.body).Decode(&c); err != nil {
return false, -1, err
}
return c.Running, c.ExitCode, nil
return resp.Running, resp.ExitCode, nil
}
func (cli *DockerCli) monitorTtySize(id string, isExec bool) error {
@ -368,17 +126,3 @@ func (cli *DockerCli) getTtySize() (int, int) {
}
return int(ws.Height), int(ws.Width)
}
func readBody(serverResp *serverResponse, err error) ([]byte, int, error) {
if serverResp.body != nil {
defer serverResp.body.Close()
}
if err != nil {
return nil, serverResp.statusCode, err
}
body, err := ioutil.ReadAll(serverResp.body)
if err != nil {
return nil, -1, err
}
return body, serverResp.statusCode, nil
}

View file

@ -1,17 +1,11 @@
package client
import (
"encoding/json"
"runtime"
"text/template"
"time"
"github.com/docker/docker/api"
"github.com/docker/docker/api/types"
Cli "github.com/docker/docker/cli"
"github.com/docker/docker/dockerversion"
flag "github.com/docker/docker/pkg/mflag"
"github.com/docker/docker/utils"
)
var versionTemplate = `Client:
@ -32,12 +26,6 @@ Server:
OS/Arch: {{.Server.Os}}/{{.Server.Arch}}{{if .Server.Experimental}}
Experimental: {{.Server.Experimental}}{{end}}{{end}}`
type versionData struct {
Client types.Version
ServerOK bool
Server types.Version
}
// CmdVersion shows Docker version information.
//
// Available version information is shown for: client Docker version, client API version, client Go version, client Git commit, client OS/Arch, server Docker version, server API version, server Go version, server Git commit, and server OS/Arch.
@ -49,59 +37,36 @@ func (cli *DockerCli) CmdVersion(args ...string) (err error) {
cmd.Require(flag.Exact, 0)
cmd.ParseFlags(args, true)
if *tmplStr == "" {
*tmplStr = versionTemplate
templateFormat := versionTemplate
if *tmplStr != "" {
templateFormat = *tmplStr
}
var tmpl *template.Template
if tmpl, err = template.New("").Funcs(funcMap).Parse(*tmplStr); err != nil {
if tmpl, err = template.New("").Funcs(funcMap).Parse(templateFormat); err != nil {
return Cli.StatusError{StatusCode: 64,
Status: "Template parsing error: " + err.Error()}
}
vd := versionData{
Client: types.Version{
Version: dockerversion.Version,
APIVersion: api.Version,
GoVersion: runtime.Version(),
GitCommit: dockerversion.GitCommit,
BuildTime: dockerversion.BuildTime,
Os: runtime.GOOS,
Arch: runtime.GOARCH,
Experimental: utils.ExperimentalBuild(),
},
vd, err := cli.client.SystemVersion()
// first we need to make BuildTime more human friendly
t, errTime := time.Parse(time.RFC3339Nano, vd.Client.BuildTime)
if errTime == nil {
vd.Client.BuildTime = t.Format(time.ANSIC)
}
defer func() {
// first we need to make BuildTime more human friendly
t, errTime := time.Parse(time.RFC3339Nano, vd.Client.BuildTime)
if errTime == nil {
vd.Client.BuildTime = t.Format(time.ANSIC)
}
if vd.ServerOK() {
t, errTime = time.Parse(time.RFC3339Nano, vd.Server.BuildTime)
if errTime == nil {
vd.Server.BuildTime = t.Format(time.ANSIC)
}
if err2 := tmpl.Execute(cli.out, vd); err2 != nil && err == nil {
err = err2
}
cli.out.Write([]byte{'\n'})
}()
serverResp, err := cli.call("GET", "/version", nil, nil)
if err != nil {
return err
}
defer serverResp.body.Close()
if err = json.NewDecoder(serverResp.body).Decode(&vd.Server); err != nil {
return Cli.StatusError{StatusCode: 1,
Status: "Error reading remote version: " + err.Error()}
if err2 := tmpl.Execute(cli.out, vd); err2 != nil && err == nil {
err = err2
}
vd.ServerOK = true
return
cli.out.Write([]byte{'\n'})
return err
}

View file

@ -1,14 +1,8 @@
package client
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"text/tabwriter"
"text/template"
"github.com/docker/docker/api/types"
Cli "github.com/docker/docker/cli"
@ -64,25 +58,11 @@ func (cli *DockerCli) CmdVolumeLs(args ...string) error {
}
}
v := url.Values{}
if volFilterArgs.Len() > 0 {
filterJSON, err := filters.ToParam(volFilterArgs)
if err != nil {
return err
}
v.Set("filters", filterJSON)
}
resp, err := cli.call("GET", "/volumes?"+v.Encode(), nil, nil)
volumes, err := cli.client.VolumeList(volFilterArgs)
if err != nil {
return err
}
var volumes types.VolumesListResponse
if err := json.NewDecoder(resp.body).Decode(&volumes); err != nil {
return err
}
w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0)
if !*quiet {
fmt.Fprintf(w, "DRIVER \tVOLUME NAME")
@ -114,68 +94,12 @@ func (cli *DockerCli) CmdVolumeInspect(args ...string) error {
return nil
}
var tmpl *template.Template
if *tmplStr != "" {
var err error
tmpl, err = template.New("").Funcs(funcMap).Parse(*tmplStr)
if err != nil {
return err
}
inspectSearcher := func(name string) (interface{}, []byte, error) {
i, err := cli.client.VolumeInspect(name)
return i, nil, err
}
var status = 0
var volumes []*types.Volume
for _, name := range cmd.Args() {
resp, err := cli.call("GET", "/volumes/"+name, nil, nil)
if err != nil {
if resp.statusCode != http.StatusNotFound {
return err
}
status = 1
fmt.Fprintf(cli.err, "Error: No such volume: %s\n", name)
continue
}
var volume types.Volume
if err := json.NewDecoder(resp.body).Decode(&volume); err != nil {
fmt.Fprintf(cli.err, "Unable to read inspect data: %v\n", err)
status = 1
break
}
if tmpl == nil {
volumes = append(volumes, &volume)
continue
}
buf := bytes.NewBufferString("")
if err := tmpl.Execute(buf, &volume); err != nil {
fmt.Fprintf(cli.err, "Template parsing error: %v\n", err)
status = 1
break
}
cli.out.Write(buf.Bytes())
cli.out.Write([]byte{'\n'})
}
if tmpl == nil {
b, err := json.MarshalIndent(volumes, "", " ")
if err != nil {
return err
}
_, err = io.Copy(cli.out, bytes.NewReader(b))
if err != nil {
return err
}
io.WriteString(cli.out, "\n")
}
if status != 0 {
return Cli.StatusError{StatusCode: status}
}
return nil
return cli.inspectElements(*tmplStr, cmd.Args(), inspectSearcher)
}
// CmdVolumeCreate creates a new container from a given image.
@ -192,24 +116,17 @@ func (cli *DockerCli) CmdVolumeCreate(args ...string) error {
cmd.Require(flag.Exact, 0)
cmd.ParseFlags(args, true)
volReq := &types.VolumeCreateRequest{
volReq := types.VolumeCreateRequest{
Driver: *flDriver,
DriverOpts: flDriverOpts.GetAll(),
Name: *flName,
}
if *flName != "" {
volReq.Name = *flName
}
resp, err := cli.call("POST", "/volumes/create", volReq, nil)
vol, err := cli.client.VolumeCreate(volReq)
if err != nil {
return err
}
var vol types.Volume
if err := json.NewDecoder(resp.body).Decode(&vol); err != nil {
return err
}
fmt.Fprintf(cli.out, "%s\n", vol.Name)
return nil
}
@ -224,8 +141,7 @@ func (cli *DockerCli) CmdVolumeRm(args ...string) error {
var status = 0
for _, name := range cmd.Args() {
_, err := cli.call("DELETE", "/volumes/"+name, nil, nil)
if err != nil {
if err := cli.client.VolumeRemove(name); err != nil {
fmt.Fprintf(cli.err, "%s\n", err)
status = 1
continue

View file

@ -20,7 +20,7 @@ func (cli *DockerCli) CmdWait(args ...string) error {
var errNames []string
for _, name := range cmd.Args() {
status, err := waitForExit(cli, name)
status, err := cli.client.ContainerWait(name)
if err != nil {
fmt.Fprintf(cli.err, "%s\n", err)
errNames = append(errNames, name)

234
api/types/client.go Normal file
View file

@ -0,0 +1,234 @@
package types
import (
"bufio"
"io"
"net"
"github.com/docker/docker/cliconfig"
"github.com/docker/docker/pkg/parsers/filters"
"github.com/docker/docker/pkg/ulimit"
"github.com/docker/docker/runconfig"
)
// ContainerAttachOptions holds parameters to attach to a container.
type ContainerAttachOptions struct {
ContainerID string
Stream bool
Stdin bool
Stdout bool
Stderr bool
}
// ContainerCommitOptions holds parameters to commit changes into a container.
type ContainerCommitOptions struct {
ContainerID string
RepositoryName string
Tag string
Comment string
Author string
Changes []string
Pause bool
Config *runconfig.Config
}
// ContainerExecInspect holds information returned by exec inspect.
type ContainerExecInspect struct {
ExecID string
ContainerID string
Running bool
ExitCode int
}
// ContainerListOptions holds parameters to list containers with.
type ContainerListOptions struct {
Quiet bool
Size bool
All bool
Latest bool
Since string
Before string
Limit int
Filter filters.Args
}
// ContainerLogsOptions holds parameters to filter logs with.
type ContainerLogsOptions struct {
ContainerID string
ShowStdout bool
ShowStderr bool
Since string
Timestamps bool
Follow bool
Tail string
}
// ContainerRemoveOptions holds parameters to remove containers.
type ContainerRemoveOptions struct {
ContainerID string
RemoveVolumes bool
RemoveLinks bool
Force bool
}
// CopyToContainerOptions holds information
// about files to copy into a container
type CopyToContainerOptions struct {
ContainerID string
Path string
Content io.Reader
AllowOverwriteDirWithFile bool
}
// EventsOptions hold parameters to filter events with.
type EventsOptions struct {
Since string
Until string
Filters filters.Args
}
// HijackedResponse holds connection information for a hijacked request.
type HijackedResponse struct {
Conn net.Conn
Reader *bufio.Reader
}
// Close closes the hijacked connection and reader.
func (h *HijackedResponse) Close() {
h.Conn.Close()
}
// CloseWriter is an interface that implement structs
// that close input streams to prevent from writing.
type CloseWriter interface {
CloseWrite() error
}
// CloseWrite closes a readWriter for writing.
func (h *HijackedResponse) CloseWrite() error {
if conn, ok := h.Conn.(CloseWriter); ok {
return conn.CloseWrite()
}
return nil
}
// ImageBuildOptions holds the information
// necessary to build images.
type ImageBuildOptions struct {
Tags []string
SuppressOutput bool
RemoteContext string
NoCache bool
Remove bool
ForceRemove bool
PullParent bool
Isolation string
CPUSetCPUs string
CPUSetMems string
CPUShares int64
CPUQuota int64
CPUPeriod int64
Memory int64
MemorySwap int64
CgroupParent string
ShmSize string
Dockerfile string
Ulimits []*ulimit.Ulimit
BuildArgs []string
AuthConfigs map[string]cliconfig.AuthConfig
Context io.Reader
}
// ImageBuildResponse holds information
// returned by a server after building
// an image.
type ImageBuildResponse struct {
Body io.ReadCloser
OSType string
}
// ImageCreateOptions holds information to create images.
type ImageCreateOptions struct {
// Parent is the image to create this image from
Parent string
// Tag is the name to tag this image
Tag string
// RegistryAuth is the base64 encoded credentials for this server
RegistryAuth string
}
// ImageImportOptions holds information to import images from the client host.
type ImageImportOptions struct {
// Source is the data to send to the server to create this image from
Source io.Reader
// Source is the name of the source to import this image from
SourceName string
// RepositoryName is the name of the repository to import this image
RepositoryName string
// Message is the message to tag the image with
Message string
// Tag is the name to tag this image
Tag string
// Changes are the raw changes to apply to the image
Changes []string
}
// ImageListOptions holds parameters to filter the list of images with.
type ImageListOptions struct {
MatchName string
All bool
Filters filters.Args
}
// ImagePullOptions holds information to pull images.
type ImagePullOptions struct {
ImageID string
Tag string
// RegistryAuth is the base64 encoded credentials for this server
RegistryAuth string
}
//ImagePushOptions holds information to push images.
type ImagePushOptions ImagePullOptions
// ImageRemoveOptions holds parameters to remove images.
type ImageRemoveOptions struct {
ImageID string
Force bool
PruneChildren bool
}
// ImageSearchOptions holds parameters to search images with.
type ImageSearchOptions struct {
Term string
RegistryAuth string
}
// ImageTagOptions holds parameters to tag an image
type ImageTagOptions struct {
ImageID string
RepositoryName string
Tag string
Force bool
}
// ResizeOptions holds parameters to resize a tty.
// It can be used to resize container ttys and
// exec process ttys too.
type ResizeOptions struct {
ID string
Height int
Width int
}
// VersionResponse holds version information for the client and the server
type VersionResponse struct {
Client *Version
Server *Version
}
// ServerOK return true when the client could connect to the docker server
// and parse the information received. It returns false otherwise.
func (v VersionResponse) ServerOK() bool {
return v.Server != nil
}

View file

@ -54,6 +54,15 @@ type AuthConfig struct {
RegistryToken string `json:"registrytoken,omitempty"`
}
// EncodeToBase64 serializes the auth configuration as JSON base64 payload
func (a AuthConfig) EncodeToBase64() (string, error) {
buf, err := json.Marshal(a)
if err != nil {
return "", err
}
return base64.URLEncoding.EncodeToString(buf), nil
}
// ConfigFile ~/.docker/config.json file info
type ConfigFile struct {
AuthConfigs map[string]AuthConfig `json:"auths"`
@ -192,7 +201,14 @@ func Load(configDir string) (*ConfigFile, error) {
}
defer file.Close()
err = configFile.LegacyLoadFromReader(file)
return &configFile, err
if err != nil {
return &configFile, err
}
if configFile.HTTPHeaders == nil {
configFile.HTTPHeaders = map[string]string{}
}
return &configFile, nil
}
// SaveToWriter encodes and writes out all the authorization information to

View file

@ -356,3 +356,14 @@ func (s *DockerSuite) TestInspectByPrefix(c *check.C) {
c.Assert(err, checker.IsNil)
c.Assert(id, checker.Equals, id3)
}
func (s *DockerSuite) TestInspectStopWhenNotFound(c *check.C) {
dockerCmd(c, "run", "--name=busybox", "-d", "busybox", "top")
dockerCmd(c, "run", "--name=not-shown", "-d", "busybox", "top")
out, _, err := dockerCmdWithError("inspect", "--type=container", "--format='{{.Name}}'", "busybox", "missing", "not-shown")
c.Assert(err, checker.Not(check.IsNil))
c.Assert(out, checker.Contains, "busybox")
c.Assert(out, checker.Not(checker.Contains), "not-shown")
c.Assert(out, checker.Contains, "Error: No such container: missing")
}

View file

@ -349,6 +349,6 @@ func (s *DockerSuite) TestLogsFollowGoroutinesNoOutput(c *check.C) {
func (s *DockerSuite) TestLogsCLIContainerNotFound(c *check.C) {
name := "testlogsnocontainer"
out, _, _ := dockerCmdWithError("logs", name)
message := fmt.Sprintf(".*No such container: %s.*\n", name)
c.Assert(out, checker.Matches, message)
message := fmt.Sprintf("Error: No such container: %s\n", name)
c.Assert(out, checker.Equals, message)
}

View file

@ -317,7 +317,7 @@ func (s *DockerSuite) TestDockerInspectMultipleNetwork(c *check.C) {
c.Assert(exitCode, checker.Equals, 1)
c.Assert(out, checker.Contains, "Error: No such network: nonexistent")
networkResources = []types.NetworkResource{}
inspectOut := strings.SplitN(out, "\n", 2)[1]
inspectOut := strings.SplitN(out, "\nError: No such network: nonexistent\n", 2)[0]
err = json.Unmarshal([]byte(inspectOut), &networkResources)
c.Assert(networkResources, checker.HasLen, 1)

View file

@ -53,15 +53,17 @@ func (s *DockerSuite) TestVolumeCliInspect(c *check.C) {
func (s *DockerSuite) TestVolumeCliInspectMulti(c *check.C) {
dockerCmd(c, "volume", "create", "--name", "test1")
dockerCmd(c, "volume", "create", "--name", "test2")
dockerCmd(c, "volume", "create", "--name", "not-shown")
out, _, err := dockerCmdWithError("volume", "inspect", "--format='{{ .Name }}'", "test1", "test2", "doesntexist")
out, _, err := dockerCmdWithError("volume", "inspect", "--format='{{ .Name }}'", "test1", "test2", "doesntexist", "not-shown")
c.Assert(err, checker.NotNil)
outArr := strings.Split(strings.TrimSpace(out), "\n")
c.Assert(len(outArr), check.Equals, 3, check.Commentf("\n%s", out))
c.Assert(strings.Contains(out, "test1\n"), check.Equals, true)
c.Assert(strings.Contains(out, "test2\n"), check.Equals, true)
c.Assert(strings.Contains(out, "Error: No such volume: doesntexist\n"), check.Equals, true)
c.Assert(out, checker.Contains, "test1")
c.Assert(out, checker.Contains, "test2")
c.Assert(out, checker.Contains, "Error: No such volume: doesntexist")
c.Assert(out, checker.Not(checker.Contains), "not-shown")
}
func (s *DockerSuite) TestVolumeCliLs(c *check.C) {