Signed-off-by: Joey Gibson <joey@joeygibson.com>
| 1 | 1 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,85 @@ |
| 0 |
+package client |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "fmt" |
|
| 4 |
+ "io" |
|
| 5 |
+ "net/url" |
|
| 6 |
+ |
|
| 7 |
+ log "github.com/Sirupsen/logrus" |
|
| 8 |
+ "github.com/docker/docker/engine" |
|
| 9 |
+ flag "github.com/docker/docker/pkg/mflag" |
|
| 10 |
+ "github.com/docker/docker/pkg/signal" |
|
| 11 |
+ "github.com/docker/docker/utils" |
|
| 12 |
+) |
|
| 13 |
+ |
|
| 14 |
+func (cli *DockerCli) CmdAttach(args ...string) error {
|
|
| 15 |
+ var ( |
|
| 16 |
+ cmd = cli.Subcmd("attach", "CONTAINER", "Attach to a running container", true)
|
|
| 17 |
+ noStdin = cmd.Bool([]string{"#nostdin", "-no-stdin"}, false, "Do not attach STDIN")
|
|
| 18 |
+ proxy = cmd.Bool([]string{"#sig-proxy", "-sig-proxy"}, true, "Proxy all received signals to the process")
|
|
| 19 |
+ ) |
|
| 20 |
+ cmd.Require(flag.Exact, 1) |
|
| 21 |
+ |
|
| 22 |
+ utils.ParseFlags(cmd, args, true) |
|
| 23 |
+ name := cmd.Arg(0) |
|
| 24 |
+ |
|
| 25 |
+ stream, _, err := cli.call("GET", "/containers/"+name+"/json", nil, false)
|
|
| 26 |
+ if err != nil {
|
|
| 27 |
+ return err |
|
| 28 |
+ } |
|
| 29 |
+ |
|
| 30 |
+ env := engine.Env{}
|
|
| 31 |
+ if err := env.Decode(stream); err != nil {
|
|
| 32 |
+ return err |
|
| 33 |
+ } |
|
| 34 |
+ |
|
| 35 |
+ if !env.GetSubEnv("State").GetBool("Running") {
|
|
| 36 |
+ return fmt.Errorf("You cannot attach to a stopped container, start it first")
|
|
| 37 |
+ } |
|
| 38 |
+ |
|
| 39 |
+ var ( |
|
| 40 |
+ config = env.GetSubEnv("Config")
|
|
| 41 |
+ tty = config.GetBool("Tty")
|
|
| 42 |
+ ) |
|
| 43 |
+ |
|
| 44 |
+ if err := cli.CheckTtyInput(!*noStdin, tty); err != nil {
|
|
| 45 |
+ return err |
|
| 46 |
+ } |
|
| 47 |
+ |
|
| 48 |
+ if tty && cli.isTerminalOut {
|
|
| 49 |
+ if err := cli.monitorTtySize(cmd.Arg(0), false); err != nil {
|
|
| 50 |
+ log.Debugf("Error monitoring TTY size: %s", err)
|
|
| 51 |
+ } |
|
| 52 |
+ } |
|
| 53 |
+ |
|
| 54 |
+ var in io.ReadCloser |
|
| 55 |
+ |
|
| 56 |
+ v := url.Values{}
|
|
| 57 |
+ v.Set("stream", "1")
|
|
| 58 |
+ if !*noStdin && config.GetBool("OpenStdin") {
|
|
| 59 |
+ v.Set("stdin", "1")
|
|
| 60 |
+ in = cli.in |
|
| 61 |
+ } |
|
| 62 |
+ |
|
| 63 |
+ v.Set("stdout", "1")
|
|
| 64 |
+ v.Set("stderr", "1")
|
|
| 65 |
+ |
|
| 66 |
+ if *proxy && !tty {
|
|
| 67 |
+ sigc := cli.forwardAllSignals(cmd.Arg(0)) |
|
| 68 |
+ defer signal.StopCatch(sigc) |
|
| 69 |
+ } |
|
| 70 |
+ |
|
| 71 |
+ if err := cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), tty, in, cli.out, cli.err, nil, nil); err != nil {
|
|
| 72 |
+ return err |
|
| 73 |
+ } |
|
| 74 |
+ |
|
| 75 |
+ _, status, err := getExitCode(cli, cmd.Arg(0)) |
|
| 76 |
+ if err != nil {
|
|
| 77 |
+ return err |
|
| 78 |
+ } |
|
| 79 |
+ if status != 0 {
|
|
| 80 |
+ return &utils.StatusError{StatusCode: status}
|
|
| 81 |
+ } |
|
| 82 |
+ |
|
| 83 |
+ return nil |
|
| 84 |
+} |
| 0 | 85 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,302 @@ |
| 0 |
+package client |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "bufio" |
|
| 4 |
+ "encoding/base64" |
|
| 5 |
+ "encoding/json" |
|
| 6 |
+ "fmt" |
|
| 7 |
+ "io" |
|
| 8 |
+ "io/ioutil" |
|
| 9 |
+ "net/http" |
|
| 10 |
+ "net/url" |
|
| 11 |
+ "os" |
|
| 12 |
+ "os/exec" |
|
| 13 |
+ "path" |
|
| 14 |
+ "path/filepath" |
|
| 15 |
+ "runtime" |
|
| 16 |
+ "strconv" |
|
| 17 |
+ "strings" |
|
| 18 |
+ |
|
| 19 |
+ log "github.com/Sirupsen/logrus" |
|
| 20 |
+ "github.com/docker/docker/api" |
|
| 21 |
+ "github.com/docker/docker/graph" |
|
| 22 |
+ "github.com/docker/docker/pkg/archive" |
|
| 23 |
+ "github.com/docker/docker/pkg/fileutils" |
|
| 24 |
+ flag "github.com/docker/docker/pkg/mflag" |
|
| 25 |
+ "github.com/docker/docker/pkg/parsers" |
|
| 26 |
+ "github.com/docker/docker/pkg/progressreader" |
|
| 27 |
+ "github.com/docker/docker/pkg/symlink" |
|
| 28 |
+ "github.com/docker/docker/pkg/units" |
|
| 29 |
+ "github.com/docker/docker/pkg/urlutil" |
|
| 30 |
+ "github.com/docker/docker/registry" |
|
| 31 |
+ "github.com/docker/docker/utils" |
|
| 32 |
+) |
|
| 33 |
+ |
|
| 34 |
+const ( |
|
| 35 |
+ tarHeaderSize = 512 |
|
| 36 |
+) |
|
| 37 |
+ |
|
| 38 |
+func (cli *DockerCli) CmdBuild(args ...string) error {
|
|
| 39 |
+ cmd := cli.Subcmd("build", "PATH | URL | -", "Build a new image from the source code at PATH", true)
|
|
| 40 |
+ tag := cmd.String([]string{"t", "-tag"}, "", "Repository name (and optionally a tag) for the image")
|
|
| 41 |
+ suppressOutput := cmd.Bool([]string{"q", "-quiet"}, false, "Suppress the verbose output generated by the containers")
|
|
| 42 |
+ noCache := cmd.Bool([]string{"#no-cache", "-no-cache"}, false, "Do not use cache when building the image")
|
|
| 43 |
+ rm := cmd.Bool([]string{"#rm", "-rm"}, true, "Remove intermediate containers after a successful build")
|
|
| 44 |
+ forceRm := cmd.Bool([]string{"-force-rm"}, false, "Always remove intermediate containers")
|
|
| 45 |
+ pull := cmd.Bool([]string{"-pull"}, false, "Always attempt to pull a newer version of the image")
|
|
| 46 |
+ dockerfileName := cmd.String([]string{"f", "-file"}, "", "Name of the Dockerfile (Default is 'PATH/Dockerfile')")
|
|
| 47 |
+ flMemoryString := cmd.String([]string{"m", "-memory"}, "", "Memory limit")
|
|
| 48 |
+ flMemorySwap := cmd.String([]string{"-memory-swap"}, "", "Total memory (memory + swap), '-1' to disable swap")
|
|
| 49 |
+ flCpuShares := cmd.Int64([]string{"c", "-cpu-shares"}, 0, "CPU shares (relative weight)")
|
|
| 50 |
+ flCpuSetCpus := cmd.String([]string{"-cpuset-cpus"}, "", "CPUs in which to allow execution (0-3, 0,1)")
|
|
| 51 |
+ |
|
| 52 |
+ cmd.Require(flag.Exact, 1) |
|
| 53 |
+ |
|
| 54 |
+ utils.ParseFlags(cmd, args, true) |
|
| 55 |
+ |
|
| 56 |
+ var ( |
|
| 57 |
+ context archive.Archive |
|
| 58 |
+ isRemote bool |
|
| 59 |
+ err error |
|
| 60 |
+ ) |
|
| 61 |
+ |
|
| 62 |
+ _, err = exec.LookPath("git")
|
|
| 63 |
+ hasGit := err == nil |
|
| 64 |
+ if cmd.Arg(0) == "-" {
|
|
| 65 |
+ // As a special case, 'docker build -' will build from either an empty context with the |
|
| 66 |
+ // contents of stdin as a Dockerfile, or a tar-ed context from stdin. |
|
| 67 |
+ buf := bufio.NewReader(cli.in) |
|
| 68 |
+ magic, err := buf.Peek(tarHeaderSize) |
|
| 69 |
+ if err != nil && err != io.EOF {
|
|
| 70 |
+ return fmt.Errorf("failed to peek context header from STDIN: %v", err)
|
|
| 71 |
+ } |
|
| 72 |
+ if !archive.IsArchive(magic) {
|
|
| 73 |
+ dockerfile, err := ioutil.ReadAll(buf) |
|
| 74 |
+ if err != nil {
|
|
| 75 |
+ return fmt.Errorf("failed to read Dockerfile from STDIN: %v", err)
|
|
| 76 |
+ } |
|
| 77 |
+ |
|
| 78 |
+ // -f option has no meaning when we're reading it from stdin, |
|
| 79 |
+ // so just use our default Dockerfile name |
|
| 80 |
+ *dockerfileName = api.DefaultDockerfileName |
|
| 81 |
+ context, err = archive.Generate(*dockerfileName, string(dockerfile)) |
|
| 82 |
+ } else {
|
|
| 83 |
+ context = ioutil.NopCloser(buf) |
|
| 84 |
+ } |
|
| 85 |
+ } else if urlutil.IsURL(cmd.Arg(0)) && (!urlutil.IsGitURL(cmd.Arg(0)) || !hasGit) {
|
|
| 86 |
+ isRemote = true |
|
| 87 |
+ } else {
|
|
| 88 |
+ root := cmd.Arg(0) |
|
| 89 |
+ if urlutil.IsGitURL(root) {
|
|
| 90 |
+ remoteURL := cmd.Arg(0) |
|
| 91 |
+ if !urlutil.IsGitTransport(remoteURL) {
|
|
| 92 |
+ remoteURL = "https://" + remoteURL |
|
| 93 |
+ } |
|
| 94 |
+ |
|
| 95 |
+ root, err = ioutil.TempDir("", "docker-build-git")
|
|
| 96 |
+ if err != nil {
|
|
| 97 |
+ return err |
|
| 98 |
+ } |
|
| 99 |
+ defer os.RemoveAll(root) |
|
| 100 |
+ |
|
| 101 |
+ if output, err := exec.Command("git", "clone", "--recursive", remoteURL, root).CombinedOutput(); err != nil {
|
|
| 102 |
+ return fmt.Errorf("Error trying to use git: %s (%s)", err, output)
|
|
| 103 |
+ } |
|
| 104 |
+ } |
|
| 105 |
+ if _, err := os.Stat(root); err != nil {
|
|
| 106 |
+ return err |
|
| 107 |
+ } |
|
| 108 |
+ |
|
| 109 |
+ absRoot, err := filepath.Abs(root) |
|
| 110 |
+ if err != nil {
|
|
| 111 |
+ return err |
|
| 112 |
+ } |
|
| 113 |
+ |
|
| 114 |
+ filename := *dockerfileName // path to Dockerfile |
|
| 115 |
+ |
|
| 116 |
+ if *dockerfileName == "" {
|
|
| 117 |
+ // No -f/--file was specified so use the default |
|
| 118 |
+ *dockerfileName = api.DefaultDockerfileName |
|
| 119 |
+ filename = filepath.Join(absRoot, *dockerfileName) |
|
| 120 |
+ |
|
| 121 |
+ // Just to be nice ;-) look for 'dockerfile' too but only |
|
| 122 |
+ // use it if we found it, otherwise ignore this check |
|
| 123 |
+ if _, err = os.Lstat(filename); os.IsNotExist(err) {
|
|
| 124 |
+ tmpFN := path.Join(absRoot, strings.ToLower(*dockerfileName)) |
|
| 125 |
+ if _, err = os.Lstat(tmpFN); err == nil {
|
|
| 126 |
+ *dockerfileName = strings.ToLower(*dockerfileName) |
|
| 127 |
+ filename = tmpFN |
|
| 128 |
+ } |
|
| 129 |
+ } |
|
| 130 |
+ } |
|
| 131 |
+ |
|
| 132 |
+ origDockerfile := *dockerfileName // used for error msg |
|
| 133 |
+ if filename, err = filepath.Abs(filename); err != nil {
|
|
| 134 |
+ return err |
|
| 135 |
+ } |
|
| 136 |
+ |
|
| 137 |
+ // Verify that 'filename' is within the build context |
|
| 138 |
+ filename, err = symlink.FollowSymlinkInScope(filename, absRoot) |
|
| 139 |
+ if err != nil {
|
|
| 140 |
+ return fmt.Errorf("The Dockerfile (%s) must be within the build context (%s)", origDockerfile, root)
|
|
| 141 |
+ } |
|
| 142 |
+ |
|
| 143 |
+ // Now reset the dockerfileName to be relative to the build context |
|
| 144 |
+ *dockerfileName, err = filepath.Rel(absRoot, filename) |
|
| 145 |
+ if err != nil {
|
|
| 146 |
+ return err |
|
| 147 |
+ } |
|
| 148 |
+ // And canonicalize dockerfile name to a platform-independent one |
|
| 149 |
+ *dockerfileName, err = archive.CanonicalTarNameForPath(*dockerfileName) |
|
| 150 |
+ if err != nil {
|
|
| 151 |
+ return fmt.Errorf("Cannot canonicalize dockerfile path %s: %v", dockerfileName, err)
|
|
| 152 |
+ } |
|
| 153 |
+ |
|
| 154 |
+ if _, err = os.Lstat(filename); os.IsNotExist(err) {
|
|
| 155 |
+ return fmt.Errorf("Cannot locate Dockerfile: %s", origDockerfile)
|
|
| 156 |
+ } |
|
| 157 |
+ var includes = []string{"."}
|
|
| 158 |
+ |
|
| 159 |
+ excludes, err := utils.ReadDockerIgnore(path.Join(root, ".dockerignore")) |
|
| 160 |
+ if err != nil {
|
|
| 161 |
+ return err |
|
| 162 |
+ } |
|
| 163 |
+ |
|
| 164 |
+ // If .dockerignore mentions .dockerignore or the Dockerfile |
|
| 165 |
+ // then make sure we send both files over to the daemon |
|
| 166 |
+ // because Dockerfile is, obviously, needed no matter what, and |
|
| 167 |
+ // .dockerignore is needed to know if either one needs to be |
|
| 168 |
+ // removed. The deamon will remove them for us, if needed, after it |
|
| 169 |
+ // parses the Dockerfile. |
|
| 170 |
+ keepThem1, _ := fileutils.Matches(".dockerignore", excludes)
|
|
| 171 |
+ keepThem2, _ := fileutils.Matches(*dockerfileName, excludes) |
|
| 172 |
+ if keepThem1 || keepThem2 {
|
|
| 173 |
+ includes = append(includes, ".dockerignore", *dockerfileName) |
|
| 174 |
+ } |
|
| 175 |
+ |
|
| 176 |
+ if err = utils.ValidateContextDirectory(root, excludes); err != nil {
|
|
| 177 |
+ return fmt.Errorf("Error checking context is accessible: '%s'. Please check permissions and try again.", err)
|
|
| 178 |
+ } |
|
| 179 |
+ options := &archive.TarOptions{
|
|
| 180 |
+ Compression: archive.Uncompressed, |
|
| 181 |
+ ExcludePatterns: excludes, |
|
| 182 |
+ IncludeFiles: includes, |
|
| 183 |
+ } |
|
| 184 |
+ context, err = archive.TarWithOptions(root, options) |
|
| 185 |
+ if err != nil {
|
|
| 186 |
+ return err |
|
| 187 |
+ } |
|
| 188 |
+ } |
|
| 189 |
+ |
|
| 190 |
+ // windows: show error message about modified file permissions |
|
| 191 |
+ // FIXME: this is not a valid warning when the daemon is running windows. should be removed once docker engine for windows can build. |
|
| 192 |
+ if runtime.GOOS == "windows" {
|
|
| 193 |
+ log.Warn(`SECURITY WARNING: You are building a Docker image from Windows against a Linux 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.`) |
|
| 194 |
+ } |
|
| 195 |
+ |
|
| 196 |
+ var body io.Reader |
|
| 197 |
+ // Setup an upload progress bar |
|
| 198 |
+ // FIXME: ProgressReader shouldn't be this annoying to use |
|
| 199 |
+ if context != nil {
|
|
| 200 |
+ sf := utils.NewStreamFormatter(false) |
|
| 201 |
+ body = progressreader.New(progressreader.Config{
|
|
| 202 |
+ In: context, |
|
| 203 |
+ Out: cli.out, |
|
| 204 |
+ Formatter: sf, |
|
| 205 |
+ NewLines: true, |
|
| 206 |
+ ID: "", |
|
| 207 |
+ Action: "Sending build context to Docker daemon", |
|
| 208 |
+ }) |
|
| 209 |
+ } |
|
| 210 |
+ |
|
| 211 |
+ var memory int64 |
|
| 212 |
+ if *flMemoryString != "" {
|
|
| 213 |
+ parsedMemory, err := units.RAMInBytes(*flMemoryString) |
|
| 214 |
+ if err != nil {
|
|
| 215 |
+ return err |
|
| 216 |
+ } |
|
| 217 |
+ memory = parsedMemory |
|
| 218 |
+ } |
|
| 219 |
+ |
|
| 220 |
+ var memorySwap int64 |
|
| 221 |
+ if *flMemorySwap != "" {
|
|
| 222 |
+ if *flMemorySwap == "-1" {
|
|
| 223 |
+ memorySwap = -1 |
|
| 224 |
+ } else {
|
|
| 225 |
+ parsedMemorySwap, err := units.RAMInBytes(*flMemorySwap) |
|
| 226 |
+ if err != nil {
|
|
| 227 |
+ return err |
|
| 228 |
+ } |
|
| 229 |
+ memorySwap = parsedMemorySwap |
|
| 230 |
+ } |
|
| 231 |
+ } |
|
| 232 |
+ // Send the build context |
|
| 233 |
+ v := &url.Values{}
|
|
| 234 |
+ |
|
| 235 |
+ //Check if the given image name can be resolved |
|
| 236 |
+ if *tag != "" {
|
|
| 237 |
+ repository, tag := parsers.ParseRepositoryTag(*tag) |
|
| 238 |
+ if err := registry.ValidateRepositoryName(repository); err != nil {
|
|
| 239 |
+ return err |
|
| 240 |
+ } |
|
| 241 |
+ if len(tag) > 0 {
|
|
| 242 |
+ if err := graph.ValidateTagName(tag); err != nil {
|
|
| 243 |
+ return err |
|
| 244 |
+ } |
|
| 245 |
+ } |
|
| 246 |
+ } |
|
| 247 |
+ |
|
| 248 |
+ v.Set("t", *tag)
|
|
| 249 |
+ |
|
| 250 |
+ if *suppressOutput {
|
|
| 251 |
+ v.Set("q", "1")
|
|
| 252 |
+ } |
|
| 253 |
+ if isRemote {
|
|
| 254 |
+ v.Set("remote", cmd.Arg(0))
|
|
| 255 |
+ } |
|
| 256 |
+ if *noCache {
|
|
| 257 |
+ v.Set("nocache", "1")
|
|
| 258 |
+ } |
|
| 259 |
+ if *rm {
|
|
| 260 |
+ v.Set("rm", "1")
|
|
| 261 |
+ } else {
|
|
| 262 |
+ v.Set("rm", "0")
|
|
| 263 |
+ } |
|
| 264 |
+ |
|
| 265 |
+ if *forceRm {
|
|
| 266 |
+ v.Set("forcerm", "1")
|
|
| 267 |
+ } |
|
| 268 |
+ |
|
| 269 |
+ if *pull {
|
|
| 270 |
+ v.Set("pull", "1")
|
|
| 271 |
+ } |
|
| 272 |
+ |
|
| 273 |
+ v.Set("cpusetcpus", *flCpuSetCpus)
|
|
| 274 |
+ v.Set("cpushares", strconv.FormatInt(*flCpuShares, 10))
|
|
| 275 |
+ v.Set("memory", strconv.FormatInt(memory, 10))
|
|
| 276 |
+ v.Set("memswap", strconv.FormatInt(memorySwap, 10))
|
|
| 277 |
+ |
|
| 278 |
+ v.Set("dockerfile", *dockerfileName)
|
|
| 279 |
+ |
|
| 280 |
+ cli.LoadConfigFile() |
|
| 281 |
+ |
|
| 282 |
+ headers := http.Header(make(map[string][]string)) |
|
| 283 |
+ buf, err := json.Marshal(cli.configFile) |
|
| 284 |
+ if err != nil {
|
|
| 285 |
+ return err |
|
| 286 |
+ } |
|
| 287 |
+ headers.Add("X-Registry-Config", base64.URLEncoding.EncodeToString(buf))
|
|
| 288 |
+ |
|
| 289 |
+ if context != nil {
|
|
| 290 |
+ headers.Set("Content-Type", "application/tar")
|
|
| 291 |
+ } |
|
| 292 |
+ err = cli.stream("POST", fmt.Sprintf("/build?%s", v.Encode()), body, cli.out, headers)
|
|
| 293 |
+ if jerr, ok := err.(*utils.JSONError); ok {
|
|
| 294 |
+ // If no error code is set, default to 1 |
|
| 295 |
+ if jerr.Code == 0 {
|
|
| 296 |
+ jerr.Code = 1 |
|
| 297 |
+ } |
|
| 298 |
+ return &utils.StatusError{Status: jerr.Message, StatusCode: jerr.Code}
|
|
| 299 |
+ } |
|
| 300 |
+ return err |
|
| 301 |
+} |
| 0 | 302 |
deleted file mode 100644 |
| ... | ... |
@@ -1,2938 +0,0 @@ |
| 1 |
-package client |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- "bufio" |
|
| 5 |
- "bytes" |
|
| 6 |
- "encoding/base64" |
|
| 7 |
- "encoding/json" |
|
| 8 |
- "errors" |
|
| 9 |
- "fmt" |
|
| 10 |
- "io" |
|
| 11 |
- "io/ioutil" |
|
| 12 |
- "net/http" |
|
| 13 |
- "net/url" |
|
| 14 |
- "os" |
|
| 15 |
- "os/exec" |
|
| 16 |
- "path" |
|
| 17 |
- "path/filepath" |
|
| 18 |
- "runtime" |
|
| 19 |
- "sort" |
|
| 20 |
- "strconv" |
|
| 21 |
- "strings" |
|
| 22 |
- "sync" |
|
| 23 |
- "text/tabwriter" |
|
| 24 |
- "text/template" |
|
| 25 |
- "time" |
|
| 26 |
- |
|
| 27 |
- log "github.com/Sirupsen/logrus" |
|
| 28 |
- "github.com/docker/docker/api" |
|
| 29 |
- "github.com/docker/docker/api/types" |
|
| 30 |
- "github.com/docker/docker/autogen/dockerversion" |
|
| 31 |
- "github.com/docker/docker/engine" |
|
| 32 |
- "github.com/docker/docker/graph" |
|
| 33 |
- "github.com/docker/docker/nat" |
|
| 34 |
- "github.com/docker/docker/opts" |
|
| 35 |
- "github.com/docker/docker/pkg/archive" |
|
| 36 |
- "github.com/docker/docker/pkg/fileutils" |
|
| 37 |
- "github.com/docker/docker/pkg/homedir" |
|
| 38 |
- flag "github.com/docker/docker/pkg/mflag" |
|
| 39 |
- "github.com/docker/docker/pkg/parsers" |
|
| 40 |
- "github.com/docker/docker/pkg/parsers/filters" |
|
| 41 |
- "github.com/docker/docker/pkg/progressreader" |
|
| 42 |
- "github.com/docker/docker/pkg/promise" |
|
| 43 |
- "github.com/docker/docker/pkg/resolvconf" |
|
| 44 |
- "github.com/docker/docker/pkg/signal" |
|
| 45 |
- "github.com/docker/docker/pkg/stringid" |
|
| 46 |
- "github.com/docker/docker/pkg/symlink" |
|
| 47 |
- "github.com/docker/docker/pkg/term" |
|
| 48 |
- "github.com/docker/docker/pkg/timeutils" |
|
| 49 |
- "github.com/docker/docker/pkg/units" |
|
| 50 |
- "github.com/docker/docker/pkg/urlutil" |
|
| 51 |
- "github.com/docker/docker/registry" |
|
| 52 |
- "github.com/docker/docker/runconfig" |
|
| 53 |
- "github.com/docker/docker/utils" |
|
| 54 |
-) |
|
| 55 |
- |
|
| 56 |
-const ( |
|
| 57 |
- tarHeaderSize = 512 |
|
| 58 |
-) |
|
| 59 |
- |
|
| 60 |
-func (cli *DockerCli) CmdHelp(args ...string) error {
|
|
| 61 |
- if len(args) > 1 {
|
|
| 62 |
- method, exists := cli.getMethod(args[:2]...) |
|
| 63 |
- if exists {
|
|
| 64 |
- method("--help")
|
|
| 65 |
- return nil |
|
| 66 |
- } |
|
| 67 |
- } |
|
| 68 |
- if len(args) > 0 {
|
|
| 69 |
- method, exists := cli.getMethod(args[0]) |
|
| 70 |
- if !exists {
|
|
| 71 |
- fmt.Fprintf(cli.err, "docker: '%s' is not a docker command. See 'docker --help'.\n", args[0]) |
|
| 72 |
- os.Exit(1) |
|
| 73 |
- } else {
|
|
| 74 |
- method("--help")
|
|
| 75 |
- return nil |
|
| 76 |
- } |
|
| 77 |
- } |
|
| 78 |
- |
|
| 79 |
- flag.Usage() |
|
| 80 |
- |
|
| 81 |
- return nil |
|
| 82 |
-} |
|
| 83 |
- |
|
| 84 |
-func (cli *DockerCli) CmdBuild(args ...string) error {
|
|
| 85 |
- cmd := cli.Subcmd("build", "PATH | URL | -", "Build a new image from the source code at PATH", true)
|
|
| 86 |
- tag := cmd.String([]string{"t", "-tag"}, "", "Repository name (and optionally a tag) for the image")
|
|
| 87 |
- suppressOutput := cmd.Bool([]string{"q", "-quiet"}, false, "Suppress the verbose output generated by the containers")
|
|
| 88 |
- noCache := cmd.Bool([]string{"#no-cache", "-no-cache"}, false, "Do not use cache when building the image")
|
|
| 89 |
- rm := cmd.Bool([]string{"#rm", "-rm"}, true, "Remove intermediate containers after a successful build")
|
|
| 90 |
- forceRm := cmd.Bool([]string{"-force-rm"}, false, "Always remove intermediate containers")
|
|
| 91 |
- pull := cmd.Bool([]string{"-pull"}, false, "Always attempt to pull a newer version of the image")
|
|
| 92 |
- dockerfileName := cmd.String([]string{"f", "-file"}, "", "Name of the Dockerfile (Default is 'PATH/Dockerfile')")
|
|
| 93 |
- flMemoryString := cmd.String([]string{"m", "-memory"}, "", "Memory limit")
|
|
| 94 |
- flMemorySwap := cmd.String([]string{"-memory-swap"}, "", "Total memory (memory + swap), '-1' to disable swap")
|
|
| 95 |
- flCpuShares := cmd.Int64([]string{"c", "-cpu-shares"}, 0, "CPU shares (relative weight)")
|
|
| 96 |
- flCpuSetCpus := cmd.String([]string{"-cpuset-cpus"}, "", "CPUs in which to allow execution (0-3, 0,1)")
|
|
| 97 |
- |
|
| 98 |
- cmd.Require(flag.Exact, 1) |
|
| 99 |
- |
|
| 100 |
- utils.ParseFlags(cmd, args, true) |
|
| 101 |
- |
|
| 102 |
- var ( |
|
| 103 |
- context archive.Archive |
|
| 104 |
- isRemote bool |
|
| 105 |
- err error |
|
| 106 |
- ) |
|
| 107 |
- |
|
| 108 |
- _, err = exec.LookPath("git")
|
|
| 109 |
- hasGit := err == nil |
|
| 110 |
- if cmd.Arg(0) == "-" {
|
|
| 111 |
- // As a special case, 'docker build -' will build from either an empty context with the |
|
| 112 |
- // contents of stdin as a Dockerfile, or a tar-ed context from stdin. |
|
| 113 |
- buf := bufio.NewReader(cli.in) |
|
| 114 |
- magic, err := buf.Peek(tarHeaderSize) |
|
| 115 |
- if err != nil && err != io.EOF {
|
|
| 116 |
- return fmt.Errorf("failed to peek context header from STDIN: %v", err)
|
|
| 117 |
- } |
|
| 118 |
- if !archive.IsArchive(magic) {
|
|
| 119 |
- dockerfile, err := ioutil.ReadAll(buf) |
|
| 120 |
- if err != nil {
|
|
| 121 |
- return fmt.Errorf("failed to read Dockerfile from STDIN: %v", err)
|
|
| 122 |
- } |
|
| 123 |
- |
|
| 124 |
- // -f option has no meaning when we're reading it from stdin, |
|
| 125 |
- // so just use our default Dockerfile name |
|
| 126 |
- *dockerfileName = api.DefaultDockerfileName |
|
| 127 |
- context, err = archive.Generate(*dockerfileName, string(dockerfile)) |
|
| 128 |
- } else {
|
|
| 129 |
- context = ioutil.NopCloser(buf) |
|
| 130 |
- } |
|
| 131 |
- } else if urlutil.IsURL(cmd.Arg(0)) && (!urlutil.IsGitURL(cmd.Arg(0)) || !hasGit) {
|
|
| 132 |
- isRemote = true |
|
| 133 |
- } else {
|
|
| 134 |
- root := cmd.Arg(0) |
|
| 135 |
- if urlutil.IsGitURL(root) {
|
|
| 136 |
- remoteURL := cmd.Arg(0) |
|
| 137 |
- if !urlutil.IsGitTransport(remoteURL) {
|
|
| 138 |
- remoteURL = "https://" + remoteURL |
|
| 139 |
- } |
|
| 140 |
- |
|
| 141 |
- root, err = ioutil.TempDir("", "docker-build-git")
|
|
| 142 |
- if err != nil {
|
|
| 143 |
- return err |
|
| 144 |
- } |
|
| 145 |
- defer os.RemoveAll(root) |
|
| 146 |
- |
|
| 147 |
- if output, err := exec.Command("git", "clone", "--recursive", remoteURL, root).CombinedOutput(); err != nil {
|
|
| 148 |
- return fmt.Errorf("Error trying to use git: %s (%s)", err, output)
|
|
| 149 |
- } |
|
| 150 |
- } |
|
| 151 |
- if _, err := os.Stat(root); err != nil {
|
|
| 152 |
- return err |
|
| 153 |
- } |
|
| 154 |
- |
|
| 155 |
- absRoot, err := filepath.Abs(root) |
|
| 156 |
- if err != nil {
|
|
| 157 |
- return err |
|
| 158 |
- } |
|
| 159 |
- |
|
| 160 |
- filename := *dockerfileName // path to Dockerfile |
|
| 161 |
- |
|
| 162 |
- if *dockerfileName == "" {
|
|
| 163 |
- // No -f/--file was specified so use the default |
|
| 164 |
- *dockerfileName = api.DefaultDockerfileName |
|
| 165 |
- filename = filepath.Join(absRoot, *dockerfileName) |
|
| 166 |
- |
|
| 167 |
- // Just to be nice ;-) look for 'dockerfile' too but only |
|
| 168 |
- // use it if we found it, otherwise ignore this check |
|
| 169 |
- if _, err = os.Lstat(filename); os.IsNotExist(err) {
|
|
| 170 |
- tmpFN := path.Join(absRoot, strings.ToLower(*dockerfileName)) |
|
| 171 |
- if _, err = os.Lstat(tmpFN); err == nil {
|
|
| 172 |
- *dockerfileName = strings.ToLower(*dockerfileName) |
|
| 173 |
- filename = tmpFN |
|
| 174 |
- } |
|
| 175 |
- } |
|
| 176 |
- } |
|
| 177 |
- |
|
| 178 |
- origDockerfile := *dockerfileName // used for error msg |
|
| 179 |
- if filename, err = filepath.Abs(filename); err != nil {
|
|
| 180 |
- return err |
|
| 181 |
- } |
|
| 182 |
- |
|
| 183 |
- // Verify that 'filename' is within the build context |
|
| 184 |
- filename, err = symlink.FollowSymlinkInScope(filename, absRoot) |
|
| 185 |
- if err != nil {
|
|
| 186 |
- return fmt.Errorf("The Dockerfile (%s) must be within the build context (%s)", origDockerfile, root)
|
|
| 187 |
- } |
|
| 188 |
- |
|
| 189 |
- // Now reset the dockerfileName to be relative to the build context |
|
| 190 |
- *dockerfileName, err = filepath.Rel(absRoot, filename) |
|
| 191 |
- if err != nil {
|
|
| 192 |
- return err |
|
| 193 |
- } |
|
| 194 |
- // And canonicalize dockerfile name to a platform-independent one |
|
| 195 |
- *dockerfileName, err = archive.CanonicalTarNameForPath(*dockerfileName) |
|
| 196 |
- if err != nil {
|
|
| 197 |
- return fmt.Errorf("Cannot canonicalize dockerfile path %s: %v", dockerfileName, err)
|
|
| 198 |
- } |
|
| 199 |
- |
|
| 200 |
- if _, err = os.Lstat(filename); os.IsNotExist(err) {
|
|
| 201 |
- return fmt.Errorf("Cannot locate Dockerfile: %s", origDockerfile)
|
|
| 202 |
- } |
|
| 203 |
- var includes = []string{"."}
|
|
| 204 |
- |
|
| 205 |
- excludes, err := utils.ReadDockerIgnore(path.Join(root, ".dockerignore")) |
|
| 206 |
- if err != nil {
|
|
| 207 |
- return err |
|
| 208 |
- } |
|
| 209 |
- |
|
| 210 |
- // If .dockerignore mentions .dockerignore or the Dockerfile |
|
| 211 |
- // then make sure we send both files over to the daemon |
|
| 212 |
- // because Dockerfile is, obviously, needed no matter what, and |
|
| 213 |
- // .dockerignore is needed to know if either one needs to be |
|
| 214 |
- // removed. The deamon will remove them for us, if needed, after it |
|
| 215 |
- // parses the Dockerfile. |
|
| 216 |
- keepThem1, _ := fileutils.Matches(".dockerignore", excludes)
|
|
| 217 |
- keepThem2, _ := fileutils.Matches(*dockerfileName, excludes) |
|
| 218 |
- if keepThem1 || keepThem2 {
|
|
| 219 |
- includes = append(includes, ".dockerignore", *dockerfileName) |
|
| 220 |
- } |
|
| 221 |
- |
|
| 222 |
- if err = utils.ValidateContextDirectory(root, excludes); err != nil {
|
|
| 223 |
- return fmt.Errorf("Error checking context is accessible: '%s'. Please check permissions and try again.", err)
|
|
| 224 |
- } |
|
| 225 |
- options := &archive.TarOptions{
|
|
| 226 |
- Compression: archive.Uncompressed, |
|
| 227 |
- ExcludePatterns: excludes, |
|
| 228 |
- IncludeFiles: includes, |
|
| 229 |
- } |
|
| 230 |
- context, err = archive.TarWithOptions(root, options) |
|
| 231 |
- if err != nil {
|
|
| 232 |
- return err |
|
| 233 |
- } |
|
| 234 |
- } |
|
| 235 |
- |
|
| 236 |
- // windows: show error message about modified file permissions |
|
| 237 |
- // FIXME: this is not a valid warning when the daemon is running windows. should be removed once docker engine for windows can build. |
|
| 238 |
- if runtime.GOOS == "windows" {
|
|
| 239 |
- log.Warn(`SECURITY WARNING: You are building a Docker image from Windows against a Linux 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.`) |
|
| 240 |
- } |
|
| 241 |
- |
|
| 242 |
- var body io.Reader |
|
| 243 |
- // Setup an upload progress bar |
|
| 244 |
- // FIXME: ProgressReader shouldn't be this annoying to use |
|
| 245 |
- if context != nil {
|
|
| 246 |
- sf := utils.NewStreamFormatter(false) |
|
| 247 |
- body = progressreader.New(progressreader.Config{
|
|
| 248 |
- In: context, |
|
| 249 |
- Out: cli.out, |
|
| 250 |
- Formatter: sf, |
|
| 251 |
- NewLines: true, |
|
| 252 |
- ID: "", |
|
| 253 |
- Action: "Sending build context to Docker daemon", |
|
| 254 |
- }) |
|
| 255 |
- } |
|
| 256 |
- |
|
| 257 |
- var memory int64 |
|
| 258 |
- if *flMemoryString != "" {
|
|
| 259 |
- parsedMemory, err := units.RAMInBytes(*flMemoryString) |
|
| 260 |
- if err != nil {
|
|
| 261 |
- return err |
|
| 262 |
- } |
|
| 263 |
- memory = parsedMemory |
|
| 264 |
- } |
|
| 265 |
- |
|
| 266 |
- var memorySwap int64 |
|
| 267 |
- if *flMemorySwap != "" {
|
|
| 268 |
- if *flMemorySwap == "-1" {
|
|
| 269 |
- memorySwap = -1 |
|
| 270 |
- } else {
|
|
| 271 |
- parsedMemorySwap, err := units.RAMInBytes(*flMemorySwap) |
|
| 272 |
- if err != nil {
|
|
| 273 |
- return err |
|
| 274 |
- } |
|
| 275 |
- memorySwap = parsedMemorySwap |
|
| 276 |
- } |
|
| 277 |
- } |
|
| 278 |
- // Send the build context |
|
| 279 |
- v := &url.Values{}
|
|
| 280 |
- |
|
| 281 |
- //Check if the given image name can be resolved |
|
| 282 |
- if *tag != "" {
|
|
| 283 |
- repository, tag := parsers.ParseRepositoryTag(*tag) |
|
| 284 |
- if err := registry.ValidateRepositoryName(repository); err != nil {
|
|
| 285 |
- return err |
|
| 286 |
- } |
|
| 287 |
- if len(tag) > 0 {
|
|
| 288 |
- if err := graph.ValidateTagName(tag); err != nil {
|
|
| 289 |
- return err |
|
| 290 |
- } |
|
| 291 |
- } |
|
| 292 |
- } |
|
| 293 |
- |
|
| 294 |
- v.Set("t", *tag)
|
|
| 295 |
- |
|
| 296 |
- if *suppressOutput {
|
|
| 297 |
- v.Set("q", "1")
|
|
| 298 |
- } |
|
| 299 |
- if isRemote {
|
|
| 300 |
- v.Set("remote", cmd.Arg(0))
|
|
| 301 |
- } |
|
| 302 |
- if *noCache {
|
|
| 303 |
- v.Set("nocache", "1")
|
|
| 304 |
- } |
|
| 305 |
- if *rm {
|
|
| 306 |
- v.Set("rm", "1")
|
|
| 307 |
- } else {
|
|
| 308 |
- v.Set("rm", "0")
|
|
| 309 |
- } |
|
| 310 |
- |
|
| 311 |
- if *forceRm {
|
|
| 312 |
- v.Set("forcerm", "1")
|
|
| 313 |
- } |
|
| 314 |
- |
|
| 315 |
- if *pull {
|
|
| 316 |
- v.Set("pull", "1")
|
|
| 317 |
- } |
|
| 318 |
- |
|
| 319 |
- v.Set("cpusetcpus", *flCpuSetCpus)
|
|
| 320 |
- v.Set("cpushares", strconv.FormatInt(*flCpuShares, 10))
|
|
| 321 |
- v.Set("memory", strconv.FormatInt(memory, 10))
|
|
| 322 |
- v.Set("memswap", strconv.FormatInt(memorySwap, 10))
|
|
| 323 |
- |
|
| 324 |
- v.Set("dockerfile", *dockerfileName)
|
|
| 325 |
- |
|
| 326 |
- cli.LoadConfigFile() |
|
| 327 |
- |
|
| 328 |
- headers := http.Header(make(map[string][]string)) |
|
| 329 |
- buf, err := json.Marshal(cli.configFile) |
|
| 330 |
- if err != nil {
|
|
| 331 |
- return err |
|
| 332 |
- } |
|
| 333 |
- headers.Add("X-Registry-Config", base64.URLEncoding.EncodeToString(buf))
|
|
| 334 |
- |
|
| 335 |
- if context != nil {
|
|
| 336 |
- headers.Set("Content-Type", "application/tar")
|
|
| 337 |
- } |
|
| 338 |
- err = cli.stream("POST", fmt.Sprintf("/build?%s", v.Encode()), body, cli.out, headers)
|
|
| 339 |
- if jerr, ok := err.(*utils.JSONError); ok {
|
|
| 340 |
- // If no error code is set, default to 1 |
|
| 341 |
- if jerr.Code == 0 {
|
|
| 342 |
- jerr.Code = 1 |
|
| 343 |
- } |
|
| 344 |
- return &utils.StatusError{Status: jerr.Message, StatusCode: jerr.Code}
|
|
| 345 |
- } |
|
| 346 |
- return err |
|
| 347 |
-} |
|
| 348 |
- |
|
| 349 |
-// 'docker login': login / register a user to registry service. |
|
| 350 |
-func (cli *DockerCli) CmdLogin(args ...string) error {
|
|
| 351 |
- cmd := cli.Subcmd("login", "[SERVER]", "Register or log in to a Docker registry server, if no server is\nspecified \""+registry.IndexServerAddress()+"\" is the default.", true)
|
|
| 352 |
- cmd.Require(flag.Max, 1) |
|
| 353 |
- |
|
| 354 |
- var username, password, email string |
|
| 355 |
- |
|
| 356 |
- cmd.StringVar(&username, []string{"u", "-username"}, "", "Username")
|
|
| 357 |
- cmd.StringVar(&password, []string{"p", "-password"}, "", "Password")
|
|
| 358 |
- cmd.StringVar(&email, []string{"e", "-email"}, "", "Email")
|
|
| 359 |
- |
|
| 360 |
- utils.ParseFlags(cmd, args, true) |
|
| 361 |
- |
|
| 362 |
- serverAddress := registry.IndexServerAddress() |
|
| 363 |
- if len(cmd.Args()) > 0 {
|
|
| 364 |
- serverAddress = cmd.Arg(0) |
|
| 365 |
- } |
|
| 366 |
- |
|
| 367 |
- promptDefault := func(prompt string, configDefault string) {
|
|
| 368 |
- if configDefault == "" {
|
|
| 369 |
- fmt.Fprintf(cli.out, "%s: ", prompt) |
|
| 370 |
- } else {
|
|
| 371 |
- fmt.Fprintf(cli.out, "%s (%s): ", prompt, configDefault) |
|
| 372 |
- } |
|
| 373 |
- } |
|
| 374 |
- |
|
| 375 |
- readInput := func(in io.Reader, out io.Writer) string {
|
|
| 376 |
- reader := bufio.NewReader(in) |
|
| 377 |
- line, _, err := reader.ReadLine() |
|
| 378 |
- if err != nil {
|
|
| 379 |
- fmt.Fprintln(out, err.Error()) |
|
| 380 |
- os.Exit(1) |
|
| 381 |
- } |
|
| 382 |
- return string(line) |
|
| 383 |
- } |
|
| 384 |
- |
|
| 385 |
- cli.LoadConfigFile() |
|
| 386 |
- authconfig, ok := cli.configFile.Configs[serverAddress] |
|
| 387 |
- if !ok {
|
|
| 388 |
- authconfig = registry.AuthConfig{}
|
|
| 389 |
- } |
|
| 390 |
- |
|
| 391 |
- if username == "" {
|
|
| 392 |
- promptDefault("Username", authconfig.Username)
|
|
| 393 |
- username = readInput(cli.in, cli.out) |
|
| 394 |
- username = strings.Trim(username, " ") |
|
| 395 |
- if username == "" {
|
|
| 396 |
- username = authconfig.Username |
|
| 397 |
- } |
|
| 398 |
- } |
|
| 399 |
- // Assume that a different username means they may not want to use |
|
| 400 |
- // the password or email from the config file, so prompt them |
|
| 401 |
- if username != authconfig.Username {
|
|
| 402 |
- if password == "" {
|
|
| 403 |
- oldState, err := term.SaveState(cli.inFd) |
|
| 404 |
- if err != nil {
|
|
| 405 |
- return err |
|
| 406 |
- } |
|
| 407 |
- fmt.Fprintf(cli.out, "Password: ") |
|
| 408 |
- term.DisableEcho(cli.inFd, oldState) |
|
| 409 |
- |
|
| 410 |
- password = readInput(cli.in, cli.out) |
|
| 411 |
- fmt.Fprint(cli.out, "\n") |
|
| 412 |
- |
|
| 413 |
- term.RestoreTerminal(cli.inFd, oldState) |
|
| 414 |
- if password == "" {
|
|
| 415 |
- return fmt.Errorf("Error : Password Required")
|
|
| 416 |
- } |
|
| 417 |
- } |
|
| 418 |
- |
|
| 419 |
- if email == "" {
|
|
| 420 |
- promptDefault("Email", authconfig.Email)
|
|
| 421 |
- email = readInput(cli.in, cli.out) |
|
| 422 |
- if email == "" {
|
|
| 423 |
- email = authconfig.Email |
|
| 424 |
- } |
|
| 425 |
- } |
|
| 426 |
- } else {
|
|
| 427 |
- // However, if they don't override the username use the |
|
| 428 |
- // password or email from the cmd line if specified. IOW, allow |
|
| 429 |
- // then to change/override them. And if not specified, just |
|
| 430 |
- // use what's in the config file |
|
| 431 |
- if password == "" {
|
|
| 432 |
- password = authconfig.Password |
|
| 433 |
- } |
|
| 434 |
- if email == "" {
|
|
| 435 |
- email = authconfig.Email |
|
| 436 |
- } |
|
| 437 |
- } |
|
| 438 |
- authconfig.Username = username |
|
| 439 |
- authconfig.Password = password |
|
| 440 |
- authconfig.Email = email |
|
| 441 |
- authconfig.ServerAddress = serverAddress |
|
| 442 |
- cli.configFile.Configs[serverAddress] = authconfig |
|
| 443 |
- |
|
| 444 |
- stream, statusCode, err := cli.call("POST", "/auth", cli.configFile.Configs[serverAddress], false)
|
|
| 445 |
- if statusCode == 401 {
|
|
| 446 |
- delete(cli.configFile.Configs, serverAddress) |
|
| 447 |
- registry.SaveConfig(cli.configFile) |
|
| 448 |
- return err |
|
| 449 |
- } |
|
| 450 |
- if err != nil {
|
|
| 451 |
- return err |
|
| 452 |
- } |
|
| 453 |
- |
|
| 454 |
- var response types.AuthResponse |
|
| 455 |
- if err := json.NewDecoder(stream).Decode(response); err != nil {
|
|
| 456 |
- cli.configFile, _ = registry.LoadConfig(homedir.Get()) |
|
| 457 |
- return err |
|
| 458 |
- } |
|
| 459 |
- |
|
| 460 |
- registry.SaveConfig(cli.configFile) |
|
| 461 |
- fmt.Fprintf(cli.out, "WARNING: login credentials saved in %s.\n", path.Join(homedir.Get(), registry.CONFIGFILE)) |
|
| 462 |
- |
|
| 463 |
- if response.Status != "" {
|
|
| 464 |
- fmt.Fprintf(cli.out, "%s\n", response.Status) |
|
| 465 |
- } |
|
| 466 |
- return nil |
|
| 467 |
-} |
|
| 468 |
- |
|
| 469 |
-// log out from a Docker registry |
|
| 470 |
-func (cli *DockerCli) CmdLogout(args ...string) error {
|
|
| 471 |
- cmd := cli.Subcmd("logout", "[SERVER]", "Log out from a Docker registry, if no server is\nspecified \""+registry.IndexServerAddress()+"\" is the default.", true)
|
|
| 472 |
- cmd.Require(flag.Max, 1) |
|
| 473 |
- |
|
| 474 |
- utils.ParseFlags(cmd, args, false) |
|
| 475 |
- serverAddress := registry.IndexServerAddress() |
|
| 476 |
- if len(cmd.Args()) > 0 {
|
|
| 477 |
- serverAddress = cmd.Arg(0) |
|
| 478 |
- } |
|
| 479 |
- |
|
| 480 |
- cli.LoadConfigFile() |
|
| 481 |
- if _, ok := cli.configFile.Configs[serverAddress]; !ok {
|
|
| 482 |
- fmt.Fprintf(cli.out, "Not logged in to %s\n", serverAddress) |
|
| 483 |
- } else {
|
|
| 484 |
- fmt.Fprintf(cli.out, "Remove login credentials for %s\n", serverAddress) |
|
| 485 |
- delete(cli.configFile.Configs, serverAddress) |
|
| 486 |
- |
|
| 487 |
- if err := registry.SaveConfig(cli.configFile); err != nil {
|
|
| 488 |
- return fmt.Errorf("Failed to save docker config: %v", err)
|
|
| 489 |
- } |
|
| 490 |
- } |
|
| 491 |
- return nil |
|
| 492 |
-} |
|
| 493 |
- |
|
| 494 |
-// 'docker wait': block until a container stops |
|
| 495 |
-func (cli *DockerCli) CmdWait(args ...string) error {
|
|
| 496 |
- cmd := cli.Subcmd("wait", "CONTAINER [CONTAINER...]", "Block until a container stops, then print its exit code.", true)
|
|
| 497 |
- cmd.Require(flag.Min, 1) |
|
| 498 |
- |
|
| 499 |
- utils.ParseFlags(cmd, args, true) |
|
| 500 |
- |
|
| 501 |
- var encounteredError error |
|
| 502 |
- for _, name := range cmd.Args() {
|
|
| 503 |
- status, err := waitForExit(cli, name) |
|
| 504 |
- if err != nil {
|
|
| 505 |
- fmt.Fprintf(cli.err, "%s\n", err) |
|
| 506 |
- encounteredError = fmt.Errorf("Error: failed to wait one or more containers")
|
|
| 507 |
- } else {
|
|
| 508 |
- fmt.Fprintf(cli.out, "%d\n", status) |
|
| 509 |
- } |
|
| 510 |
- } |
|
| 511 |
- return encounteredError |
|
| 512 |
-} |
|
| 513 |
- |
|
| 514 |
-// 'docker version': show version information |
|
| 515 |
-func (cli *DockerCli) CmdVersion(args ...string) error {
|
|
| 516 |
- cmd := cli.Subcmd("version", "", "Show the Docker version information.", true)
|
|
| 517 |
- cmd.Require(flag.Exact, 0) |
|
| 518 |
- |
|
| 519 |
- utils.ParseFlags(cmd, args, false) |
|
| 520 |
- |
|
| 521 |
- if dockerversion.VERSION != "" {
|
|
| 522 |
- fmt.Fprintf(cli.out, "Client version: %s\n", dockerversion.VERSION) |
|
| 523 |
- } |
|
| 524 |
- fmt.Fprintf(cli.out, "Client API version: %s\n", api.APIVERSION) |
|
| 525 |
- fmt.Fprintf(cli.out, "Go version (client): %s\n", runtime.Version()) |
|
| 526 |
- if dockerversion.GITCOMMIT != "" {
|
|
| 527 |
- fmt.Fprintf(cli.out, "Git commit (client): %s\n", dockerversion.GITCOMMIT) |
|
| 528 |
- } |
|
| 529 |
- fmt.Fprintf(cli.out, "OS/Arch (client): %s/%s\n", runtime.GOOS, runtime.GOARCH) |
|
| 530 |
- |
|
| 531 |
- body, _, err := readBody(cli.call("GET", "/version", nil, false))
|
|
| 532 |
- if err != nil {
|
|
| 533 |
- return err |
|
| 534 |
- } |
|
| 535 |
- |
|
| 536 |
- out := engine.NewOutput() |
|
| 537 |
- remoteVersion, err := out.AddEnv() |
|
| 538 |
- if err != nil {
|
|
| 539 |
- log.Errorf("Error reading remote version: %s", err)
|
|
| 540 |
- return err |
|
| 541 |
- } |
|
| 542 |
- if _, err := out.Write(body); err != nil {
|
|
| 543 |
- log.Errorf("Error reading remote version: %s", err)
|
|
| 544 |
- return err |
|
| 545 |
- } |
|
| 546 |
- out.Close() |
|
| 547 |
- fmt.Fprintf(cli.out, "Server version: %s\n", remoteVersion.Get("Version"))
|
|
| 548 |
- if apiVersion := remoteVersion.Get("ApiVersion"); apiVersion != "" {
|
|
| 549 |
- fmt.Fprintf(cli.out, "Server API version: %s\n", apiVersion) |
|
| 550 |
- } |
|
| 551 |
- fmt.Fprintf(cli.out, "Go version (server): %s\n", remoteVersion.Get("GoVersion"))
|
|
| 552 |
- fmt.Fprintf(cli.out, "Git commit (server): %s\n", remoteVersion.Get("GitCommit"))
|
|
| 553 |
- fmt.Fprintf(cli.out, "OS/Arch (server): %s/%s\n", remoteVersion.Get("Os"), remoteVersion.Get("Arch"))
|
|
| 554 |
- return nil |
|
| 555 |
-} |
|
| 556 |
- |
|
| 557 |
-// 'docker info': display system-wide information. |
|
| 558 |
-func (cli *DockerCli) CmdInfo(args ...string) error {
|
|
| 559 |
- cmd := cli.Subcmd("info", "", "Display system-wide information", true)
|
|
| 560 |
- cmd.Require(flag.Exact, 0) |
|
| 561 |
- utils.ParseFlags(cmd, args, false) |
|
| 562 |
- |
|
| 563 |
- body, _, err := readBody(cli.call("GET", "/info", nil, false))
|
|
| 564 |
- if err != nil {
|
|
| 565 |
- return err |
|
| 566 |
- } |
|
| 567 |
- |
|
| 568 |
- out := engine.NewOutput() |
|
| 569 |
- remoteInfo, err := out.AddEnv() |
|
| 570 |
- if err != nil {
|
|
| 571 |
- return err |
|
| 572 |
- } |
|
| 573 |
- |
|
| 574 |
- if _, err := out.Write(body); err != nil {
|
|
| 575 |
- log.Errorf("Error reading remote info: %s", err)
|
|
| 576 |
- return err |
|
| 577 |
- } |
|
| 578 |
- out.Close() |
|
| 579 |
- |
|
| 580 |
- if remoteInfo.Exists("Containers") {
|
|
| 581 |
- fmt.Fprintf(cli.out, "Containers: %d\n", remoteInfo.GetInt("Containers"))
|
|
| 582 |
- } |
|
| 583 |
- if remoteInfo.Exists("Images") {
|
|
| 584 |
- fmt.Fprintf(cli.out, "Images: %d\n", remoteInfo.GetInt("Images"))
|
|
| 585 |
- } |
|
| 586 |
- if remoteInfo.Exists("Driver") {
|
|
| 587 |
- fmt.Fprintf(cli.out, "Storage Driver: %s\n", remoteInfo.Get("Driver"))
|
|
| 588 |
- } |
|
| 589 |
- if remoteInfo.Exists("DriverStatus") {
|
|
| 590 |
- var driverStatus [][2]string |
|
| 591 |
- if err := remoteInfo.GetJson("DriverStatus", &driverStatus); err != nil {
|
|
| 592 |
- return err |
|
| 593 |
- } |
|
| 594 |
- for _, pair := range driverStatus {
|
|
| 595 |
- fmt.Fprintf(cli.out, " %s: %s\n", pair[0], pair[1]) |
|
| 596 |
- } |
|
| 597 |
- } |
|
| 598 |
- if remoteInfo.Exists("ExecutionDriver") {
|
|
| 599 |
- fmt.Fprintf(cli.out, "Execution Driver: %s\n", remoteInfo.Get("ExecutionDriver"))
|
|
| 600 |
- } |
|
| 601 |
- if remoteInfo.Exists("KernelVersion") {
|
|
| 602 |
- fmt.Fprintf(cli.out, "Kernel Version: %s\n", remoteInfo.Get("KernelVersion"))
|
|
| 603 |
- } |
|
| 604 |
- if remoteInfo.Exists("OperatingSystem") {
|
|
| 605 |
- fmt.Fprintf(cli.out, "Operating System: %s\n", remoteInfo.Get("OperatingSystem"))
|
|
| 606 |
- } |
|
| 607 |
- if remoteInfo.Exists("NCPU") {
|
|
| 608 |
- fmt.Fprintf(cli.out, "CPUs: %d\n", remoteInfo.GetInt("NCPU"))
|
|
| 609 |
- } |
|
| 610 |
- if remoteInfo.Exists("MemTotal") {
|
|
| 611 |
- fmt.Fprintf(cli.out, "Total Memory: %s\n", units.BytesSize(float64(remoteInfo.GetInt64("MemTotal"))))
|
|
| 612 |
- } |
|
| 613 |
- if remoteInfo.Exists("Name") {
|
|
| 614 |
- fmt.Fprintf(cli.out, "Name: %s\n", remoteInfo.Get("Name"))
|
|
| 615 |
- } |
|
| 616 |
- if remoteInfo.Exists("ID") {
|
|
| 617 |
- fmt.Fprintf(cli.out, "ID: %s\n", remoteInfo.Get("ID"))
|
|
| 618 |
- } |
|
| 619 |
- |
|
| 620 |
- if remoteInfo.GetBool("Debug") || os.Getenv("DEBUG") != "" {
|
|
| 621 |
- if remoteInfo.Exists("Debug") {
|
|
| 622 |
- fmt.Fprintf(cli.out, "Debug mode (server): %v\n", remoteInfo.GetBool("Debug"))
|
|
| 623 |
- } |
|
| 624 |
- fmt.Fprintf(cli.out, "Debug mode (client): %v\n", os.Getenv("DEBUG") != "")
|
|
| 625 |
- if remoteInfo.Exists("NFd") {
|
|
| 626 |
- fmt.Fprintf(cli.out, "Fds: %d\n", remoteInfo.GetInt("NFd"))
|
|
| 627 |
- } |
|
| 628 |
- if remoteInfo.Exists("NGoroutines") {
|
|
| 629 |
- fmt.Fprintf(cli.out, "Goroutines: %d\n", remoteInfo.GetInt("NGoroutines"))
|
|
| 630 |
- } |
|
| 631 |
- if remoteInfo.Exists("SystemTime") {
|
|
| 632 |
- t, err := remoteInfo.GetTime("SystemTime")
|
|
| 633 |
- if err != nil {
|
|
| 634 |
- log.Errorf("Error reading system time: %v", err)
|
|
| 635 |
- } else {
|
|
| 636 |
- fmt.Fprintf(cli.out, "System Time: %s\n", t.Format(time.UnixDate)) |
|
| 637 |
- } |
|
| 638 |
- } |
|
| 639 |
- if remoteInfo.Exists("NEventsListener") {
|
|
| 640 |
- fmt.Fprintf(cli.out, "EventsListeners: %d\n", remoteInfo.GetInt("NEventsListener"))
|
|
| 641 |
- } |
|
| 642 |
- if initSha1 := remoteInfo.Get("InitSha1"); initSha1 != "" {
|
|
| 643 |
- fmt.Fprintf(cli.out, "Init SHA1: %s\n", initSha1) |
|
| 644 |
- } |
|
| 645 |
- if initPath := remoteInfo.Get("InitPath"); initPath != "" {
|
|
| 646 |
- fmt.Fprintf(cli.out, "Init Path: %s\n", initPath) |
|
| 647 |
- } |
|
| 648 |
- if root := remoteInfo.Get("DockerRootDir"); root != "" {
|
|
| 649 |
- fmt.Fprintf(cli.out, "Docker Root Dir: %s\n", root) |
|
| 650 |
- } |
|
| 651 |
- } |
|
| 652 |
- if remoteInfo.Exists("HttpProxy") {
|
|
| 653 |
- fmt.Fprintf(cli.out, "Http Proxy: %s\n", remoteInfo.Get("HttpProxy"))
|
|
| 654 |
- } |
|
| 655 |
- if remoteInfo.Exists("HttpsProxy") {
|
|
| 656 |
- fmt.Fprintf(cli.out, "Https Proxy: %s\n", remoteInfo.Get("HttpsProxy"))
|
|
| 657 |
- } |
|
| 658 |
- if remoteInfo.Exists("NoProxy") {
|
|
| 659 |
- fmt.Fprintf(cli.out, "No Proxy: %s\n", remoteInfo.Get("NoProxy"))
|
|
| 660 |
- } |
|
| 661 |
- if len(remoteInfo.GetList("IndexServerAddress")) != 0 {
|
|
| 662 |
- cli.LoadConfigFile() |
|
| 663 |
- u := cli.configFile.Configs[remoteInfo.Get("IndexServerAddress")].Username
|
|
| 664 |
- if len(u) > 0 {
|
|
| 665 |
- fmt.Fprintf(cli.out, "Username: %v\n", u) |
|
| 666 |
- fmt.Fprintf(cli.out, "Registry: %v\n", remoteInfo.GetList("IndexServerAddress"))
|
|
| 667 |
- } |
|
| 668 |
- } |
|
| 669 |
- if remoteInfo.Exists("MemoryLimit") && !remoteInfo.GetBool("MemoryLimit") {
|
|
| 670 |
- fmt.Fprintf(cli.err, "WARNING: No memory limit support\n") |
|
| 671 |
- } |
|
| 672 |
- if remoteInfo.Exists("SwapLimit") && !remoteInfo.GetBool("SwapLimit") {
|
|
| 673 |
- fmt.Fprintf(cli.err, "WARNING: No swap limit support\n") |
|
| 674 |
- } |
|
| 675 |
- if remoteInfo.Exists("IPv4Forwarding") && !remoteInfo.GetBool("IPv4Forwarding") {
|
|
| 676 |
- fmt.Fprintf(cli.err, "WARNING: IPv4 forwarding is disabled.\n") |
|
| 677 |
- } |
|
| 678 |
- if remoteInfo.Exists("Labels") {
|
|
| 679 |
- fmt.Fprintln(cli.out, "Labels:") |
|
| 680 |
- for _, attribute := range remoteInfo.GetList("Labels") {
|
|
| 681 |
- fmt.Fprintf(cli.out, " %s\n", attribute) |
|
| 682 |
- } |
|
| 683 |
- } |
|
| 684 |
- |
|
| 685 |
- return nil |
|
| 686 |
-} |
|
| 687 |
- |
|
| 688 |
-func (cli *DockerCli) CmdStop(args ...string) error {
|
|
| 689 |
- cmd := cli.Subcmd("stop", "CONTAINER [CONTAINER...]", "Stop a running container by sending SIGTERM and then SIGKILL after a\ngrace period", true)
|
|
| 690 |
- nSeconds := cmd.Int([]string{"t", "-time"}, 10, "Seconds to wait for stop before killing it")
|
|
| 691 |
- cmd.Require(flag.Min, 1) |
|
| 692 |
- |
|
| 693 |
- utils.ParseFlags(cmd, args, true) |
|
| 694 |
- |
|
| 695 |
- v := url.Values{}
|
|
| 696 |
- v.Set("t", strconv.Itoa(*nSeconds))
|
|
| 697 |
- |
|
| 698 |
- var encounteredError error |
|
| 699 |
- for _, name := range cmd.Args() {
|
|
| 700 |
- _, _, err := readBody(cli.call("POST", "/containers/"+name+"/stop?"+v.Encode(), nil, false))
|
|
| 701 |
- if err != nil {
|
|
| 702 |
- fmt.Fprintf(cli.err, "%s\n", err) |
|
| 703 |
- encounteredError = fmt.Errorf("Error: failed to stop one or more containers")
|
|
| 704 |
- } else {
|
|
| 705 |
- fmt.Fprintf(cli.out, "%s\n", name) |
|
| 706 |
- } |
|
| 707 |
- } |
|
| 708 |
- return encounteredError |
|
| 709 |
-} |
|
| 710 |
- |
|
| 711 |
-func (cli *DockerCli) CmdRestart(args ...string) error {
|
|
| 712 |
- cmd := cli.Subcmd("restart", "CONTAINER [CONTAINER...]", "Restart a running container", true)
|
|
| 713 |
- nSeconds := cmd.Int([]string{"t", "-time"}, 10, "Seconds to wait for stop before killing the container")
|
|
| 714 |
- cmd.Require(flag.Min, 1) |
|
| 715 |
- |
|
| 716 |
- utils.ParseFlags(cmd, args, true) |
|
| 717 |
- |
|
| 718 |
- v := url.Values{}
|
|
| 719 |
- v.Set("t", strconv.Itoa(*nSeconds))
|
|
| 720 |
- |
|
| 721 |
- var encounteredError error |
|
| 722 |
- for _, name := range cmd.Args() {
|
|
| 723 |
- _, _, err := readBody(cli.call("POST", "/containers/"+name+"/restart?"+v.Encode(), nil, false))
|
|
| 724 |
- if err != nil {
|
|
| 725 |
- fmt.Fprintf(cli.err, "%s\n", err) |
|
| 726 |
- encounteredError = fmt.Errorf("Error: failed to restart one or more containers")
|
|
| 727 |
- } else {
|
|
| 728 |
- fmt.Fprintf(cli.out, "%s\n", name) |
|
| 729 |
- } |
|
| 730 |
- } |
|
| 731 |
- return encounteredError |
|
| 732 |
-} |
|
| 733 |
- |
|
| 734 |
-func (cli *DockerCli) forwardAllSignals(cid string) chan os.Signal {
|
|
| 735 |
- sigc := make(chan os.Signal, 128) |
|
| 736 |
- signal.CatchAll(sigc) |
|
| 737 |
- go func() {
|
|
| 738 |
- for s := range sigc {
|
|
| 739 |
- if s == signal.SIGCHLD {
|
|
| 740 |
- continue |
|
| 741 |
- } |
|
| 742 |
- var sig string |
|
| 743 |
- for sigStr, sigN := range signal.SignalMap {
|
|
| 744 |
- if sigN == s {
|
|
| 745 |
- sig = sigStr |
|
| 746 |
- break |
|
| 747 |
- } |
|
| 748 |
- } |
|
| 749 |
- if sig == "" {
|
|
| 750 |
- log.Errorf("Unsupported signal: %v. Discarding.", s)
|
|
| 751 |
- } |
|
| 752 |
- if _, _, err := readBody(cli.call("POST", fmt.Sprintf("/containers/%s/kill?signal=%s", cid, sig), nil, false)); err != nil {
|
|
| 753 |
- log.Debugf("Error sending signal: %s", err)
|
|
| 754 |
- } |
|
| 755 |
- } |
|
| 756 |
- }() |
|
| 757 |
- return sigc |
|
| 758 |
-} |
|
| 759 |
- |
|
| 760 |
-func (cli *DockerCli) CmdStart(args ...string) error {
|
|
| 761 |
- var ( |
|
| 762 |
- cErr chan error |
|
| 763 |
- tty bool |
|
| 764 |
- |
|
| 765 |
- cmd = cli.Subcmd("start", "CONTAINER [CONTAINER...]", "Start one or more stopped containers", true)
|
|
| 766 |
- attach = cmd.Bool([]string{"a", "-attach"}, false, "Attach STDOUT/STDERR and forward signals")
|
|
| 767 |
- openStdin = cmd.Bool([]string{"i", "-interactive"}, false, "Attach container's STDIN")
|
|
| 768 |
- ) |
|
| 769 |
- |
|
| 770 |
- cmd.Require(flag.Min, 1) |
|
| 771 |
- utils.ParseFlags(cmd, args, true) |
|
| 772 |
- |
|
| 773 |
- if *attach || *openStdin {
|
|
| 774 |
- if cmd.NArg() > 1 {
|
|
| 775 |
- return fmt.Errorf("You cannot start and attach multiple containers at once.")
|
|
| 776 |
- } |
|
| 777 |
- |
|
| 778 |
- stream, _, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/json", nil, false)
|
|
| 779 |
- if err != nil {
|
|
| 780 |
- return err |
|
| 781 |
- } |
|
| 782 |
- |
|
| 783 |
- env := engine.Env{}
|
|
| 784 |
- if err := env.Decode(stream); err != nil {
|
|
| 785 |
- return err |
|
| 786 |
- } |
|
| 787 |
- config := env.GetSubEnv("Config")
|
|
| 788 |
- tty = config.GetBool("Tty")
|
|
| 789 |
- |
|
| 790 |
- if !tty {
|
|
| 791 |
- sigc := cli.forwardAllSignals(cmd.Arg(0)) |
|
| 792 |
- defer signal.StopCatch(sigc) |
|
| 793 |
- } |
|
| 794 |
- |
|
| 795 |
- var in io.ReadCloser |
|
| 796 |
- |
|
| 797 |
- v := url.Values{}
|
|
| 798 |
- v.Set("stream", "1")
|
|
| 799 |
- |
|
| 800 |
- if *openStdin && config.GetBool("OpenStdin") {
|
|
| 801 |
- v.Set("stdin", "1")
|
|
| 802 |
- in = cli.in |
|
| 803 |
- } |
|
| 804 |
- |
|
| 805 |
- v.Set("stdout", "1")
|
|
| 806 |
- v.Set("stderr", "1")
|
|
| 807 |
- |
|
| 808 |
- hijacked := make(chan io.Closer) |
|
| 809 |
- // Block the return until the chan gets closed |
|
| 810 |
- defer func() {
|
|
| 811 |
- log.Debugf("CmdStart() returned, defer waiting for hijack to finish.")
|
|
| 812 |
- if _, ok := <-hijacked; ok {
|
|
| 813 |
- log.Errorf("Hijack did not finish (chan still open)")
|
|
| 814 |
- } |
|
| 815 |
- cli.in.Close() |
|
| 816 |
- }() |
|
| 817 |
- cErr = promise.Go(func() error {
|
|
| 818 |
- return cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), tty, in, cli.out, cli.err, hijacked, nil)
|
|
| 819 |
- }) |
|
| 820 |
- |
|
| 821 |
- // Acknowledge the hijack before starting |
|
| 822 |
- select {
|
|
| 823 |
- case closer := <-hijacked: |
|
| 824 |
- // Make sure that the hijack gets closed when returning (results |
|
| 825 |
- // in closing the hijack chan and freeing server's goroutines) |
|
| 826 |
- if closer != nil {
|
|
| 827 |
- defer closer.Close() |
|
| 828 |
- } |
|
| 829 |
- case err := <-cErr: |
|
| 830 |
- if err != nil {
|
|
| 831 |
- return err |
|
| 832 |
- } |
|
| 833 |
- } |
|
| 834 |
- } |
|
| 835 |
- |
|
| 836 |
- var encounteredError error |
|
| 837 |
- for _, name := range cmd.Args() {
|
|
| 838 |
- _, _, err := readBody(cli.call("POST", "/containers/"+name+"/start", nil, false))
|
|
| 839 |
- if err != nil {
|
|
| 840 |
- if !*attach && !*openStdin {
|
|
| 841 |
- // attach and openStdin is false means it could be starting multiple containers |
|
| 842 |
- // when a container start failed, show the error message and start next |
|
| 843 |
- fmt.Fprintf(cli.err, "%s\n", err) |
|
| 844 |
- encounteredError = fmt.Errorf("Error: failed to start one or more containers")
|
|
| 845 |
- } else {
|
|
| 846 |
- encounteredError = err |
|
| 847 |
- } |
|
| 848 |
- } else {
|
|
| 849 |
- if !*attach && !*openStdin {
|
|
| 850 |
- fmt.Fprintf(cli.out, "%s\n", name) |
|
| 851 |
- } |
|
| 852 |
- } |
|
| 853 |
- } |
|
| 854 |
- |
|
| 855 |
- if encounteredError != nil {
|
|
| 856 |
- return encounteredError |
|
| 857 |
- } |
|
| 858 |
- |
|
| 859 |
- if *openStdin || *attach {
|
|
| 860 |
- if tty && cli.isTerminalOut {
|
|
| 861 |
- if err := cli.monitorTtySize(cmd.Arg(0), false); err != nil {
|
|
| 862 |
- log.Errorf("Error monitoring TTY size: %s", err)
|
|
| 863 |
- } |
|
| 864 |
- } |
|
| 865 |
- if attchErr := <-cErr; attchErr != nil {
|
|
| 866 |
- return attchErr |
|
| 867 |
- } |
|
| 868 |
- _, status, err := getExitCode(cli, cmd.Arg(0)) |
|
| 869 |
- if err != nil {
|
|
| 870 |
- return err |
|
| 871 |
- } |
|
| 872 |
- if status != 0 {
|
|
| 873 |
- return &utils.StatusError{StatusCode: status}
|
|
| 874 |
- } |
|
| 875 |
- } |
|
| 876 |
- return nil |
|
| 877 |
-} |
|
| 878 |
- |
|
| 879 |
-func (cli *DockerCli) CmdUnpause(args ...string) error {
|
|
| 880 |
- cmd := cli.Subcmd("unpause", "CONTAINER [CONTAINER...]", "Unpause all processes within a container", true)
|
|
| 881 |
- cmd.Require(flag.Min, 1) |
|
| 882 |
- utils.ParseFlags(cmd, args, false) |
|
| 883 |
- |
|
| 884 |
- var encounteredError error |
|
| 885 |
- for _, name := range cmd.Args() {
|
|
| 886 |
- if _, _, err := readBody(cli.call("POST", fmt.Sprintf("/containers/%s/unpause", name), nil, false)); err != nil {
|
|
| 887 |
- fmt.Fprintf(cli.err, "%s\n", err) |
|
| 888 |
- encounteredError = fmt.Errorf("Error: failed to unpause container named %s", name)
|
|
| 889 |
- } else {
|
|
| 890 |
- fmt.Fprintf(cli.out, "%s\n", name) |
|
| 891 |
- } |
|
| 892 |
- } |
|
| 893 |
- return encounteredError |
|
| 894 |
-} |
|
| 895 |
- |
|
| 896 |
-func (cli *DockerCli) CmdPause(args ...string) error {
|
|
| 897 |
- cmd := cli.Subcmd("pause", "CONTAINER [CONTAINER...]", "Pause all processes within a container", true)
|
|
| 898 |
- cmd.Require(flag.Min, 1) |
|
| 899 |
- utils.ParseFlags(cmd, args, false) |
|
| 900 |
- |
|
| 901 |
- var encounteredError error |
|
| 902 |
- for _, name := range cmd.Args() {
|
|
| 903 |
- if _, _, err := readBody(cli.call("POST", fmt.Sprintf("/containers/%s/pause", name), nil, false)); err != nil {
|
|
| 904 |
- fmt.Fprintf(cli.err, "%s\n", err) |
|
| 905 |
- encounteredError = fmt.Errorf("Error: failed to pause container named %s", name)
|
|
| 906 |
- } else {
|
|
| 907 |
- fmt.Fprintf(cli.out, "%s\n", name) |
|
| 908 |
- } |
|
| 909 |
- } |
|
| 910 |
- return encounteredError |
|
| 911 |
-} |
|
| 912 |
- |
|
| 913 |
-func (cli *DockerCli) CmdRename(args ...string) error {
|
|
| 914 |
- cmd := cli.Subcmd("rename", "OLD_NAME NEW_NAME", "Rename a container", true)
|
|
| 915 |
- if err := cmd.Parse(args); err != nil {
|
|
| 916 |
- return nil |
|
| 917 |
- } |
|
| 918 |
- |
|
| 919 |
- if cmd.NArg() != 2 {
|
|
| 920 |
- cmd.Usage() |
|
| 921 |
- return nil |
|
| 922 |
- } |
|
| 923 |
- old_name := cmd.Arg(0) |
|
| 924 |
- new_name := cmd.Arg(1) |
|
| 925 |
- |
|
| 926 |
- if _, _, err := readBody(cli.call("POST", fmt.Sprintf("/containers/%s/rename?name=%s", old_name, new_name), nil, false)); err != nil {
|
|
| 927 |
- fmt.Fprintf(cli.err, "%s\n", err) |
|
| 928 |
- return fmt.Errorf("Error: failed to rename container named %s", old_name)
|
|
| 929 |
- } |
|
| 930 |
- return nil |
|
| 931 |
-} |
|
| 932 |
- |
|
| 933 |
-func (cli *DockerCli) CmdInspect(args ...string) error {
|
|
| 934 |
- cmd := cli.Subcmd("inspect", "CONTAINER|IMAGE [CONTAINER|IMAGE...]", "Return low-level information on a container or image", true)
|
|
| 935 |
- tmplStr := cmd.String([]string{"f", "#format", "-format"}, "", "Format the output using the given go template")
|
|
| 936 |
- cmd.Require(flag.Min, 1) |
|
| 937 |
- |
|
| 938 |
- utils.ParseFlags(cmd, args, true) |
|
| 939 |
- |
|
| 940 |
- var tmpl *template.Template |
|
| 941 |
- if *tmplStr != "" {
|
|
| 942 |
- var err error |
|
| 943 |
- if tmpl, err = template.New("").Funcs(funcMap).Parse(*tmplStr); err != nil {
|
|
| 944 |
- fmt.Fprintf(cli.err, "Template parsing error: %v\n", err) |
|
| 945 |
- return &utils.StatusError{StatusCode: 64,
|
|
| 946 |
- Status: "Template parsing error: " + err.Error()} |
|
| 947 |
- } |
|
| 948 |
- } |
|
| 949 |
- |
|
| 950 |
- indented := new(bytes.Buffer) |
|
| 951 |
- indented.WriteByte('[')
|
|
| 952 |
- status := 0 |
|
| 953 |
- |
|
| 954 |
- for _, name := range cmd.Args() {
|
|
| 955 |
- obj, _, err := readBody(cli.call("GET", "/containers/"+name+"/json", nil, false))
|
|
| 956 |
- if err != nil {
|
|
| 957 |
- if strings.Contains(err.Error(), "Too many") {
|
|
| 958 |
- fmt.Fprintf(cli.err, "Error: %v", err) |
|
| 959 |
- status = 1 |
|
| 960 |
- continue |
|
| 961 |
- } |
|
| 962 |
- |
|
| 963 |
- obj, _, err = readBody(cli.call("GET", "/images/"+name+"/json", nil, false))
|
|
| 964 |
- if err != nil {
|
|
| 965 |
- if strings.Contains(err.Error(), "No such") {
|
|
| 966 |
- fmt.Fprintf(cli.err, "Error: No such image or container: %s\n", name) |
|
| 967 |
- } else {
|
|
| 968 |
- fmt.Fprintf(cli.err, "%s", err) |
|
| 969 |
- } |
|
| 970 |
- status = 1 |
|
| 971 |
- continue |
|
| 972 |
- } |
|
| 973 |
- } |
|
| 974 |
- |
|
| 975 |
- if tmpl == nil {
|
|
| 976 |
- if err = json.Indent(indented, obj, "", " "); err != nil {
|
|
| 977 |
- fmt.Fprintf(cli.err, "%s\n", err) |
|
| 978 |
- status = 1 |
|
| 979 |
- continue |
|
| 980 |
- } |
|
| 981 |
- } else {
|
|
| 982 |
- // Has template, will render |
|
| 983 |
- var value interface{}
|
|
| 984 |
- if err := json.Unmarshal(obj, &value); err != nil {
|
|
| 985 |
- fmt.Fprintf(cli.err, "%s\n", err) |
|
| 986 |
- status = 1 |
|
| 987 |
- continue |
|
| 988 |
- } |
|
| 989 |
- if err := tmpl.Execute(cli.out, value); err != nil {
|
|
| 990 |
- return err |
|
| 991 |
- } |
|
| 992 |
- cli.out.Write([]byte{'\n'})
|
|
| 993 |
- } |
|
| 994 |
- indented.WriteString(",")
|
|
| 995 |
- } |
|
| 996 |
- |
|
| 997 |
- if indented.Len() > 1 {
|
|
| 998 |
- // Remove trailing ',' |
|
| 999 |
- indented.Truncate(indented.Len() - 1) |
|
| 1000 |
- } |
|
| 1001 |
- indented.WriteString("]\n")
|
|
| 1002 |
- |
|
| 1003 |
- if tmpl == nil {
|
|
| 1004 |
- if _, err := io.Copy(cli.out, indented); err != nil {
|
|
| 1005 |
- return err |
|
| 1006 |
- } |
|
| 1007 |
- } |
|
| 1008 |
- |
|
| 1009 |
- if status != 0 {
|
|
| 1010 |
- return &utils.StatusError{StatusCode: status}
|
|
| 1011 |
- } |
|
| 1012 |
- return nil |
|
| 1013 |
-} |
|
| 1014 |
- |
|
| 1015 |
-func (cli *DockerCli) CmdTop(args ...string) error {
|
|
| 1016 |
- cmd := cli.Subcmd("top", "CONTAINER [ps OPTIONS]", "Display the running processes of a container", true)
|
|
| 1017 |
- cmd.Require(flag.Min, 1) |
|
| 1018 |
- |
|
| 1019 |
- utils.ParseFlags(cmd, args, true) |
|
| 1020 |
- |
|
| 1021 |
- val := url.Values{}
|
|
| 1022 |
- if cmd.NArg() > 1 {
|
|
| 1023 |
- val.Set("ps_args", strings.Join(cmd.Args()[1:], " "))
|
|
| 1024 |
- } |
|
| 1025 |
- |
|
| 1026 |
- stream, _, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/top?"+val.Encode(), nil, false)
|
|
| 1027 |
- if err != nil {
|
|
| 1028 |
- return err |
|
| 1029 |
- } |
|
| 1030 |
- var procs engine.Env |
|
| 1031 |
- if err := procs.Decode(stream); err != nil {
|
|
| 1032 |
- return err |
|
| 1033 |
- } |
|
| 1034 |
- w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0) |
|
| 1035 |
- fmt.Fprintln(w, strings.Join(procs.GetList("Titles"), "\t"))
|
|
| 1036 |
- processes := [][]string{}
|
|
| 1037 |
- if err := procs.GetJson("Processes", &processes); err != nil {
|
|
| 1038 |
- return err |
|
| 1039 |
- } |
|
| 1040 |
- for _, proc := range processes {
|
|
| 1041 |
- fmt.Fprintln(w, strings.Join(proc, "\t")) |
|
| 1042 |
- } |
|
| 1043 |
- w.Flush() |
|
| 1044 |
- return nil |
|
| 1045 |
-} |
|
| 1046 |
- |
|
| 1047 |
-func (cli *DockerCli) CmdPort(args ...string) error {
|
|
| 1048 |
- cmd := cli.Subcmd("port", "CONTAINER [PRIVATE_PORT[/PROTO]]", "List port mappings for the CONTAINER, or lookup the public-facing port that\nis NAT-ed to the PRIVATE_PORT", true)
|
|
| 1049 |
- cmd.Require(flag.Min, 1) |
|
| 1050 |
- utils.ParseFlags(cmd, args, true) |
|
| 1051 |
- |
|
| 1052 |
- stream, _, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/json", nil, false)
|
|
| 1053 |
- if err != nil {
|
|
| 1054 |
- return err |
|
| 1055 |
- } |
|
| 1056 |
- |
|
| 1057 |
- env := engine.Env{}
|
|
| 1058 |
- if err := env.Decode(stream); err != nil {
|
|
| 1059 |
- return err |
|
| 1060 |
- } |
|
| 1061 |
- ports := nat.PortMap{}
|
|
| 1062 |
- if err := env.GetSubEnv("NetworkSettings").GetJson("Ports", &ports); err != nil {
|
|
| 1063 |
- return err |
|
| 1064 |
- } |
|
| 1065 |
- |
|
| 1066 |
- if cmd.NArg() == 2 {
|
|
| 1067 |
- var ( |
|
| 1068 |
- port = cmd.Arg(1) |
|
| 1069 |
- proto = "tcp" |
|
| 1070 |
- parts = strings.SplitN(port, "/", 2) |
|
| 1071 |
- ) |
|
| 1072 |
- |
|
| 1073 |
- if len(parts) == 2 && len(parts[1]) != 0 {
|
|
| 1074 |
- port = parts[0] |
|
| 1075 |
- proto = parts[1] |
|
| 1076 |
- } |
|
| 1077 |
- natPort := port + "/" + proto |
|
| 1078 |
- if frontends, exists := ports[nat.Port(port+"/"+proto)]; exists && frontends != nil {
|
|
| 1079 |
- for _, frontend := range frontends {
|
|
| 1080 |
- fmt.Fprintf(cli.out, "%s:%s\n", frontend.HostIp, frontend.HostPort) |
|
| 1081 |
- } |
|
| 1082 |
- return nil |
|
| 1083 |
- } |
|
| 1084 |
- return fmt.Errorf("Error: No public port '%s' published for %s", natPort, cmd.Arg(0))
|
|
| 1085 |
- } |
|
| 1086 |
- |
|
| 1087 |
- for from, frontends := range ports {
|
|
| 1088 |
- for _, frontend := range frontends {
|
|
| 1089 |
- fmt.Fprintf(cli.out, "%s -> %s:%s\n", from, frontend.HostIp, frontend.HostPort) |
|
| 1090 |
- } |
|
| 1091 |
- } |
|
| 1092 |
- |
|
| 1093 |
- return nil |
|
| 1094 |
-} |
|
| 1095 |
- |
|
| 1096 |
-// 'docker rmi IMAGE' removes all images with the name IMAGE |
|
| 1097 |
-func (cli *DockerCli) CmdRmi(args ...string) error {
|
|
| 1098 |
- var ( |
|
| 1099 |
- cmd = cli.Subcmd("rmi", "IMAGE [IMAGE...]", "Remove one or more images", true)
|
|
| 1100 |
- force = cmd.Bool([]string{"f", "-force"}, false, "Force removal of the image")
|
|
| 1101 |
- noprune = cmd.Bool([]string{"-no-prune"}, false, "Do not delete untagged parents")
|
|
| 1102 |
- ) |
|
| 1103 |
- cmd.Require(flag.Min, 1) |
|
| 1104 |
- |
|
| 1105 |
- utils.ParseFlags(cmd, args, true) |
|
| 1106 |
- |
|
| 1107 |
- v := url.Values{}
|
|
| 1108 |
- if *force {
|
|
| 1109 |
- v.Set("force", "1")
|
|
| 1110 |
- } |
|
| 1111 |
- if *noprune {
|
|
| 1112 |
- v.Set("noprune", "1")
|
|
| 1113 |
- } |
|
| 1114 |
- |
|
| 1115 |
- var encounteredError error |
|
| 1116 |
- for _, name := range cmd.Args() {
|
|
| 1117 |
- body, _, err := readBody(cli.call("DELETE", "/images/"+name+"?"+v.Encode(), nil, false))
|
|
| 1118 |
- if err != nil {
|
|
| 1119 |
- fmt.Fprintf(cli.err, "%s\n", err) |
|
| 1120 |
- encounteredError = fmt.Errorf("Error: failed to remove one or more images")
|
|
| 1121 |
- } else {
|
|
| 1122 |
- outs := engine.NewTable("Created", 0)
|
|
| 1123 |
- if _, err := outs.ReadListFrom(body); err != nil {
|
|
| 1124 |
- fmt.Fprintf(cli.err, "%s\n", err) |
|
| 1125 |
- encounteredError = fmt.Errorf("Error: failed to remove one or more images")
|
|
| 1126 |
- continue |
|
| 1127 |
- } |
|
| 1128 |
- for _, out := range outs.Data {
|
|
| 1129 |
- if out.Get("Deleted") != "" {
|
|
| 1130 |
- fmt.Fprintf(cli.out, "Deleted: %s\n", out.Get("Deleted"))
|
|
| 1131 |
- } else {
|
|
| 1132 |
- fmt.Fprintf(cli.out, "Untagged: %s\n", out.Get("Untagged"))
|
|
| 1133 |
- } |
|
| 1134 |
- } |
|
| 1135 |
- } |
|
| 1136 |
- } |
|
| 1137 |
- return encounteredError |
|
| 1138 |
-} |
|
| 1139 |
- |
|
| 1140 |
-func (cli *DockerCli) CmdHistory(args ...string) error {
|
|
| 1141 |
- cmd := cli.Subcmd("history", "IMAGE", "Show the history of an image", true)
|
|
| 1142 |
- quiet := cmd.Bool([]string{"q", "-quiet"}, false, "Only show numeric IDs")
|
|
| 1143 |
- noTrunc := cmd.Bool([]string{"#notrunc", "-no-trunc"}, false, "Don't truncate output")
|
|
| 1144 |
- cmd.Require(flag.Exact, 1) |
|
| 1145 |
- |
|
| 1146 |
- utils.ParseFlags(cmd, args, true) |
|
| 1147 |
- |
|
| 1148 |
- body, _, err := readBody(cli.call("GET", "/images/"+cmd.Arg(0)+"/history", nil, false))
|
|
| 1149 |
- if err != nil {
|
|
| 1150 |
- return err |
|
| 1151 |
- } |
|
| 1152 |
- |
|
| 1153 |
- outs := engine.NewTable("Created", 0)
|
|
| 1154 |
- if _, err := outs.ReadListFrom(body); err != nil {
|
|
| 1155 |
- return err |
|
| 1156 |
- } |
|
| 1157 |
- |
|
| 1158 |
- w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0) |
|
| 1159 |
- if !*quiet {
|
|
| 1160 |
- fmt.Fprintln(w, "IMAGE\tCREATED\tCREATED BY\tSIZE") |
|
| 1161 |
- } |
|
| 1162 |
- |
|
| 1163 |
- for _, out := range outs.Data {
|
|
| 1164 |
- outID := out.Get("Id")
|
|
| 1165 |
- if !*quiet {
|
|
| 1166 |
- if *noTrunc {
|
|
| 1167 |
- fmt.Fprintf(w, "%s\t", outID) |
|
| 1168 |
- } else {
|
|
| 1169 |
- fmt.Fprintf(w, "%s\t", stringid.TruncateID(outID)) |
|
| 1170 |
- } |
|
| 1171 |
- |
|
| 1172 |
- fmt.Fprintf(w, "%s ago\t", units.HumanDuration(time.Now().UTC().Sub(time.Unix(out.GetInt64("Created"), 0))))
|
|
| 1173 |
- |
|
| 1174 |
- if *noTrunc {
|
|
| 1175 |
- fmt.Fprintf(w, "%s\t", out.Get("CreatedBy"))
|
|
| 1176 |
- } else {
|
|
| 1177 |
- fmt.Fprintf(w, "%s\t", utils.Trunc(out.Get("CreatedBy"), 45))
|
|
| 1178 |
- } |
|
| 1179 |
- fmt.Fprintf(w, "%s\n", units.HumanSize(float64(out.GetInt64("Size"))))
|
|
| 1180 |
- } else {
|
|
| 1181 |
- if *noTrunc {
|
|
| 1182 |
- fmt.Fprintln(w, outID) |
|
| 1183 |
- } else {
|
|
| 1184 |
- fmt.Fprintln(w, stringid.TruncateID(outID)) |
|
| 1185 |
- } |
|
| 1186 |
- } |
|
| 1187 |
- } |
|
| 1188 |
- w.Flush() |
|
| 1189 |
- return nil |
|
| 1190 |
-} |
|
| 1191 |
- |
|
| 1192 |
-func (cli *DockerCli) CmdRm(args ...string) error {
|
|
| 1193 |
- cmd := cli.Subcmd("rm", "CONTAINER [CONTAINER...]", "Remove one or more containers", true)
|
|
| 1194 |
- v := cmd.Bool([]string{"v", "-volumes"}, false, "Remove the volumes associated with the container")
|
|
| 1195 |
- link := cmd.Bool([]string{"l", "#link", "-link"}, false, "Remove the specified link")
|
|
| 1196 |
- force := cmd.Bool([]string{"f", "-force"}, false, "Force the removal of a running container (uses SIGKILL)")
|
|
| 1197 |
- cmd.Require(flag.Min, 1) |
|
| 1198 |
- |
|
| 1199 |
- utils.ParseFlags(cmd, args, true) |
|
| 1200 |
- |
|
| 1201 |
- val := url.Values{}
|
|
| 1202 |
- if *v {
|
|
| 1203 |
- val.Set("v", "1")
|
|
| 1204 |
- } |
|
| 1205 |
- if *link {
|
|
| 1206 |
- val.Set("link", "1")
|
|
| 1207 |
- } |
|
| 1208 |
- |
|
| 1209 |
- if *force {
|
|
| 1210 |
- val.Set("force", "1")
|
|
| 1211 |
- } |
|
| 1212 |
- |
|
| 1213 |
- var encounteredError error |
|
| 1214 |
- for _, name := range cmd.Args() {
|
|
| 1215 |
- _, _, err := readBody(cli.call("DELETE", "/containers/"+name+"?"+val.Encode(), nil, false))
|
|
| 1216 |
- if err != nil {
|
|
| 1217 |
- fmt.Fprintf(cli.err, "%s\n", err) |
|
| 1218 |
- encounteredError = fmt.Errorf("Error: failed to remove one or more containers")
|
|
| 1219 |
- } else {
|
|
| 1220 |
- fmt.Fprintf(cli.out, "%s\n", name) |
|
| 1221 |
- } |
|
| 1222 |
- } |
|
| 1223 |
- return encounteredError |
|
| 1224 |
-} |
|
| 1225 |
- |
|
| 1226 |
-// 'docker kill NAME' kills a running container |
|
| 1227 |
-func (cli *DockerCli) CmdKill(args ...string) error {
|
|
| 1228 |
- cmd := cli.Subcmd("kill", "CONTAINER [CONTAINER...]", "Kill a running container using SIGKILL or a specified signal", true)
|
|
| 1229 |
- signal := cmd.String([]string{"s", "-signal"}, "KILL", "Signal to send to the container")
|
|
| 1230 |
- cmd.Require(flag.Min, 1) |
|
| 1231 |
- |
|
| 1232 |
- utils.ParseFlags(cmd, args, true) |
|
| 1233 |
- |
|
| 1234 |
- var encounteredError error |
|
| 1235 |
- for _, name := range cmd.Args() {
|
|
| 1236 |
- if _, _, err := readBody(cli.call("POST", fmt.Sprintf("/containers/%s/kill?signal=%s", name, *signal), nil, false)); err != nil {
|
|
| 1237 |
- fmt.Fprintf(cli.err, "%s\n", err) |
|
| 1238 |
- encounteredError = fmt.Errorf("Error: failed to kill one or more containers")
|
|
| 1239 |
- } else {
|
|
| 1240 |
- fmt.Fprintf(cli.out, "%s\n", name) |
|
| 1241 |
- } |
|
| 1242 |
- } |
|
| 1243 |
- return encounteredError |
|
| 1244 |
-} |
|
| 1245 |
- |
|
| 1246 |
-func (cli *DockerCli) CmdImport(args ...string) error {
|
|
| 1247 |
- cmd := cli.Subcmd("import", "URL|- [REPOSITORY[:TAG]]", "Create an empty filesystem image and import the contents of the\ntarball (.tar, .tar.gz, .tgz, .bzip, .tar.xz, .txz) into it, then\noptionally tag it.", true)
|
|
| 1248 |
- flChanges := opts.NewListOpts(nil) |
|
| 1249 |
- cmd.Var(&flChanges, []string{"c", "-change"}, "Apply Dockerfile instruction to the created image")
|
|
| 1250 |
- cmd.Require(flag.Min, 1) |
|
| 1251 |
- |
|
| 1252 |
- utils.ParseFlags(cmd, args, true) |
|
| 1253 |
- |
|
| 1254 |
- var ( |
|
| 1255 |
- v = url.Values{}
|
|
| 1256 |
- src = cmd.Arg(0) |
|
| 1257 |
- repository = cmd.Arg(1) |
|
| 1258 |
- ) |
|
| 1259 |
- |
|
| 1260 |
- v.Set("fromSrc", src)
|
|
| 1261 |
- v.Set("repo", repository)
|
|
| 1262 |
- for _, change := range flChanges.GetAll() {
|
|
| 1263 |
- v.Add("changes", change)
|
|
| 1264 |
- } |
|
| 1265 |
- if cmd.NArg() == 3 {
|
|
| 1266 |
- fmt.Fprintf(cli.err, "[DEPRECATED] The format 'URL|- [REPOSITORY [TAG]]' has been deprecated. Please use URL|- [REPOSITORY[:TAG]]\n") |
|
| 1267 |
- v.Set("tag", cmd.Arg(2))
|
|
| 1268 |
- } |
|
| 1269 |
- |
|
| 1270 |
- if repository != "" {
|
|
| 1271 |
- //Check if the given image name can be resolved |
|
| 1272 |
- repo, _ := parsers.ParseRepositoryTag(repository) |
|
| 1273 |
- if err := registry.ValidateRepositoryName(repo); err != nil {
|
|
| 1274 |
- return err |
|
| 1275 |
- } |
|
| 1276 |
- } |
|
| 1277 |
- |
|
| 1278 |
- var in io.Reader |
|
| 1279 |
- |
|
| 1280 |
- if src == "-" {
|
|
| 1281 |
- in = cli.in |
|
| 1282 |
- } |
|
| 1283 |
- |
|
| 1284 |
- return cli.stream("POST", "/images/create?"+v.Encode(), in, cli.out, nil)
|
|
| 1285 |
-} |
|
| 1286 |
- |
|
| 1287 |
-func (cli *DockerCli) CmdPush(args ...string) error {
|
|
| 1288 |
- cmd := cli.Subcmd("push", "NAME[:TAG]", "Push an image or a repository to the registry", true)
|
|
| 1289 |
- cmd.Require(flag.Exact, 1) |
|
| 1290 |
- |
|
| 1291 |
- utils.ParseFlags(cmd, args, true) |
|
| 1292 |
- |
|
| 1293 |
- name := cmd.Arg(0) |
|
| 1294 |
- |
|
| 1295 |
- cli.LoadConfigFile() |
|
| 1296 |
- |
|
| 1297 |
- remote, tag := parsers.ParseRepositoryTag(name) |
|
| 1298 |
- |
|
| 1299 |
- // Resolve the Repository name from fqn to RepositoryInfo |
|
| 1300 |
- repoInfo, err := registry.ParseRepositoryInfo(remote) |
|
| 1301 |
- if err != nil {
|
|
| 1302 |
- return err |
|
| 1303 |
- } |
|
| 1304 |
- // Resolve the Auth config relevant for this server |
|
| 1305 |
- authConfig := cli.configFile.ResolveAuthConfig(repoInfo.Index) |
|
| 1306 |
- // If we're not using a custom registry, we know the restrictions |
|
| 1307 |
- // applied to repository names and can warn the user in advance. |
|
| 1308 |
- // Custom repositories can have different rules, and we must also |
|
| 1309 |
- // allow pushing by image ID. |
|
| 1310 |
- if repoInfo.Official {
|
|
| 1311 |
- username := authConfig.Username |
|
| 1312 |
- if username == "" {
|
|
| 1313 |
- username = "<user>" |
|
| 1314 |
- } |
|
| 1315 |
- return fmt.Errorf("You cannot push a \"root\" repository. Please rename your repository to <user>/<repo> (ex: %s/%s)", username, repoInfo.LocalName)
|
|
| 1316 |
- } |
|
| 1317 |
- |
|
| 1318 |
- v := url.Values{}
|
|
| 1319 |
- v.Set("tag", tag)
|
|
| 1320 |
- |
|
| 1321 |
- push := func(authConfig registry.AuthConfig) error {
|
|
| 1322 |
- buf, err := json.Marshal(authConfig) |
|
| 1323 |
- if err != nil {
|
|
| 1324 |
- return err |
|
| 1325 |
- } |
|
| 1326 |
- registryAuthHeader := []string{
|
|
| 1327 |
- base64.URLEncoding.EncodeToString(buf), |
|
| 1328 |
- } |
|
| 1329 |
- |
|
| 1330 |
- return cli.stream("POST", "/images/"+remote+"/push?"+v.Encode(), nil, cli.out, map[string][]string{
|
|
| 1331 |
- "X-Registry-Auth": registryAuthHeader, |
|
| 1332 |
- }) |
|
| 1333 |
- } |
|
| 1334 |
- |
|
| 1335 |
- if err := push(authConfig); err != nil {
|
|
| 1336 |
- if strings.Contains(err.Error(), "Status 401") {
|
|
| 1337 |
- fmt.Fprintln(cli.out, "\nPlease login prior to push:") |
|
| 1338 |
- if err := cli.CmdLogin(repoInfo.Index.GetAuthConfigKey()); err != nil {
|
|
| 1339 |
- return err |
|
| 1340 |
- } |
|
| 1341 |
- authConfig := cli.configFile.ResolveAuthConfig(repoInfo.Index) |
|
| 1342 |
- return push(authConfig) |
|
| 1343 |
- } |
|
| 1344 |
- return err |
|
| 1345 |
- } |
|
| 1346 |
- return nil |
|
| 1347 |
-} |
|
| 1348 |
- |
|
| 1349 |
-func (cli *DockerCli) CmdPull(args ...string) error {
|
|
| 1350 |
- cmd := cli.Subcmd("pull", "NAME[:TAG|@DIGEST]", "Pull an image or a repository from the registry", true)
|
|
| 1351 |
- allTags := cmd.Bool([]string{"a", "-all-tags"}, false, "Download all tagged images in the repository")
|
|
| 1352 |
- cmd.Require(flag.Exact, 1) |
|
| 1353 |
- |
|
| 1354 |
- utils.ParseFlags(cmd, args, true) |
|
| 1355 |
- |
|
| 1356 |
- var ( |
|
| 1357 |
- v = url.Values{}
|
|
| 1358 |
- remote = cmd.Arg(0) |
|
| 1359 |
- newRemote = remote |
|
| 1360 |
- ) |
|
| 1361 |
- taglessRemote, tag := parsers.ParseRepositoryTag(remote) |
|
| 1362 |
- if tag == "" && !*allTags {
|
|
| 1363 |
- newRemote = utils.ImageReference(taglessRemote, graph.DEFAULTTAG) |
|
| 1364 |
- } |
|
| 1365 |
- if tag != "" && *allTags {
|
|
| 1366 |
- return fmt.Errorf("tag can't be used with --all-tags/-a")
|
|
| 1367 |
- } |
|
| 1368 |
- |
|
| 1369 |
- v.Set("fromImage", newRemote)
|
|
| 1370 |
- |
|
| 1371 |
- // Resolve the Repository name from fqn to RepositoryInfo |
|
| 1372 |
- repoInfo, err := registry.ParseRepositoryInfo(taglessRemote) |
|
| 1373 |
- if err != nil {
|
|
| 1374 |
- return err |
|
| 1375 |
- } |
|
| 1376 |
- |
|
| 1377 |
- cli.LoadConfigFile() |
|
| 1378 |
- |
|
| 1379 |
- // Resolve the Auth config relevant for this server |
|
| 1380 |
- authConfig := cli.configFile.ResolveAuthConfig(repoInfo.Index) |
|
| 1381 |
- |
|
| 1382 |
- pull := func(authConfig registry.AuthConfig) error {
|
|
| 1383 |
- buf, err := json.Marshal(authConfig) |
|
| 1384 |
- if err != nil {
|
|
| 1385 |
- return err |
|
| 1386 |
- } |
|
| 1387 |
- registryAuthHeader := []string{
|
|
| 1388 |
- base64.URLEncoding.EncodeToString(buf), |
|
| 1389 |
- } |
|
| 1390 |
- |
|
| 1391 |
- return cli.stream("POST", "/images/create?"+v.Encode(), nil, cli.out, map[string][]string{
|
|
| 1392 |
- "X-Registry-Auth": registryAuthHeader, |
|
| 1393 |
- }) |
|
| 1394 |
- } |
|
| 1395 |
- |
|
| 1396 |
- if err := pull(authConfig); err != nil {
|
|
| 1397 |
- if strings.Contains(err.Error(), "Status 401") {
|
|
| 1398 |
- fmt.Fprintln(cli.out, "\nPlease login prior to pull:") |
|
| 1399 |
- if err := cli.CmdLogin(repoInfo.Index.GetAuthConfigKey()); err != nil {
|
|
| 1400 |
- return err |
|
| 1401 |
- } |
|
| 1402 |
- authConfig := cli.configFile.ResolveAuthConfig(repoInfo.Index) |
|
| 1403 |
- return pull(authConfig) |
|
| 1404 |
- } |
|
| 1405 |
- return err |
|
| 1406 |
- } |
|
| 1407 |
- |
|
| 1408 |
- return nil |
|
| 1409 |
-} |
|
| 1410 |
- |
|
| 1411 |
-func (cli *DockerCli) CmdImages(args ...string) error {
|
|
| 1412 |
- cmd := cli.Subcmd("images", "[REPOSITORY]", "List images", true)
|
|
| 1413 |
- quiet := cmd.Bool([]string{"q", "-quiet"}, false, "Only show numeric IDs")
|
|
| 1414 |
- all := cmd.Bool([]string{"a", "-all"}, false, "Show all images (default hides intermediate images)")
|
|
| 1415 |
- noTrunc := cmd.Bool([]string{"#notrunc", "-no-trunc"}, false, "Don't truncate output")
|
|
| 1416 |
- showDigests := cmd.Bool([]string{"-digests"}, false, "Show digests")
|
|
| 1417 |
- // FIXME: --viz and --tree are deprecated. Remove them in a future version. |
|
| 1418 |
- flViz := cmd.Bool([]string{"#v", "#viz", "#-viz"}, false, "Output graph in graphviz format")
|
|
| 1419 |
- flTree := cmd.Bool([]string{"#t", "#tree", "#-tree"}, false, "Output graph in tree format")
|
|
| 1420 |
- |
|
| 1421 |
- flFilter := opts.NewListOpts(nil) |
|
| 1422 |
- cmd.Var(&flFilter, []string{"f", "-filter"}, "Filter output based on conditions provided")
|
|
| 1423 |
- cmd.Require(flag.Max, 1) |
|
| 1424 |
- |
|
| 1425 |
- utils.ParseFlags(cmd, args, true) |
|
| 1426 |
- |
|
| 1427 |
- // Consolidate all filter flags, and sanity check them early. |
|
| 1428 |
- // They'll get process in the daemon/server. |
|
| 1429 |
- imageFilterArgs := filters.Args{}
|
|
| 1430 |
- for _, f := range flFilter.GetAll() {
|
|
| 1431 |
- var err error |
|
| 1432 |
- imageFilterArgs, err = filters.ParseFlag(f, imageFilterArgs) |
|
| 1433 |
- if err != nil {
|
|
| 1434 |
- return err |
|
| 1435 |
- } |
|
| 1436 |
- } |
|
| 1437 |
- |
|
| 1438 |
- matchName := cmd.Arg(0) |
|
| 1439 |
- // FIXME: --viz and --tree are deprecated. Remove them in a future version. |
|
| 1440 |
- if *flViz || *flTree {
|
|
| 1441 |
- v := url.Values{
|
|
| 1442 |
- "all": []string{"1"},
|
|
| 1443 |
- } |
|
| 1444 |
- if len(imageFilterArgs) > 0 {
|
|
| 1445 |
- filterJson, err := filters.ToParam(imageFilterArgs) |
|
| 1446 |
- if err != nil {
|
|
| 1447 |
- return err |
|
| 1448 |
- } |
|
| 1449 |
- v.Set("filters", filterJson)
|
|
| 1450 |
- } |
|
| 1451 |
- |
|
| 1452 |
- body, _, err := readBody(cli.call("GET", "/images/json?"+v.Encode(), nil, false))
|
|
| 1453 |
- if err != nil {
|
|
| 1454 |
- return err |
|
| 1455 |
- } |
|
| 1456 |
- |
|
| 1457 |
- outs := engine.NewTable("Created", 0)
|
|
| 1458 |
- if _, err := outs.ReadListFrom(body); err != nil {
|
|
| 1459 |
- return err |
|
| 1460 |
- } |
|
| 1461 |
- |
|
| 1462 |
- var ( |
|
| 1463 |
- printNode func(cli *DockerCli, noTrunc bool, image *engine.Env, prefix string) |
|
| 1464 |
- startImage *engine.Env |
|
| 1465 |
- |
|
| 1466 |
- roots = engine.NewTable("Created", outs.Len())
|
|
| 1467 |
- byParent = make(map[string]*engine.Table) |
|
| 1468 |
- ) |
|
| 1469 |
- |
|
| 1470 |
- for _, image := range outs.Data {
|
|
| 1471 |
- if image.Get("ParentId") == "" {
|
|
| 1472 |
- roots.Add(image) |
|
| 1473 |
- } else {
|
|
| 1474 |
- if children, exists := byParent[image.Get("ParentId")]; exists {
|
|
| 1475 |
- children.Add(image) |
|
| 1476 |
- } else {
|
|
| 1477 |
- byParent[image.Get("ParentId")] = engine.NewTable("Created", 1)
|
|
| 1478 |
- byParent[image.Get("ParentId")].Add(image)
|
|
| 1479 |
- } |
|
| 1480 |
- } |
|
| 1481 |
- |
|
| 1482 |
- if matchName != "" {
|
|
| 1483 |
- if matchName == image.Get("Id") || matchName == stringid.TruncateID(image.Get("Id")) {
|
|
| 1484 |
- startImage = image |
|
| 1485 |
- } |
|
| 1486 |
- |
|
| 1487 |
- for _, repotag := range image.GetList("RepoTags") {
|
|
| 1488 |
- if repotag == matchName {
|
|
| 1489 |
- startImage = image |
|
| 1490 |
- } |
|
| 1491 |
- } |
|
| 1492 |
- } |
|
| 1493 |
- } |
|
| 1494 |
- |
|
| 1495 |
- if *flViz {
|
|
| 1496 |
- fmt.Fprintf(cli.out, "digraph docker {\n")
|
|
| 1497 |
- printNode = (*DockerCli).printVizNode |
|
| 1498 |
- } else {
|
|
| 1499 |
- printNode = (*DockerCli).printTreeNode |
|
| 1500 |
- } |
|
| 1501 |
- |
|
| 1502 |
- if startImage != nil {
|
|
| 1503 |
- root := engine.NewTable("Created", 1)
|
|
| 1504 |
- root.Add(startImage) |
|
| 1505 |
- cli.WalkTree(*noTrunc, root, byParent, "", printNode) |
|
| 1506 |
- } else if matchName == "" {
|
|
| 1507 |
- cli.WalkTree(*noTrunc, roots, byParent, "", printNode) |
|
| 1508 |
- } |
|
| 1509 |
- if *flViz {
|
|
| 1510 |
- fmt.Fprintf(cli.out, " base [style=invisible]\n}\n") |
|
| 1511 |
- } |
|
| 1512 |
- } else {
|
|
| 1513 |
- v := url.Values{}
|
|
| 1514 |
- if len(imageFilterArgs) > 0 {
|
|
| 1515 |
- filterJson, err := filters.ToParam(imageFilterArgs) |
|
| 1516 |
- if err != nil {
|
|
| 1517 |
- return err |
|
| 1518 |
- } |
|
| 1519 |
- v.Set("filters", filterJson)
|
|
| 1520 |
- } |
|
| 1521 |
- |
|
| 1522 |
- if cmd.NArg() == 1 {
|
|
| 1523 |
- // FIXME rename this parameter, to not be confused with the filters flag |
|
| 1524 |
- v.Set("filter", matchName)
|
|
| 1525 |
- } |
|
| 1526 |
- if *all {
|
|
| 1527 |
- v.Set("all", "1")
|
|
| 1528 |
- } |
|
| 1529 |
- |
|
| 1530 |
- body, _, err := readBody(cli.call("GET", "/images/json?"+v.Encode(), nil, false))
|
|
| 1531 |
- |
|
| 1532 |
- if err != nil {
|
|
| 1533 |
- return err |
|
| 1534 |
- } |
|
| 1535 |
- |
|
| 1536 |
- outs := engine.NewTable("Created", 0)
|
|
| 1537 |
- if _, err := outs.ReadListFrom(body); err != nil {
|
|
| 1538 |
- return err |
|
| 1539 |
- } |
|
| 1540 |
- |
|
| 1541 |
- w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0) |
|
| 1542 |
- if !*quiet {
|
|
| 1543 |
- if *showDigests {
|
|
| 1544 |
- fmt.Fprintln(w, "REPOSITORY\tTAG\tDIGEST\tIMAGE ID\tCREATED\tVIRTUAL SIZE") |
|
| 1545 |
- } else {
|
|
| 1546 |
- fmt.Fprintln(w, "REPOSITORY\tTAG\tIMAGE ID\tCREATED\tVIRTUAL SIZE") |
|
| 1547 |
- } |
|
| 1548 |
- } |
|
| 1549 |
- |
|
| 1550 |
- for _, out := range outs.Data {
|
|
| 1551 |
- outID := out.Get("Id")
|
|
| 1552 |
- if !*noTrunc {
|
|
| 1553 |
- outID = stringid.TruncateID(outID) |
|
| 1554 |
- } |
|
| 1555 |
- |
|
| 1556 |
- repoTags := out.GetList("RepoTags")
|
|
| 1557 |
- repoDigests := out.GetList("RepoDigests")
|
|
| 1558 |
- |
|
| 1559 |
- if len(repoTags) == 1 && repoTags[0] == "<none>:<none>" && len(repoDigests) == 1 && repoDigests[0] == "<none>@<none>" {
|
|
| 1560 |
- // dangling image - clear out either repoTags or repoDigsts so we only show it once below |
|
| 1561 |
- repoDigests = []string{}
|
|
| 1562 |
- } |
|
| 1563 |
- |
|
| 1564 |
- // combine the tags and digests lists |
|
| 1565 |
- tagsAndDigests := append(repoTags, repoDigests...) |
|
| 1566 |
- for _, repoAndRef := range tagsAndDigests {
|
|
| 1567 |
- repo, ref := parsers.ParseRepositoryTag(repoAndRef) |
|
| 1568 |
- // default tag and digest to none - if there's a value, it'll be set below |
|
| 1569 |
- tag := "<none>" |
|
| 1570 |
- digest := "<none>" |
|
| 1571 |
- if utils.DigestReference(ref) {
|
|
| 1572 |
- digest = ref |
|
| 1573 |
- } else {
|
|
| 1574 |
- tag = ref |
|
| 1575 |
- } |
|
| 1576 |
- |
|
| 1577 |
- if !*quiet {
|
|
| 1578 |
- if *showDigests {
|
|
| 1579 |
- fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s ago\t%s\n", repo, tag, digest, outID, units.HumanDuration(time.Now().UTC().Sub(time.Unix(out.GetInt64("Created"), 0))), units.HumanSize(float64(out.GetInt64("VirtualSize"))))
|
|
| 1580 |
- } else {
|
|
| 1581 |
- fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\n", repo, tag, outID, units.HumanDuration(time.Now().UTC().Sub(time.Unix(out.GetInt64("Created"), 0))), units.HumanSize(float64(out.GetInt64("VirtualSize"))))
|
|
| 1582 |
- } |
|
| 1583 |
- } else {
|
|
| 1584 |
- fmt.Fprintln(w, outID) |
|
| 1585 |
- } |
|
| 1586 |
- } |
|
| 1587 |
- } |
|
| 1588 |
- |
|
| 1589 |
- if !*quiet {
|
|
| 1590 |
- w.Flush() |
|
| 1591 |
- } |
|
| 1592 |
- } |
|
| 1593 |
- return nil |
|
| 1594 |
-} |
|
| 1595 |
- |
|
| 1596 |
-// FIXME: --viz and --tree are deprecated. Remove them in a future version. |
|
| 1597 |
-func (cli *DockerCli) WalkTree(noTrunc bool, images *engine.Table, byParent map[string]*engine.Table, prefix string, printNode func(cli *DockerCli, noTrunc bool, image *engine.Env, prefix string)) {
|
|
| 1598 |
- length := images.Len() |
|
| 1599 |
- if length > 1 {
|
|
| 1600 |
- for index, image := range images.Data {
|
|
| 1601 |
- if index+1 == length {
|
|
| 1602 |
- printNode(cli, noTrunc, image, prefix+"└─") |
|
| 1603 |
- if subimages, exists := byParent[image.Get("Id")]; exists {
|
|
| 1604 |
- cli.WalkTree(noTrunc, subimages, byParent, prefix+" ", printNode) |
|
| 1605 |
- } |
|
| 1606 |
- } else {
|
|
| 1607 |
- printNode(cli, noTrunc, image, prefix+"\u251C─") |
|
| 1608 |
- if subimages, exists := byParent[image.Get("Id")]; exists {
|
|
| 1609 |
- cli.WalkTree(noTrunc, subimages, byParent, prefix+"\u2502 ", printNode) |
|
| 1610 |
- } |
|
| 1611 |
- } |
|
| 1612 |
- } |
|
| 1613 |
- } else {
|
|
| 1614 |
- for _, image := range images.Data {
|
|
| 1615 |
- printNode(cli, noTrunc, image, prefix+"└─") |
|
| 1616 |
- if subimages, exists := byParent[image.Get("Id")]; exists {
|
|
| 1617 |
- cli.WalkTree(noTrunc, subimages, byParent, prefix+" ", printNode) |
|
| 1618 |
- } |
|
| 1619 |
- } |
|
| 1620 |
- } |
|
| 1621 |
-} |
|
| 1622 |
- |
|
| 1623 |
-// FIXME: --viz and --tree are deprecated. Remove them in a future version. |
|
| 1624 |
-func (cli *DockerCli) printVizNode(noTrunc bool, image *engine.Env, prefix string) {
|
|
| 1625 |
- var ( |
|
| 1626 |
- imageID string |
|
| 1627 |
- parentID string |
|
| 1628 |
- ) |
|
| 1629 |
- if noTrunc {
|
|
| 1630 |
- imageID = image.Get("Id")
|
|
| 1631 |
- parentID = image.Get("ParentId")
|
|
| 1632 |
- } else {
|
|
| 1633 |
- imageID = stringid.TruncateID(image.Get("Id"))
|
|
| 1634 |
- parentID = stringid.TruncateID(image.Get("ParentId"))
|
|
| 1635 |
- } |
|
| 1636 |
- if parentID == "" {
|
|
| 1637 |
- fmt.Fprintf(cli.out, " base -> \"%s\" [style=invis]\n", imageID) |
|
| 1638 |
- } else {
|
|
| 1639 |
- fmt.Fprintf(cli.out, " \"%s\" -> \"%s\"\n", parentID, imageID) |
|
| 1640 |
- } |
|
| 1641 |
- if image.GetList("RepoTags")[0] != "<none>:<none>" {
|
|
| 1642 |
- fmt.Fprintf(cli.out, " \"%s\" [label=\"%s\\n%s\",shape=box,fillcolor=\"paleturquoise\",style=\"filled,rounded\"];\n", |
|
| 1643 |
- imageID, imageID, strings.Join(image.GetList("RepoTags"), "\\n"))
|
|
| 1644 |
- } |
|
| 1645 |
-} |
|
| 1646 |
- |
|
| 1647 |
-// FIXME: --viz and --tree are deprecated. Remove them in a future version. |
|
| 1648 |
-func (cli *DockerCli) printTreeNode(noTrunc bool, image *engine.Env, prefix string) {
|
|
| 1649 |
- var imageID string |
|
| 1650 |
- if noTrunc {
|
|
| 1651 |
- imageID = image.Get("Id")
|
|
| 1652 |
- } else {
|
|
| 1653 |
- imageID = stringid.TruncateID(image.Get("Id"))
|
|
| 1654 |
- } |
|
| 1655 |
- |
|
| 1656 |
- fmt.Fprintf(cli.out, "%s%s Virtual Size: %s", prefix, imageID, units.HumanSize(float64(image.GetInt64("VirtualSize"))))
|
|
| 1657 |
- if image.GetList("RepoTags")[0] != "<none>:<none>" {
|
|
| 1658 |
- fmt.Fprintf(cli.out, " Tags: %s\n", strings.Join(image.GetList("RepoTags"), ", "))
|
|
| 1659 |
- } else {
|
|
| 1660 |
- fmt.Fprint(cli.out, "\n") |
|
| 1661 |
- } |
|
| 1662 |
-} |
|
| 1663 |
- |
|
| 1664 |
-func (cli *DockerCli) CmdPs(args ...string) error {
|
|
| 1665 |
- var ( |
|
| 1666 |
- err error |
|
| 1667 |
- |
|
| 1668 |
- psFilterArgs = filters.Args{}
|
|
| 1669 |
- v = url.Values{}
|
|
| 1670 |
- |
|
| 1671 |
- cmd = cli.Subcmd("ps", "", "List containers", true)
|
|
| 1672 |
- quiet = cmd.Bool([]string{"q", "-quiet"}, false, "Only display numeric IDs")
|
|
| 1673 |
- size = cmd.Bool([]string{"s", "-size"}, false, "Display total file sizes")
|
|
| 1674 |
- all = cmd.Bool([]string{"a", "-all"}, false, "Show all containers (default shows just running)")
|
|
| 1675 |
- noTrunc = cmd.Bool([]string{"#notrunc", "-no-trunc"}, false, "Don't truncate output")
|
|
| 1676 |
- nLatest = cmd.Bool([]string{"l", "-latest"}, false, "Show the latest created container, include non-running")
|
|
| 1677 |
- since = cmd.String([]string{"#sinceId", "#-since-id", "-since"}, "", "Show created since Id or Name, include non-running")
|
|
| 1678 |
- before = cmd.String([]string{"#beforeId", "#-before-id", "-before"}, "", "Show only container created before Id or Name")
|
|
| 1679 |
- last = cmd.Int([]string{"n"}, -1, "Show n last created containers, include non-running")
|
|
| 1680 |
- flFilter = opts.NewListOpts(nil) |
|
| 1681 |
- ) |
|
| 1682 |
- cmd.Require(flag.Exact, 0) |
|
| 1683 |
- |
|
| 1684 |
- cmd.Var(&flFilter, []string{"f", "-filter"}, "Filter output based on conditions provided")
|
|
| 1685 |
- |
|
| 1686 |
- utils.ParseFlags(cmd, args, true) |
|
| 1687 |
- if *last == -1 && *nLatest {
|
|
| 1688 |
- *last = 1 |
|
| 1689 |
- } |
|
| 1690 |
- |
|
| 1691 |
- if *all {
|
|
| 1692 |
- v.Set("all", "1")
|
|
| 1693 |
- } |
|
| 1694 |
- |
|
| 1695 |
- if *last != -1 {
|
|
| 1696 |
- v.Set("limit", strconv.Itoa(*last))
|
|
| 1697 |
- } |
|
| 1698 |
- |
|
| 1699 |
- if *since != "" {
|
|
| 1700 |
- v.Set("since", *since)
|
|
| 1701 |
- } |
|
| 1702 |
- |
|
| 1703 |
- if *before != "" {
|
|
| 1704 |
- v.Set("before", *before)
|
|
| 1705 |
- } |
|
| 1706 |
- |
|
| 1707 |
- if *size {
|
|
| 1708 |
- v.Set("size", "1")
|
|
| 1709 |
- } |
|
| 1710 |
- |
|
| 1711 |
- // Consolidate all filter flags, and sanity check them. |
|
| 1712 |
- // They'll get processed in the daemon/server. |
|
| 1713 |
- for _, f := range flFilter.GetAll() {
|
|
| 1714 |
- if psFilterArgs, err = filters.ParseFlag(f, psFilterArgs); err != nil {
|
|
| 1715 |
- return err |
|
| 1716 |
- } |
|
| 1717 |
- } |
|
| 1718 |
- |
|
| 1719 |
- if len(psFilterArgs) > 0 {
|
|
| 1720 |
- filterJson, err := filters.ToParam(psFilterArgs) |
|
| 1721 |
- if err != nil {
|
|
| 1722 |
- return err |
|
| 1723 |
- } |
|
| 1724 |
- |
|
| 1725 |
- v.Set("filters", filterJson)
|
|
| 1726 |
- } |
|
| 1727 |
- |
|
| 1728 |
- body, _, err := readBody(cli.call("GET", "/containers/json?"+v.Encode(), nil, false))
|
|
| 1729 |
- if err != nil {
|
|
| 1730 |
- return err |
|
| 1731 |
- } |
|
| 1732 |
- |
|
| 1733 |
- outs := engine.NewTable("Created", 0)
|
|
| 1734 |
- if _, err := outs.ReadListFrom(body); err != nil {
|
|
| 1735 |
- return err |
|
| 1736 |
- } |
|
| 1737 |
- |
|
| 1738 |
- w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0) |
|
| 1739 |
- if !*quiet {
|
|
| 1740 |
- fmt.Fprint(w, "CONTAINER ID\tIMAGE\tCOMMAND\tCREATED\tSTATUS\tPORTS\tNAMES") |
|
| 1741 |
- |
|
| 1742 |
- if *size {
|
|
| 1743 |
- fmt.Fprintln(w, "\tSIZE") |
|
| 1744 |
- } else {
|
|
| 1745 |
- fmt.Fprint(w, "\n") |
|
| 1746 |
- } |
|
| 1747 |
- } |
|
| 1748 |
- |
|
| 1749 |
- stripNamePrefix := func(ss []string) []string {
|
|
| 1750 |
- for i, s := range ss {
|
|
| 1751 |
- ss[i] = s[1:] |
|
| 1752 |
- } |
|
| 1753 |
- |
|
| 1754 |
- return ss |
|
| 1755 |
- } |
|
| 1756 |
- |
|
| 1757 |
- for _, out := range outs.Data {
|
|
| 1758 |
- outID := out.Get("Id")
|
|
| 1759 |
- |
|
| 1760 |
- if !*noTrunc {
|
|
| 1761 |
- outID = stringid.TruncateID(outID) |
|
| 1762 |
- } |
|
| 1763 |
- |
|
| 1764 |
- if *quiet {
|
|
| 1765 |
- fmt.Fprintln(w, outID) |
|
| 1766 |
- |
|
| 1767 |
- continue |
|
| 1768 |
- } |
|
| 1769 |
- |
|
| 1770 |
- var ( |
|
| 1771 |
- outNames = stripNamePrefix(out.GetList("Names"))
|
|
| 1772 |
- outCommand = strconv.Quote(out.Get("Command"))
|
|
| 1773 |
- ports = engine.NewTable("", 0)
|
|
| 1774 |
- ) |
|
| 1775 |
- |
|
| 1776 |
- if !*noTrunc {
|
|
| 1777 |
- outCommand = utils.Trunc(outCommand, 20) |
|
| 1778 |
- |
|
| 1779 |
- // only display the default name for the container with notrunc is passed |
|
| 1780 |
- for _, name := range outNames {
|
|
| 1781 |
- if len(strings.Split(name, "/")) == 1 {
|
|
| 1782 |
- outNames = []string{name}
|
|
| 1783 |
- |
|
| 1784 |
- break |
|
| 1785 |
- } |
|
| 1786 |
- } |
|
| 1787 |
- } |
|
| 1788 |
- |
|
| 1789 |
- ports.ReadListFrom([]byte(out.Get("Ports")))
|
|
| 1790 |
- |
|
| 1791 |
- image := out.Get("Image")
|
|
| 1792 |
- if image == "" {
|
|
| 1793 |
- image = "<no image>" |
|
| 1794 |
- } |
|
| 1795 |
- |
|
| 1796 |
- fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\t%s\t%s\t", outID, image, outCommand, |
|
| 1797 |
- units.HumanDuration(time.Now().UTC().Sub(time.Unix(out.GetInt64("Created"), 0))),
|
|
| 1798 |
- out.Get("Status"), api.DisplayablePorts(ports), strings.Join(outNames, ","))
|
|
| 1799 |
- |
|
| 1800 |
- if *size {
|
|
| 1801 |
- if out.GetInt("SizeRootFs") > 0 {
|
|
| 1802 |
- fmt.Fprintf(w, "%s (virtual %s)\n", units.HumanSize(float64(out.GetInt64("SizeRw"))), units.HumanSize(float64(out.GetInt64("SizeRootFs"))))
|
|
| 1803 |
- } else {
|
|
| 1804 |
- fmt.Fprintf(w, "%s\n", units.HumanSize(float64(out.GetInt64("SizeRw"))))
|
|
| 1805 |
- } |
|
| 1806 |
- |
|
| 1807 |
- continue |
|
| 1808 |
- } |
|
| 1809 |
- |
|
| 1810 |
- fmt.Fprint(w, "\n") |
|
| 1811 |
- } |
|
| 1812 |
- |
|
| 1813 |
- if !*quiet {
|
|
| 1814 |
- w.Flush() |
|
| 1815 |
- } |
|
| 1816 |
- |
|
| 1817 |
- return nil |
|
| 1818 |
-} |
|
| 1819 |
- |
|
| 1820 |
-func (cli *DockerCli) CmdCommit(args ...string) error {
|
|
| 1821 |
- cmd := cli.Subcmd("commit", "CONTAINER [REPOSITORY[:TAG]]", "Create a new image from a container's changes", true)
|
|
| 1822 |
- flPause := cmd.Bool([]string{"p", "-pause"}, true, "Pause container during commit")
|
|
| 1823 |
- flComment := cmd.String([]string{"m", "-message"}, "", "Commit message")
|
|
| 1824 |
- flAuthor := cmd.String([]string{"a", "#author", "-author"}, "", "Author (e.g., \"John Hannibal Smith <hannibal@a-team.com>\")")
|
|
| 1825 |
- flChanges := opts.NewListOpts(nil) |
|
| 1826 |
- cmd.Var(&flChanges, []string{"c", "-change"}, "Apply Dockerfile instruction to the created image")
|
|
| 1827 |
- // FIXME: --run is deprecated, it will be replaced with inline Dockerfile commands. |
|
| 1828 |
- flConfig := cmd.String([]string{"#run", "#-run"}, "", "This option is deprecated and will be removed in a future version in favor of inline Dockerfile-compatible commands")
|
|
| 1829 |
- cmd.Require(flag.Max, 2) |
|
| 1830 |
- cmd.Require(flag.Min, 1) |
|
| 1831 |
- utils.ParseFlags(cmd, args, true) |
|
| 1832 |
- |
|
| 1833 |
- var ( |
|
| 1834 |
- name = cmd.Arg(0) |
|
| 1835 |
- repository, tag = parsers.ParseRepositoryTag(cmd.Arg(1)) |
|
| 1836 |
- ) |
|
| 1837 |
- |
|
| 1838 |
- //Check if the given image name can be resolved |
|
| 1839 |
- if repository != "" {
|
|
| 1840 |
- if err := registry.ValidateRepositoryName(repository); err != nil {
|
|
| 1841 |
- return err |
|
| 1842 |
- } |
|
| 1843 |
- } |
|
| 1844 |
- |
|
| 1845 |
- v := url.Values{}
|
|
| 1846 |
- v.Set("container", name)
|
|
| 1847 |
- v.Set("repo", repository)
|
|
| 1848 |
- v.Set("tag", tag)
|
|
| 1849 |
- v.Set("comment", *flComment)
|
|
| 1850 |
- v.Set("author", *flAuthor)
|
|
| 1851 |
- for _, change := range flChanges.GetAll() {
|
|
| 1852 |
- v.Add("changes", change)
|
|
| 1853 |
- } |
|
| 1854 |
- |
|
| 1855 |
- if *flPause != true {
|
|
| 1856 |
- v.Set("pause", "0")
|
|
| 1857 |
- } |
|
| 1858 |
- |
|
| 1859 |
- var ( |
|
| 1860 |
- config *runconfig.Config |
|
| 1861 |
- env engine.Env |
|
| 1862 |
- ) |
|
| 1863 |
- if *flConfig != "" {
|
|
| 1864 |
- config = &runconfig.Config{}
|
|
| 1865 |
- if err := json.Unmarshal([]byte(*flConfig), config); err != nil {
|
|
| 1866 |
- return err |
|
| 1867 |
- } |
|
| 1868 |
- } |
|
| 1869 |
- stream, _, err := cli.call("POST", "/commit?"+v.Encode(), config, false)
|
|
| 1870 |
- if err != nil {
|
|
| 1871 |
- return err |
|
| 1872 |
- } |
|
| 1873 |
- if err := env.Decode(stream); err != nil {
|
|
| 1874 |
- return err |
|
| 1875 |
- } |
|
| 1876 |
- |
|
| 1877 |
- fmt.Fprintf(cli.out, "%s\n", env.Get("Id"))
|
|
| 1878 |
- return nil |
|
| 1879 |
-} |
|
| 1880 |
- |
|
| 1881 |
-func (cli *DockerCli) CmdEvents(args ...string) error {
|
|
| 1882 |
- cmd := cli.Subcmd("events", "", "Get real time events from the server", true)
|
|
| 1883 |
- since := cmd.String([]string{"#since", "-since"}, "", "Show all events created since timestamp")
|
|
| 1884 |
- until := cmd.String([]string{"-until"}, "", "Stream events until this timestamp")
|
|
| 1885 |
- flFilter := opts.NewListOpts(nil) |
|
| 1886 |
- cmd.Var(&flFilter, []string{"f", "-filter"}, "Filter output based on conditions provided")
|
|
| 1887 |
- cmd.Require(flag.Exact, 0) |
|
| 1888 |
- |
|
| 1889 |
- utils.ParseFlags(cmd, args, true) |
|
| 1890 |
- |
|
| 1891 |
- var ( |
|
| 1892 |
- v = url.Values{}
|
|
| 1893 |
- loc = time.FixedZone(time.Now().Zone()) |
|
| 1894 |
- eventFilterArgs = filters.Args{}
|
|
| 1895 |
- ) |
|
| 1896 |
- |
|
| 1897 |
- // Consolidate all filter flags, and sanity check them early. |
|
| 1898 |
- // They'll get process in the daemon/server. |
|
| 1899 |
- for _, f := range flFilter.GetAll() {
|
|
| 1900 |
- var err error |
|
| 1901 |
- eventFilterArgs, err = filters.ParseFlag(f, eventFilterArgs) |
|
| 1902 |
- if err != nil {
|
|
| 1903 |
- return err |
|
| 1904 |
- } |
|
| 1905 |
- } |
|
| 1906 |
- var setTime = func(key, value string) {
|
|
| 1907 |
- format := timeutils.RFC3339NanoFixed |
|
| 1908 |
- if len(value) < len(format) {
|
|
| 1909 |
- format = format[:len(value)] |
|
| 1910 |
- } |
|
| 1911 |
- if t, err := time.ParseInLocation(format, value, loc); err == nil {
|
|
| 1912 |
- v.Set(key, strconv.FormatInt(t.Unix(), 10)) |
|
| 1913 |
- } else {
|
|
| 1914 |
- v.Set(key, value) |
|
| 1915 |
- } |
|
| 1916 |
- } |
|
| 1917 |
- if *since != "" {
|
|
| 1918 |
- setTime("since", *since)
|
|
| 1919 |
- } |
|
| 1920 |
- if *until != "" {
|
|
| 1921 |
- setTime("until", *until)
|
|
| 1922 |
- } |
|
| 1923 |
- if len(eventFilterArgs) > 0 {
|
|
| 1924 |
- filterJson, err := filters.ToParam(eventFilterArgs) |
|
| 1925 |
- if err != nil {
|
|
| 1926 |
- return err |
|
| 1927 |
- } |
|
| 1928 |
- v.Set("filters", filterJson)
|
|
| 1929 |
- } |
|
| 1930 |
- if err := cli.stream("GET", "/events?"+v.Encode(), nil, cli.out, nil); err != nil {
|
|
| 1931 |
- return err |
|
| 1932 |
- } |
|
| 1933 |
- return nil |
|
| 1934 |
-} |
|
| 1935 |
- |
|
| 1936 |
-func (cli *DockerCli) CmdExport(args ...string) error {
|
|
| 1937 |
- cmd := cli.Subcmd("export", "CONTAINER", "Export a filesystem as a tar archive (streamed to STDOUT by default)", true)
|
|
| 1938 |
- outfile := cmd.String([]string{"o", "-output"}, "", "Write to a file, instead of STDOUT")
|
|
| 1939 |
- cmd.Require(flag.Exact, 1) |
|
| 1940 |
- |
|
| 1941 |
- utils.ParseFlags(cmd, args, true) |
|
| 1942 |
- |
|
| 1943 |
- var ( |
|
| 1944 |
- output io.Writer = cli.out |
|
| 1945 |
- err error |
|
| 1946 |
- ) |
|
| 1947 |
- if *outfile != "" {
|
|
| 1948 |
- output, err = os.Create(*outfile) |
|
| 1949 |
- if err != nil {
|
|
| 1950 |
- return err |
|
| 1951 |
- } |
|
| 1952 |
- } else if cli.isTerminalOut {
|
|
| 1953 |
- return errors.New("Cowardly refusing to save to a terminal. Use the -o flag or redirect.")
|
|
| 1954 |
- } |
|
| 1955 |
- |
|
| 1956 |
- if len(cmd.Args()) == 1 {
|
|
| 1957 |
- image := cmd.Arg(0) |
|
| 1958 |
- if err := cli.stream("GET", "/containers/"+image+"/export", nil, output, nil); err != nil {
|
|
| 1959 |
- return err |
|
| 1960 |
- } |
|
| 1961 |
- } else {
|
|
| 1962 |
- v := url.Values{}
|
|
| 1963 |
- for _, arg := range cmd.Args() {
|
|
| 1964 |
- v.Add("names", arg)
|
|
| 1965 |
- } |
|
| 1966 |
- if err := cli.stream("GET", "/containers/get?"+v.Encode(), nil, output, nil); err != nil {
|
|
| 1967 |
- return err |
|
| 1968 |
- } |
|
| 1969 |
- } |
|
| 1970 |
- |
|
| 1971 |
- return nil |
|
| 1972 |
-} |
|
| 1973 |
- |
|
| 1974 |
-func (cli *DockerCli) CmdDiff(args ...string) error {
|
|
| 1975 |
- cmd := cli.Subcmd("diff", "CONTAINER", "Inspect changes on a container's filesystem", true)
|
|
| 1976 |
- cmd.Require(flag.Exact, 1) |
|
| 1977 |
- |
|
| 1978 |
- utils.ParseFlags(cmd, args, true) |
|
| 1979 |
- |
|
| 1980 |
- body, _, err := readBody(cli.call("GET", "/containers/"+cmd.Arg(0)+"/changes", nil, false))
|
|
| 1981 |
- |
|
| 1982 |
- if err != nil {
|
|
| 1983 |
- return err |
|
| 1984 |
- } |
|
| 1985 |
- |
|
| 1986 |
- outs := engine.NewTable("", 0)
|
|
| 1987 |
- if _, err := outs.ReadListFrom(body); err != nil {
|
|
| 1988 |
- return err |
|
| 1989 |
- } |
|
| 1990 |
- for _, change := range outs.Data {
|
|
| 1991 |
- var kind string |
|
| 1992 |
- switch change.GetInt("Kind") {
|
|
| 1993 |
- case archive.ChangeModify: |
|
| 1994 |
- kind = "C" |
|
| 1995 |
- case archive.ChangeAdd: |
|
| 1996 |
- kind = "A" |
|
| 1997 |
- case archive.ChangeDelete: |
|
| 1998 |
- kind = "D" |
|
| 1999 |
- } |
|
| 2000 |
- fmt.Fprintf(cli.out, "%s %s\n", kind, change.Get("Path"))
|
|
| 2001 |
- } |
|
| 2002 |
- return nil |
|
| 2003 |
-} |
|
| 2004 |
- |
|
| 2005 |
-func (cli *DockerCli) CmdLogs(args ...string) error {
|
|
| 2006 |
- var ( |
|
| 2007 |
- cmd = cli.Subcmd("logs", "CONTAINER", "Fetch the logs of a container", true)
|
|
| 2008 |
- follow = cmd.Bool([]string{"f", "-follow"}, false, "Follow log output")
|
|
| 2009 |
- times = cmd.Bool([]string{"t", "-timestamps"}, false, "Show timestamps")
|
|
| 2010 |
- tail = cmd.String([]string{"-tail"}, "all", "Number of lines to show from the end of the logs")
|
|
| 2011 |
- ) |
|
| 2012 |
- cmd.Require(flag.Exact, 1) |
|
| 2013 |
- |
|
| 2014 |
- utils.ParseFlags(cmd, args, true) |
|
| 2015 |
- |
|
| 2016 |
- name := cmd.Arg(0) |
|
| 2017 |
- |
|
| 2018 |
- stream, _, err := cli.call("GET", "/containers/"+name+"/json", nil, false)
|
|
| 2019 |
- if err != nil {
|
|
| 2020 |
- return err |
|
| 2021 |
- } |
|
| 2022 |
- |
|
| 2023 |
- env := engine.Env{}
|
|
| 2024 |
- if err := env.Decode(stream); err != nil {
|
|
| 2025 |
- return err |
|
| 2026 |
- } |
|
| 2027 |
- |
|
| 2028 |
- if env.GetSubEnv("HostConfig").GetSubEnv("LogConfig").Get("Type") != "json-file" {
|
|
| 2029 |
- return fmt.Errorf("\"logs\" command is supported only for \"json-file\" logging driver")
|
|
| 2030 |
- } |
|
| 2031 |
- |
|
| 2032 |
- v := url.Values{}
|
|
| 2033 |
- v.Set("stdout", "1")
|
|
| 2034 |
- v.Set("stderr", "1")
|
|
| 2035 |
- |
|
| 2036 |
- if *times {
|
|
| 2037 |
- v.Set("timestamps", "1")
|
|
| 2038 |
- } |
|
| 2039 |
- |
|
| 2040 |
- if *follow {
|
|
| 2041 |
- v.Set("follow", "1")
|
|
| 2042 |
- } |
|
| 2043 |
- v.Set("tail", *tail)
|
|
| 2044 |
- |
|
| 2045 |
- return cli.streamHelper("GET", "/containers/"+name+"/logs?"+v.Encode(), env.GetSubEnv("Config").GetBool("Tty"), nil, cli.out, cli.err, nil)
|
|
| 2046 |
-} |
|
| 2047 |
- |
|
| 2048 |
-func (cli *DockerCli) CmdAttach(args ...string) error {
|
|
| 2049 |
- var ( |
|
| 2050 |
- cmd = cli.Subcmd("attach", "CONTAINER", "Attach to a running container", true)
|
|
| 2051 |
- noStdin = cmd.Bool([]string{"#nostdin", "-no-stdin"}, false, "Do not attach STDIN")
|
|
| 2052 |
- proxy = cmd.Bool([]string{"#sig-proxy", "-sig-proxy"}, true, "Proxy all received signals to the process")
|
|
| 2053 |
- ) |
|
| 2054 |
- cmd.Require(flag.Exact, 1) |
|
| 2055 |
- |
|
| 2056 |
- utils.ParseFlags(cmd, args, true) |
|
| 2057 |
- name := cmd.Arg(0) |
|
| 2058 |
- |
|
| 2059 |
- stream, _, err := cli.call("GET", "/containers/"+name+"/json", nil, false)
|
|
| 2060 |
- if err != nil {
|
|
| 2061 |
- return err |
|
| 2062 |
- } |
|
| 2063 |
- |
|
| 2064 |
- env := engine.Env{}
|
|
| 2065 |
- if err := env.Decode(stream); err != nil {
|
|
| 2066 |
- return err |
|
| 2067 |
- } |
|
| 2068 |
- |
|
| 2069 |
- if !env.GetSubEnv("State").GetBool("Running") {
|
|
| 2070 |
- return fmt.Errorf("You cannot attach to a stopped container, start it first")
|
|
| 2071 |
- } |
|
| 2072 |
- |
|
| 2073 |
- var ( |
|
| 2074 |
- config = env.GetSubEnv("Config")
|
|
| 2075 |
- tty = config.GetBool("Tty")
|
|
| 2076 |
- ) |
|
| 2077 |
- |
|
| 2078 |
- if err := cli.CheckTtyInput(!*noStdin, tty); err != nil {
|
|
| 2079 |
- return err |
|
| 2080 |
- } |
|
| 2081 |
- |
|
| 2082 |
- if tty && cli.isTerminalOut {
|
|
| 2083 |
- if err := cli.monitorTtySize(cmd.Arg(0), false); err != nil {
|
|
| 2084 |
- log.Debugf("Error monitoring TTY size: %s", err)
|
|
| 2085 |
- } |
|
| 2086 |
- } |
|
| 2087 |
- |
|
| 2088 |
- var in io.ReadCloser |
|
| 2089 |
- |
|
| 2090 |
- v := url.Values{}
|
|
| 2091 |
- v.Set("stream", "1")
|
|
| 2092 |
- if !*noStdin && config.GetBool("OpenStdin") {
|
|
| 2093 |
- v.Set("stdin", "1")
|
|
| 2094 |
- in = cli.in |
|
| 2095 |
- } |
|
| 2096 |
- |
|
| 2097 |
- v.Set("stdout", "1")
|
|
| 2098 |
- v.Set("stderr", "1")
|
|
| 2099 |
- |
|
| 2100 |
- if *proxy && !tty {
|
|
| 2101 |
- sigc := cli.forwardAllSignals(cmd.Arg(0)) |
|
| 2102 |
- defer signal.StopCatch(sigc) |
|
| 2103 |
- } |
|
| 2104 |
- |
|
| 2105 |
- if err := cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), tty, in, cli.out, cli.err, nil, nil); err != nil {
|
|
| 2106 |
- return err |
|
| 2107 |
- } |
|
| 2108 |
- |
|
| 2109 |
- _, status, err := getExitCode(cli, cmd.Arg(0)) |
|
| 2110 |
- if err != nil {
|
|
| 2111 |
- return err |
|
| 2112 |
- } |
|
| 2113 |
- if status != 0 {
|
|
| 2114 |
- return &utils.StatusError{StatusCode: status}
|
|
| 2115 |
- } |
|
| 2116 |
- |
|
| 2117 |
- return nil |
|
| 2118 |
-} |
|
| 2119 |
- |
|
| 2120 |
-func (cli *DockerCli) CmdSearch(args ...string) error {
|
|
| 2121 |
- cmd := cli.Subcmd("search", "TERM", "Search the Docker Hub for images", true)
|
|
| 2122 |
- noTrunc := cmd.Bool([]string{"#notrunc", "-no-trunc"}, false, "Don't truncate output")
|
|
| 2123 |
- trusted := cmd.Bool([]string{"#t", "#trusted", "#-trusted"}, false, "Only show trusted builds")
|
|
| 2124 |
- automated := cmd.Bool([]string{"-automated"}, false, "Only show automated builds")
|
|
| 2125 |
- stars := cmd.Int([]string{"s", "#stars", "-stars"}, 0, "Only displays with at least x stars")
|
|
| 2126 |
- cmd.Require(flag.Exact, 1) |
|
| 2127 |
- |
|
| 2128 |
- utils.ParseFlags(cmd, args, true) |
|
| 2129 |
- |
|
| 2130 |
- v := url.Values{}
|
|
| 2131 |
- v.Set("term", cmd.Arg(0))
|
|
| 2132 |
- |
|
| 2133 |
- body, _, err := readBody(cli.call("GET", "/images/search?"+v.Encode(), nil, true))
|
|
| 2134 |
- |
|
| 2135 |
- if err != nil {
|
|
| 2136 |
- return err |
|
| 2137 |
- } |
|
| 2138 |
- outs := engine.NewTable("star_count", 0)
|
|
| 2139 |
- if _, err := outs.ReadListFrom(body); err != nil {
|
|
| 2140 |
- return err |
|
| 2141 |
- } |
|
| 2142 |
- w := tabwriter.NewWriter(cli.out, 10, 1, 3, ' ', 0) |
|
| 2143 |
- fmt.Fprintf(w, "NAME\tDESCRIPTION\tSTARS\tOFFICIAL\tAUTOMATED\n") |
|
| 2144 |
- for _, out := range outs.Data {
|
|
| 2145 |
- if ((*automated || *trusted) && (!out.GetBool("is_trusted") && !out.GetBool("is_automated"))) || (*stars > out.GetInt("star_count")) {
|
|
| 2146 |
- continue |
|
| 2147 |
- } |
|
| 2148 |
- desc := strings.Replace(out.Get("description"), "\n", " ", -1)
|
|
| 2149 |
- desc = strings.Replace(desc, "\r", " ", -1) |
|
| 2150 |
- if !*noTrunc && len(desc) > 45 {
|
|
| 2151 |
- desc = utils.Trunc(desc, 42) + "..." |
|
| 2152 |
- } |
|
| 2153 |
- fmt.Fprintf(w, "%s\t%s\t%d\t", out.Get("name"), desc, out.GetInt("star_count"))
|
|
| 2154 |
- if out.GetBool("is_official") {
|
|
| 2155 |
- fmt.Fprint(w, "[OK]") |
|
| 2156 |
- |
|
| 2157 |
- } |
|
| 2158 |
- fmt.Fprint(w, "\t") |
|
| 2159 |
- if out.GetBool("is_automated") || out.GetBool("is_trusted") {
|
|
| 2160 |
- fmt.Fprint(w, "[OK]") |
|
| 2161 |
- } |
|
| 2162 |
- fmt.Fprint(w, "\n") |
|
| 2163 |
- } |
|
| 2164 |
- w.Flush() |
|
| 2165 |
- return nil |
|
| 2166 |
-} |
|
| 2167 |
- |
|
| 2168 |
-// Ports type - Used to parse multiple -p flags |
|
| 2169 |
-type ports []int |
|
| 2170 |
- |
|
| 2171 |
-func (cli *DockerCli) CmdTag(args ...string) error {
|
|
| 2172 |
- cmd := cli.Subcmd("tag", "IMAGE[:TAG] [REGISTRYHOST/][USERNAME/]NAME[:TAG]", "Tag an image into a repository", true)
|
|
| 2173 |
- force := cmd.Bool([]string{"f", "#force", "-force"}, false, "Force")
|
|
| 2174 |
- cmd.Require(flag.Exact, 2) |
|
| 2175 |
- |
|
| 2176 |
- utils.ParseFlags(cmd, args, true) |
|
| 2177 |
- |
|
| 2178 |
- var ( |
|
| 2179 |
- repository, tag = parsers.ParseRepositoryTag(cmd.Arg(1)) |
|
| 2180 |
- v = url.Values{}
|
|
| 2181 |
- ) |
|
| 2182 |
- |
|
| 2183 |
- //Check if the given image name can be resolved |
|
| 2184 |
- if err := registry.ValidateRepositoryName(repository); err != nil {
|
|
| 2185 |
- return err |
|
| 2186 |
- } |
|
| 2187 |
- v.Set("repo", repository)
|
|
| 2188 |
- v.Set("tag", tag)
|
|
| 2189 |
- |
|
| 2190 |
- if *force {
|
|
| 2191 |
- v.Set("force", "1")
|
|
| 2192 |
- } |
|
| 2193 |
- |
|
| 2194 |
- if _, _, err := readBody(cli.call("POST", "/images/"+cmd.Arg(0)+"/tag?"+v.Encode(), nil, false)); err != nil {
|
|
| 2195 |
- return err |
|
| 2196 |
- } |
|
| 2197 |
- return nil |
|
| 2198 |
-} |
|
| 2199 |
- |
|
| 2200 |
-func (cli *DockerCli) pullImage(image string) error {
|
|
| 2201 |
- return cli.pullImageCustomOut(image, cli.out) |
|
| 2202 |
-} |
|
| 2203 |
- |
|
| 2204 |
-func (cli *DockerCli) pullImageCustomOut(image string, out io.Writer) error {
|
|
| 2205 |
- v := url.Values{}
|
|
| 2206 |
- repos, tag := parsers.ParseRepositoryTag(image) |
|
| 2207 |
- // pull only the image tagged 'latest' if no tag was specified |
|
| 2208 |
- if tag == "" {
|
|
| 2209 |
- tag = graph.DEFAULTTAG |
|
| 2210 |
- } |
|
| 2211 |
- v.Set("fromImage", repos)
|
|
| 2212 |
- v.Set("tag", tag)
|
|
| 2213 |
- |
|
| 2214 |
- // Resolve the Repository name from fqn to RepositoryInfo |
|
| 2215 |
- repoInfo, err := registry.ParseRepositoryInfo(repos) |
|
| 2216 |
- if err != nil {
|
|
| 2217 |
- return err |
|
| 2218 |
- } |
|
| 2219 |
- |
|
| 2220 |
- // Load the auth config file, to be able to pull the image |
|
| 2221 |
- cli.LoadConfigFile() |
|
| 2222 |
- |
|
| 2223 |
- // Resolve the Auth config relevant for this server |
|
| 2224 |
- authConfig := cli.configFile.ResolveAuthConfig(repoInfo.Index) |
|
| 2225 |
- buf, err := json.Marshal(authConfig) |
|
| 2226 |
- if err != nil {
|
|
| 2227 |
- return err |
|
| 2228 |
- } |
|
| 2229 |
- |
|
| 2230 |
- registryAuthHeader := []string{
|
|
| 2231 |
- base64.URLEncoding.EncodeToString(buf), |
|
| 2232 |
- } |
|
| 2233 |
- if err = cli.stream("POST", "/images/create?"+v.Encode(), nil, out, map[string][]string{"X-Registry-Auth": registryAuthHeader}); err != nil {
|
|
| 2234 |
- return err |
|
| 2235 |
- } |
|
| 2236 |
- return nil |
|
| 2237 |
-} |
|
| 2238 |
- |
|
| 2239 |
-type cidFile struct {
|
|
| 2240 |
- path string |
|
| 2241 |
- file *os.File |
|
| 2242 |
- written bool |
|
| 2243 |
-} |
|
| 2244 |
- |
|
| 2245 |
-func newCIDFile(path string) (*cidFile, error) {
|
|
| 2246 |
- if _, err := os.Stat(path); err == nil {
|
|
| 2247 |
- return nil, fmt.Errorf("Container ID file found, make sure the other container isn't running or delete %s", path)
|
|
| 2248 |
- } |
|
| 2249 |
- |
|
| 2250 |
- f, err := os.Create(path) |
|
| 2251 |
- if err != nil {
|
|
| 2252 |
- return nil, fmt.Errorf("Failed to create the container ID file: %s", err)
|
|
| 2253 |
- } |
|
| 2254 |
- |
|
| 2255 |
- return &cidFile{path: path, file: f}, nil
|
|
| 2256 |
-} |
|
| 2257 |
- |
|
| 2258 |
-func (cid *cidFile) Close() error {
|
|
| 2259 |
- cid.file.Close() |
|
| 2260 |
- |
|
| 2261 |
- if !cid.written {
|
|
| 2262 |
- if err := os.Remove(cid.path); err != nil {
|
|
| 2263 |
- return fmt.Errorf("failed to remove the CID file '%s': %s \n", cid.path, err)
|
|
| 2264 |
- } |
|
| 2265 |
- } |
|
| 2266 |
- |
|
| 2267 |
- return nil |
|
| 2268 |
-} |
|
| 2269 |
- |
|
| 2270 |
-func (cid *cidFile) Write(id string) error {
|
|
| 2271 |
- if _, err := cid.file.Write([]byte(id)); err != nil {
|
|
| 2272 |
- return fmt.Errorf("Failed to write the container ID to the file: %s", err)
|
|
| 2273 |
- } |
|
| 2274 |
- cid.written = true |
|
| 2275 |
- return nil |
|
| 2276 |
-} |
|
| 2277 |
- |
|
| 2278 |
-func (cli *DockerCli) createContainer(config *runconfig.Config, hostConfig *runconfig.HostConfig, cidfile, name string) (*types.ContainerCreateResponse, error) {
|
|
| 2279 |
- containerValues := url.Values{}
|
|
| 2280 |
- if name != "" {
|
|
| 2281 |
- containerValues.Set("name", name)
|
|
| 2282 |
- } |
|
| 2283 |
- |
|
| 2284 |
- mergedConfig := runconfig.MergeConfigs(config, hostConfig) |
|
| 2285 |
- |
|
| 2286 |
- var containerIDFile *cidFile |
|
| 2287 |
- if cidfile != "" {
|
|
| 2288 |
- var err error |
|
| 2289 |
- if containerIDFile, err = newCIDFile(cidfile); err != nil {
|
|
| 2290 |
- return nil, err |
|
| 2291 |
- } |
|
| 2292 |
- defer containerIDFile.Close() |
|
| 2293 |
- } |
|
| 2294 |
- |
|
| 2295 |
- //create the container |
|
| 2296 |
- stream, statusCode, err := cli.call("POST", "/containers/create?"+containerValues.Encode(), mergedConfig, false)
|
|
| 2297 |
- //if image not found try to pull it |
|
| 2298 |
- if statusCode == 404 {
|
|
| 2299 |
- repo, tag := parsers.ParseRepositoryTag(config.Image) |
|
| 2300 |
- if tag == "" {
|
|
| 2301 |
- tag = graph.DEFAULTTAG |
|
| 2302 |
- } |
|
| 2303 |
- fmt.Fprintf(cli.err, "Unable to find image '%s' locally\n", utils.ImageReference(repo, tag)) |
|
| 2304 |
- |
|
| 2305 |
- // we don't want to write to stdout anything apart from container.ID |
|
| 2306 |
- if err = cli.pullImageCustomOut(config.Image, cli.err); err != nil {
|
|
| 2307 |
- return nil, err |
|
| 2308 |
- } |
|
| 2309 |
- // Retry |
|
| 2310 |
- if stream, _, err = cli.call("POST", "/containers/create?"+containerValues.Encode(), mergedConfig, false); err != nil {
|
|
| 2311 |
- return nil, err |
|
| 2312 |
- } |
|
| 2313 |
- } else if err != nil {
|
|
| 2314 |
- return nil, err |
|
| 2315 |
- } |
|
| 2316 |
- |
|
| 2317 |
- var response types.ContainerCreateResponse |
|
| 2318 |
- if err := json.NewDecoder(stream).Decode(&response); err != nil {
|
|
| 2319 |
- return nil, err |
|
| 2320 |
- } |
|
| 2321 |
- for _, warning := range response.Warnings {
|
|
| 2322 |
- fmt.Fprintf(cli.err, "WARNING: %s\n", warning) |
|
| 2323 |
- } |
|
| 2324 |
- if containerIDFile != nil {
|
|
| 2325 |
- if err = containerIDFile.Write(response.ID); err != nil {
|
|
| 2326 |
- return nil, err |
|
| 2327 |
- } |
|
| 2328 |
- } |
|
| 2329 |
- return &response, nil |
|
| 2330 |
-} |
|
| 2331 |
- |
|
| 2332 |
-func (cli *DockerCli) CmdCreate(args ...string) error {
|
|
| 2333 |
- cmd := cli.Subcmd("create", "IMAGE [COMMAND] [ARG...]", "Create a new container", true)
|
|
| 2334 |
- |
|
| 2335 |
- // These are flags not stored in Config/HostConfig |
|
| 2336 |
- var ( |
|
| 2337 |
- flName = cmd.String([]string{"-name"}, "", "Assign a name to the container")
|
|
| 2338 |
- ) |
|
| 2339 |
- |
|
| 2340 |
- config, hostConfig, cmd, err := runconfig.Parse(cmd, args) |
|
| 2341 |
- if err != nil {
|
|
| 2342 |
- utils.ReportError(cmd, err.Error(), true) |
|
| 2343 |
- } |
|
| 2344 |
- if config.Image == "" {
|
|
| 2345 |
- cmd.Usage() |
|
| 2346 |
- return nil |
|
| 2347 |
- } |
|
| 2348 |
- response, err := cli.createContainer(config, hostConfig, hostConfig.ContainerIDFile, *flName) |
|
| 2349 |
- if err != nil {
|
|
| 2350 |
- return err |
|
| 2351 |
- } |
|
| 2352 |
- fmt.Fprintf(cli.out, "%s\n", response.ID) |
|
| 2353 |
- return nil |
|
| 2354 |
-} |
|
| 2355 |
- |
|
| 2356 |
-func (cli *DockerCli) CmdRun(args ...string) error {
|
|
| 2357 |
- // FIXME: just use runconfig.Parse already |
|
| 2358 |
- cmd := cli.Subcmd("run", "IMAGE [COMMAND] [ARG...]", "Run a command in a new container", true)
|
|
| 2359 |
- |
|
| 2360 |
- // These are flags not stored in Config/HostConfig |
|
| 2361 |
- var ( |
|
| 2362 |
- flAutoRemove = cmd.Bool([]string{"#rm", "-rm"}, false, "Automatically remove the container when it exits")
|
|
| 2363 |
- flDetach = cmd.Bool([]string{"d", "-detach"}, false, "Run container in background and print container ID")
|
|
| 2364 |
- flSigProxy = cmd.Bool([]string{"#sig-proxy", "-sig-proxy"}, true, "Proxy received signals to the process")
|
|
| 2365 |
- flName = cmd.String([]string{"#name", "-name"}, "", "Assign a name to the container")
|
|
| 2366 |
- flAttach *opts.ListOpts |
|
| 2367 |
- |
|
| 2368 |
- ErrConflictAttachDetach = fmt.Errorf("Conflicting options: -a and -d")
|
|
| 2369 |
- ErrConflictRestartPolicyAndAutoRemove = fmt.Errorf("Conflicting options: --restart and --rm")
|
|
| 2370 |
- ErrConflictDetachAutoRemove = fmt.Errorf("Conflicting options: --rm and -d")
|
|
| 2371 |
- ) |
|
| 2372 |
- |
|
| 2373 |
- config, hostConfig, cmd, err := runconfig.Parse(cmd, args) |
|
| 2374 |
- // just in case the Parse does not exit |
|
| 2375 |
- if err != nil {
|
|
| 2376 |
- utils.ReportError(cmd, err.Error(), true) |
|
| 2377 |
- } |
|
| 2378 |
- |
|
| 2379 |
- if len(hostConfig.Dns) > 0 {
|
|
| 2380 |
- // check the DNS settings passed via --dns against |
|
| 2381 |
- // localhost regexp to warn if they are trying to |
|
| 2382 |
- // set a DNS to a localhost address |
|
| 2383 |
- for _, dnsIP := range hostConfig.Dns {
|
|
| 2384 |
- if resolvconf.IsLocalhost(dnsIP) {
|
|
| 2385 |
- fmt.Fprintf(cli.err, "WARNING: Localhost DNS setting (--dns=%s) may fail in containers.\n", dnsIP) |
|
| 2386 |
- break |
|
| 2387 |
- } |
|
| 2388 |
- } |
|
| 2389 |
- } |
|
| 2390 |
- if config.Image == "" {
|
|
| 2391 |
- cmd.Usage() |
|
| 2392 |
- return nil |
|
| 2393 |
- } |
|
| 2394 |
- |
|
| 2395 |
- if !*flDetach {
|
|
| 2396 |
- if err := cli.CheckTtyInput(config.AttachStdin, config.Tty); err != nil {
|
|
| 2397 |
- return err |
|
| 2398 |
- } |
|
| 2399 |
- } else {
|
|
| 2400 |
- if fl := cmd.Lookup("-attach"); fl != nil {
|
|
| 2401 |
- flAttach = fl.Value.(*opts.ListOpts) |
|
| 2402 |
- if flAttach.Len() != 0 {
|
|
| 2403 |
- return ErrConflictAttachDetach |
|
| 2404 |
- } |
|
| 2405 |
- } |
|
| 2406 |
- if *flAutoRemove {
|
|
| 2407 |
- return ErrConflictDetachAutoRemove |
|
| 2408 |
- } |
|
| 2409 |
- |
|
| 2410 |
- config.AttachStdin = false |
|
| 2411 |
- config.AttachStdout = false |
|
| 2412 |
- config.AttachStderr = false |
|
| 2413 |
- config.StdinOnce = false |
|
| 2414 |
- } |
|
| 2415 |
- |
|
| 2416 |
- // Disable flSigProxy when in TTY mode |
|
| 2417 |
- sigProxy := *flSigProxy |
|
| 2418 |
- if config.Tty {
|
|
| 2419 |
- sigProxy = false |
|
| 2420 |
- } |
|
| 2421 |
- |
|
| 2422 |
- createResponse, err := cli.createContainer(config, hostConfig, hostConfig.ContainerIDFile, *flName) |
|
| 2423 |
- if err != nil {
|
|
| 2424 |
- return err |
|
| 2425 |
- } |
|
| 2426 |
- if sigProxy {
|
|
| 2427 |
- sigc := cli.forwardAllSignals(createResponse.ID) |
|
| 2428 |
- defer signal.StopCatch(sigc) |
|
| 2429 |
- } |
|
| 2430 |
- var ( |
|
| 2431 |
- waitDisplayId chan struct{}
|
|
| 2432 |
- errCh chan error |
|
| 2433 |
- ) |
|
| 2434 |
- if !config.AttachStdout && !config.AttachStderr {
|
|
| 2435 |
- // Make this asynchronous to allow the client to write to stdin before having to read the ID |
|
| 2436 |
- waitDisplayId = make(chan struct{})
|
|
| 2437 |
- go func() {
|
|
| 2438 |
- defer close(waitDisplayId) |
|
| 2439 |
- fmt.Fprintf(cli.out, "%s\n", createResponse.ID) |
|
| 2440 |
- }() |
|
| 2441 |
- } |
|
| 2442 |
- if *flAutoRemove && (hostConfig.RestartPolicy.Name == "always" || hostConfig.RestartPolicy.Name == "on-failure") {
|
|
| 2443 |
- return ErrConflictRestartPolicyAndAutoRemove |
|
| 2444 |
- } |
|
| 2445 |
- // We need to instantiate the chan because the select needs it. It can |
|
| 2446 |
- // be closed but can't be uninitialized. |
|
| 2447 |
- hijacked := make(chan io.Closer) |
|
| 2448 |
- // Block the return until the chan gets closed |
|
| 2449 |
- defer func() {
|
|
| 2450 |
- log.Debugf("End of CmdRun(), Waiting for hijack to finish.")
|
|
| 2451 |
- if _, ok := <-hijacked; ok {
|
|
| 2452 |
- log.Errorf("Hijack did not finish (chan still open)")
|
|
| 2453 |
- } |
|
| 2454 |
- }() |
|
| 2455 |
- if config.AttachStdin || config.AttachStdout || config.AttachStderr {
|
|
| 2456 |
- var ( |
|
| 2457 |
- out, stderr io.Writer |
|
| 2458 |
- in io.ReadCloser |
|
| 2459 |
- v = url.Values{}
|
|
| 2460 |
- ) |
|
| 2461 |
- v.Set("stream", "1")
|
|
| 2462 |
- if config.AttachStdin {
|
|
| 2463 |
- v.Set("stdin", "1")
|
|
| 2464 |
- in = cli.in |
|
| 2465 |
- } |
|
| 2466 |
- if config.AttachStdout {
|
|
| 2467 |
- v.Set("stdout", "1")
|
|
| 2468 |
- out = cli.out |
|
| 2469 |
- } |
|
| 2470 |
- if config.AttachStderr {
|
|
| 2471 |
- v.Set("stderr", "1")
|
|
| 2472 |
- if config.Tty {
|
|
| 2473 |
- stderr = cli.out |
|
| 2474 |
- } else {
|
|
| 2475 |
- stderr = cli.err |
|
| 2476 |
- } |
|
| 2477 |
- } |
|
| 2478 |
- errCh = promise.Go(func() error {
|
|
| 2479 |
- return cli.hijack("POST", "/containers/"+createResponse.ID+"/attach?"+v.Encode(), config.Tty, in, out, stderr, hijacked, nil)
|
|
| 2480 |
- }) |
|
| 2481 |
- } else {
|
|
| 2482 |
- close(hijacked) |
|
| 2483 |
- } |
|
| 2484 |
- // Acknowledge the hijack before starting |
|
| 2485 |
- select {
|
|
| 2486 |
- case closer := <-hijacked: |
|
| 2487 |
- // Make sure that the hijack gets closed when returning (results |
|
| 2488 |
- // in closing the hijack chan and freeing server's goroutines) |
|
| 2489 |
- if closer != nil {
|
|
| 2490 |
- defer closer.Close() |
|
| 2491 |
- } |
|
| 2492 |
- case err := <-errCh: |
|
| 2493 |
- if err != nil {
|
|
| 2494 |
- log.Debugf("Error hijack: %s", err)
|
|
| 2495 |
- return err |
|
| 2496 |
- } |
|
| 2497 |
- } |
|
| 2498 |
- |
|
| 2499 |
- defer func() {
|
|
| 2500 |
- if *flAutoRemove {
|
|
| 2501 |
- if _, _, err = readBody(cli.call("DELETE", "/containers/"+createResponse.ID+"?v=1", nil, false)); err != nil {
|
|
| 2502 |
- log.Errorf("Error deleting container: %s", err)
|
|
| 2503 |
- } |
|
| 2504 |
- } |
|
| 2505 |
- }() |
|
| 2506 |
- |
|
| 2507 |
- //start the container |
|
| 2508 |
- if _, _, err = readBody(cli.call("POST", "/containers/"+createResponse.ID+"/start", nil, false)); err != nil {
|
|
| 2509 |
- return err |
|
| 2510 |
- } |
|
| 2511 |
- |
|
| 2512 |
- if (config.AttachStdin || config.AttachStdout || config.AttachStderr) && config.Tty && cli.isTerminalOut {
|
|
| 2513 |
- if err := cli.monitorTtySize(createResponse.ID, false); err != nil {
|
|
| 2514 |
- log.Errorf("Error monitoring TTY size: %s", err)
|
|
| 2515 |
- } |
|
| 2516 |
- } |
|
| 2517 |
- |
|
| 2518 |
- if errCh != nil {
|
|
| 2519 |
- if err := <-errCh; err != nil {
|
|
| 2520 |
- log.Debugf("Error hijack: %s", err)
|
|
| 2521 |
- return err |
|
| 2522 |
- } |
|
| 2523 |
- } |
|
| 2524 |
- |
|
| 2525 |
- // Detached mode: wait for the id to be displayed and return. |
|
| 2526 |
- if !config.AttachStdout && !config.AttachStderr {
|
|
| 2527 |
- // Detached mode |
|
| 2528 |
- <-waitDisplayId |
|
| 2529 |
- return nil |
|
| 2530 |
- } |
|
| 2531 |
- |
|
| 2532 |
- var status int |
|
| 2533 |
- |
|
| 2534 |
- // Attached mode |
|
| 2535 |
- if *flAutoRemove {
|
|
| 2536 |
- // Autoremove: wait for the container to finish, retrieve |
|
| 2537 |
- // the exit code and remove the container |
|
| 2538 |
- if _, _, err := readBody(cli.call("POST", "/containers/"+createResponse.ID+"/wait", nil, false)); err != nil {
|
|
| 2539 |
- return err |
|
| 2540 |
- } |
|
| 2541 |
- if _, status, err = getExitCode(cli, createResponse.ID); err != nil {
|
|
| 2542 |
- return err |
|
| 2543 |
- } |
|
| 2544 |
- } else {
|
|
| 2545 |
- // No Autoremove: Simply retrieve the exit code |
|
| 2546 |
- if !config.Tty {
|
|
| 2547 |
- // In non-TTY mode, we can't detach, so we must wait for container exit |
|
| 2548 |
- if status, err = waitForExit(cli, createResponse.ID); err != nil {
|
|
| 2549 |
- return err |
|
| 2550 |
- } |
|
| 2551 |
- } else {
|
|
| 2552 |
- // In TTY mode, there is a race: if the process dies too slowly, the state could |
|
| 2553 |
- // be updated after the getExitCode call and result in the wrong exit code being reported |
|
| 2554 |
- if _, status, err = getExitCode(cli, createResponse.ID); err != nil {
|
|
| 2555 |
- return err |
|
| 2556 |
- } |
|
| 2557 |
- } |
|
| 2558 |
- } |
|
| 2559 |
- if status != 0 {
|
|
| 2560 |
- return &utils.StatusError{StatusCode: status}
|
|
| 2561 |
- } |
|
| 2562 |
- return nil |
|
| 2563 |
-} |
|
| 2564 |
- |
|
| 2565 |
-func (cli *DockerCli) CmdCp(args ...string) error {
|
|
| 2566 |
- cmd := cli.Subcmd("cp", "CONTAINER:PATH HOSTDIR|-", "Copy files/folders from a PATH on the container to a HOSTDIR on the host\nrunning the command. Use '-' to write the data\nas a tar file to STDOUT.", true)
|
|
| 2567 |
- cmd.Require(flag.Exact, 2) |
|
| 2568 |
- |
|
| 2569 |
- utils.ParseFlags(cmd, args, true) |
|
| 2570 |
- |
|
| 2571 |
- var copyData engine.Env |
|
| 2572 |
- info := strings.Split(cmd.Arg(0), ":") |
|
| 2573 |
- |
|
| 2574 |
- if len(info) != 2 {
|
|
| 2575 |
- return fmt.Errorf("Error: Path not specified")
|
|
| 2576 |
- } |
|
| 2577 |
- |
|
| 2578 |
- copyData.Set("Resource", info[1])
|
|
| 2579 |
- copyData.Set("HostPath", cmd.Arg(1))
|
|
| 2580 |
- |
|
| 2581 |
- stream, statusCode, err := cli.call("POST", "/containers/"+info[0]+"/copy", copyData, false)
|
|
| 2582 |
- if stream != nil {
|
|
| 2583 |
- defer stream.Close() |
|
| 2584 |
- } |
|
| 2585 |
- if statusCode == 404 {
|
|
| 2586 |
- return fmt.Errorf("No such container: %v", info[0])
|
|
| 2587 |
- } |
|
| 2588 |
- if err != nil {
|
|
| 2589 |
- return err |
|
| 2590 |
- } |
|
| 2591 |
- |
|
| 2592 |
- if statusCode == 200 {
|
|
| 2593 |
- dest := copyData.Get("HostPath")
|
|
| 2594 |
- |
|
| 2595 |
- if dest == "-" {
|
|
| 2596 |
- _, err = io.Copy(cli.out, stream) |
|
| 2597 |
- } else {
|
|
| 2598 |
- err = archive.Untar(stream, dest, &archive.TarOptions{NoLchown: true})
|
|
| 2599 |
- } |
|
| 2600 |
- if err != nil {
|
|
| 2601 |
- return err |
|
| 2602 |
- } |
|
| 2603 |
- } |
|
| 2604 |
- return nil |
|
| 2605 |
-} |
|
| 2606 |
- |
|
| 2607 |
-func (cli *DockerCli) CmdSave(args ...string) error {
|
|
| 2608 |
- cmd := cli.Subcmd("save", "IMAGE [IMAGE...]", "Save an image(s) to a tar archive (streamed to STDOUT by default)", true)
|
|
| 2609 |
- outfile := cmd.String([]string{"o", "-output"}, "", "Write to an file, instead of STDOUT")
|
|
| 2610 |
- cmd.Require(flag.Min, 1) |
|
| 2611 |
- |
|
| 2612 |
- utils.ParseFlags(cmd, args, true) |
|
| 2613 |
- |
|
| 2614 |
- var ( |
|
| 2615 |
- output io.Writer = cli.out |
|
| 2616 |
- err error |
|
| 2617 |
- ) |
|
| 2618 |
- if *outfile != "" {
|
|
| 2619 |
- output, err = os.Create(*outfile) |
|
| 2620 |
- if err != nil {
|
|
| 2621 |
- return err |
|
| 2622 |
- } |
|
| 2623 |
- } else if cli.isTerminalOut {
|
|
| 2624 |
- return errors.New("Cowardly refusing to save to a terminal. Use the -o flag or redirect.")
|
|
| 2625 |
- } |
|
| 2626 |
- |
|
| 2627 |
- if len(cmd.Args()) == 1 {
|
|
| 2628 |
- image := cmd.Arg(0) |
|
| 2629 |
- if err := cli.stream("GET", "/images/"+image+"/get", nil, output, nil); err != nil {
|
|
| 2630 |
- return err |
|
| 2631 |
- } |
|
| 2632 |
- } else {
|
|
| 2633 |
- v := url.Values{}
|
|
| 2634 |
- for _, arg := range cmd.Args() {
|
|
| 2635 |
- v.Add("names", arg)
|
|
| 2636 |
- } |
|
| 2637 |
- if err := cli.stream("GET", "/images/get?"+v.Encode(), nil, output, nil); err != nil {
|
|
| 2638 |
- return err |
|
| 2639 |
- } |
|
| 2640 |
- } |
|
| 2641 |
- return nil |
|
| 2642 |
-} |
|
| 2643 |
- |
|
| 2644 |
-func (cli *DockerCli) CmdLoad(args ...string) error {
|
|
| 2645 |
- cmd := cli.Subcmd("load", "", "Load an image from a tar archive on STDIN", true)
|
|
| 2646 |
- infile := cmd.String([]string{"i", "-input"}, "", "Read from a tar archive file, instead of STDIN")
|
|
| 2647 |
- cmd.Require(flag.Exact, 0) |
|
| 2648 |
- |
|
| 2649 |
- utils.ParseFlags(cmd, args, true) |
|
| 2650 |
- |
|
| 2651 |
- var ( |
|
| 2652 |
- input io.Reader = cli.in |
|
| 2653 |
- err error |
|
| 2654 |
- ) |
|
| 2655 |
- if *infile != "" {
|
|
| 2656 |
- input, err = os.Open(*infile) |
|
| 2657 |
- if err != nil {
|
|
| 2658 |
- return err |
|
| 2659 |
- } |
|
| 2660 |
- } |
|
| 2661 |
- if err := cli.stream("POST", "/images/load", input, cli.out, nil); err != nil {
|
|
| 2662 |
- return err |
|
| 2663 |
- } |
|
| 2664 |
- return nil |
|
| 2665 |
-} |
|
| 2666 |
- |
|
| 2667 |
-func (cli *DockerCli) CmdExec(args ...string) error {
|
|
| 2668 |
- cmd := cli.Subcmd("exec", "CONTAINER COMMAND [ARG...]", "Run a command in a running container", true)
|
|
| 2669 |
- |
|
| 2670 |
- execConfig, err := runconfig.ParseExec(cmd, args) |
|
| 2671 |
- // just in case the ParseExec does not exit |
|
| 2672 |
- if execConfig.Container == "" || err != nil {
|
|
| 2673 |
- return &utils.StatusError{StatusCode: 1}
|
|
| 2674 |
- } |
|
| 2675 |
- |
|
| 2676 |
- stream, _, err := cli.call("POST", "/containers/"+execConfig.Container+"/exec", execConfig, false)
|
|
| 2677 |
- if err != nil {
|
|
| 2678 |
- return err |
|
| 2679 |
- } |
|
| 2680 |
- |
|
| 2681 |
- var response types.ContainerExecCreateResponse |
|
| 2682 |
- if err := json.NewDecoder(stream).Decode(&response); err != nil {
|
|
| 2683 |
- return err |
|
| 2684 |
- } |
|
| 2685 |
- for _, warning := range response.Warnings {
|
|
| 2686 |
- fmt.Fprintf(cli.err, "WARNING: %s\n", warning) |
|
| 2687 |
- } |
|
| 2688 |
- |
|
| 2689 |
- execID := response.ID |
|
| 2690 |
- |
|
| 2691 |
- if execID == "" {
|
|
| 2692 |
- fmt.Fprintf(cli.out, "exec ID empty") |
|
| 2693 |
- return nil |
|
| 2694 |
- } |
|
| 2695 |
- |
|
| 2696 |
- if !execConfig.Detach {
|
|
| 2697 |
- if err := cli.CheckTtyInput(execConfig.AttachStdin, execConfig.Tty); err != nil {
|
|
| 2698 |
- return err |
|
| 2699 |
- } |
|
| 2700 |
- } else {
|
|
| 2701 |
- if _, _, err := readBody(cli.call("POST", "/exec/"+execID+"/start", execConfig, false)); err != nil {
|
|
| 2702 |
- return err |
|
| 2703 |
- } |
|
| 2704 |
- // For now don't print this - wait for when we support exec wait() |
|
| 2705 |
- // fmt.Fprintf(cli.out, "%s\n", execID) |
|
| 2706 |
- return nil |
|
| 2707 |
- } |
|
| 2708 |
- |
|
| 2709 |
- // Interactive exec requested. |
|
| 2710 |
- var ( |
|
| 2711 |
- out, stderr io.Writer |
|
| 2712 |
- in io.ReadCloser |
|
| 2713 |
- hijacked = make(chan io.Closer) |
|
| 2714 |
- errCh chan error |
|
| 2715 |
- ) |
|
| 2716 |
- |
|
| 2717 |
- // Block the return until the chan gets closed |
|
| 2718 |
- defer func() {
|
|
| 2719 |
- log.Debugf("End of CmdExec(), Waiting for hijack to finish.")
|
|
| 2720 |
- if _, ok := <-hijacked; ok {
|
|
| 2721 |
- log.Errorf("Hijack did not finish (chan still open)")
|
|
| 2722 |
- } |
|
| 2723 |
- }() |
|
| 2724 |
- |
|
| 2725 |
- if execConfig.AttachStdin {
|
|
| 2726 |
- in = cli.in |
|
| 2727 |
- } |
|
| 2728 |
- if execConfig.AttachStdout {
|
|
| 2729 |
- out = cli.out |
|
| 2730 |
- } |
|
| 2731 |
- if execConfig.AttachStderr {
|
|
| 2732 |
- if execConfig.Tty {
|
|
| 2733 |
- stderr = cli.out |
|
| 2734 |
- } else {
|
|
| 2735 |
- stderr = cli.err |
|
| 2736 |
- } |
|
| 2737 |
- } |
|
| 2738 |
- errCh = promise.Go(func() error {
|
|
| 2739 |
- return cli.hijack("POST", "/exec/"+execID+"/start", execConfig.Tty, in, out, stderr, hijacked, execConfig)
|
|
| 2740 |
- }) |
|
| 2741 |
- |
|
| 2742 |
- // Acknowledge the hijack before starting |
|
| 2743 |
- select {
|
|
| 2744 |
- case closer := <-hijacked: |
|
| 2745 |
- // Make sure that hijack gets closed when returning. (result |
|
| 2746 |
- // in closing hijack chan and freeing server's goroutines. |
|
| 2747 |
- if closer != nil {
|
|
| 2748 |
- defer closer.Close() |
|
| 2749 |
- } |
|
| 2750 |
- case err := <-errCh: |
|
| 2751 |
- if err != nil {
|
|
| 2752 |
- log.Debugf("Error hijack: %s", err)
|
|
| 2753 |
- return err |
|
| 2754 |
- } |
|
| 2755 |
- } |
|
| 2756 |
- |
|
| 2757 |
- if execConfig.Tty && cli.isTerminalIn {
|
|
| 2758 |
- if err := cli.monitorTtySize(execID, true); err != nil {
|
|
| 2759 |
- log.Errorf("Error monitoring TTY size: %s", err)
|
|
| 2760 |
- } |
|
| 2761 |
- } |
|
| 2762 |
- |
|
| 2763 |
- if err := <-errCh; err != nil {
|
|
| 2764 |
- log.Debugf("Error hijack: %s", err)
|
|
| 2765 |
- return err |
|
| 2766 |
- } |
|
| 2767 |
- |
|
| 2768 |
- var status int |
|
| 2769 |
- if _, status, err = getExecExitCode(cli, execID); err != nil {
|
|
| 2770 |
- return err |
|
| 2771 |
- } |
|
| 2772 |
- |
|
| 2773 |
- if status != 0 {
|
|
| 2774 |
- return &utils.StatusError{StatusCode: status}
|
|
| 2775 |
- } |
|
| 2776 |
- |
|
| 2777 |
- return nil |
|
| 2778 |
-} |
|
| 2779 |
- |
|
| 2780 |
-type containerStats struct {
|
|
| 2781 |
- Name string |
|
| 2782 |
- CpuPercentage float64 |
|
| 2783 |
- Memory float64 |
|
| 2784 |
- MemoryLimit float64 |
|
| 2785 |
- MemoryPercentage float64 |
|
| 2786 |
- NetworkRx float64 |
|
| 2787 |
- NetworkTx float64 |
|
| 2788 |
- mu sync.RWMutex |
|
| 2789 |
- err error |
|
| 2790 |
-} |
|
| 2791 |
- |
|
| 2792 |
-func (s *containerStats) Collect(cli *DockerCli) {
|
|
| 2793 |
- stream, _, err := cli.call("GET", "/containers/"+s.Name+"/stats", nil, false)
|
|
| 2794 |
- if err != nil {
|
|
| 2795 |
- s.err = err |
|
| 2796 |
- return |
|
| 2797 |
- } |
|
| 2798 |
- defer stream.Close() |
|
| 2799 |
- var ( |
|
| 2800 |
- previousCpu uint64 |
|
| 2801 |
- previousSystem uint64 |
|
| 2802 |
- start = true |
|
| 2803 |
- dec = json.NewDecoder(stream) |
|
| 2804 |
- u = make(chan error, 1) |
|
| 2805 |
- ) |
|
| 2806 |
- go func() {
|
|
| 2807 |
- for {
|
|
| 2808 |
- var v *types.Stats |
|
| 2809 |
- if err := dec.Decode(&v); err != nil {
|
|
| 2810 |
- u <- err |
|
| 2811 |
- return |
|
| 2812 |
- } |
|
| 2813 |
- var ( |
|
| 2814 |
- memPercent = float64(v.MemoryStats.Usage) / float64(v.MemoryStats.Limit) * 100.0 |
|
| 2815 |
- cpuPercent = 0.0 |
|
| 2816 |
- ) |
|
| 2817 |
- if !start {
|
|
| 2818 |
- cpuPercent = calculateCpuPercent(previousCpu, previousSystem, v) |
|
| 2819 |
- } |
|
| 2820 |
- start = false |
|
| 2821 |
- s.mu.Lock() |
|
| 2822 |
- s.CpuPercentage = cpuPercent |
|
| 2823 |
- s.Memory = float64(v.MemoryStats.Usage) |
|
| 2824 |
- s.MemoryLimit = float64(v.MemoryStats.Limit) |
|
| 2825 |
- s.MemoryPercentage = memPercent |
|
| 2826 |
- s.NetworkRx = float64(v.Network.RxBytes) |
|
| 2827 |
- s.NetworkTx = float64(v.Network.TxBytes) |
|
| 2828 |
- s.mu.Unlock() |
|
| 2829 |
- previousCpu = v.CpuStats.CpuUsage.TotalUsage |
|
| 2830 |
- previousSystem = v.CpuStats.SystemUsage |
|
| 2831 |
- u <- nil |
|
| 2832 |
- } |
|
| 2833 |
- }() |
|
| 2834 |
- for {
|
|
| 2835 |
- select {
|
|
| 2836 |
- case <-time.After(2 * time.Second): |
|
| 2837 |
- // zero out the values if we have not received an update within |
|
| 2838 |
- // the specified duration. |
|
| 2839 |
- s.mu.Lock() |
|
| 2840 |
- s.CpuPercentage = 0 |
|
| 2841 |
- s.Memory = 0 |
|
| 2842 |
- s.MemoryPercentage = 0 |
|
| 2843 |
- s.mu.Unlock() |
|
| 2844 |
- case err := <-u: |
|
| 2845 |
- if err != nil {
|
|
| 2846 |
- s.mu.Lock() |
|
| 2847 |
- s.err = err |
|
| 2848 |
- s.mu.Unlock() |
|
| 2849 |
- return |
|
| 2850 |
- } |
|
| 2851 |
- } |
|
| 2852 |
- } |
|
| 2853 |
-} |
|
| 2854 |
- |
|
| 2855 |
-func (s *containerStats) Display(w io.Writer) error {
|
|
| 2856 |
- s.mu.RLock() |
|
| 2857 |
- defer s.mu.RUnlock() |
|
| 2858 |
- if s.err != nil {
|
|
| 2859 |
- return s.err |
|
| 2860 |
- } |
|
| 2861 |
- fmt.Fprintf(w, "%s\t%.2f%%\t%s/%s\t%.2f%%\t%s/%s\n", |
|
| 2862 |
- s.Name, |
|
| 2863 |
- s.CpuPercentage, |
|
| 2864 |
- units.BytesSize(s.Memory), units.BytesSize(s.MemoryLimit), |
|
| 2865 |
- s.MemoryPercentage, |
|
| 2866 |
- units.BytesSize(s.NetworkRx), units.BytesSize(s.NetworkTx)) |
|
| 2867 |
- return nil |
|
| 2868 |
-} |
|
| 2869 |
- |
|
| 2870 |
-func (cli *DockerCli) CmdStats(args ...string) error {
|
|
| 2871 |
- cmd := cli.Subcmd("stats", "CONTAINER [CONTAINER...]", "Display a live stream of one or more containers' resource usage statistics", true)
|
|
| 2872 |
- cmd.Require(flag.Min, 1) |
|
| 2873 |
- utils.ParseFlags(cmd, args, true) |
|
| 2874 |
- |
|
| 2875 |
- names := cmd.Args() |
|
| 2876 |
- sort.Strings(names) |
|
| 2877 |
- var ( |
|
| 2878 |
- cStats []*containerStats |
|
| 2879 |
- w = tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0) |
|
| 2880 |
- ) |
|
| 2881 |
- printHeader := func() {
|
|
| 2882 |
- fmt.Fprint(cli.out, "\033[2J") |
|
| 2883 |
- fmt.Fprint(cli.out, "\033[H") |
|
| 2884 |
- fmt.Fprintln(w, "CONTAINER\tCPU %\tMEM USAGE/LIMIT\tMEM %\tNET I/O") |
|
| 2885 |
- } |
|
| 2886 |
- for _, n := range names {
|
|
| 2887 |
- s := &containerStats{Name: n}
|
|
| 2888 |
- cStats = append(cStats, s) |
|
| 2889 |
- go s.Collect(cli) |
|
| 2890 |
- } |
|
| 2891 |
- // do a quick pause so that any failed connections for containers that do not exist are able to be |
|
| 2892 |
- // evicted before we display the initial or default values. |
|
| 2893 |
- time.Sleep(500 * time.Millisecond) |
|
| 2894 |
- var errs []string |
|
| 2895 |
- for _, c := range cStats {
|
|
| 2896 |
- c.mu.Lock() |
|
| 2897 |
- if c.err != nil {
|
|
| 2898 |
- errs = append(errs, fmt.Sprintf("%s: %v", c.Name, c.err))
|
|
| 2899 |
- } |
|
| 2900 |
- c.mu.Unlock() |
|
| 2901 |
- } |
|
| 2902 |
- if len(errs) > 0 {
|
|
| 2903 |
- return fmt.Errorf("%s", strings.Join(errs, ", "))
|
|
| 2904 |
- } |
|
| 2905 |
- for _ = range time.Tick(500 * time.Millisecond) {
|
|
| 2906 |
- printHeader() |
|
| 2907 |
- toRemove := []int{}
|
|
| 2908 |
- for i, s := range cStats {
|
|
| 2909 |
- if err := s.Display(w); err != nil {
|
|
| 2910 |
- toRemove = append(toRemove, i) |
|
| 2911 |
- } |
|
| 2912 |
- } |
|
| 2913 |
- for j := len(toRemove) - 1; j >= 0; j-- {
|
|
| 2914 |
- i := toRemove[j] |
|
| 2915 |
- cStats = append(cStats[:i], cStats[i+1:]...) |
|
| 2916 |
- } |
|
| 2917 |
- if len(cStats) == 0 {
|
|
| 2918 |
- return nil |
|
| 2919 |
- } |
|
| 2920 |
- w.Flush() |
|
| 2921 |
- } |
|
| 2922 |
- return nil |
|
| 2923 |
-} |
|
| 2924 |
- |
|
| 2925 |
-func calculateCpuPercent(previousCpu, previousSystem uint64, v *types.Stats) float64 {
|
|
| 2926 |
- var ( |
|
| 2927 |
- cpuPercent = 0.0 |
|
| 2928 |
- // calculate the change for the cpu usage of the container in between readings |
|
| 2929 |
- cpuDelta = float64(v.CpuStats.CpuUsage.TotalUsage - previousCpu) |
|
| 2930 |
- // calculate the change for the entire system between readings |
|
| 2931 |
- systemDelta = float64(v.CpuStats.SystemUsage - previousSystem) |
|
| 2932 |
- ) |
|
| 2933 |
- |
|
| 2934 |
- if systemDelta > 0.0 && cpuDelta > 0.0 {
|
|
| 2935 |
- cpuPercent = (cpuDelta / systemDelta) * float64(len(v.CpuStats.CpuUsage.PercpuUsage)) * 100.0 |
|
| 2936 |
- } |
|
| 2937 |
- return cpuPercent |
|
| 2938 |
-} |
| 2939 | 1 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,76 @@ |
| 0 |
+package client |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "encoding/json" |
|
| 4 |
+ "fmt" |
|
| 5 |
+ "net/url" |
|
| 6 |
+ |
|
| 7 |
+ "github.com/docker/docker/engine" |
|
| 8 |
+ "github.com/docker/docker/opts" |
|
| 9 |
+ flag "github.com/docker/docker/pkg/mflag" |
|
| 10 |
+ "github.com/docker/docker/pkg/parsers" |
|
| 11 |
+ "github.com/docker/docker/registry" |
|
| 12 |
+ "github.com/docker/docker/runconfig" |
|
| 13 |
+ "github.com/docker/docker/utils" |
|
| 14 |
+) |
|
| 15 |
+ |
|
| 16 |
+func (cli *DockerCli) CmdCommit(args ...string) error {
|
|
| 17 |
+ cmd := cli.Subcmd("commit", "CONTAINER [REPOSITORY[:TAG]]", "Create a new image from a container's changes", true)
|
|
| 18 |
+ flPause := cmd.Bool([]string{"p", "-pause"}, true, "Pause container during commit")
|
|
| 19 |
+ flComment := cmd.String([]string{"m", "-message"}, "", "Commit message")
|
|
| 20 |
+ flAuthor := cmd.String([]string{"a", "#author", "-author"}, "", "Author (e.g., \"John Hannibal Smith <hannibal@a-team.com>\")")
|
|
| 21 |
+ flChanges := opts.NewListOpts(nil) |
|
| 22 |
+ cmd.Var(&flChanges, []string{"c", "-change"}, "Apply Dockerfile instruction to the created image")
|
|
| 23 |
+ // FIXME: --run is deprecated, it will be replaced with inline Dockerfile commands. |
|
| 24 |
+ flConfig := cmd.String([]string{"#run", "#-run"}, "", "This option is deprecated and will be removed in a future version in favor of inline Dockerfile-compatible commands")
|
|
| 25 |
+ cmd.Require(flag.Max, 2) |
|
| 26 |
+ cmd.Require(flag.Min, 1) |
|
| 27 |
+ utils.ParseFlags(cmd, args, true) |
|
| 28 |
+ |
|
| 29 |
+ var ( |
|
| 30 |
+ name = cmd.Arg(0) |
|
| 31 |
+ repository, tag = parsers.ParseRepositoryTag(cmd.Arg(1)) |
|
| 32 |
+ ) |
|
| 33 |
+ |
|
| 34 |
+ //Check if the given image name can be resolved |
|
| 35 |
+ if repository != "" {
|
|
| 36 |
+ if err := registry.ValidateRepositoryName(repository); err != nil {
|
|
| 37 |
+ return err |
|
| 38 |
+ } |
|
| 39 |
+ } |
|
| 40 |
+ |
|
| 41 |
+ v := url.Values{}
|
|
| 42 |
+ v.Set("container", name)
|
|
| 43 |
+ v.Set("repo", repository)
|
|
| 44 |
+ v.Set("tag", tag)
|
|
| 45 |
+ v.Set("comment", *flComment)
|
|
| 46 |
+ v.Set("author", *flAuthor)
|
|
| 47 |
+ for _, change := range flChanges.GetAll() {
|
|
| 48 |
+ v.Add("changes", change)
|
|
| 49 |
+ } |
|
| 50 |
+ |
|
| 51 |
+ if *flPause != true {
|
|
| 52 |
+ v.Set("pause", "0")
|
|
| 53 |
+ } |
|
| 54 |
+ |
|
| 55 |
+ var ( |
|
| 56 |
+ config *runconfig.Config |
|
| 57 |
+ env engine.Env |
|
| 58 |
+ ) |
|
| 59 |
+ if *flConfig != "" {
|
|
| 60 |
+ config = &runconfig.Config{}
|
|
| 61 |
+ if err := json.Unmarshal([]byte(*flConfig), config); err != nil {
|
|
| 62 |
+ return err |
|
| 63 |
+ } |
|
| 64 |
+ } |
|
| 65 |
+ stream, _, err := cli.call("POST", "/commit?"+v.Encode(), config, false)
|
|
| 66 |
+ if err != nil {
|
|
| 67 |
+ return err |
|
| 68 |
+ } |
|
| 69 |
+ if err := env.Decode(stream); err != nil {
|
|
| 70 |
+ return err |
|
| 71 |
+ } |
|
| 72 |
+ |
|
| 73 |
+ fmt.Fprintf(cli.out, "%s\n", env.Get("Id"))
|
|
| 74 |
+ return nil |
|
| 75 |
+} |
| 0 | 76 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,54 @@ |
| 0 |
+package client |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "fmt" |
|
| 4 |
+ "io" |
|
| 5 |
+ "strings" |
|
| 6 |
+ |
|
| 7 |
+ "github.com/docker/docker/engine" |
|
| 8 |
+ "github.com/docker/docker/pkg/archive" |
|
| 9 |
+ flag "github.com/docker/docker/pkg/mflag" |
|
| 10 |
+ "github.com/docker/docker/utils" |
|
| 11 |
+) |
|
| 12 |
+ |
|
| 13 |
+func (cli *DockerCli) CmdCp(args ...string) error {
|
|
| 14 |
+ cmd := cli.Subcmd("cp", "CONTAINER:PATH HOSTDIR|-", "Copy files/folders from a PATH on the container to a HOSTDIR on the host\nrunning the command. Use '-' to write the data\nas a tar file to STDOUT.", true)
|
|
| 15 |
+ cmd.Require(flag.Exact, 2) |
|
| 16 |
+ |
|
| 17 |
+ utils.ParseFlags(cmd, args, true) |
|
| 18 |
+ |
|
| 19 |
+ var copyData engine.Env |
|
| 20 |
+ info := strings.Split(cmd.Arg(0), ":") |
|
| 21 |
+ |
|
| 22 |
+ if len(info) != 2 {
|
|
| 23 |
+ return fmt.Errorf("Error: Path not specified")
|
|
| 24 |
+ } |
|
| 25 |
+ |
|
| 26 |
+ copyData.Set("Resource", info[1])
|
|
| 27 |
+ copyData.Set("HostPath", cmd.Arg(1))
|
|
| 28 |
+ |
|
| 29 |
+ stream, statusCode, err := cli.call("POST", "/containers/"+info[0]+"/copy", copyData, false)
|
|
| 30 |
+ if stream != nil {
|
|
| 31 |
+ defer stream.Close() |
|
| 32 |
+ } |
|
| 33 |
+ if statusCode == 404 {
|
|
| 34 |
+ return fmt.Errorf("No such container: %v", info[0])
|
|
| 35 |
+ } |
|
| 36 |
+ if err != nil {
|
|
| 37 |
+ return err |
|
| 38 |
+ } |
|
| 39 |
+ |
|
| 40 |
+ if statusCode == 200 {
|
|
| 41 |
+ dest := copyData.Get("HostPath")
|
|
| 42 |
+ |
|
| 43 |
+ if dest == "-" {
|
|
| 44 |
+ _, err = io.Copy(cli.out, stream) |
|
| 45 |
+ } else {
|
|
| 46 |
+ err = archive.Untar(stream, dest, &archive.TarOptions{NoLchown: true})
|
|
| 47 |
+ } |
|
| 48 |
+ if err != nil {
|
|
| 49 |
+ return err |
|
| 50 |
+ } |
|
| 51 |
+ } |
|
| 52 |
+ return nil |
|
| 53 |
+} |
| 0 | 54 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,153 @@ |
| 0 |
+package client |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "encoding/base64" |
|
| 4 |
+ "encoding/json" |
|
| 5 |
+ "fmt" |
|
| 6 |
+ "io" |
|
| 7 |
+ "net/url" |
|
| 8 |
+ "os" |
|
| 9 |
+ |
|
| 10 |
+ "github.com/docker/docker/api/types" |
|
| 11 |
+ "github.com/docker/docker/graph" |
|
| 12 |
+ "github.com/docker/docker/pkg/parsers" |
|
| 13 |
+ "github.com/docker/docker/registry" |
|
| 14 |
+ "github.com/docker/docker/runconfig" |
|
| 15 |
+ "github.com/docker/docker/utils" |
|
| 16 |
+) |
|
| 17 |
+ |
|
| 18 |
+func (cli *DockerCli) pullImage(image string) error {
|
|
| 19 |
+ return cli.pullImageCustomOut(image, cli.out) |
|
| 20 |
+} |
|
| 21 |
+ |
|
| 22 |
+func (cli *DockerCli) pullImageCustomOut(image string, out io.Writer) error {
|
|
| 23 |
+ v := url.Values{}
|
|
| 24 |
+ repos, tag := parsers.ParseRepositoryTag(image) |
|
| 25 |
+ // pull only the image tagged 'latest' if no tag was specified |
|
| 26 |
+ if tag == "" {
|
|
| 27 |
+ tag = graph.DEFAULTTAG |
|
| 28 |
+ } |
|
| 29 |
+ v.Set("fromImage", repos)
|
|
| 30 |
+ v.Set("tag", tag)
|
|
| 31 |
+ |
|
| 32 |
+ // Resolve the Repository name from fqn to RepositoryInfo |
|
| 33 |
+ repoInfo, err := registry.ParseRepositoryInfo(repos) |
|
| 34 |
+ if err != nil {
|
|
| 35 |
+ return err |
|
| 36 |
+ } |
|
| 37 |
+ |
|
| 38 |
+ // Load the auth config file, to be able to pull the image |
|
| 39 |
+ cli.LoadConfigFile() |
|
| 40 |
+ |
|
| 41 |
+ // Resolve the Auth config relevant for this server |
|
| 42 |
+ authConfig := cli.configFile.ResolveAuthConfig(repoInfo.Index) |
|
| 43 |
+ buf, err := json.Marshal(authConfig) |
|
| 44 |
+ if err != nil {
|
|
| 45 |
+ return err |
|
| 46 |
+ } |
|
| 47 |
+ |
|
| 48 |
+ registryAuthHeader := []string{
|
|
| 49 |
+ base64.URLEncoding.EncodeToString(buf), |
|
| 50 |
+ } |
|
| 51 |
+ if err = cli.stream("POST", "/images/create?"+v.Encode(), nil, out, map[string][]string{"X-Registry-Auth": registryAuthHeader}); err != nil {
|
|
| 52 |
+ return err |
|
| 53 |
+ } |
|
| 54 |
+ return nil |
|
| 55 |
+} |
|
| 56 |
+ |
|
| 57 |
+type cidFile struct {
|
|
| 58 |
+ path string |
|
| 59 |
+ file *os.File |
|
| 60 |
+ written bool |
|
| 61 |
+} |
|
| 62 |
+ |
|
| 63 |
+func newCIDFile(path string) (*cidFile, error) {
|
|
| 64 |
+ if _, err := os.Stat(path); err == nil {
|
|
| 65 |
+ return nil, fmt.Errorf("Container ID file found, make sure the other container isn't running or delete %s", path)
|
|
| 66 |
+ } |
|
| 67 |
+ |
|
| 68 |
+ f, err := os.Create(path) |
|
| 69 |
+ if err != nil {
|
|
| 70 |
+ return nil, fmt.Errorf("Failed to create the container ID file: %s", err)
|
|
| 71 |
+ } |
|
| 72 |
+ |
|
| 73 |
+ return &cidFile{path: path, file: f}, nil
|
|
| 74 |
+} |
|
| 75 |
+ |
|
| 76 |
+func (cli *DockerCli) createContainer(config *runconfig.Config, hostConfig *runconfig.HostConfig, cidfile, name string) (*types.ContainerCreateResponse, error) {
|
|
| 77 |
+ containerValues := url.Values{}
|
|
| 78 |
+ if name != "" {
|
|
| 79 |
+ containerValues.Set("name", name)
|
|
| 80 |
+ } |
|
| 81 |
+ |
|
| 82 |
+ mergedConfig := runconfig.MergeConfigs(config, hostConfig) |
|
| 83 |
+ |
|
| 84 |
+ var containerIDFile *cidFile |
|
| 85 |
+ if cidfile != "" {
|
|
| 86 |
+ var err error |
|
| 87 |
+ if containerIDFile, err = newCIDFile(cidfile); err != nil {
|
|
| 88 |
+ return nil, err |
|
| 89 |
+ } |
|
| 90 |
+ defer containerIDFile.Close() |
|
| 91 |
+ } |
|
| 92 |
+ |
|
| 93 |
+ //create the container |
|
| 94 |
+ stream, statusCode, err := cli.call("POST", "/containers/create?"+containerValues.Encode(), mergedConfig, false)
|
|
| 95 |
+ //if image not found try to pull it |
|
| 96 |
+ if statusCode == 404 {
|
|
| 97 |
+ repo, tag := parsers.ParseRepositoryTag(config.Image) |
|
| 98 |
+ if tag == "" {
|
|
| 99 |
+ tag = graph.DEFAULTTAG |
|
| 100 |
+ } |
|
| 101 |
+ fmt.Fprintf(cli.err, "Unable to find image '%s' locally\n", utils.ImageReference(repo, tag)) |
|
| 102 |
+ |
|
| 103 |
+ // we don't want to write to stdout anything apart from container.ID |
|
| 104 |
+ if err = cli.pullImageCustomOut(config.Image, cli.err); err != nil {
|
|
| 105 |
+ return nil, err |
|
| 106 |
+ } |
|
| 107 |
+ // Retry |
|
| 108 |
+ if stream, _, err = cli.call("POST", "/containers/create?"+containerValues.Encode(), mergedConfig, false); err != nil {
|
|
| 109 |
+ return nil, err |
|
| 110 |
+ } |
|
| 111 |
+ } else if err != nil {
|
|
| 112 |
+ return nil, err |
|
| 113 |
+ } |
|
| 114 |
+ |
|
| 115 |
+ var response types.ContainerCreateResponse |
|
| 116 |
+ if err := json.NewDecoder(stream).Decode(&response); err != nil {
|
|
| 117 |
+ return nil, err |
|
| 118 |
+ } |
|
| 119 |
+ for _, warning := range response.Warnings {
|
|
| 120 |
+ fmt.Fprintf(cli.err, "WARNING: %s\n", warning) |
|
| 121 |
+ } |
|
| 122 |
+ if containerIDFile != nil {
|
|
| 123 |
+ if err = containerIDFile.Write(response.ID); err != nil {
|
|
| 124 |
+ return nil, err |
|
| 125 |
+ } |
|
| 126 |
+ } |
|
| 127 |
+ return &response, nil |
|
| 128 |
+} |
|
| 129 |
+ |
|
| 130 |
+func (cli *DockerCli) CmdCreate(args ...string) error {
|
|
| 131 |
+ cmd := cli.Subcmd("create", "IMAGE [COMMAND] [ARG...]", "Create a new container", true)
|
|
| 132 |
+ |
|
| 133 |
+ // These are flags not stored in Config/HostConfig |
|
| 134 |
+ var ( |
|
| 135 |
+ flName = cmd.String([]string{"-name"}, "", "Assign a name to the container")
|
|
| 136 |
+ ) |
|
| 137 |
+ |
|
| 138 |
+ config, hostConfig, cmd, err := runconfig.Parse(cmd, args) |
|
| 139 |
+ if err != nil {
|
|
| 140 |
+ utils.ReportError(cmd, err.Error(), true) |
|
| 141 |
+ } |
|
| 142 |
+ if config.Image == "" {
|
|
| 143 |
+ cmd.Usage() |
|
| 144 |
+ return nil |
|
| 145 |
+ } |
|
| 146 |
+ response, err := cli.createContainer(config, hostConfig, hostConfig.ContainerIDFile, *flName) |
|
| 147 |
+ if err != nil {
|
|
| 148 |
+ return err |
|
| 149 |
+ } |
|
| 150 |
+ fmt.Fprintf(cli.out, "%s\n", response.ID) |
|
| 151 |
+ return nil |
|
| 152 |
+} |
| 0 | 153 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,41 @@ |
| 0 |
+package client |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "fmt" |
|
| 4 |
+ |
|
| 5 |
+ "github.com/docker/docker/engine" |
|
| 6 |
+ "github.com/docker/docker/pkg/archive" |
|
| 7 |
+ flag "github.com/docker/docker/pkg/mflag" |
|
| 8 |
+ "github.com/docker/docker/utils" |
|
| 9 |
+) |
|
| 10 |
+ |
|
| 11 |
+func (cli *DockerCli) CmdDiff(args ...string) error {
|
|
| 12 |
+ cmd := cli.Subcmd("diff", "CONTAINER", "Inspect changes on a container's filesystem", true)
|
|
| 13 |
+ cmd.Require(flag.Exact, 1) |
|
| 14 |
+ |
|
| 15 |
+ utils.ParseFlags(cmd, args, true) |
|
| 16 |
+ |
|
| 17 |
+ body, _, err := readBody(cli.call("GET", "/containers/"+cmd.Arg(0)+"/changes", nil, false))
|
|
| 18 |
+ |
|
| 19 |
+ if err != nil {
|
|
| 20 |
+ return err |
|
| 21 |
+ } |
|
| 22 |
+ |
|
| 23 |
+ outs := engine.NewTable("", 0)
|
|
| 24 |
+ if _, err := outs.ReadListFrom(body); err != nil {
|
|
| 25 |
+ return err |
|
| 26 |
+ } |
|
| 27 |
+ for _, change := range outs.Data {
|
|
| 28 |
+ var kind string |
|
| 29 |
+ switch change.GetInt("Kind") {
|
|
| 30 |
+ case archive.ChangeModify: |
|
| 31 |
+ kind = "C" |
|
| 32 |
+ case archive.ChangeAdd: |
|
| 33 |
+ kind = "A" |
|
| 34 |
+ case archive.ChangeDelete: |
|
| 35 |
+ kind = "D" |
|
| 36 |
+ } |
|
| 37 |
+ fmt.Fprintf(cli.out, "%s %s\n", kind, change.Get("Path"))
|
|
| 38 |
+ } |
|
| 39 |
+ return nil |
|
| 40 |
+} |
| 0 | 41 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,68 @@ |
| 0 |
+package client |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "net/url" |
|
| 4 |
+ "strconv" |
|
| 5 |
+ "time" |
|
| 6 |
+ |
|
| 7 |
+ "github.com/docker/docker/opts" |
|
| 8 |
+ flag "github.com/docker/docker/pkg/mflag" |
|
| 9 |
+ "github.com/docker/docker/pkg/parsers/filters" |
|
| 10 |
+ "github.com/docker/docker/pkg/timeutils" |
|
| 11 |
+ "github.com/docker/docker/utils" |
|
| 12 |
+) |
|
| 13 |
+ |
|
| 14 |
+func (cli *DockerCli) CmdEvents(args ...string) error {
|
|
| 15 |
+ cmd := cli.Subcmd("events", "", "Get real time events from the server", true)
|
|
| 16 |
+ since := cmd.String([]string{"#since", "-since"}, "", "Show all events created since timestamp")
|
|
| 17 |
+ until := cmd.String([]string{"-until"}, "", "Stream events until this timestamp")
|
|
| 18 |
+ flFilter := opts.NewListOpts(nil) |
|
| 19 |
+ cmd.Var(&flFilter, []string{"f", "-filter"}, "Filter output based on conditions provided")
|
|
| 20 |
+ cmd.Require(flag.Exact, 0) |
|
| 21 |
+ |
|
| 22 |
+ utils.ParseFlags(cmd, args, true) |
|
| 23 |
+ |
|
| 24 |
+ var ( |
|
| 25 |
+ v = url.Values{}
|
|
| 26 |
+ loc = time.FixedZone(time.Now().Zone()) |
|
| 27 |
+ eventFilterArgs = filters.Args{}
|
|
| 28 |
+ ) |
|
| 29 |
+ |
|
| 30 |
+ // Consolidate all filter flags, and sanity check them early. |
|
| 31 |
+ // They'll get process in the daemon/server. |
|
| 32 |
+ for _, f := range flFilter.GetAll() {
|
|
| 33 |
+ var err error |
|
| 34 |
+ eventFilterArgs, err = filters.ParseFlag(f, eventFilterArgs) |
|
| 35 |
+ if err != nil {
|
|
| 36 |
+ return err |
|
| 37 |
+ } |
|
| 38 |
+ } |
|
| 39 |
+ var setTime = func(key, value string) {
|
|
| 40 |
+ format := timeutils.RFC3339NanoFixed |
|
| 41 |
+ if len(value) < len(format) {
|
|
| 42 |
+ format = format[:len(value)] |
|
| 43 |
+ } |
|
| 44 |
+ if t, err := time.ParseInLocation(format, value, loc); err == nil {
|
|
| 45 |
+ v.Set(key, strconv.FormatInt(t.Unix(), 10)) |
|
| 46 |
+ } else {
|
|
| 47 |
+ v.Set(key, value) |
|
| 48 |
+ } |
|
| 49 |
+ } |
|
| 50 |
+ if *since != "" {
|
|
| 51 |
+ setTime("since", *since)
|
|
| 52 |
+ } |
|
| 53 |
+ if *until != "" {
|
|
| 54 |
+ setTime("until", *until)
|
|
| 55 |
+ } |
|
| 56 |
+ if len(eventFilterArgs) > 0 {
|
|
| 57 |
+ filterJson, err := filters.ToParam(eventFilterArgs) |
|
| 58 |
+ if err != nil {
|
|
| 59 |
+ return err |
|
| 60 |
+ } |
|
| 61 |
+ v.Set("filters", filterJson)
|
|
| 62 |
+ } |
|
| 63 |
+ if err := cli.stream("GET", "/events?"+v.Encode(), nil, cli.out, nil); err != nil {
|
|
| 64 |
+ return err |
|
| 65 |
+ } |
|
| 66 |
+ return nil |
|
| 67 |
+} |
| 0 | 68 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,126 @@ |
| 0 |
+package client |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "encoding/json" |
|
| 4 |
+ "fmt" |
|
| 5 |
+ "io" |
|
| 6 |
+ |
|
| 7 |
+ log "github.com/Sirupsen/logrus" |
|
| 8 |
+ "github.com/docker/docker/api/types" |
|
| 9 |
+ "github.com/docker/docker/pkg/promise" |
|
| 10 |
+ "github.com/docker/docker/runconfig" |
|
| 11 |
+ "github.com/docker/docker/utils" |
|
| 12 |
+) |
|
| 13 |
+ |
|
| 14 |
+func (cli *DockerCli) CmdExec(args ...string) error {
|
|
| 15 |
+ cmd := cli.Subcmd("exec", "CONTAINER COMMAND [ARG...]", "Run a command in a running container", true)
|
|
| 16 |
+ |
|
| 17 |
+ execConfig, err := runconfig.ParseExec(cmd, args) |
|
| 18 |
+ // just in case the ParseExec does not exit |
|
| 19 |
+ if execConfig.Container == "" || err != nil {
|
|
| 20 |
+ return &utils.StatusError{StatusCode: 1}
|
|
| 21 |
+ } |
|
| 22 |
+ |
|
| 23 |
+ stream, _, err := cli.call("POST", "/containers/"+execConfig.Container+"/exec", execConfig, false)
|
|
| 24 |
+ if err != nil {
|
|
| 25 |
+ return err |
|
| 26 |
+ } |
|
| 27 |
+ |
|
| 28 |
+ var response types.ContainerExecCreateResponse |
|
| 29 |
+ if err := json.NewDecoder(stream).Decode(&response); err != nil {
|
|
| 30 |
+ return err |
|
| 31 |
+ } |
|
| 32 |
+ for _, warning := range response.Warnings {
|
|
| 33 |
+ fmt.Fprintf(cli.err, "WARNING: %s\n", warning) |
|
| 34 |
+ } |
|
| 35 |
+ |
|
| 36 |
+ execID := response.ID |
|
| 37 |
+ |
|
| 38 |
+ if execID == "" {
|
|
| 39 |
+ fmt.Fprintf(cli.out, "exec ID empty") |
|
| 40 |
+ return nil |
|
| 41 |
+ } |
|
| 42 |
+ |
|
| 43 |
+ if !execConfig.Detach {
|
|
| 44 |
+ if err := cli.CheckTtyInput(execConfig.AttachStdin, execConfig.Tty); err != nil {
|
|
| 45 |
+ return err |
|
| 46 |
+ } |
|
| 47 |
+ } else {
|
|
| 48 |
+ if _, _, err := readBody(cli.call("POST", "/exec/"+execID+"/start", execConfig, false)); err != nil {
|
|
| 49 |
+ return err |
|
| 50 |
+ } |
|
| 51 |
+ // For now don't print this - wait for when we support exec wait() |
|
| 52 |
+ // fmt.Fprintf(cli.out, "%s\n", execID) |
|
| 53 |
+ return nil |
|
| 54 |
+ } |
|
| 55 |
+ |
|
| 56 |
+ // Interactive exec requested. |
|
| 57 |
+ var ( |
|
| 58 |
+ out, stderr io.Writer |
|
| 59 |
+ in io.ReadCloser |
|
| 60 |
+ hijacked = make(chan io.Closer) |
|
| 61 |
+ errCh chan error |
|
| 62 |
+ ) |
|
| 63 |
+ |
|
| 64 |
+ // Block the return until the chan gets closed |
|
| 65 |
+ defer func() {
|
|
| 66 |
+ log.Debugf("End of CmdExec(), Waiting for hijack to finish.")
|
|
| 67 |
+ if _, ok := <-hijacked; ok {
|
|
| 68 |
+ log.Errorf("Hijack did not finish (chan still open)")
|
|
| 69 |
+ } |
|
| 70 |
+ }() |
|
| 71 |
+ |
|
| 72 |
+ if execConfig.AttachStdin {
|
|
| 73 |
+ in = cli.in |
|
| 74 |
+ } |
|
| 75 |
+ if execConfig.AttachStdout {
|
|
| 76 |
+ out = cli.out |
|
| 77 |
+ } |
|
| 78 |
+ if execConfig.AttachStderr {
|
|
| 79 |
+ if execConfig.Tty {
|
|
| 80 |
+ stderr = cli.out |
|
| 81 |
+ } else {
|
|
| 82 |
+ stderr = cli.err |
|
| 83 |
+ } |
|
| 84 |
+ } |
|
| 85 |
+ errCh = promise.Go(func() error {
|
|
| 86 |
+ return cli.hijack("POST", "/exec/"+execID+"/start", execConfig.Tty, in, out, stderr, hijacked, execConfig)
|
|
| 87 |
+ }) |
|
| 88 |
+ |
|
| 89 |
+ // Acknowledge the hijack before starting |
|
| 90 |
+ select {
|
|
| 91 |
+ case closer := <-hijacked: |
|
| 92 |
+ // Make sure that hijack gets closed when returning. (result |
|
| 93 |
+ // in closing hijack chan and freeing server's goroutines. |
|
| 94 |
+ if closer != nil {
|
|
| 95 |
+ defer closer.Close() |
|
| 96 |
+ } |
|
| 97 |
+ case err := <-errCh: |
|
| 98 |
+ if err != nil {
|
|
| 99 |
+ log.Debugf("Error hijack: %s", err)
|
|
| 100 |
+ return err |
|
| 101 |
+ } |
|
| 102 |
+ } |
|
| 103 |
+ |
|
| 104 |
+ if execConfig.Tty && cli.isTerminalIn {
|
|
| 105 |
+ if err := cli.monitorTtySize(execID, true); err != nil {
|
|
| 106 |
+ log.Errorf("Error monitoring TTY size: %s", err)
|
|
| 107 |
+ } |
|
| 108 |
+ } |
|
| 109 |
+ |
|
| 110 |
+ if err := <-errCh; err != nil {
|
|
| 111 |
+ log.Debugf("Error hijack: %s", err)
|
|
| 112 |
+ return err |
|
| 113 |
+ } |
|
| 114 |
+ |
|
| 115 |
+ var status int |
|
| 116 |
+ if _, status, err = getExecExitCode(cli, execID); err != nil {
|
|
| 117 |
+ return err |
|
| 118 |
+ } |
|
| 119 |
+ |
|
| 120 |
+ if status != 0 {
|
|
| 121 |
+ return &utils.StatusError{StatusCode: status}
|
|
| 122 |
+ } |
|
| 123 |
+ |
|
| 124 |
+ return nil |
|
| 125 |
+} |
| 0 | 126 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,49 @@ |
| 0 |
+package client |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "errors" |
|
| 4 |
+ "io" |
|
| 5 |
+ "net/url" |
|
| 6 |
+ "os" |
|
| 7 |
+ |
|
| 8 |
+ flag "github.com/docker/docker/pkg/mflag" |
|
| 9 |
+ "github.com/docker/docker/utils" |
|
| 10 |
+) |
|
| 11 |
+ |
|
| 12 |
+func (cli *DockerCli) CmdExport(args ...string) error {
|
|
| 13 |
+ cmd := cli.Subcmd("export", "CONTAINER", "Export a filesystem as a tar archive (streamed to STDOUT by default)", true)
|
|
| 14 |
+ outfile := cmd.String([]string{"o", "-output"}, "", "Write to a file, instead of STDOUT")
|
|
| 15 |
+ cmd.Require(flag.Exact, 1) |
|
| 16 |
+ |
|
| 17 |
+ utils.ParseFlags(cmd, args, true) |
|
| 18 |
+ |
|
| 19 |
+ var ( |
|
| 20 |
+ output io.Writer = cli.out |
|
| 21 |
+ err error |
|
| 22 |
+ ) |
|
| 23 |
+ if *outfile != "" {
|
|
| 24 |
+ output, err = os.Create(*outfile) |
|
| 25 |
+ if err != nil {
|
|
| 26 |
+ return err |
|
| 27 |
+ } |
|
| 28 |
+ } else if cli.isTerminalOut {
|
|
| 29 |
+ return errors.New("Cowardly refusing to save to a terminal. Use the -o flag or redirect.")
|
|
| 30 |
+ } |
|
| 31 |
+ |
|
| 32 |
+ if len(cmd.Args()) == 1 {
|
|
| 33 |
+ image := cmd.Arg(0) |
|
| 34 |
+ if err := cli.stream("GET", "/containers/"+image+"/export", nil, output, nil); err != nil {
|
|
| 35 |
+ return err |
|
| 36 |
+ } |
|
| 37 |
+ } else {
|
|
| 38 |
+ v := url.Values{}
|
|
| 39 |
+ for _, arg := range cmd.Args() {
|
|
| 40 |
+ v.Add("names", arg)
|
|
| 41 |
+ } |
|
| 42 |
+ if err := cli.stream("GET", "/containers/get?"+v.Encode(), nil, output, nil); err != nil {
|
|
| 43 |
+ return err |
|
| 44 |
+ } |
|
| 45 |
+ } |
|
| 46 |
+ |
|
| 47 |
+ return nil |
|
| 48 |
+} |
| 0 | 49 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,32 @@ |
| 0 |
+package client |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "fmt" |
|
| 4 |
+ "os" |
|
| 5 |
+ |
|
| 6 |
+ flag "github.com/docker/docker/pkg/mflag" |
|
| 7 |
+) |
|
| 8 |
+ |
|
| 9 |
+func (cli *DockerCli) CmdHelp(args ...string) error {
|
|
| 10 |
+ if len(args) > 1 {
|
|
| 11 |
+ method, exists := cli.getMethod(args[:2]...) |
|
| 12 |
+ if exists {
|
|
| 13 |
+ method("--help")
|
|
| 14 |
+ return nil |
|
| 15 |
+ } |
|
| 16 |
+ } |
|
| 17 |
+ if len(args) > 0 {
|
|
| 18 |
+ method, exists := cli.getMethod(args[0]) |
|
| 19 |
+ if !exists {
|
|
| 20 |
+ fmt.Fprintf(cli.err, "docker: '%s' is not a docker command. See 'docker --help'.\n", args[0]) |
|
| 21 |
+ os.Exit(1) |
|
| 22 |
+ } else {
|
|
| 23 |
+ method("--help")
|
|
| 24 |
+ return nil |
|
| 25 |
+ } |
|
| 26 |
+ } |
|
| 27 |
+ |
|
| 28 |
+ flag.Usage() |
|
| 29 |
+ |
|
| 30 |
+ return nil |
|
| 31 |
+} |
| 0 | 32 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,65 @@ |
| 0 |
+package client |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "fmt" |
|
| 4 |
+ "text/tabwriter" |
|
| 5 |
+ "time" |
|
| 6 |
+ |
|
| 7 |
+ "github.com/docker/docker/engine" |
|
| 8 |
+ flag "github.com/docker/docker/pkg/mflag" |
|
| 9 |
+ "github.com/docker/docker/pkg/stringid" |
|
| 10 |
+ "github.com/docker/docker/pkg/units" |
|
| 11 |
+ "github.com/docker/docker/utils" |
|
| 12 |
+) |
|
| 13 |
+ |
|
| 14 |
+func (cli *DockerCli) CmdHistory(args ...string) error {
|
|
| 15 |
+ cmd := cli.Subcmd("history", "IMAGE", "Show the history of an image", true)
|
|
| 16 |
+ quiet := cmd.Bool([]string{"q", "-quiet"}, false, "Only show numeric IDs")
|
|
| 17 |
+ noTrunc := cmd.Bool([]string{"#notrunc", "-no-trunc"}, false, "Don't truncate output")
|
|
| 18 |
+ cmd.Require(flag.Exact, 1) |
|
| 19 |
+ |
|
| 20 |
+ utils.ParseFlags(cmd, args, true) |
|
| 21 |
+ |
|
| 22 |
+ body, _, err := readBody(cli.call("GET", "/images/"+cmd.Arg(0)+"/history", nil, false))
|
|
| 23 |
+ if err != nil {
|
|
| 24 |
+ return err |
|
| 25 |
+ } |
|
| 26 |
+ |
|
| 27 |
+ outs := engine.NewTable("Created", 0)
|
|
| 28 |
+ if _, err := outs.ReadListFrom(body); err != nil {
|
|
| 29 |
+ return err |
|
| 30 |
+ } |
|
| 31 |
+ |
|
| 32 |
+ w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0) |
|
| 33 |
+ if !*quiet {
|
|
| 34 |
+ fmt.Fprintln(w, "IMAGE\tCREATED\tCREATED BY\tSIZE") |
|
| 35 |
+ } |
|
| 36 |
+ |
|
| 37 |
+ for _, out := range outs.Data {
|
|
| 38 |
+ outID := out.Get("Id")
|
|
| 39 |
+ if !*quiet {
|
|
| 40 |
+ if *noTrunc {
|
|
| 41 |
+ fmt.Fprintf(w, "%s\t", outID) |
|
| 42 |
+ } else {
|
|
| 43 |
+ fmt.Fprintf(w, "%s\t", stringid.TruncateID(outID)) |
|
| 44 |
+ } |
|
| 45 |
+ |
|
| 46 |
+ fmt.Fprintf(w, "%s ago\t", units.HumanDuration(time.Now().UTC().Sub(time.Unix(out.GetInt64("Created"), 0))))
|
|
| 47 |
+ |
|
| 48 |
+ if *noTrunc {
|
|
| 49 |
+ fmt.Fprintf(w, "%s\t", out.Get("CreatedBy"))
|
|
| 50 |
+ } else {
|
|
| 51 |
+ fmt.Fprintf(w, "%s\t", utils.Trunc(out.Get("CreatedBy"), 45))
|
|
| 52 |
+ } |
|
| 53 |
+ fmt.Fprintf(w, "%s\n", units.HumanSize(float64(out.GetInt64("Size"))))
|
|
| 54 |
+ } else {
|
|
| 55 |
+ if *noTrunc {
|
|
| 56 |
+ fmt.Fprintln(w, outID) |
|
| 57 |
+ } else {
|
|
| 58 |
+ fmt.Fprintln(w, stringid.TruncateID(outID)) |
|
| 59 |
+ } |
|
| 60 |
+ } |
|
| 61 |
+ } |
|
| 62 |
+ w.Flush() |
|
| 63 |
+ return nil |
|
| 64 |
+} |
| 0 | 65 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,271 @@ |
| 0 |
+package client |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "fmt" |
|
| 4 |
+ "net/url" |
|
| 5 |
+ "strings" |
|
| 6 |
+ "text/tabwriter" |
|
| 7 |
+ "time" |
|
| 8 |
+ |
|
| 9 |
+ "github.com/docker/docker/engine" |
|
| 10 |
+ "github.com/docker/docker/opts" |
|
| 11 |
+ flag "github.com/docker/docker/pkg/mflag" |
|
| 12 |
+ "github.com/docker/docker/pkg/parsers" |
|
| 13 |
+ "github.com/docker/docker/pkg/parsers/filters" |
|
| 14 |
+ "github.com/docker/docker/pkg/stringid" |
|
| 15 |
+ "github.com/docker/docker/pkg/units" |
|
| 16 |
+ "github.com/docker/docker/utils" |
|
| 17 |
+) |
|
| 18 |
+ |
|
| 19 |
+// FIXME: --viz and --tree are deprecated. Remove them in a future version. |
|
| 20 |
+func (cli *DockerCli) WalkTree(noTrunc bool, images *engine.Table, byParent map[string]*engine.Table, prefix string, printNode func(cli *DockerCli, noTrunc bool, image *engine.Env, prefix string)) {
|
|
| 21 |
+ length := images.Len() |
|
| 22 |
+ if length > 1 {
|
|
| 23 |
+ for index, image := range images.Data {
|
|
| 24 |
+ if index+1 == length {
|
|
| 25 |
+ printNode(cli, noTrunc, image, prefix+"└─") |
|
| 26 |
+ if subimages, exists := byParent[image.Get("Id")]; exists {
|
|
| 27 |
+ cli.WalkTree(noTrunc, subimages, byParent, prefix+" ", printNode) |
|
| 28 |
+ } |
|
| 29 |
+ } else {
|
|
| 30 |
+ printNode(cli, noTrunc, image, prefix+"\u251C─") |
|
| 31 |
+ if subimages, exists := byParent[image.Get("Id")]; exists {
|
|
| 32 |
+ cli.WalkTree(noTrunc, subimages, byParent, prefix+"\u2502 ", printNode) |
|
| 33 |
+ } |
|
| 34 |
+ } |
|
| 35 |
+ } |
|
| 36 |
+ } else {
|
|
| 37 |
+ for _, image := range images.Data {
|
|
| 38 |
+ printNode(cli, noTrunc, image, prefix+"└─") |
|
| 39 |
+ if subimages, exists := byParent[image.Get("Id")]; exists {
|
|
| 40 |
+ cli.WalkTree(noTrunc, subimages, byParent, prefix+" ", printNode) |
|
| 41 |
+ } |
|
| 42 |
+ } |
|
| 43 |
+ } |
|
| 44 |
+} |
|
| 45 |
+ |
|
| 46 |
+// FIXME: --viz and --tree are deprecated. Remove them in a future version. |
|
| 47 |
+func (cli *DockerCli) printVizNode(noTrunc bool, image *engine.Env, prefix string) {
|
|
| 48 |
+ var ( |
|
| 49 |
+ imageID string |
|
| 50 |
+ parentID string |
|
| 51 |
+ ) |
|
| 52 |
+ if noTrunc {
|
|
| 53 |
+ imageID = image.Get("Id")
|
|
| 54 |
+ parentID = image.Get("ParentId")
|
|
| 55 |
+ } else {
|
|
| 56 |
+ imageID = stringid.TruncateID(image.Get("Id"))
|
|
| 57 |
+ parentID = stringid.TruncateID(image.Get("ParentId"))
|
|
| 58 |
+ } |
|
| 59 |
+ if parentID == "" {
|
|
| 60 |
+ fmt.Fprintf(cli.out, " base -> \"%s\" [style=invis]\n", imageID) |
|
| 61 |
+ } else {
|
|
| 62 |
+ fmt.Fprintf(cli.out, " \"%s\" -> \"%s\"\n", parentID, imageID) |
|
| 63 |
+ } |
|
| 64 |
+ if image.GetList("RepoTags")[0] != "<none>:<none>" {
|
|
| 65 |
+ fmt.Fprintf(cli.out, " \"%s\" [label=\"%s\\n%s\",shape=box,fillcolor=\"paleturquoise\",style=\"filled,rounded\"];\n", |
|
| 66 |
+ imageID, imageID, strings.Join(image.GetList("RepoTags"), "\\n"))
|
|
| 67 |
+ } |
|
| 68 |
+} |
|
| 69 |
+ |
|
| 70 |
+// FIXME: --viz and --tree are deprecated. Remove them in a future version. |
|
| 71 |
+func (cli *DockerCli) printTreeNode(noTrunc bool, image *engine.Env, prefix string) {
|
|
| 72 |
+ var imageID string |
|
| 73 |
+ if noTrunc {
|
|
| 74 |
+ imageID = image.Get("Id")
|
|
| 75 |
+ } else {
|
|
| 76 |
+ imageID = stringid.TruncateID(image.Get("Id"))
|
|
| 77 |
+ } |
|
| 78 |
+ |
|
| 79 |
+ fmt.Fprintf(cli.out, "%s%s Virtual Size: %s", prefix, imageID, units.HumanSize(float64(image.GetInt64("VirtualSize"))))
|
|
| 80 |
+ if image.GetList("RepoTags")[0] != "<none>:<none>" {
|
|
| 81 |
+ fmt.Fprintf(cli.out, " Tags: %s\n", strings.Join(image.GetList("RepoTags"), ", "))
|
|
| 82 |
+ } else {
|
|
| 83 |
+ fmt.Fprint(cli.out, "\n") |
|
| 84 |
+ } |
|
| 85 |
+} |
|
| 86 |
+ |
|
| 87 |
+func (cli *DockerCli) CmdImages(args ...string) error {
|
|
| 88 |
+ cmd := cli.Subcmd("images", "[REPOSITORY]", "List images", true)
|
|
| 89 |
+ quiet := cmd.Bool([]string{"q", "-quiet"}, false, "Only show numeric IDs")
|
|
| 90 |
+ all := cmd.Bool([]string{"a", "-all"}, false, "Show all images (default hides intermediate images)")
|
|
| 91 |
+ noTrunc := cmd.Bool([]string{"#notrunc", "-no-trunc"}, false, "Don't truncate output")
|
|
| 92 |
+ showDigests := cmd.Bool([]string{"-digests"}, false, "Show digests")
|
|
| 93 |
+ // FIXME: --viz and --tree are deprecated. Remove them in a future version. |
|
| 94 |
+ flViz := cmd.Bool([]string{"#v", "#viz", "#-viz"}, false, "Output graph in graphviz format")
|
|
| 95 |
+ flTree := cmd.Bool([]string{"#t", "#tree", "#-tree"}, false, "Output graph in tree format")
|
|
| 96 |
+ |
|
| 97 |
+ flFilter := opts.NewListOpts(nil) |
|
| 98 |
+ cmd.Var(&flFilter, []string{"f", "-filter"}, "Filter output based on conditions provided")
|
|
| 99 |
+ cmd.Require(flag.Max, 1) |
|
| 100 |
+ |
|
| 101 |
+ utils.ParseFlags(cmd, args, true) |
|
| 102 |
+ |
|
| 103 |
+ // Consolidate all filter flags, and sanity check them early. |
|
| 104 |
+ // They'll get process in the daemon/server. |
|
| 105 |
+ imageFilterArgs := filters.Args{}
|
|
| 106 |
+ for _, f := range flFilter.GetAll() {
|
|
| 107 |
+ var err error |
|
| 108 |
+ imageFilterArgs, err = filters.ParseFlag(f, imageFilterArgs) |
|
| 109 |
+ if err != nil {
|
|
| 110 |
+ return err |
|
| 111 |
+ } |
|
| 112 |
+ } |
|
| 113 |
+ |
|
| 114 |
+ matchName := cmd.Arg(0) |
|
| 115 |
+ // FIXME: --viz and --tree are deprecated. Remove them in a future version. |
|
| 116 |
+ if *flViz || *flTree {
|
|
| 117 |
+ v := url.Values{
|
|
| 118 |
+ "all": []string{"1"},
|
|
| 119 |
+ } |
|
| 120 |
+ if len(imageFilterArgs) > 0 {
|
|
| 121 |
+ filterJson, err := filters.ToParam(imageFilterArgs) |
|
| 122 |
+ if err != nil {
|
|
| 123 |
+ return err |
|
| 124 |
+ } |
|
| 125 |
+ v.Set("filters", filterJson)
|
|
| 126 |
+ } |
|
| 127 |
+ |
|
| 128 |
+ body, _, err := readBody(cli.call("GET", "/images/json?"+v.Encode(), nil, false))
|
|
| 129 |
+ if err != nil {
|
|
| 130 |
+ return err |
|
| 131 |
+ } |
|
| 132 |
+ |
|
| 133 |
+ outs := engine.NewTable("Created", 0)
|
|
| 134 |
+ if _, err := outs.ReadListFrom(body); err != nil {
|
|
| 135 |
+ return err |
|
| 136 |
+ } |
|
| 137 |
+ |
|
| 138 |
+ var ( |
|
| 139 |
+ printNode func(cli *DockerCli, noTrunc bool, image *engine.Env, prefix string) |
|
| 140 |
+ startImage *engine.Env |
|
| 141 |
+ |
|
| 142 |
+ roots = engine.NewTable("Created", outs.Len())
|
|
| 143 |
+ byParent = make(map[string]*engine.Table) |
|
| 144 |
+ ) |
|
| 145 |
+ |
|
| 146 |
+ for _, image := range outs.Data {
|
|
| 147 |
+ if image.Get("ParentId") == "" {
|
|
| 148 |
+ roots.Add(image) |
|
| 149 |
+ } else {
|
|
| 150 |
+ if children, exists := byParent[image.Get("ParentId")]; exists {
|
|
| 151 |
+ children.Add(image) |
|
| 152 |
+ } else {
|
|
| 153 |
+ byParent[image.Get("ParentId")] = engine.NewTable("Created", 1)
|
|
| 154 |
+ byParent[image.Get("ParentId")].Add(image)
|
|
| 155 |
+ } |
|
| 156 |
+ } |
|
| 157 |
+ |
|
| 158 |
+ if matchName != "" {
|
|
| 159 |
+ if matchName == image.Get("Id") || matchName == stringid.TruncateID(image.Get("Id")) {
|
|
| 160 |
+ startImage = image |
|
| 161 |
+ } |
|
| 162 |
+ |
|
| 163 |
+ for _, repotag := range image.GetList("RepoTags") {
|
|
| 164 |
+ if repotag == matchName {
|
|
| 165 |
+ startImage = image |
|
| 166 |
+ } |
|
| 167 |
+ } |
|
| 168 |
+ } |
|
| 169 |
+ } |
|
| 170 |
+ |
|
| 171 |
+ if *flViz {
|
|
| 172 |
+ fmt.Fprintf(cli.out, "digraph docker {\n")
|
|
| 173 |
+ printNode = (*DockerCli).printVizNode |
|
| 174 |
+ } else {
|
|
| 175 |
+ printNode = (*DockerCli).printTreeNode |
|
| 176 |
+ } |
|
| 177 |
+ |
|
| 178 |
+ if startImage != nil {
|
|
| 179 |
+ root := engine.NewTable("Created", 1)
|
|
| 180 |
+ root.Add(startImage) |
|
| 181 |
+ cli.WalkTree(*noTrunc, root, byParent, "", printNode) |
|
| 182 |
+ } else if matchName == "" {
|
|
| 183 |
+ cli.WalkTree(*noTrunc, roots, byParent, "", printNode) |
|
| 184 |
+ } |
|
| 185 |
+ if *flViz {
|
|
| 186 |
+ fmt.Fprintf(cli.out, " base [style=invisible]\n}\n") |
|
| 187 |
+ } |
|
| 188 |
+ } else {
|
|
| 189 |
+ v := url.Values{}
|
|
| 190 |
+ if len(imageFilterArgs) > 0 {
|
|
| 191 |
+ filterJson, err := filters.ToParam(imageFilterArgs) |
|
| 192 |
+ if err != nil {
|
|
| 193 |
+ return err |
|
| 194 |
+ } |
|
| 195 |
+ v.Set("filters", filterJson)
|
|
| 196 |
+ } |
|
| 197 |
+ |
|
| 198 |
+ if cmd.NArg() == 1 {
|
|
| 199 |
+ // FIXME rename this parameter, to not be confused with the filters flag |
|
| 200 |
+ v.Set("filter", matchName)
|
|
| 201 |
+ } |
|
| 202 |
+ if *all {
|
|
| 203 |
+ v.Set("all", "1")
|
|
| 204 |
+ } |
|
| 205 |
+ |
|
| 206 |
+ body, _, err := readBody(cli.call("GET", "/images/json?"+v.Encode(), nil, false))
|
|
| 207 |
+ |
|
| 208 |
+ if err != nil {
|
|
| 209 |
+ return err |
|
| 210 |
+ } |
|
| 211 |
+ |
|
| 212 |
+ outs := engine.NewTable("Created", 0)
|
|
| 213 |
+ if _, err := outs.ReadListFrom(body); err != nil {
|
|
| 214 |
+ return err |
|
| 215 |
+ } |
|
| 216 |
+ |
|
| 217 |
+ w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0) |
|
| 218 |
+ if !*quiet {
|
|
| 219 |
+ if *showDigests {
|
|
| 220 |
+ fmt.Fprintln(w, "REPOSITORY\tTAG\tDIGEST\tIMAGE ID\tCREATED\tVIRTUAL SIZE") |
|
| 221 |
+ } else {
|
|
| 222 |
+ fmt.Fprintln(w, "REPOSITORY\tTAG\tIMAGE ID\tCREATED\tVIRTUAL SIZE") |
|
| 223 |
+ } |
|
| 224 |
+ } |
|
| 225 |
+ |
|
| 226 |
+ for _, out := range outs.Data {
|
|
| 227 |
+ outID := out.Get("Id")
|
|
| 228 |
+ if !*noTrunc {
|
|
| 229 |
+ outID = stringid.TruncateID(outID) |
|
| 230 |
+ } |
|
| 231 |
+ |
|
| 232 |
+ repoTags := out.GetList("RepoTags")
|
|
| 233 |
+ repoDigests := out.GetList("RepoDigests")
|
|
| 234 |
+ |
|
| 235 |
+ if len(repoTags) == 1 && repoTags[0] == "<none>:<none>" && len(repoDigests) == 1 && repoDigests[0] == "<none>@<none>" {
|
|
| 236 |
+ // dangling image - clear out either repoTags or repoDigsts so we only show it once below |
|
| 237 |
+ repoDigests = []string{}
|
|
| 238 |
+ } |
|
| 239 |
+ |
|
| 240 |
+ // combine the tags and digests lists |
|
| 241 |
+ tagsAndDigests := append(repoTags, repoDigests...) |
|
| 242 |
+ for _, repoAndRef := range tagsAndDigests {
|
|
| 243 |
+ repo, ref := parsers.ParseRepositoryTag(repoAndRef) |
|
| 244 |
+ // default tag and digest to none - if there's a value, it'll be set below |
|
| 245 |
+ tag := "<none>" |
|
| 246 |
+ digest := "<none>" |
|
| 247 |
+ if utils.DigestReference(ref) {
|
|
| 248 |
+ digest = ref |
|
| 249 |
+ } else {
|
|
| 250 |
+ tag = ref |
|
| 251 |
+ } |
|
| 252 |
+ |
|
| 253 |
+ if !*quiet {
|
|
| 254 |
+ if *showDigests {
|
|
| 255 |
+ fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s ago\t%s\n", repo, tag, digest, outID, units.HumanDuration(time.Now().UTC().Sub(time.Unix(out.GetInt64("Created"), 0))), units.HumanSize(float64(out.GetInt64("VirtualSize"))))
|
|
| 256 |
+ } else {
|
|
| 257 |
+ fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\n", repo, tag, outID, units.HumanDuration(time.Now().UTC().Sub(time.Unix(out.GetInt64("Created"), 0))), units.HumanSize(float64(out.GetInt64("VirtualSize"))))
|
|
| 258 |
+ } |
|
| 259 |
+ } else {
|
|
| 260 |
+ fmt.Fprintln(w, outID) |
|
| 261 |
+ } |
|
| 262 |
+ } |
|
| 263 |
+ } |
|
| 264 |
+ |
|
| 265 |
+ if !*quiet {
|
|
| 266 |
+ w.Flush() |
|
| 267 |
+ } |
|
| 268 |
+ } |
|
| 269 |
+ return nil |
|
| 270 |
+} |
| 0 | 271 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,54 @@ |
| 0 |
+package client |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "fmt" |
|
| 4 |
+ "io" |
|
| 5 |
+ "net/url" |
|
| 6 |
+ |
|
| 7 |
+ "github.com/docker/docker/opts" |
|
| 8 |
+ flag "github.com/docker/docker/pkg/mflag" |
|
| 9 |
+ "github.com/docker/docker/pkg/parsers" |
|
| 10 |
+ "github.com/docker/docker/registry" |
|
| 11 |
+ "github.com/docker/docker/utils" |
|
| 12 |
+) |
|
| 13 |
+ |
|
| 14 |
+func (cli *DockerCli) CmdImport(args ...string) error {
|
|
| 15 |
+ cmd := cli.Subcmd("import", "URL|- [REPOSITORY[:TAG]]", "Create an empty filesystem image and import the contents of the\ntarball (.tar, .tar.gz, .tgz, .bzip, .tar.xz, .txz) into it, then\noptionally tag it.", true)
|
|
| 16 |
+ flChanges := opts.NewListOpts(nil) |
|
| 17 |
+ cmd.Var(&flChanges, []string{"c", "-change"}, "Apply Dockerfile instruction to the created image")
|
|
| 18 |
+ cmd.Require(flag.Min, 1) |
|
| 19 |
+ |
|
| 20 |
+ utils.ParseFlags(cmd, args, true) |
|
| 21 |
+ |
|
| 22 |
+ var ( |
|
| 23 |
+ v = url.Values{}
|
|
| 24 |
+ src = cmd.Arg(0) |
|
| 25 |
+ repository = cmd.Arg(1) |
|
| 26 |
+ ) |
|
| 27 |
+ |
|
| 28 |
+ v.Set("fromSrc", src)
|
|
| 29 |
+ v.Set("repo", repository)
|
|
| 30 |
+ for _, change := range flChanges.GetAll() {
|
|
| 31 |
+ v.Add("changes", change)
|
|
| 32 |
+ } |
|
| 33 |
+ if cmd.NArg() == 3 {
|
|
| 34 |
+ fmt.Fprintf(cli.err, "[DEPRECATED] The format 'URL|- [REPOSITORY [TAG]]' has been deprecated. Please use URL|- [REPOSITORY[:TAG]]\n") |
|
| 35 |
+ v.Set("tag", cmd.Arg(2))
|
|
| 36 |
+ } |
|
| 37 |
+ |
|
| 38 |
+ if repository != "" {
|
|
| 39 |
+ //Check if the given image name can be resolved |
|
| 40 |
+ repo, _ := parsers.ParseRepositoryTag(repository) |
|
| 41 |
+ if err := registry.ValidateRepositoryName(repo); err != nil {
|
|
| 42 |
+ return err |
|
| 43 |
+ } |
|
| 44 |
+ } |
|
| 45 |
+ |
|
| 46 |
+ var in io.Reader |
|
| 47 |
+ |
|
| 48 |
+ if src == "-" {
|
|
| 49 |
+ in = cli.in |
|
| 50 |
+ } |
|
| 51 |
+ |
|
| 52 |
+ return cli.stream("POST", "/images/create?"+v.Encode(), in, cli.out, nil)
|
|
| 53 |
+} |
| 0 | 54 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,144 @@ |
| 0 |
+package client |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "fmt" |
|
| 4 |
+ "os" |
|
| 5 |
+ "time" |
|
| 6 |
+ |
|
| 7 |
+ log "github.com/Sirupsen/logrus" |
|
| 8 |
+ "github.com/docker/docker/engine" |
|
| 9 |
+ flag "github.com/docker/docker/pkg/mflag" |
|
| 10 |
+ "github.com/docker/docker/pkg/units" |
|
| 11 |
+ "github.com/docker/docker/utils" |
|
| 12 |
+) |
|
| 13 |
+ |
|
| 14 |
+// 'docker info': display system-wide information. |
|
| 15 |
+func (cli *DockerCli) CmdInfo(args ...string) error {
|
|
| 16 |
+ cmd := cli.Subcmd("info", "", "Display system-wide information", true)
|
|
| 17 |
+ cmd.Require(flag.Exact, 0) |
|
| 18 |
+ utils.ParseFlags(cmd, args, false) |
|
| 19 |
+ |
|
| 20 |
+ body, _, err := readBody(cli.call("GET", "/info", nil, false))
|
|
| 21 |
+ if err != nil {
|
|
| 22 |
+ return err |
|
| 23 |
+ } |
|
| 24 |
+ |
|
| 25 |
+ out := engine.NewOutput() |
|
| 26 |
+ remoteInfo, err := out.AddEnv() |
|
| 27 |
+ if err != nil {
|
|
| 28 |
+ return err |
|
| 29 |
+ } |
|
| 30 |
+ |
|
| 31 |
+ if _, err := out.Write(body); err != nil {
|
|
| 32 |
+ log.Errorf("Error reading remote info: %s", err)
|
|
| 33 |
+ return err |
|
| 34 |
+ } |
|
| 35 |
+ out.Close() |
|
| 36 |
+ |
|
| 37 |
+ if remoteInfo.Exists("Containers") {
|
|
| 38 |
+ fmt.Fprintf(cli.out, "Containers: %d\n", remoteInfo.GetInt("Containers"))
|
|
| 39 |
+ } |
|
| 40 |
+ if remoteInfo.Exists("Images") {
|
|
| 41 |
+ fmt.Fprintf(cli.out, "Images: %d\n", remoteInfo.GetInt("Images"))
|
|
| 42 |
+ } |
|
| 43 |
+ if remoteInfo.Exists("Driver") {
|
|
| 44 |
+ fmt.Fprintf(cli.out, "Storage Driver: %s\n", remoteInfo.Get("Driver"))
|
|
| 45 |
+ } |
|
| 46 |
+ if remoteInfo.Exists("DriverStatus") {
|
|
| 47 |
+ var driverStatus [][2]string |
|
| 48 |
+ if err := remoteInfo.GetJson("DriverStatus", &driverStatus); err != nil {
|
|
| 49 |
+ return err |
|
| 50 |
+ } |
|
| 51 |
+ for _, pair := range driverStatus {
|
|
| 52 |
+ fmt.Fprintf(cli.out, " %s: %s\n", pair[0], pair[1]) |
|
| 53 |
+ } |
|
| 54 |
+ } |
|
| 55 |
+ if remoteInfo.Exists("ExecutionDriver") {
|
|
| 56 |
+ fmt.Fprintf(cli.out, "Execution Driver: %s\n", remoteInfo.Get("ExecutionDriver"))
|
|
| 57 |
+ } |
|
| 58 |
+ if remoteInfo.Exists("KernelVersion") {
|
|
| 59 |
+ fmt.Fprintf(cli.out, "Kernel Version: %s\n", remoteInfo.Get("KernelVersion"))
|
|
| 60 |
+ } |
|
| 61 |
+ if remoteInfo.Exists("OperatingSystem") {
|
|
| 62 |
+ fmt.Fprintf(cli.out, "Operating System: %s\n", remoteInfo.Get("OperatingSystem"))
|
|
| 63 |
+ } |
|
| 64 |
+ if remoteInfo.Exists("NCPU") {
|
|
| 65 |
+ fmt.Fprintf(cli.out, "CPUs: %d\n", remoteInfo.GetInt("NCPU"))
|
|
| 66 |
+ } |
|
| 67 |
+ if remoteInfo.Exists("MemTotal") {
|
|
| 68 |
+ fmt.Fprintf(cli.out, "Total Memory: %s\n", units.BytesSize(float64(remoteInfo.GetInt64("MemTotal"))))
|
|
| 69 |
+ } |
|
| 70 |
+ if remoteInfo.Exists("Name") {
|
|
| 71 |
+ fmt.Fprintf(cli.out, "Name: %s\n", remoteInfo.Get("Name"))
|
|
| 72 |
+ } |
|
| 73 |
+ if remoteInfo.Exists("ID") {
|
|
| 74 |
+ fmt.Fprintf(cli.out, "ID: %s\n", remoteInfo.Get("ID"))
|
|
| 75 |
+ } |
|
| 76 |
+ |
|
| 77 |
+ if remoteInfo.GetBool("Debug") || os.Getenv("DEBUG") != "" {
|
|
| 78 |
+ if remoteInfo.Exists("Debug") {
|
|
| 79 |
+ fmt.Fprintf(cli.out, "Debug mode (server): %v\n", remoteInfo.GetBool("Debug"))
|
|
| 80 |
+ } |
|
| 81 |
+ fmt.Fprintf(cli.out, "Debug mode (client): %v\n", os.Getenv("DEBUG") != "")
|
|
| 82 |
+ if remoteInfo.Exists("NFd") {
|
|
| 83 |
+ fmt.Fprintf(cli.out, "Fds: %d\n", remoteInfo.GetInt("NFd"))
|
|
| 84 |
+ } |
|
| 85 |
+ if remoteInfo.Exists("NGoroutines") {
|
|
| 86 |
+ fmt.Fprintf(cli.out, "Goroutines: %d\n", remoteInfo.GetInt("NGoroutines"))
|
|
| 87 |
+ } |
|
| 88 |
+ if remoteInfo.Exists("SystemTime") {
|
|
| 89 |
+ t, err := remoteInfo.GetTime("SystemTime")
|
|
| 90 |
+ if err != nil {
|
|
| 91 |
+ log.Errorf("Error reading system time: %v", err)
|
|
| 92 |
+ } else {
|
|
| 93 |
+ fmt.Fprintf(cli.out, "System Time: %s\n", t.Format(time.UnixDate)) |
|
| 94 |
+ } |
|
| 95 |
+ } |
|
| 96 |
+ if remoteInfo.Exists("NEventsListener") {
|
|
| 97 |
+ fmt.Fprintf(cli.out, "EventsListeners: %d\n", remoteInfo.GetInt("NEventsListener"))
|
|
| 98 |
+ } |
|
| 99 |
+ if initSha1 := remoteInfo.Get("InitSha1"); initSha1 != "" {
|
|
| 100 |
+ fmt.Fprintf(cli.out, "Init SHA1: %s\n", initSha1) |
|
| 101 |
+ } |
|
| 102 |
+ if initPath := remoteInfo.Get("InitPath"); initPath != "" {
|
|
| 103 |
+ fmt.Fprintf(cli.out, "Init Path: %s\n", initPath) |
|
| 104 |
+ } |
|
| 105 |
+ if root := remoteInfo.Get("DockerRootDir"); root != "" {
|
|
| 106 |
+ fmt.Fprintf(cli.out, "Docker Root Dir: %s\n", root) |
|
| 107 |
+ } |
|
| 108 |
+ } |
|
| 109 |
+ if remoteInfo.Exists("HttpProxy") {
|
|
| 110 |
+ fmt.Fprintf(cli.out, "Http Proxy: %s\n", remoteInfo.Get("HttpProxy"))
|
|
| 111 |
+ } |
|
| 112 |
+ if remoteInfo.Exists("HttpsProxy") {
|
|
| 113 |
+ fmt.Fprintf(cli.out, "Https Proxy: %s\n", remoteInfo.Get("HttpsProxy"))
|
|
| 114 |
+ } |
|
| 115 |
+ if remoteInfo.Exists("NoProxy") {
|
|
| 116 |
+ fmt.Fprintf(cli.out, "No Proxy: %s\n", remoteInfo.Get("NoProxy"))
|
|
| 117 |
+ } |
|
| 118 |
+ if len(remoteInfo.GetList("IndexServerAddress")) != 0 {
|
|
| 119 |
+ cli.LoadConfigFile() |
|
| 120 |
+ u := cli.configFile.Configs[remoteInfo.Get("IndexServerAddress")].Username
|
|
| 121 |
+ if len(u) > 0 {
|
|
| 122 |
+ fmt.Fprintf(cli.out, "Username: %v\n", u) |
|
| 123 |
+ fmt.Fprintf(cli.out, "Registry: %v\n", remoteInfo.GetList("IndexServerAddress"))
|
|
| 124 |
+ } |
|
| 125 |
+ } |
|
| 126 |
+ if remoteInfo.Exists("MemoryLimit") && !remoteInfo.GetBool("MemoryLimit") {
|
|
| 127 |
+ fmt.Fprintf(cli.err, "WARNING: No memory limit support\n") |
|
| 128 |
+ } |
|
| 129 |
+ if remoteInfo.Exists("SwapLimit") && !remoteInfo.GetBool("SwapLimit") {
|
|
| 130 |
+ fmt.Fprintf(cli.err, "WARNING: No swap limit support\n") |
|
| 131 |
+ } |
|
| 132 |
+ if remoteInfo.Exists("IPv4Forwarding") && !remoteInfo.GetBool("IPv4Forwarding") {
|
|
| 133 |
+ fmt.Fprintf(cli.err, "WARNING: IPv4 forwarding is disabled.\n") |
|
| 134 |
+ } |
|
| 135 |
+ if remoteInfo.Exists("Labels") {
|
|
| 136 |
+ fmt.Fprintln(cli.out, "Labels:") |
|
| 137 |
+ for _, attribute := range remoteInfo.GetList("Labels") {
|
|
| 138 |
+ fmt.Fprintf(cli.out, " %s\n", attribute) |
|
| 139 |
+ } |
|
| 140 |
+ } |
|
| 141 |
+ |
|
| 142 |
+ return nil |
|
| 143 |
+} |
| 0 | 144 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,95 @@ |
| 0 |
+package client |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "bytes" |
|
| 4 |
+ "encoding/json" |
|
| 5 |
+ "fmt" |
|
| 6 |
+ "io" |
|
| 7 |
+ "strings" |
|
| 8 |
+ "text/template" |
|
| 9 |
+ |
|
| 10 |
+ flag "github.com/docker/docker/pkg/mflag" |
|
| 11 |
+ "github.com/docker/docker/utils" |
|
| 12 |
+) |
|
| 13 |
+ |
|
| 14 |
+func (cli *DockerCli) CmdInspect(args ...string) error {
|
|
| 15 |
+ cmd := cli.Subcmd("inspect", "CONTAINER|IMAGE [CONTAINER|IMAGE...]", "Return low-level information on a container or image", true)
|
|
| 16 |
+ tmplStr := cmd.String([]string{"f", "#format", "-format"}, "", "Format the output using the given go template")
|
|
| 17 |
+ cmd.Require(flag.Min, 1) |
|
| 18 |
+ |
|
| 19 |
+ utils.ParseFlags(cmd, args, true) |
|
| 20 |
+ |
|
| 21 |
+ var tmpl *template.Template |
|
| 22 |
+ if *tmplStr != "" {
|
|
| 23 |
+ var err error |
|
| 24 |
+ if tmpl, err = template.New("").Funcs(funcMap).Parse(*tmplStr); err != nil {
|
|
| 25 |
+ fmt.Fprintf(cli.err, "Template parsing error: %v\n", err) |
|
| 26 |
+ return &utils.StatusError{StatusCode: 64,
|
|
| 27 |
+ Status: "Template parsing error: " + err.Error()} |
|
| 28 |
+ } |
|
| 29 |
+ } |
|
| 30 |
+ |
|
| 31 |
+ indented := new(bytes.Buffer) |
|
| 32 |
+ indented.WriteByte('[')
|
|
| 33 |
+ status := 0 |
|
| 34 |
+ |
|
| 35 |
+ for _, name := range cmd.Args() {
|
|
| 36 |
+ obj, _, err := readBody(cli.call("GET", "/containers/"+name+"/json", nil, false))
|
|
| 37 |
+ if err != nil {
|
|
| 38 |
+ if strings.Contains(err.Error(), "Too many") {
|
|
| 39 |
+ fmt.Fprintf(cli.err, "Error: %v", err) |
|
| 40 |
+ status = 1 |
|
| 41 |
+ continue |
|
| 42 |
+ } |
|
| 43 |
+ |
|
| 44 |
+ obj, _, err = readBody(cli.call("GET", "/images/"+name+"/json", nil, false))
|
|
| 45 |
+ if err != nil {
|
|
| 46 |
+ if strings.Contains(err.Error(), "No such") {
|
|
| 47 |
+ fmt.Fprintf(cli.err, "Error: No such image or container: %s\n", name) |
|
| 48 |
+ } else {
|
|
| 49 |
+ fmt.Fprintf(cli.err, "%s", err) |
|
| 50 |
+ } |
|
| 51 |
+ status = 1 |
|
| 52 |
+ continue |
|
| 53 |
+ } |
|
| 54 |
+ } |
|
| 55 |
+ |
|
| 56 |
+ if tmpl == nil {
|
|
| 57 |
+ if err = json.Indent(indented, obj, "", " "); err != nil {
|
|
| 58 |
+ fmt.Fprintf(cli.err, "%s\n", err) |
|
| 59 |
+ status = 1 |
|
| 60 |
+ continue |
|
| 61 |
+ } |
|
| 62 |
+ } else {
|
|
| 63 |
+ // Has template, will render |
|
| 64 |
+ var value interface{}
|
|
| 65 |
+ if err := json.Unmarshal(obj, &value); err != nil {
|
|
| 66 |
+ fmt.Fprintf(cli.err, "%s\n", err) |
|
| 67 |
+ status = 1 |
|
| 68 |
+ continue |
|
| 69 |
+ } |
|
| 70 |
+ if err := tmpl.Execute(cli.out, value); err != nil {
|
|
| 71 |
+ return err |
|
| 72 |
+ } |
|
| 73 |
+ cli.out.Write([]byte{'\n'})
|
|
| 74 |
+ } |
|
| 75 |
+ indented.WriteString(",")
|
|
| 76 |
+ } |
|
| 77 |
+ |
|
| 78 |
+ if indented.Len() > 1 {
|
|
| 79 |
+ // Remove trailing ',' |
|
| 80 |
+ indented.Truncate(indented.Len() - 1) |
|
| 81 |
+ } |
|
| 82 |
+ indented.WriteString("]\n")
|
|
| 83 |
+ |
|
| 84 |
+ if tmpl == nil {
|
|
| 85 |
+ if _, err := io.Copy(cli.out, indented); err != nil {
|
|
| 86 |
+ return err |
|
| 87 |
+ } |
|
| 88 |
+ } |
|
| 89 |
+ |
|
| 90 |
+ if status != 0 {
|
|
| 91 |
+ return &utils.StatusError{StatusCode: status}
|
|
| 92 |
+ } |
|
| 93 |
+ return nil |
|
| 94 |
+} |
| 0 | 95 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,28 @@ |
| 0 |
+package client |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "fmt" |
|
| 4 |
+ |
|
| 5 |
+ flag "github.com/docker/docker/pkg/mflag" |
|
| 6 |
+ "github.com/docker/docker/utils" |
|
| 7 |
+) |
|
| 8 |
+ |
|
| 9 |
+// 'docker kill NAME' kills a running container |
|
| 10 |
+func (cli *DockerCli) CmdKill(args ...string) error {
|
|
| 11 |
+ cmd := cli.Subcmd("kill", "CONTAINER [CONTAINER...]", "Kill a running container using SIGKILL or a specified signal", true)
|
|
| 12 |
+ signal := cmd.String([]string{"s", "-signal"}, "KILL", "Signal to send to the container")
|
|
| 13 |
+ cmd.Require(flag.Min, 1) |
|
| 14 |
+ |
|
| 15 |
+ utils.ParseFlags(cmd, args, true) |
|
| 16 |
+ |
|
| 17 |
+ var encounteredError error |
|
| 18 |
+ for _, name := range cmd.Args() {
|
|
| 19 |
+ if _, _, err := readBody(cli.call("POST", fmt.Sprintf("/containers/%s/kill?signal=%s", name, *signal), nil, false)); err != nil {
|
|
| 20 |
+ fmt.Fprintf(cli.err, "%s\n", err) |
|
| 21 |
+ encounteredError = fmt.Errorf("Error: failed to kill one or more containers")
|
|
| 22 |
+ } else {
|
|
| 23 |
+ fmt.Fprintf(cli.out, "%s\n", name) |
|
| 24 |
+ } |
|
| 25 |
+ } |
|
| 26 |
+ return encounteredError |
|
| 27 |
+} |
| 0 | 28 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,32 @@ |
| 0 |
+package client |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "io" |
|
| 4 |
+ "os" |
|
| 5 |
+ |
|
| 6 |
+ flag "github.com/docker/docker/pkg/mflag" |
|
| 7 |
+ "github.com/docker/docker/utils" |
|
| 8 |
+) |
|
| 9 |
+ |
|
| 10 |
+func (cli *DockerCli) CmdLoad(args ...string) error {
|
|
| 11 |
+ cmd := cli.Subcmd("load", "", "Load an image from a tar archive on STDIN", true)
|
|
| 12 |
+ infile := cmd.String([]string{"i", "-input"}, "", "Read from a tar archive file, instead of STDIN")
|
|
| 13 |
+ cmd.Require(flag.Exact, 0) |
|
| 14 |
+ |
|
| 15 |
+ utils.ParseFlags(cmd, args, true) |
|
| 16 |
+ |
|
| 17 |
+ var ( |
|
| 18 |
+ input io.Reader = cli.in |
|
| 19 |
+ err error |
|
| 20 |
+ ) |
|
| 21 |
+ if *infile != "" {
|
|
| 22 |
+ input, err = os.Open(*infile) |
|
| 23 |
+ if err != nil {
|
|
| 24 |
+ return err |
|
| 25 |
+ } |
|
| 26 |
+ } |
|
| 27 |
+ if err := cli.stream("POST", "/images/load", input, cli.out, nil); err != nil {
|
|
| 28 |
+ return err |
|
| 29 |
+ } |
|
| 30 |
+ return nil |
|
| 31 |
+} |
| 0 | 32 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,138 @@ |
| 0 |
+package client |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "bufio" |
|
| 4 |
+ "encoding/json" |
|
| 5 |
+ "fmt" |
|
| 6 |
+ "io" |
|
| 7 |
+ "os" |
|
| 8 |
+ "path" |
|
| 9 |
+ "strings" |
|
| 10 |
+ |
|
| 11 |
+ "github.com/docker/docker/api/types" |
|
| 12 |
+ "github.com/docker/docker/pkg/homedir" |
|
| 13 |
+ flag "github.com/docker/docker/pkg/mflag" |
|
| 14 |
+ "github.com/docker/docker/pkg/term" |
|
| 15 |
+ "github.com/docker/docker/registry" |
|
| 16 |
+ "github.com/docker/docker/utils" |
|
| 17 |
+) |
|
| 18 |
+ |
|
| 19 |
+// 'docker login': login / register a user to registry service. |
|
| 20 |
+func (cli *DockerCli) CmdLogin(args ...string) error {
|
|
| 21 |
+ cmd := cli.Subcmd("login", "[SERVER]", "Register or log in to a Docker registry server, if no server is\nspecified \""+registry.IndexServerAddress()+"\" is the default.", true)
|
|
| 22 |
+ cmd.Require(flag.Max, 1) |
|
| 23 |
+ |
|
| 24 |
+ var username, password, email string |
|
| 25 |
+ |
|
| 26 |
+ cmd.StringVar(&username, []string{"u", "-username"}, "", "Username")
|
|
| 27 |
+ cmd.StringVar(&password, []string{"p", "-password"}, "", "Password")
|
|
| 28 |
+ cmd.StringVar(&email, []string{"e", "-email"}, "", "Email")
|
|
| 29 |
+ |
|
| 30 |
+ utils.ParseFlags(cmd, args, true) |
|
| 31 |
+ |
|
| 32 |
+ serverAddress := registry.IndexServerAddress() |
|
| 33 |
+ if len(cmd.Args()) > 0 {
|
|
| 34 |
+ serverAddress = cmd.Arg(0) |
|
| 35 |
+ } |
|
| 36 |
+ |
|
| 37 |
+ promptDefault := func(prompt string, configDefault string) {
|
|
| 38 |
+ if configDefault == "" {
|
|
| 39 |
+ fmt.Fprintf(cli.out, "%s: ", prompt) |
|
| 40 |
+ } else {
|
|
| 41 |
+ fmt.Fprintf(cli.out, "%s (%s): ", prompt, configDefault) |
|
| 42 |
+ } |
|
| 43 |
+ } |
|
| 44 |
+ |
|
| 45 |
+ readInput := func(in io.Reader, out io.Writer) string {
|
|
| 46 |
+ reader := bufio.NewReader(in) |
|
| 47 |
+ line, _, err := reader.ReadLine() |
|
| 48 |
+ if err != nil {
|
|
| 49 |
+ fmt.Fprintln(out, err.Error()) |
|
| 50 |
+ os.Exit(1) |
|
| 51 |
+ } |
|
| 52 |
+ return string(line) |
|
| 53 |
+ } |
|
| 54 |
+ |
|
| 55 |
+ cli.LoadConfigFile() |
|
| 56 |
+ authconfig, ok := cli.configFile.Configs[serverAddress] |
|
| 57 |
+ if !ok {
|
|
| 58 |
+ authconfig = registry.AuthConfig{}
|
|
| 59 |
+ } |
|
| 60 |
+ |
|
| 61 |
+ if username == "" {
|
|
| 62 |
+ promptDefault("Username", authconfig.Username)
|
|
| 63 |
+ username = readInput(cli.in, cli.out) |
|
| 64 |
+ username = strings.Trim(username, " ") |
|
| 65 |
+ if username == "" {
|
|
| 66 |
+ username = authconfig.Username |
|
| 67 |
+ } |
|
| 68 |
+ } |
|
| 69 |
+ // Assume that a different username means they may not want to use |
|
| 70 |
+ // the password or email from the config file, so prompt them |
|
| 71 |
+ if username != authconfig.Username {
|
|
| 72 |
+ if password == "" {
|
|
| 73 |
+ oldState, err := term.SaveState(cli.inFd) |
|
| 74 |
+ if err != nil {
|
|
| 75 |
+ return err |
|
| 76 |
+ } |
|
| 77 |
+ fmt.Fprintf(cli.out, "Password: ") |
|
| 78 |
+ term.DisableEcho(cli.inFd, oldState) |
|
| 79 |
+ |
|
| 80 |
+ password = readInput(cli.in, cli.out) |
|
| 81 |
+ fmt.Fprint(cli.out, "\n") |
|
| 82 |
+ |
|
| 83 |
+ term.RestoreTerminal(cli.inFd, oldState) |
|
| 84 |
+ if password == "" {
|
|
| 85 |
+ return fmt.Errorf("Error : Password Required")
|
|
| 86 |
+ } |
|
| 87 |
+ } |
|
| 88 |
+ |
|
| 89 |
+ if email == "" {
|
|
| 90 |
+ promptDefault("Email", authconfig.Email)
|
|
| 91 |
+ email = readInput(cli.in, cli.out) |
|
| 92 |
+ if email == "" {
|
|
| 93 |
+ email = authconfig.Email |
|
| 94 |
+ } |
|
| 95 |
+ } |
|
| 96 |
+ } else {
|
|
| 97 |
+ // However, if they don't override the username use the |
|
| 98 |
+ // password or email from the cmd line if specified. IOW, allow |
|
| 99 |
+ // then to change/override them. And if not specified, just |
|
| 100 |
+ // use what's in the config file |
|
| 101 |
+ if password == "" {
|
|
| 102 |
+ password = authconfig.Password |
|
| 103 |
+ } |
|
| 104 |
+ if email == "" {
|
|
| 105 |
+ email = authconfig.Email |
|
| 106 |
+ } |
|
| 107 |
+ } |
|
| 108 |
+ authconfig.Username = username |
|
| 109 |
+ authconfig.Password = password |
|
| 110 |
+ authconfig.Email = email |
|
| 111 |
+ authconfig.ServerAddress = serverAddress |
|
| 112 |
+ cli.configFile.Configs[serverAddress] = authconfig |
|
| 113 |
+ |
|
| 114 |
+ stream, statusCode, err := cli.call("POST", "/auth", cli.configFile.Configs[serverAddress], false)
|
|
| 115 |
+ if statusCode == 401 {
|
|
| 116 |
+ delete(cli.configFile.Configs, serverAddress) |
|
| 117 |
+ registry.SaveConfig(cli.configFile) |
|
| 118 |
+ return err |
|
| 119 |
+ } |
|
| 120 |
+ if err != nil {
|
|
| 121 |
+ return err |
|
| 122 |
+ } |
|
| 123 |
+ |
|
| 124 |
+ var response types.AuthResponse |
|
| 125 |
+ if err := json.NewDecoder(stream).Decode(response); err != nil {
|
|
| 126 |
+ cli.configFile, _ = registry.LoadConfig(homedir.Get()) |
|
| 127 |
+ return err |
|
| 128 |
+ } |
|
| 129 |
+ |
|
| 130 |
+ registry.SaveConfig(cli.configFile) |
|
| 131 |
+ fmt.Fprintf(cli.out, "WARNING: login credentials saved in %s.\n", path.Join(homedir.Get(), registry.CONFIGFILE)) |
|
| 132 |
+ |
|
| 133 |
+ if response.Status != "" {
|
|
| 134 |
+ fmt.Fprintf(cli.out, "%s\n", response.Status) |
|
| 135 |
+ } |
|
| 136 |
+ return nil |
|
| 137 |
+} |
| 0 | 138 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,34 @@ |
| 0 |
+package client |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "fmt" |
|
| 4 |
+ |
|
| 5 |
+ flag "github.com/docker/docker/pkg/mflag" |
|
| 6 |
+ "github.com/docker/docker/registry" |
|
| 7 |
+ "github.com/docker/docker/utils" |
|
| 8 |
+) |
|
| 9 |
+ |
|
| 10 |
+// 'docker logout': log out a user from a registry service. |
|
| 11 |
+func (cli *DockerCli) CmdLogout(args ...string) error {
|
|
| 12 |
+ cmd := cli.Subcmd("logout", "[SERVER]", "Log out from a Docker registry, if no server is\nspecified \""+registry.IndexServerAddress()+"\" is the default.", true)
|
|
| 13 |
+ cmd.Require(flag.Max, 1) |
|
| 14 |
+ |
|
| 15 |
+ utils.ParseFlags(cmd, args, false) |
|
| 16 |
+ serverAddress := registry.IndexServerAddress() |
|
| 17 |
+ if len(cmd.Args()) > 0 {
|
|
| 18 |
+ serverAddress = cmd.Arg(0) |
|
| 19 |
+ } |
|
| 20 |
+ |
|
| 21 |
+ cli.LoadConfigFile() |
|
| 22 |
+ if _, ok := cli.configFile.Configs[serverAddress]; !ok {
|
|
| 23 |
+ fmt.Fprintf(cli.out, "Not logged in to %s\n", serverAddress) |
|
| 24 |
+ } else {
|
|
| 25 |
+ fmt.Fprintf(cli.out, "Remove login credentials for %s\n", serverAddress) |
|
| 26 |
+ delete(cli.configFile.Configs, serverAddress) |
|
| 27 |
+ |
|
| 28 |
+ if err := registry.SaveConfig(cli.configFile); err != nil {
|
|
| 29 |
+ return fmt.Errorf("Failed to save docker config: %v", err)
|
|
| 30 |
+ } |
|
| 31 |
+ } |
|
| 32 |
+ return nil |
|
| 33 |
+} |
| 0 | 34 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,53 @@ |
| 0 |
+package client |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "fmt" |
|
| 4 |
+ "net/url" |
|
| 5 |
+ |
|
| 6 |
+ "github.com/docker/docker/engine" |
|
| 7 |
+ flag "github.com/docker/docker/pkg/mflag" |
|
| 8 |
+ "github.com/docker/docker/utils" |
|
| 9 |
+) |
|
| 10 |
+ |
|
| 11 |
+func (cli *DockerCli) CmdLogs(args ...string) error {
|
|
| 12 |
+ var ( |
|
| 13 |
+ cmd = cli.Subcmd("logs", "CONTAINER", "Fetch the logs of a container", true)
|
|
| 14 |
+ follow = cmd.Bool([]string{"f", "-follow"}, false, "Follow log output")
|
|
| 15 |
+ times = cmd.Bool([]string{"t", "-timestamps"}, false, "Show timestamps")
|
|
| 16 |
+ tail = cmd.String([]string{"-tail"}, "all", "Number of lines to show from the end of the logs")
|
|
| 17 |
+ ) |
|
| 18 |
+ cmd.Require(flag.Exact, 1) |
|
| 19 |
+ |
|
| 20 |
+ utils.ParseFlags(cmd, args, true) |
|
| 21 |
+ |
|
| 22 |
+ name := cmd.Arg(0) |
|
| 23 |
+ |
|
| 24 |
+ stream, _, err := cli.call("GET", "/containers/"+name+"/json", nil, false)
|
|
| 25 |
+ if err != nil {
|
|
| 26 |
+ return err |
|
| 27 |
+ } |
|
| 28 |
+ |
|
| 29 |
+ env := engine.Env{}
|
|
| 30 |
+ if err := env.Decode(stream); err != nil {
|
|
| 31 |
+ return err |
|
| 32 |
+ } |
|
| 33 |
+ |
|
| 34 |
+ if env.GetSubEnv("HostConfig").GetSubEnv("LogConfig").Get("Type") != "json-file" {
|
|
| 35 |
+ return fmt.Errorf("\"logs\" command is supported only for \"json-file\" logging driver")
|
|
| 36 |
+ } |
|
| 37 |
+ |
|
| 38 |
+ v := url.Values{}
|
|
| 39 |
+ v.Set("stdout", "1")
|
|
| 40 |
+ v.Set("stderr", "1")
|
|
| 41 |
+ |
|
| 42 |
+ if *times {
|
|
| 43 |
+ v.Set("timestamps", "1")
|
|
| 44 |
+ } |
|
| 45 |
+ |
|
| 46 |
+ if *follow {
|
|
| 47 |
+ v.Set("follow", "1")
|
|
| 48 |
+ } |
|
| 49 |
+ v.Set("tail", *tail)
|
|
| 50 |
+ |
|
| 51 |
+ return cli.streamHelper("GET", "/containers/"+name+"/logs?"+v.Encode(), env.GetSubEnv("Config").GetBool("Tty"), nil, cli.out, cli.err, nil)
|
|
| 52 |
+} |
| 0 | 53 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,25 @@ |
| 0 |
+package client |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "fmt" |
|
| 4 |
+ |
|
| 5 |
+ flag "github.com/docker/docker/pkg/mflag" |
|
| 6 |
+ "github.com/docker/docker/utils" |
|
| 7 |
+) |
|
| 8 |
+ |
|
| 9 |
+func (cli *DockerCli) CmdPause(args ...string) error {
|
|
| 10 |
+ cmd := cli.Subcmd("pause", "CONTAINER [CONTAINER...]", "Pause all processes within a container", true)
|
|
| 11 |
+ cmd.Require(flag.Min, 1) |
|
| 12 |
+ utils.ParseFlags(cmd, args, false) |
|
| 13 |
+ |
|
| 14 |
+ var encounteredError error |
|
| 15 |
+ for _, name := range cmd.Args() {
|
|
| 16 |
+ if _, _, err := readBody(cli.call("POST", fmt.Sprintf("/containers/%s/pause", name), nil, false)); err != nil {
|
|
| 17 |
+ fmt.Fprintf(cli.err, "%s\n", err) |
|
| 18 |
+ encounteredError = fmt.Errorf("Error: failed to pause container named %s", name)
|
|
| 19 |
+ } else {
|
|
| 20 |
+ fmt.Fprintf(cli.out, "%s\n", name) |
|
| 21 |
+ } |
|
| 22 |
+ } |
|
| 23 |
+ return encounteredError |
|
| 24 |
+} |
| 0 | 25 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,60 @@ |
| 0 |
+package client |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "fmt" |
|
| 4 |
+ "strings" |
|
| 5 |
+ |
|
| 6 |
+ "github.com/docker/docker/engine" |
|
| 7 |
+ "github.com/docker/docker/nat" |
|
| 8 |
+ flag "github.com/docker/docker/pkg/mflag" |
|
| 9 |
+ "github.com/docker/docker/utils" |
|
| 10 |
+) |
|
| 11 |
+ |
|
| 12 |
+func (cli *DockerCli) CmdPort(args ...string) error {
|
|
| 13 |
+ cmd := cli.Subcmd("port", "CONTAINER [PRIVATE_PORT[/PROTO]]", "List port mappings for the CONTAINER, or lookup the public-facing port that\nis NAT-ed to the PRIVATE_PORT", true)
|
|
| 14 |
+ cmd.Require(flag.Min, 1) |
|
| 15 |
+ utils.ParseFlags(cmd, args, true) |
|
| 16 |
+ |
|
| 17 |
+ stream, _, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/json", nil, false)
|
|
| 18 |
+ if err != nil {
|
|
| 19 |
+ return err |
|
| 20 |
+ } |
|
| 21 |
+ |
|
| 22 |
+ env := engine.Env{}
|
|
| 23 |
+ if err := env.Decode(stream); err != nil {
|
|
| 24 |
+ return err |
|
| 25 |
+ } |
|
| 26 |
+ ports := nat.PortMap{}
|
|
| 27 |
+ if err := env.GetSubEnv("NetworkSettings").GetJson("Ports", &ports); err != nil {
|
|
| 28 |
+ return err |
|
| 29 |
+ } |
|
| 30 |
+ |
|
| 31 |
+ if cmd.NArg() == 2 {
|
|
| 32 |
+ var ( |
|
| 33 |
+ port = cmd.Arg(1) |
|
| 34 |
+ proto = "tcp" |
|
| 35 |
+ parts = strings.SplitN(port, "/", 2) |
|
| 36 |
+ ) |
|
| 37 |
+ |
|
| 38 |
+ if len(parts) == 2 && len(parts[1]) != 0 {
|
|
| 39 |
+ port = parts[0] |
|
| 40 |
+ proto = parts[1] |
|
| 41 |
+ } |
|
| 42 |
+ natPort := port + "/" + proto |
|
| 43 |
+ if frontends, exists := ports[nat.Port(port+"/"+proto)]; exists && frontends != nil {
|
|
| 44 |
+ for _, frontend := range frontends {
|
|
| 45 |
+ fmt.Fprintf(cli.out, "%s:%s\n", frontend.HostIp, frontend.HostPort) |
|
| 46 |
+ } |
|
| 47 |
+ return nil |
|
| 48 |
+ } |
|
| 49 |
+ return fmt.Errorf("Error: No public port '%s' published for %s", natPort, cmd.Arg(0))
|
|
| 50 |
+ } |
|
| 51 |
+ |
|
| 52 |
+ for from, frontends := range ports {
|
|
| 53 |
+ for _, frontend := range frontends {
|
|
| 54 |
+ fmt.Fprintf(cli.out, "%s -> %s:%s\n", from, frontend.HostIp, frontend.HostPort) |
|
| 55 |
+ } |
|
| 56 |
+ } |
|
| 57 |
+ |
|
| 58 |
+ return nil |
|
| 59 |
+} |
| 0 | 60 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,175 @@ |
| 0 |
+package client |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "fmt" |
|
| 4 |
+ "net/url" |
|
| 5 |
+ "strconv" |
|
| 6 |
+ "strings" |
|
| 7 |
+ "text/tabwriter" |
|
| 8 |
+ "time" |
|
| 9 |
+ |
|
| 10 |
+ "github.com/docker/docker/api" |
|
| 11 |
+ "github.com/docker/docker/engine" |
|
| 12 |
+ "github.com/docker/docker/opts" |
|
| 13 |
+ flag "github.com/docker/docker/pkg/mflag" |
|
| 14 |
+ "github.com/docker/docker/pkg/parsers/filters" |
|
| 15 |
+ "github.com/docker/docker/pkg/stringid" |
|
| 16 |
+ "github.com/docker/docker/pkg/units" |
|
| 17 |
+ "github.com/docker/docker/utils" |
|
| 18 |
+) |
|
| 19 |
+ |
|
| 20 |
+func (cli *DockerCli) CmdPs(args ...string) error {
|
|
| 21 |
+ var ( |
|
| 22 |
+ err error |
|
| 23 |
+ |
|
| 24 |
+ psFilterArgs = filters.Args{}
|
|
| 25 |
+ v = url.Values{}
|
|
| 26 |
+ |
|
| 27 |
+ cmd = cli.Subcmd("ps", "", "List containers", true)
|
|
| 28 |
+ quiet = cmd.Bool([]string{"q", "-quiet"}, false, "Only display numeric IDs")
|
|
| 29 |
+ size = cmd.Bool([]string{"s", "-size"}, false, "Display total file sizes")
|
|
| 30 |
+ all = cmd.Bool([]string{"a", "-all"}, false, "Show all containers (default shows just running)")
|
|
| 31 |
+ noTrunc = cmd.Bool([]string{"#notrunc", "-no-trunc"}, false, "Don't truncate output")
|
|
| 32 |
+ nLatest = cmd.Bool([]string{"l", "-latest"}, false, "Show the latest created container, include non-running")
|
|
| 33 |
+ since = cmd.String([]string{"#sinceId", "#-since-id", "-since"}, "", "Show created since Id or Name, include non-running")
|
|
| 34 |
+ before = cmd.String([]string{"#beforeId", "#-before-id", "-before"}, "", "Show only container created before Id or Name")
|
|
| 35 |
+ last = cmd.Int([]string{"n"}, -1, "Show n last created containers, include non-running")
|
|
| 36 |
+ flFilter = opts.NewListOpts(nil) |
|
| 37 |
+ ) |
|
| 38 |
+ cmd.Require(flag.Exact, 0) |
|
| 39 |
+ |
|
| 40 |
+ cmd.Var(&flFilter, []string{"f", "-filter"}, "Filter output based on conditions provided")
|
|
| 41 |
+ |
|
| 42 |
+ utils.ParseFlags(cmd, args, true) |
|
| 43 |
+ if *last == -1 && *nLatest {
|
|
| 44 |
+ *last = 1 |
|
| 45 |
+ } |
|
| 46 |
+ |
|
| 47 |
+ if *all {
|
|
| 48 |
+ v.Set("all", "1")
|
|
| 49 |
+ } |
|
| 50 |
+ |
|
| 51 |
+ if *last != -1 {
|
|
| 52 |
+ v.Set("limit", strconv.Itoa(*last))
|
|
| 53 |
+ } |
|
| 54 |
+ |
|
| 55 |
+ if *since != "" {
|
|
| 56 |
+ v.Set("since", *since)
|
|
| 57 |
+ } |
|
| 58 |
+ |
|
| 59 |
+ if *before != "" {
|
|
| 60 |
+ v.Set("before", *before)
|
|
| 61 |
+ } |
|
| 62 |
+ |
|
| 63 |
+ if *size {
|
|
| 64 |
+ v.Set("size", "1")
|
|
| 65 |
+ } |
|
| 66 |
+ |
|
| 67 |
+ // Consolidate all filter flags, and sanity check them. |
|
| 68 |
+ // They'll get processed in the daemon/server. |
|
| 69 |
+ for _, f := range flFilter.GetAll() {
|
|
| 70 |
+ if psFilterArgs, err = filters.ParseFlag(f, psFilterArgs); err != nil {
|
|
| 71 |
+ return err |
|
| 72 |
+ } |
|
| 73 |
+ } |
|
| 74 |
+ |
|
| 75 |
+ if len(psFilterArgs) > 0 {
|
|
| 76 |
+ filterJson, err := filters.ToParam(psFilterArgs) |
|
| 77 |
+ if err != nil {
|
|
| 78 |
+ return err |
|
| 79 |
+ } |
|
| 80 |
+ |
|
| 81 |
+ v.Set("filters", filterJson)
|
|
| 82 |
+ } |
|
| 83 |
+ |
|
| 84 |
+ body, _, err := readBody(cli.call("GET", "/containers/json?"+v.Encode(), nil, false))
|
|
| 85 |
+ if err != nil {
|
|
| 86 |
+ return err |
|
| 87 |
+ } |
|
| 88 |
+ |
|
| 89 |
+ outs := engine.NewTable("Created", 0)
|
|
| 90 |
+ if _, err := outs.ReadListFrom(body); err != nil {
|
|
| 91 |
+ return err |
|
| 92 |
+ } |
|
| 93 |
+ |
|
| 94 |
+ w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0) |
|
| 95 |
+ if !*quiet {
|
|
| 96 |
+ fmt.Fprint(w, "CONTAINER ID\tIMAGE\tCOMMAND\tCREATED\tSTATUS\tPORTS\tNAMES") |
|
| 97 |
+ |
|
| 98 |
+ if *size {
|
|
| 99 |
+ fmt.Fprintln(w, "\tSIZE") |
|
| 100 |
+ } else {
|
|
| 101 |
+ fmt.Fprint(w, "\n") |
|
| 102 |
+ } |
|
| 103 |
+ } |
|
| 104 |
+ |
|
| 105 |
+ stripNamePrefix := func(ss []string) []string {
|
|
| 106 |
+ for i, s := range ss {
|
|
| 107 |
+ ss[i] = s[1:] |
|
| 108 |
+ } |
|
| 109 |
+ |
|
| 110 |
+ return ss |
|
| 111 |
+ } |
|
| 112 |
+ |
|
| 113 |
+ for _, out := range outs.Data {
|
|
| 114 |
+ outID := out.Get("Id")
|
|
| 115 |
+ |
|
| 116 |
+ if !*noTrunc {
|
|
| 117 |
+ outID = stringid.TruncateID(outID) |
|
| 118 |
+ } |
|
| 119 |
+ |
|
| 120 |
+ if *quiet {
|
|
| 121 |
+ fmt.Fprintln(w, outID) |
|
| 122 |
+ |
|
| 123 |
+ continue |
|
| 124 |
+ } |
|
| 125 |
+ |
|
| 126 |
+ var ( |
|
| 127 |
+ outNames = stripNamePrefix(out.GetList("Names"))
|
|
| 128 |
+ outCommand = strconv.Quote(out.Get("Command"))
|
|
| 129 |
+ ports = engine.NewTable("", 0)
|
|
| 130 |
+ ) |
|
| 131 |
+ |
|
| 132 |
+ if !*noTrunc {
|
|
| 133 |
+ outCommand = utils.Trunc(outCommand, 20) |
|
| 134 |
+ |
|
| 135 |
+ // only display the default name for the container with notrunc is passed |
|
| 136 |
+ for _, name := range outNames {
|
|
| 137 |
+ if len(strings.Split(name, "/")) == 1 {
|
|
| 138 |
+ outNames = []string{name}
|
|
| 139 |
+ |
|
| 140 |
+ break |
|
| 141 |
+ } |
|
| 142 |
+ } |
|
| 143 |
+ } |
|
| 144 |
+ |
|
| 145 |
+ ports.ReadListFrom([]byte(out.Get("Ports")))
|
|
| 146 |
+ |
|
| 147 |
+ image := out.Get("Image")
|
|
| 148 |
+ if image == "" {
|
|
| 149 |
+ image = "<no image>" |
|
| 150 |
+ } |
|
| 151 |
+ |
|
| 152 |
+ fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\t%s\t%s\t", outID, image, outCommand, |
|
| 153 |
+ units.HumanDuration(time.Now().UTC().Sub(time.Unix(out.GetInt64("Created"), 0))),
|
|
| 154 |
+ out.Get("Status"), api.DisplayablePorts(ports), strings.Join(outNames, ","))
|
|
| 155 |
+ |
|
| 156 |
+ if *size {
|
|
| 157 |
+ if out.GetInt("SizeRootFs") > 0 {
|
|
| 158 |
+ fmt.Fprintf(w, "%s (virtual %s)\n", units.HumanSize(float64(out.GetInt64("SizeRw"))), units.HumanSize(float64(out.GetInt64("SizeRootFs"))))
|
|
| 159 |
+ } else {
|
|
| 160 |
+ fmt.Fprintf(w, "%s\n", units.HumanSize(float64(out.GetInt64("SizeRw"))))
|
|
| 161 |
+ } |
|
| 162 |
+ |
|
| 163 |
+ continue |
|
| 164 |
+ } |
|
| 165 |
+ |
|
| 166 |
+ fmt.Fprint(w, "\n") |
|
| 167 |
+ } |
|
| 168 |
+ |
|
| 169 |
+ if !*quiet {
|
|
| 170 |
+ w.Flush() |
|
| 171 |
+ } |
|
| 172 |
+ |
|
| 173 |
+ return nil |
|
| 174 |
+} |
| 0 | 175 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,77 @@ |
| 0 |
+package client |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "encoding/base64" |
|
| 4 |
+ "encoding/json" |
|
| 5 |
+ "fmt" |
|
| 6 |
+ "net/url" |
|
| 7 |
+ "strings" |
|
| 8 |
+ |
|
| 9 |
+ "github.com/docker/docker/graph" |
|
| 10 |
+ flag "github.com/docker/docker/pkg/mflag" |
|
| 11 |
+ "github.com/docker/docker/pkg/parsers" |
|
| 12 |
+ "github.com/docker/docker/registry" |
|
| 13 |
+ "github.com/docker/docker/utils" |
|
| 14 |
+) |
|
| 15 |
+ |
|
| 16 |
+func (cli *DockerCli) CmdPull(args ...string) error {
|
|
| 17 |
+ cmd := cli.Subcmd("pull", "NAME[:TAG|@DIGEST]", "Pull an image or a repository from the registry", true)
|
|
| 18 |
+ allTags := cmd.Bool([]string{"a", "-all-tags"}, false, "Download all tagged images in the repository")
|
|
| 19 |
+ cmd.Require(flag.Exact, 1) |
|
| 20 |
+ |
|
| 21 |
+ utils.ParseFlags(cmd, args, true) |
|
| 22 |
+ |
|
| 23 |
+ var ( |
|
| 24 |
+ v = url.Values{}
|
|
| 25 |
+ remote = cmd.Arg(0) |
|
| 26 |
+ newRemote = remote |
|
| 27 |
+ ) |
|
| 28 |
+ taglessRemote, tag := parsers.ParseRepositoryTag(remote) |
|
| 29 |
+ if tag == "" && !*allTags {
|
|
| 30 |
+ newRemote = utils.ImageReference(taglessRemote, graph.DEFAULTTAG) |
|
| 31 |
+ } |
|
| 32 |
+ if tag != "" && *allTags {
|
|
| 33 |
+ return fmt.Errorf("tag can't be used with --all-tags/-a")
|
|
| 34 |
+ } |
|
| 35 |
+ |
|
| 36 |
+ v.Set("fromImage", newRemote)
|
|
| 37 |
+ |
|
| 38 |
+ // Resolve the Repository name from fqn to RepositoryInfo |
|
| 39 |
+ repoInfo, err := registry.ParseRepositoryInfo(taglessRemote) |
|
| 40 |
+ if err != nil {
|
|
| 41 |
+ return err |
|
| 42 |
+ } |
|
| 43 |
+ |
|
| 44 |
+ cli.LoadConfigFile() |
|
| 45 |
+ |
|
| 46 |
+ // Resolve the Auth config relevant for this server |
|
| 47 |
+ authConfig := cli.configFile.ResolveAuthConfig(repoInfo.Index) |
|
| 48 |
+ |
|
| 49 |
+ pull := func(authConfig registry.AuthConfig) error {
|
|
| 50 |
+ buf, err := json.Marshal(authConfig) |
|
| 51 |
+ if err != nil {
|
|
| 52 |
+ return err |
|
| 53 |
+ } |
|
| 54 |
+ registryAuthHeader := []string{
|
|
| 55 |
+ base64.URLEncoding.EncodeToString(buf), |
|
| 56 |
+ } |
|
| 57 |
+ |
|
| 58 |
+ return cli.stream("POST", "/images/create?"+v.Encode(), nil, cli.out, map[string][]string{
|
|
| 59 |
+ "X-Registry-Auth": registryAuthHeader, |
|
| 60 |
+ }) |
|
| 61 |
+ } |
|
| 62 |
+ |
|
| 63 |
+ if err := pull(authConfig); err != nil {
|
|
| 64 |
+ if strings.Contains(err.Error(), "Status 401") {
|
|
| 65 |
+ fmt.Fprintln(cli.out, "\nPlease login prior to pull:") |
|
| 66 |
+ if err := cli.CmdLogin(repoInfo.Index.GetAuthConfigKey()); err != nil {
|
|
| 67 |
+ return err |
|
| 68 |
+ } |
|
| 69 |
+ authConfig := cli.configFile.ResolveAuthConfig(repoInfo.Index) |
|
| 70 |
+ return pull(authConfig) |
|
| 71 |
+ } |
|
| 72 |
+ return err |
|
| 73 |
+ } |
|
| 74 |
+ |
|
| 75 |
+ return nil |
|
| 76 |
+} |
| 0 | 77 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,76 @@ |
| 0 |
+package client |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "encoding/base64" |
|
| 4 |
+ "encoding/json" |
|
| 5 |
+ "fmt" |
|
| 6 |
+ "net/url" |
|
| 7 |
+ "strings" |
|
| 8 |
+ |
|
| 9 |
+ flag "github.com/docker/docker/pkg/mflag" |
|
| 10 |
+ "github.com/docker/docker/pkg/parsers" |
|
| 11 |
+ "github.com/docker/docker/registry" |
|
| 12 |
+ "github.com/docker/docker/utils" |
|
| 13 |
+) |
|
| 14 |
+ |
|
| 15 |
+func (cli *DockerCli) CmdPush(args ...string) error {
|
|
| 16 |
+ cmd := cli.Subcmd("push", "NAME[:TAG]", "Push an image or a repository to the registry", true)
|
|
| 17 |
+ cmd.Require(flag.Exact, 1) |
|
| 18 |
+ |
|
| 19 |
+ utils.ParseFlags(cmd, args, true) |
|
| 20 |
+ |
|
| 21 |
+ name := cmd.Arg(0) |
|
| 22 |
+ |
|
| 23 |
+ cli.LoadConfigFile() |
|
| 24 |
+ |
|
| 25 |
+ remote, tag := parsers.ParseRepositoryTag(name) |
|
| 26 |
+ |
|
| 27 |
+ // Resolve the Repository name from fqn to RepositoryInfo |
|
| 28 |
+ repoInfo, err := registry.ParseRepositoryInfo(remote) |
|
| 29 |
+ if err != nil {
|
|
| 30 |
+ return err |
|
| 31 |
+ } |
|
| 32 |
+ // Resolve the Auth config relevant for this server |
|
| 33 |
+ authConfig := cli.configFile.ResolveAuthConfig(repoInfo.Index) |
|
| 34 |
+ // If we're not using a custom registry, we know the restrictions |
|
| 35 |
+ // applied to repository names and can warn the user in advance. |
|
| 36 |
+ // Custom repositories can have different rules, and we must also |
|
| 37 |
+ // allow pushing by image ID. |
|
| 38 |
+ if repoInfo.Official {
|
|
| 39 |
+ username := authConfig.Username |
|
| 40 |
+ if username == "" {
|
|
| 41 |
+ username = "<user>" |
|
| 42 |
+ } |
|
| 43 |
+ return fmt.Errorf("You cannot push a \"root\" repository. Please rename your repository to <user>/<repo> (ex: %s/%s)", username, repoInfo.LocalName)
|
|
| 44 |
+ } |
|
| 45 |
+ |
|
| 46 |
+ v := url.Values{}
|
|
| 47 |
+ v.Set("tag", tag)
|
|
| 48 |
+ |
|
| 49 |
+ push := func(authConfig registry.AuthConfig) error {
|
|
| 50 |
+ buf, err := json.Marshal(authConfig) |
|
| 51 |
+ if err != nil {
|
|
| 52 |
+ return err |
|
| 53 |
+ } |
|
| 54 |
+ registryAuthHeader := []string{
|
|
| 55 |
+ base64.URLEncoding.EncodeToString(buf), |
|
| 56 |
+ } |
|
| 57 |
+ |
|
| 58 |
+ return cli.stream("POST", "/images/"+remote+"/push?"+v.Encode(), nil, cli.out, map[string][]string{
|
|
| 59 |
+ "X-Registry-Auth": registryAuthHeader, |
|
| 60 |
+ }) |
|
| 61 |
+ } |
|
| 62 |
+ |
|
| 63 |
+ if err := push(authConfig); err != nil {
|
|
| 64 |
+ if strings.Contains(err.Error(), "Status 401") {
|
|
| 65 |
+ fmt.Fprintln(cli.out, "\nPlease login prior to push:") |
|
| 66 |
+ if err := cli.CmdLogin(repoInfo.Index.GetAuthConfigKey()); err != nil {
|
|
| 67 |
+ return err |
|
| 68 |
+ } |
|
| 69 |
+ authConfig := cli.configFile.ResolveAuthConfig(repoInfo.Index) |
|
| 70 |
+ return push(authConfig) |
|
| 71 |
+ } |
|
| 72 |
+ return err |
|
| 73 |
+ } |
|
| 74 |
+ return nil |
|
| 75 |
+} |
| 0 | 76 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,23 @@ |
| 0 |
+package client |
|
| 1 |
+ |
|
| 2 |
+import "fmt" |
|
| 3 |
+ |
|
| 4 |
+func (cli *DockerCli) CmdRename(args ...string) error {
|
|
| 5 |
+ cmd := cli.Subcmd("rename", "OLD_NAME NEW_NAME", "Rename a container", true)
|
|
| 6 |
+ if err := cmd.Parse(args); err != nil {
|
|
| 7 |
+ return nil |
|
| 8 |
+ } |
|
| 9 |
+ |
|
| 10 |
+ if cmd.NArg() != 2 {
|
|
| 11 |
+ cmd.Usage() |
|
| 12 |
+ return nil |
|
| 13 |
+ } |
|
| 14 |
+ old_name := cmd.Arg(0) |
|
| 15 |
+ new_name := cmd.Arg(1) |
|
| 16 |
+ |
|
| 17 |
+ if _, _, err := readBody(cli.call("POST", fmt.Sprintf("/containers/%s/rename?name=%s", old_name, new_name), nil, false)); err != nil {
|
|
| 18 |
+ fmt.Fprintf(cli.err, "%s\n", err) |
|
| 19 |
+ return fmt.Errorf("Error: failed to rename container named %s", old_name)
|
|
| 20 |
+ } |
|
| 21 |
+ return nil |
|
| 22 |
+} |
| 0 | 23 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,33 @@ |
| 0 |
+package client |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "fmt" |
|
| 4 |
+ "net/url" |
|
| 5 |
+ "strconv" |
|
| 6 |
+ |
|
| 7 |
+ flag "github.com/docker/docker/pkg/mflag" |
|
| 8 |
+ "github.com/docker/docker/utils" |
|
| 9 |
+) |
|
| 10 |
+ |
|
| 11 |
+func (cli *DockerCli) CmdRestart(args ...string) error {
|
|
| 12 |
+ cmd := cli.Subcmd("restart", "CONTAINER [CONTAINER...]", "Restart a running container", true)
|
|
| 13 |
+ nSeconds := cmd.Int([]string{"t", "-time"}, 10, "Seconds to wait for stop before killing the container")
|
|
| 14 |
+ cmd.Require(flag.Min, 1) |
|
| 15 |
+ |
|
| 16 |
+ utils.ParseFlags(cmd, args, true) |
|
| 17 |
+ |
|
| 18 |
+ v := url.Values{}
|
|
| 19 |
+ v.Set("t", strconv.Itoa(*nSeconds))
|
|
| 20 |
+ |
|
| 21 |
+ var encounteredError error |
|
| 22 |
+ for _, name := range cmd.Args() {
|
|
| 23 |
+ _, _, err := readBody(cli.call("POST", "/containers/"+name+"/restart?"+v.Encode(), nil, false))
|
|
| 24 |
+ if err != nil {
|
|
| 25 |
+ fmt.Fprintf(cli.err, "%s\n", err) |
|
| 26 |
+ encounteredError = fmt.Errorf("Error: failed to restart one or more containers")
|
|
| 27 |
+ } else {
|
|
| 28 |
+ fmt.Fprintf(cli.out, "%s\n", name) |
|
| 29 |
+ } |
|
| 30 |
+ } |
|
| 31 |
+ return encounteredError |
|
| 32 |
+} |
| 0 | 33 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,43 @@ |
| 0 |
+package client |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "fmt" |
|
| 4 |
+ "net/url" |
|
| 5 |
+ |
|
| 6 |
+ flag "github.com/docker/docker/pkg/mflag" |
|
| 7 |
+ "github.com/docker/docker/utils" |
|
| 8 |
+) |
|
| 9 |
+ |
|
| 10 |
+func (cli *DockerCli) CmdRm(args ...string) error {
|
|
| 11 |
+ cmd := cli.Subcmd("rm", "CONTAINER [CONTAINER...]", "Remove one or more containers", true)
|
|
| 12 |
+ v := cmd.Bool([]string{"v", "-volumes"}, false, "Remove the volumes associated with the container")
|
|
| 13 |
+ link := cmd.Bool([]string{"l", "#link", "-link"}, false, "Remove the specified link")
|
|
| 14 |
+ force := cmd.Bool([]string{"f", "-force"}, false, "Force the removal of a running container (uses SIGKILL)")
|
|
| 15 |
+ cmd.Require(flag.Min, 1) |
|
| 16 |
+ |
|
| 17 |
+ utils.ParseFlags(cmd, args, true) |
|
| 18 |
+ |
|
| 19 |
+ val := url.Values{}
|
|
| 20 |
+ if *v {
|
|
| 21 |
+ val.Set("v", "1")
|
|
| 22 |
+ } |
|
| 23 |
+ if *link {
|
|
| 24 |
+ val.Set("link", "1")
|
|
| 25 |
+ } |
|
| 26 |
+ |
|
| 27 |
+ if *force {
|
|
| 28 |
+ val.Set("force", "1")
|
|
| 29 |
+ } |
|
| 30 |
+ |
|
| 31 |
+ var encounteredError error |
|
| 32 |
+ for _, name := range cmd.Args() {
|
|
| 33 |
+ _, _, err := readBody(cli.call("DELETE", "/containers/"+name+"?"+val.Encode(), nil, false))
|
|
| 34 |
+ if err != nil {
|
|
| 35 |
+ fmt.Fprintf(cli.err, "%s\n", err) |
|
| 36 |
+ encounteredError = fmt.Errorf("Error: failed to remove one or more containers")
|
|
| 37 |
+ } else {
|
|
| 38 |
+ fmt.Fprintf(cli.out, "%s\n", name) |
|
| 39 |
+ } |
|
| 40 |
+ } |
|
| 41 |
+ return encounteredError |
|
| 42 |
+} |
| 0 | 43 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,54 @@ |
| 0 |
+package client |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "fmt" |
|
| 4 |
+ "net/url" |
|
| 5 |
+ |
|
| 6 |
+ "github.com/docker/docker/engine" |
|
| 7 |
+ flag "github.com/docker/docker/pkg/mflag" |
|
| 8 |
+ "github.com/docker/docker/utils" |
|
| 9 |
+) |
|
| 10 |
+ |
|
| 11 |
+// 'docker rmi IMAGE' removes all images with the name IMAGE |
|
| 12 |
+func (cli *DockerCli) CmdRmi(args ...string) error {
|
|
| 13 |
+ var ( |
|
| 14 |
+ cmd = cli.Subcmd("rmi", "IMAGE [IMAGE...]", "Remove one or more images", true)
|
|
| 15 |
+ force = cmd.Bool([]string{"f", "-force"}, false, "Force removal of the image")
|
|
| 16 |
+ noprune = cmd.Bool([]string{"-no-prune"}, false, "Do not delete untagged parents")
|
|
| 17 |
+ ) |
|
| 18 |
+ cmd.Require(flag.Min, 1) |
|
| 19 |
+ |
|
| 20 |
+ utils.ParseFlags(cmd, args, true) |
|
| 21 |
+ |
|
| 22 |
+ v := url.Values{}
|
|
| 23 |
+ if *force {
|
|
| 24 |
+ v.Set("force", "1")
|
|
| 25 |
+ } |
|
| 26 |
+ if *noprune {
|
|
| 27 |
+ v.Set("noprune", "1")
|
|
| 28 |
+ } |
|
| 29 |
+ |
|
| 30 |
+ var encounteredError error |
|
| 31 |
+ for _, name := range cmd.Args() {
|
|
| 32 |
+ body, _, err := readBody(cli.call("DELETE", "/images/"+name+"?"+v.Encode(), nil, false))
|
|
| 33 |
+ if err != nil {
|
|
| 34 |
+ fmt.Fprintf(cli.err, "%s\n", err) |
|
| 35 |
+ encounteredError = fmt.Errorf("Error: failed to remove one or more images")
|
|
| 36 |
+ } else {
|
|
| 37 |
+ outs := engine.NewTable("Created", 0)
|
|
| 38 |
+ if _, err := outs.ReadListFrom(body); err != nil {
|
|
| 39 |
+ fmt.Fprintf(cli.err, "%s\n", err) |
|
| 40 |
+ encounteredError = fmt.Errorf("Error: failed to remove one or more images")
|
|
| 41 |
+ continue |
|
| 42 |
+ } |
|
| 43 |
+ for _, out := range outs.Data {
|
|
| 44 |
+ if out.Get("Deleted") != "" {
|
|
| 45 |
+ fmt.Fprintf(cli.out, "Deleted: %s\n", out.Get("Deleted"))
|
|
| 46 |
+ } else {
|
|
| 47 |
+ fmt.Fprintf(cli.out, "Untagged: %s\n", out.Get("Untagged"))
|
|
| 48 |
+ } |
|
| 49 |
+ } |
|
| 50 |
+ } |
|
| 51 |
+ } |
|
| 52 |
+ return encounteredError |
|
| 53 |
+} |
| 0 | 54 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,245 @@ |
| 0 |
+package client |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "fmt" |
|
| 4 |
+ "io" |
|
| 5 |
+ "net/url" |
|
| 6 |
+ "os" |
|
| 7 |
+ |
|
| 8 |
+ log "github.com/Sirupsen/logrus" |
|
| 9 |
+ "github.com/docker/docker/opts" |
|
| 10 |
+ "github.com/docker/docker/pkg/promise" |
|
| 11 |
+ "github.com/docker/docker/pkg/resolvconf" |
|
| 12 |
+ "github.com/docker/docker/pkg/signal" |
|
| 13 |
+ "github.com/docker/docker/runconfig" |
|
| 14 |
+ "github.com/docker/docker/utils" |
|
| 15 |
+) |
|
| 16 |
+ |
|
| 17 |
+func (cid *cidFile) Close() error {
|
|
| 18 |
+ cid.file.Close() |
|
| 19 |
+ |
|
| 20 |
+ if !cid.written {
|
|
| 21 |
+ if err := os.Remove(cid.path); err != nil {
|
|
| 22 |
+ return fmt.Errorf("failed to remove the CID file '%s': %s \n", cid.path, err)
|
|
| 23 |
+ } |
|
| 24 |
+ } |
|
| 25 |
+ |
|
| 26 |
+ return nil |
|
| 27 |
+} |
|
| 28 |
+ |
|
| 29 |
+func (cid *cidFile) Write(id string) error {
|
|
| 30 |
+ if _, err := cid.file.Write([]byte(id)); err != nil {
|
|
| 31 |
+ return fmt.Errorf("Failed to write the container ID to the file: %s", err)
|
|
| 32 |
+ } |
|
| 33 |
+ cid.written = true |
|
| 34 |
+ return nil |
|
| 35 |
+} |
|
| 36 |
+ |
|
| 37 |
+func (cli *DockerCli) CmdRun(args ...string) error {
|
|
| 38 |
+ // FIXME: just use runconfig.Parse already |
|
| 39 |
+ cmd := cli.Subcmd("run", "IMAGE [COMMAND] [ARG...]", "Run a command in a new container", true)
|
|
| 40 |
+ |
|
| 41 |
+ // These are flags not stored in Config/HostConfig |
|
| 42 |
+ var ( |
|
| 43 |
+ flAutoRemove = cmd.Bool([]string{"#rm", "-rm"}, false, "Automatically remove the container when it exits")
|
|
| 44 |
+ flDetach = cmd.Bool([]string{"d", "-detach"}, false, "Run container in background and print container ID")
|
|
| 45 |
+ flSigProxy = cmd.Bool([]string{"#sig-proxy", "-sig-proxy"}, true, "Proxy received signals to the process")
|
|
| 46 |
+ flName = cmd.String([]string{"#name", "-name"}, "", "Assign a name to the container")
|
|
| 47 |
+ flAttach *opts.ListOpts |
|
| 48 |
+ |
|
| 49 |
+ ErrConflictAttachDetach = fmt.Errorf("Conflicting options: -a and -d")
|
|
| 50 |
+ ErrConflictRestartPolicyAndAutoRemove = fmt.Errorf("Conflicting options: --restart and --rm")
|
|
| 51 |
+ ErrConflictDetachAutoRemove = fmt.Errorf("Conflicting options: --rm and -d")
|
|
| 52 |
+ ) |
|
| 53 |
+ |
|
| 54 |
+ config, hostConfig, cmd, err := runconfig.Parse(cmd, args) |
|
| 55 |
+ // just in case the Parse does not exit |
|
| 56 |
+ if err != nil {
|
|
| 57 |
+ utils.ReportError(cmd, err.Error(), true) |
|
| 58 |
+ } |
|
| 59 |
+ |
|
| 60 |
+ if len(hostConfig.Dns) > 0 {
|
|
| 61 |
+ // check the DNS settings passed via --dns against |
|
| 62 |
+ // localhost regexp to warn if they are trying to |
|
| 63 |
+ // set a DNS to a localhost address |
|
| 64 |
+ for _, dnsIP := range hostConfig.Dns {
|
|
| 65 |
+ if resolvconf.IsLocalhost(dnsIP) {
|
|
| 66 |
+ fmt.Fprintf(cli.err, "WARNING: Localhost DNS setting (--dns=%s) may fail in containers.\n", dnsIP) |
|
| 67 |
+ break |
|
| 68 |
+ } |
|
| 69 |
+ } |
|
| 70 |
+ } |
|
| 71 |
+ if config.Image == "" {
|
|
| 72 |
+ cmd.Usage() |
|
| 73 |
+ return nil |
|
| 74 |
+ } |
|
| 75 |
+ |
|
| 76 |
+ if !*flDetach {
|
|
| 77 |
+ if err := cli.CheckTtyInput(config.AttachStdin, config.Tty); err != nil {
|
|
| 78 |
+ return err |
|
| 79 |
+ } |
|
| 80 |
+ } else {
|
|
| 81 |
+ if fl := cmd.Lookup("-attach"); fl != nil {
|
|
| 82 |
+ flAttach = fl.Value.(*opts.ListOpts) |
|
| 83 |
+ if flAttach.Len() != 0 {
|
|
| 84 |
+ return ErrConflictAttachDetach |
|
| 85 |
+ } |
|
| 86 |
+ } |
|
| 87 |
+ if *flAutoRemove {
|
|
| 88 |
+ return ErrConflictDetachAutoRemove |
|
| 89 |
+ } |
|
| 90 |
+ |
|
| 91 |
+ config.AttachStdin = false |
|
| 92 |
+ config.AttachStdout = false |
|
| 93 |
+ config.AttachStderr = false |
|
| 94 |
+ config.StdinOnce = false |
|
| 95 |
+ } |
|
| 96 |
+ |
|
| 97 |
+ // Disable flSigProxy when in TTY mode |
|
| 98 |
+ sigProxy := *flSigProxy |
|
| 99 |
+ if config.Tty {
|
|
| 100 |
+ sigProxy = false |
|
| 101 |
+ } |
|
| 102 |
+ |
|
| 103 |
+ createResponse, err := cli.createContainer(config, hostConfig, hostConfig.ContainerIDFile, *flName) |
|
| 104 |
+ if err != nil {
|
|
| 105 |
+ return err |
|
| 106 |
+ } |
|
| 107 |
+ if sigProxy {
|
|
| 108 |
+ sigc := cli.forwardAllSignals(createResponse.ID) |
|
| 109 |
+ defer signal.StopCatch(sigc) |
|
| 110 |
+ } |
|
| 111 |
+ var ( |
|
| 112 |
+ waitDisplayId chan struct{}
|
|
| 113 |
+ errCh chan error |
|
| 114 |
+ ) |
|
| 115 |
+ if !config.AttachStdout && !config.AttachStderr {
|
|
| 116 |
+ // Make this asynchronous to allow the client to write to stdin before having to read the ID |
|
| 117 |
+ waitDisplayId = make(chan struct{})
|
|
| 118 |
+ go func() {
|
|
| 119 |
+ defer close(waitDisplayId) |
|
| 120 |
+ fmt.Fprintf(cli.out, "%s\n", createResponse.ID) |
|
| 121 |
+ }() |
|
| 122 |
+ } |
|
| 123 |
+ if *flAutoRemove && (hostConfig.RestartPolicy.Name == "always" || hostConfig.RestartPolicy.Name == "on-failure") {
|
|
| 124 |
+ return ErrConflictRestartPolicyAndAutoRemove |
|
| 125 |
+ } |
|
| 126 |
+ // We need to instantiate the chan because the select needs it. It can |
|
| 127 |
+ // be closed but can't be uninitialized. |
|
| 128 |
+ hijacked := make(chan io.Closer) |
|
| 129 |
+ // Block the return until the chan gets closed |
|
| 130 |
+ defer func() {
|
|
| 131 |
+ log.Debugf("End of CmdRun(), Waiting for hijack to finish.")
|
|
| 132 |
+ if _, ok := <-hijacked; ok {
|
|
| 133 |
+ log.Errorf("Hijack did not finish (chan still open)")
|
|
| 134 |
+ } |
|
| 135 |
+ }() |
|
| 136 |
+ if config.AttachStdin || config.AttachStdout || config.AttachStderr {
|
|
| 137 |
+ var ( |
|
| 138 |
+ out, stderr io.Writer |
|
| 139 |
+ in io.ReadCloser |
|
| 140 |
+ v = url.Values{}
|
|
| 141 |
+ ) |
|
| 142 |
+ v.Set("stream", "1")
|
|
| 143 |
+ if config.AttachStdin {
|
|
| 144 |
+ v.Set("stdin", "1")
|
|
| 145 |
+ in = cli.in |
|
| 146 |
+ } |
|
| 147 |
+ if config.AttachStdout {
|
|
| 148 |
+ v.Set("stdout", "1")
|
|
| 149 |
+ out = cli.out |
|
| 150 |
+ } |
|
| 151 |
+ if config.AttachStderr {
|
|
| 152 |
+ v.Set("stderr", "1")
|
|
| 153 |
+ if config.Tty {
|
|
| 154 |
+ stderr = cli.out |
|
| 155 |
+ } else {
|
|
| 156 |
+ stderr = cli.err |
|
| 157 |
+ } |
|
| 158 |
+ } |
|
| 159 |
+ errCh = promise.Go(func() error {
|
|
| 160 |
+ return cli.hijack("POST", "/containers/"+createResponse.ID+"/attach?"+v.Encode(), config.Tty, in, out, stderr, hijacked, nil)
|
|
| 161 |
+ }) |
|
| 162 |
+ } else {
|
|
| 163 |
+ close(hijacked) |
|
| 164 |
+ } |
|
| 165 |
+ // Acknowledge the hijack before starting |
|
| 166 |
+ select {
|
|
| 167 |
+ case closer := <-hijacked: |
|
| 168 |
+ // Make sure that the hijack gets closed when returning (results |
|
| 169 |
+ // in closing the hijack chan and freeing server's goroutines) |
|
| 170 |
+ if closer != nil {
|
|
| 171 |
+ defer closer.Close() |
|
| 172 |
+ } |
|
| 173 |
+ case err := <-errCh: |
|
| 174 |
+ if err != nil {
|
|
| 175 |
+ log.Debugf("Error hijack: %s", err)
|
|
| 176 |
+ return err |
|
| 177 |
+ } |
|
| 178 |
+ } |
|
| 179 |
+ |
|
| 180 |
+ defer func() {
|
|
| 181 |
+ if *flAutoRemove {
|
|
| 182 |
+ if _, _, err = readBody(cli.call("DELETE", "/containers/"+createResponse.ID+"?v=1", nil, false)); err != nil {
|
|
| 183 |
+ log.Errorf("Error deleting container: %s", err)
|
|
| 184 |
+ } |
|
| 185 |
+ } |
|
| 186 |
+ }() |
|
| 187 |
+ |
|
| 188 |
+ //start the container |
|
| 189 |
+ if _, _, err = readBody(cli.call("POST", "/containers/"+createResponse.ID+"/start", nil, false)); err != nil {
|
|
| 190 |
+ return err |
|
| 191 |
+ } |
|
| 192 |
+ |
|
| 193 |
+ if (config.AttachStdin || config.AttachStdout || config.AttachStderr) && config.Tty && cli.isTerminalOut {
|
|
| 194 |
+ if err := cli.monitorTtySize(createResponse.ID, false); err != nil {
|
|
| 195 |
+ log.Errorf("Error monitoring TTY size: %s", err)
|
|
| 196 |
+ } |
|
| 197 |
+ } |
|
| 198 |
+ |
|
| 199 |
+ if errCh != nil {
|
|
| 200 |
+ if err := <-errCh; err != nil {
|
|
| 201 |
+ log.Debugf("Error hijack: %s", err)
|
|
| 202 |
+ return err |
|
| 203 |
+ } |
|
| 204 |
+ } |
|
| 205 |
+ |
|
| 206 |
+ // Detached mode: wait for the id to be displayed and return. |
|
| 207 |
+ if !config.AttachStdout && !config.AttachStderr {
|
|
| 208 |
+ // Detached mode |
|
| 209 |
+ <-waitDisplayId |
|
| 210 |
+ return nil |
|
| 211 |
+ } |
|
| 212 |
+ |
|
| 213 |
+ var status int |
|
| 214 |
+ |
|
| 215 |
+ // Attached mode |
|
| 216 |
+ if *flAutoRemove {
|
|
| 217 |
+ // Autoremove: wait for the container to finish, retrieve |
|
| 218 |
+ // the exit code and remove the container |
|
| 219 |
+ if _, _, err := readBody(cli.call("POST", "/containers/"+createResponse.ID+"/wait", nil, false)); err != nil {
|
|
| 220 |
+ return err |
|
| 221 |
+ } |
|
| 222 |
+ if _, status, err = getExitCode(cli, createResponse.ID); err != nil {
|
|
| 223 |
+ return err |
|
| 224 |
+ } |
|
| 225 |
+ } else {
|
|
| 226 |
+ // No Autoremove: Simply retrieve the exit code |
|
| 227 |
+ if !config.Tty {
|
|
| 228 |
+ // In non-TTY mode, we can't detach, so we must wait for container exit |
|
| 229 |
+ if status, err = waitForExit(cli, createResponse.ID); err != nil {
|
|
| 230 |
+ return err |
|
| 231 |
+ } |
|
| 232 |
+ } else {
|
|
| 233 |
+ // In TTY mode, there is a race: if the process dies too slowly, the state could |
|
| 234 |
+ // be updated after the getExitCode call and result in the wrong exit code being reported |
|
| 235 |
+ if _, status, err = getExitCode(cli, createResponse.ID); err != nil {
|
|
| 236 |
+ return err |
|
| 237 |
+ } |
|
| 238 |
+ } |
|
| 239 |
+ } |
|
| 240 |
+ if status != 0 {
|
|
| 241 |
+ return &utils.StatusError{StatusCode: status}
|
|
| 242 |
+ } |
|
| 243 |
+ return nil |
|
| 244 |
+} |
| 0 | 245 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,48 @@ |
| 0 |
+package client |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "errors" |
|
| 4 |
+ "io" |
|
| 5 |
+ "net/url" |
|
| 6 |
+ "os" |
|
| 7 |
+ |
|
| 8 |
+ flag "github.com/docker/docker/pkg/mflag" |
|
| 9 |
+ "github.com/docker/docker/utils" |
|
| 10 |
+) |
|
| 11 |
+ |
|
| 12 |
+func (cli *DockerCli) CmdSave(args ...string) error {
|
|
| 13 |
+ cmd := cli.Subcmd("save", "IMAGE [IMAGE...]", "Save an image(s) to a tar archive (streamed to STDOUT by default)", true)
|
|
| 14 |
+ outfile := cmd.String([]string{"o", "-output"}, "", "Write to an file, instead of STDOUT")
|
|
| 15 |
+ cmd.Require(flag.Min, 1) |
|
| 16 |
+ |
|
| 17 |
+ utils.ParseFlags(cmd, args, true) |
|
| 18 |
+ |
|
| 19 |
+ var ( |
|
| 20 |
+ output io.Writer = cli.out |
|
| 21 |
+ err error |
|
| 22 |
+ ) |
|
| 23 |
+ if *outfile != "" {
|
|
| 24 |
+ output, err = os.Create(*outfile) |
|
| 25 |
+ if err != nil {
|
|
| 26 |
+ return err |
|
| 27 |
+ } |
|
| 28 |
+ } else if cli.isTerminalOut {
|
|
| 29 |
+ return errors.New("Cowardly refusing to save to a terminal. Use the -o flag or redirect.")
|
|
| 30 |
+ } |
|
| 31 |
+ |
|
| 32 |
+ if len(cmd.Args()) == 1 {
|
|
| 33 |
+ image := cmd.Arg(0) |
|
| 34 |
+ if err := cli.stream("GET", "/images/"+image+"/get", nil, output, nil); err != nil {
|
|
| 35 |
+ return err |
|
| 36 |
+ } |
|
| 37 |
+ } else {
|
|
| 38 |
+ v := url.Values{}
|
|
| 39 |
+ for _, arg := range cmd.Args() {
|
|
| 40 |
+ v.Add("names", arg)
|
|
| 41 |
+ } |
|
| 42 |
+ if err := cli.stream("GET", "/images/get?"+v.Encode(), nil, output, nil); err != nil {
|
|
| 43 |
+ return err |
|
| 44 |
+ } |
|
| 45 |
+ } |
|
| 46 |
+ return nil |
|
| 47 |
+} |
| 0 | 48 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,60 @@ |
| 0 |
+package client |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "fmt" |
|
| 4 |
+ "net/url" |
|
| 5 |
+ "strings" |
|
| 6 |
+ "text/tabwriter" |
|
| 7 |
+ |
|
| 8 |
+ "github.com/docker/docker/engine" |
|
| 9 |
+ flag "github.com/docker/docker/pkg/mflag" |
|
| 10 |
+ "github.com/docker/docker/utils" |
|
| 11 |
+) |
|
| 12 |
+ |
|
| 13 |
+func (cli *DockerCli) CmdSearch(args ...string) error {
|
|
| 14 |
+ cmd := cli.Subcmd("search", "TERM", "Search the Docker Hub for images", true)
|
|
| 15 |
+ noTrunc := cmd.Bool([]string{"#notrunc", "-no-trunc"}, false, "Don't truncate output")
|
|
| 16 |
+ trusted := cmd.Bool([]string{"#t", "#trusted", "#-trusted"}, false, "Only show trusted builds")
|
|
| 17 |
+ automated := cmd.Bool([]string{"-automated"}, false, "Only show automated builds")
|
|
| 18 |
+ stars := cmd.Int([]string{"s", "#stars", "-stars"}, 0, "Only displays with at least x stars")
|
|
| 19 |
+ cmd.Require(flag.Exact, 1) |
|
| 20 |
+ |
|
| 21 |
+ utils.ParseFlags(cmd, args, true) |
|
| 22 |
+ |
|
| 23 |
+ v := url.Values{}
|
|
| 24 |
+ v.Set("term", cmd.Arg(0))
|
|
| 25 |
+ |
|
| 26 |
+ body, _, err := readBody(cli.call("GET", "/images/search?"+v.Encode(), nil, true))
|
|
| 27 |
+ |
|
| 28 |
+ if err != nil {
|
|
| 29 |
+ return err |
|
| 30 |
+ } |
|
| 31 |
+ outs := engine.NewTable("star_count", 0)
|
|
| 32 |
+ if _, err := outs.ReadListFrom(body); err != nil {
|
|
| 33 |
+ return err |
|
| 34 |
+ } |
|
| 35 |
+ w := tabwriter.NewWriter(cli.out, 10, 1, 3, ' ', 0) |
|
| 36 |
+ fmt.Fprintf(w, "NAME\tDESCRIPTION\tSTARS\tOFFICIAL\tAUTOMATED\n") |
|
| 37 |
+ for _, out := range outs.Data {
|
|
| 38 |
+ if ((*automated || *trusted) && (!out.GetBool("is_trusted") && !out.GetBool("is_automated"))) || (*stars > out.GetInt("star_count")) {
|
|
| 39 |
+ continue |
|
| 40 |
+ } |
|
| 41 |
+ desc := strings.Replace(out.Get("description"), "\n", " ", -1)
|
|
| 42 |
+ desc = strings.Replace(desc, "\r", " ", -1) |
|
| 43 |
+ if !*noTrunc && len(desc) > 45 {
|
|
| 44 |
+ desc = utils.Trunc(desc, 42) + "..." |
|
| 45 |
+ } |
|
| 46 |
+ fmt.Fprintf(w, "%s\t%s\t%d\t", out.Get("name"), desc, out.GetInt("star_count"))
|
|
| 47 |
+ if out.GetBool("is_official") {
|
|
| 48 |
+ fmt.Fprint(w, "[OK]") |
|
| 49 |
+ |
|
| 50 |
+ } |
|
| 51 |
+ fmt.Fprint(w, "\t") |
|
| 52 |
+ if out.GetBool("is_automated") || out.GetBool("is_trusted") {
|
|
| 53 |
+ fmt.Fprint(w, "[OK]") |
|
| 54 |
+ } |
|
| 55 |
+ fmt.Fprint(w, "\n") |
|
| 56 |
+ } |
|
| 57 |
+ w.Flush() |
|
| 58 |
+ return nil |
|
| 59 |
+} |
| 0 | 60 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,160 @@ |
| 0 |
+package client |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "fmt" |
|
| 4 |
+ "io" |
|
| 5 |
+ "net/url" |
|
| 6 |
+ "os" |
|
| 7 |
+ |
|
| 8 |
+ log "github.com/Sirupsen/logrus" |
|
| 9 |
+ "github.com/docker/docker/engine" |
|
| 10 |
+ flag "github.com/docker/docker/pkg/mflag" |
|
| 11 |
+ "github.com/docker/docker/pkg/promise" |
|
| 12 |
+ "github.com/docker/docker/pkg/signal" |
|
| 13 |
+ "github.com/docker/docker/utils" |
|
| 14 |
+) |
|
| 15 |
+ |
|
| 16 |
+func (cli *DockerCli) forwardAllSignals(cid string) chan os.Signal {
|
|
| 17 |
+ sigc := make(chan os.Signal, 128) |
|
| 18 |
+ signal.CatchAll(sigc) |
|
| 19 |
+ go func() {
|
|
| 20 |
+ for s := range sigc {
|
|
| 21 |
+ if s == signal.SIGCHLD {
|
|
| 22 |
+ continue |
|
| 23 |
+ } |
|
| 24 |
+ var sig string |
|
| 25 |
+ for sigStr, sigN := range signal.SignalMap {
|
|
| 26 |
+ if sigN == s {
|
|
| 27 |
+ sig = sigStr |
|
| 28 |
+ break |
|
| 29 |
+ } |
|
| 30 |
+ } |
|
| 31 |
+ if sig == "" {
|
|
| 32 |
+ log.Errorf("Unsupported signal: %v. Discarding.", s)
|
|
| 33 |
+ } |
|
| 34 |
+ if _, _, err := readBody(cli.call("POST", fmt.Sprintf("/containers/%s/kill?signal=%s", cid, sig), nil, false)); err != nil {
|
|
| 35 |
+ log.Debugf("Error sending signal: %s", err)
|
|
| 36 |
+ } |
|
| 37 |
+ } |
|
| 38 |
+ }() |
|
| 39 |
+ return sigc |
|
| 40 |
+} |
|
| 41 |
+ |
|
| 42 |
+func (cli *DockerCli) CmdStart(args ...string) error {
|
|
| 43 |
+ var ( |
|
| 44 |
+ cErr chan error |
|
| 45 |
+ tty bool |
|
| 46 |
+ |
|
| 47 |
+ cmd = cli.Subcmd("start", "CONTAINER [CONTAINER...]", "Start one or more stopped containers", true)
|
|
| 48 |
+ attach = cmd.Bool([]string{"a", "-attach"}, false, "Attach STDOUT/STDERR and forward signals")
|
|
| 49 |
+ openStdin = cmd.Bool([]string{"i", "-interactive"}, false, "Attach container's STDIN")
|
|
| 50 |
+ ) |
|
| 51 |
+ |
|
| 52 |
+ cmd.Require(flag.Min, 1) |
|
| 53 |
+ utils.ParseFlags(cmd, args, true) |
|
| 54 |
+ |
|
| 55 |
+ if *attach || *openStdin {
|
|
| 56 |
+ if cmd.NArg() > 1 {
|
|
| 57 |
+ return fmt.Errorf("You cannot start and attach multiple containers at once.")
|
|
| 58 |
+ } |
|
| 59 |
+ |
|
| 60 |
+ stream, _, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/json", nil, false)
|
|
| 61 |
+ if err != nil {
|
|
| 62 |
+ return err |
|
| 63 |
+ } |
|
| 64 |
+ |
|
| 65 |
+ env := engine.Env{}
|
|
| 66 |
+ if err := env.Decode(stream); err != nil {
|
|
| 67 |
+ return err |
|
| 68 |
+ } |
|
| 69 |
+ config := env.GetSubEnv("Config")
|
|
| 70 |
+ tty = config.GetBool("Tty")
|
|
| 71 |
+ |
|
| 72 |
+ if !tty {
|
|
| 73 |
+ sigc := cli.forwardAllSignals(cmd.Arg(0)) |
|
| 74 |
+ defer signal.StopCatch(sigc) |
|
| 75 |
+ } |
|
| 76 |
+ |
|
| 77 |
+ var in io.ReadCloser |
|
| 78 |
+ |
|
| 79 |
+ v := url.Values{}
|
|
| 80 |
+ v.Set("stream", "1")
|
|
| 81 |
+ |
|
| 82 |
+ if *openStdin && config.GetBool("OpenStdin") {
|
|
| 83 |
+ v.Set("stdin", "1")
|
|
| 84 |
+ in = cli.in |
|
| 85 |
+ } |
|
| 86 |
+ |
|
| 87 |
+ v.Set("stdout", "1")
|
|
| 88 |
+ v.Set("stderr", "1")
|
|
| 89 |
+ |
|
| 90 |
+ hijacked := make(chan io.Closer) |
|
| 91 |
+ // Block the return until the chan gets closed |
|
| 92 |
+ defer func() {
|
|
| 93 |
+ log.Debugf("CmdStart() returned, defer waiting for hijack to finish.")
|
|
| 94 |
+ if _, ok := <-hijacked; ok {
|
|
| 95 |
+ log.Errorf("Hijack did not finish (chan still open)")
|
|
| 96 |
+ } |
|
| 97 |
+ cli.in.Close() |
|
| 98 |
+ }() |
|
| 99 |
+ cErr = promise.Go(func() error {
|
|
| 100 |
+ return cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), tty, in, cli.out, cli.err, hijacked, nil)
|
|
| 101 |
+ }) |
|
| 102 |
+ |
|
| 103 |
+ // Acknowledge the hijack before starting |
|
| 104 |
+ select {
|
|
| 105 |
+ case closer := <-hijacked: |
|
| 106 |
+ // Make sure that the hijack gets closed when returning (results |
|
| 107 |
+ // in closing the hijack chan and freeing server's goroutines) |
|
| 108 |
+ if closer != nil {
|
|
| 109 |
+ defer closer.Close() |
|
| 110 |
+ } |
|
| 111 |
+ case err := <-cErr: |
|
| 112 |
+ if err != nil {
|
|
| 113 |
+ return err |
|
| 114 |
+ } |
|
| 115 |
+ } |
|
| 116 |
+ } |
|
| 117 |
+ |
|
| 118 |
+ var encounteredError error |
|
| 119 |
+ for _, name := range cmd.Args() {
|
|
| 120 |
+ _, _, err := readBody(cli.call("POST", "/containers/"+name+"/start", nil, false))
|
|
| 121 |
+ if err != nil {
|
|
| 122 |
+ if !*attach && !*openStdin {
|
|
| 123 |
+ // attach and openStdin is false means it could be starting multiple containers |
|
| 124 |
+ // when a container start failed, show the error message and start next |
|
| 125 |
+ fmt.Fprintf(cli.err, "%s\n", err) |
|
| 126 |
+ encounteredError = fmt.Errorf("Error: failed to start one or more containers")
|
|
| 127 |
+ } else {
|
|
| 128 |
+ encounteredError = err |
|
| 129 |
+ } |
|
| 130 |
+ } else {
|
|
| 131 |
+ if !*attach && !*openStdin {
|
|
| 132 |
+ fmt.Fprintf(cli.out, "%s\n", name) |
|
| 133 |
+ } |
|
| 134 |
+ } |
|
| 135 |
+ } |
|
| 136 |
+ |
|
| 137 |
+ if encounteredError != nil {
|
|
| 138 |
+ return encounteredError |
|
| 139 |
+ } |
|
| 140 |
+ |
|
| 141 |
+ if *openStdin || *attach {
|
|
| 142 |
+ if tty && cli.isTerminalOut {
|
|
| 143 |
+ if err := cli.monitorTtySize(cmd.Arg(0), false); err != nil {
|
|
| 144 |
+ log.Errorf("Error monitoring TTY size: %s", err)
|
|
| 145 |
+ } |
|
| 146 |
+ } |
|
| 147 |
+ if attchErr := <-cErr; attchErr != nil {
|
|
| 148 |
+ return attchErr |
|
| 149 |
+ } |
|
| 150 |
+ _, status, err := getExitCode(cli, cmd.Arg(0)) |
|
| 151 |
+ if err != nil {
|
|
| 152 |
+ return err |
|
| 153 |
+ } |
|
| 154 |
+ if status != 0 {
|
|
| 155 |
+ return &utils.StatusError{StatusCode: status}
|
|
| 156 |
+ } |
|
| 157 |
+ } |
|
| 158 |
+ return nil |
|
| 159 |
+} |
| 0 | 160 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,177 @@ |
| 0 |
+package client |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "encoding/json" |
|
| 4 |
+ "fmt" |
|
| 5 |
+ "io" |
|
| 6 |
+ "sort" |
|
| 7 |
+ "strings" |
|
| 8 |
+ "sync" |
|
| 9 |
+ "text/tabwriter" |
|
| 10 |
+ "time" |
|
| 11 |
+ |
|
| 12 |
+ "github.com/docker/docker/api/types" |
|
| 13 |
+ flag "github.com/docker/docker/pkg/mflag" |
|
| 14 |
+ "github.com/docker/docker/pkg/units" |
|
| 15 |
+ "github.com/docker/docker/utils" |
|
| 16 |
+) |
|
| 17 |
+ |
|
| 18 |
+type containerStats struct {
|
|
| 19 |
+ Name string |
|
| 20 |
+ CpuPercentage float64 |
|
| 21 |
+ Memory float64 |
|
| 22 |
+ MemoryLimit float64 |
|
| 23 |
+ MemoryPercentage float64 |
|
| 24 |
+ NetworkRx float64 |
|
| 25 |
+ NetworkTx float64 |
|
| 26 |
+ mu sync.RWMutex |
|
| 27 |
+ err error |
|
| 28 |
+} |
|
| 29 |
+ |
|
| 30 |
+func (s *containerStats) Collect(cli *DockerCli) {
|
|
| 31 |
+ stream, _, err := cli.call("GET", "/containers/"+s.Name+"/stats", nil, false)
|
|
| 32 |
+ if err != nil {
|
|
| 33 |
+ s.err = err |
|
| 34 |
+ return |
|
| 35 |
+ } |
|
| 36 |
+ defer stream.Close() |
|
| 37 |
+ var ( |
|
| 38 |
+ previousCpu uint64 |
|
| 39 |
+ previousSystem uint64 |
|
| 40 |
+ start = true |
|
| 41 |
+ dec = json.NewDecoder(stream) |
|
| 42 |
+ u = make(chan error, 1) |
|
| 43 |
+ ) |
|
| 44 |
+ go func() {
|
|
| 45 |
+ for {
|
|
| 46 |
+ var v *types.Stats |
|
| 47 |
+ if err := dec.Decode(&v); err != nil {
|
|
| 48 |
+ u <- err |
|
| 49 |
+ return |
|
| 50 |
+ } |
|
| 51 |
+ var ( |
|
| 52 |
+ memPercent = float64(v.MemoryStats.Usage) / float64(v.MemoryStats.Limit) * 100.0 |
|
| 53 |
+ cpuPercent = 0.0 |
|
| 54 |
+ ) |
|
| 55 |
+ if !start {
|
|
| 56 |
+ cpuPercent = calculateCpuPercent(previousCpu, previousSystem, v) |
|
| 57 |
+ } |
|
| 58 |
+ start = false |
|
| 59 |
+ s.mu.Lock() |
|
| 60 |
+ s.CpuPercentage = cpuPercent |
|
| 61 |
+ s.Memory = float64(v.MemoryStats.Usage) |
|
| 62 |
+ s.MemoryLimit = float64(v.MemoryStats.Limit) |
|
| 63 |
+ s.MemoryPercentage = memPercent |
|
| 64 |
+ s.NetworkRx = float64(v.Network.RxBytes) |
|
| 65 |
+ s.NetworkTx = float64(v.Network.TxBytes) |
|
| 66 |
+ s.mu.Unlock() |
|
| 67 |
+ previousCpu = v.CpuStats.CpuUsage.TotalUsage |
|
| 68 |
+ previousSystem = v.CpuStats.SystemUsage |
|
| 69 |
+ u <- nil |
|
| 70 |
+ } |
|
| 71 |
+ }() |
|
| 72 |
+ for {
|
|
| 73 |
+ select {
|
|
| 74 |
+ case <-time.After(2 * time.Second): |
|
| 75 |
+ // zero out the values if we have not received an update within |
|
| 76 |
+ // the specified duration. |
|
| 77 |
+ s.mu.Lock() |
|
| 78 |
+ s.CpuPercentage = 0 |
|
| 79 |
+ s.Memory = 0 |
|
| 80 |
+ s.MemoryPercentage = 0 |
|
| 81 |
+ s.mu.Unlock() |
|
| 82 |
+ case err := <-u: |
|
| 83 |
+ if err != nil {
|
|
| 84 |
+ s.mu.Lock() |
|
| 85 |
+ s.err = err |
|
| 86 |
+ s.mu.Unlock() |
|
| 87 |
+ return |
|
| 88 |
+ } |
|
| 89 |
+ } |
|
| 90 |
+ } |
|
| 91 |
+} |
|
| 92 |
+ |
|
| 93 |
+func (s *containerStats) Display(w io.Writer) error {
|
|
| 94 |
+ s.mu.RLock() |
|
| 95 |
+ defer s.mu.RUnlock() |
|
| 96 |
+ if s.err != nil {
|
|
| 97 |
+ return s.err |
|
| 98 |
+ } |
|
| 99 |
+ fmt.Fprintf(w, "%s\t%.2f%%\t%s/%s\t%.2f%%\t%s/%s\n", |
|
| 100 |
+ s.Name, |
|
| 101 |
+ s.CpuPercentage, |
|
| 102 |
+ units.BytesSize(s.Memory), units.BytesSize(s.MemoryLimit), |
|
| 103 |
+ s.MemoryPercentage, |
|
| 104 |
+ units.BytesSize(s.NetworkRx), units.BytesSize(s.NetworkTx)) |
|
| 105 |
+ return nil |
|
| 106 |
+} |
|
| 107 |
+ |
|
| 108 |
+func (cli *DockerCli) CmdStats(args ...string) error {
|
|
| 109 |
+ cmd := cli.Subcmd("stats", "CONTAINER [CONTAINER...]", "Display a live stream of one or more containers' resource usage statistics", true)
|
|
| 110 |
+ cmd.Require(flag.Min, 1) |
|
| 111 |
+ utils.ParseFlags(cmd, args, true) |
|
| 112 |
+ |
|
| 113 |
+ names := cmd.Args() |
|
| 114 |
+ sort.Strings(names) |
|
| 115 |
+ var ( |
|
| 116 |
+ cStats []*containerStats |
|
| 117 |
+ w = tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0) |
|
| 118 |
+ ) |
|
| 119 |
+ printHeader := func() {
|
|
| 120 |
+ fmt.Fprint(cli.out, "\033[2J") |
|
| 121 |
+ fmt.Fprint(cli.out, "\033[H") |
|
| 122 |
+ fmt.Fprintln(w, "CONTAINER\tCPU %\tMEM USAGE/LIMIT\tMEM %\tNET I/O") |
|
| 123 |
+ } |
|
| 124 |
+ for _, n := range names {
|
|
| 125 |
+ s := &containerStats{Name: n}
|
|
| 126 |
+ cStats = append(cStats, s) |
|
| 127 |
+ go s.Collect(cli) |
|
| 128 |
+ } |
|
| 129 |
+ // do a quick pause so that any failed connections for containers that do not exist are able to be |
|
| 130 |
+ // evicted before we display the initial or default values. |
|
| 131 |
+ time.Sleep(500 * time.Millisecond) |
|
| 132 |
+ var errs []string |
|
| 133 |
+ for _, c := range cStats {
|
|
| 134 |
+ c.mu.Lock() |
|
| 135 |
+ if c.err != nil {
|
|
| 136 |
+ errs = append(errs, fmt.Sprintf("%s: %v", c.Name, c.err))
|
|
| 137 |
+ } |
|
| 138 |
+ c.mu.Unlock() |
|
| 139 |
+ } |
|
| 140 |
+ if len(errs) > 0 {
|
|
| 141 |
+ return fmt.Errorf("%s", strings.Join(errs, ", "))
|
|
| 142 |
+ } |
|
| 143 |
+ for _ = range time.Tick(500 * time.Millisecond) {
|
|
| 144 |
+ printHeader() |
|
| 145 |
+ toRemove := []int{}
|
|
| 146 |
+ for i, s := range cStats {
|
|
| 147 |
+ if err := s.Display(w); err != nil {
|
|
| 148 |
+ toRemove = append(toRemove, i) |
|
| 149 |
+ } |
|
| 150 |
+ } |
|
| 151 |
+ for j := len(toRemove) - 1; j >= 0; j-- {
|
|
| 152 |
+ i := toRemove[j] |
|
| 153 |
+ cStats = append(cStats[:i], cStats[i+1:]...) |
|
| 154 |
+ } |
|
| 155 |
+ if len(cStats) == 0 {
|
|
| 156 |
+ return nil |
|
| 157 |
+ } |
|
| 158 |
+ w.Flush() |
|
| 159 |
+ } |
|
| 160 |
+ return nil |
|
| 161 |
+} |
|
| 162 |
+ |
|
| 163 |
+func calculateCpuPercent(previousCpu, previousSystem uint64, v *types.Stats) float64 {
|
|
| 164 |
+ var ( |
|
| 165 |
+ cpuPercent = 0.0 |
|
| 166 |
+ // calculate the change for the cpu usage of the container in between readings |
|
| 167 |
+ cpuDelta = float64(v.CpuStats.CpuUsage.TotalUsage - previousCpu) |
|
| 168 |
+ // calculate the change for the entire system between readings |
|
| 169 |
+ systemDelta = float64(v.CpuStats.SystemUsage - previousSystem) |
|
| 170 |
+ ) |
|
| 171 |
+ |
|
| 172 |
+ if systemDelta > 0.0 && cpuDelta > 0.0 {
|
|
| 173 |
+ cpuPercent = (cpuDelta / systemDelta) * float64(len(v.CpuStats.CpuUsage.PercpuUsage)) * 100.0 |
|
| 174 |
+ } |
|
| 175 |
+ return cpuPercent |
|
| 176 |
+} |
| 0 | 177 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,33 @@ |
| 0 |
+package client |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "fmt" |
|
| 4 |
+ "net/url" |
|
| 5 |
+ "strconv" |
|
| 6 |
+ |
|
| 7 |
+ flag "github.com/docker/docker/pkg/mflag" |
|
| 8 |
+ "github.com/docker/docker/utils" |
|
| 9 |
+) |
|
| 10 |
+ |
|
| 11 |
+func (cli *DockerCli) CmdStop(args ...string) error {
|
|
| 12 |
+ cmd := cli.Subcmd("stop", "CONTAINER [CONTAINER...]", "Stop a running container by sending SIGTERM and then SIGKILL after a\ngrace period", true)
|
|
| 13 |
+ nSeconds := cmd.Int([]string{"t", "-time"}, 10, "Seconds to wait for stop before killing it")
|
|
| 14 |
+ cmd.Require(flag.Min, 1) |
|
| 15 |
+ |
|
| 16 |
+ utils.ParseFlags(cmd, args, true) |
|
| 17 |
+ |
|
| 18 |
+ v := url.Values{}
|
|
| 19 |
+ v.Set("t", strconv.Itoa(*nSeconds))
|
|
| 20 |
+ |
|
| 21 |
+ var encounteredError error |
|
| 22 |
+ for _, name := range cmd.Args() {
|
|
| 23 |
+ _, _, err := readBody(cli.call("POST", "/containers/"+name+"/stop?"+v.Encode(), nil, false))
|
|
| 24 |
+ if err != nil {
|
|
| 25 |
+ fmt.Fprintf(cli.err, "%s\n", err) |
|
| 26 |
+ encounteredError = fmt.Errorf("Error: failed to stop one or more containers")
|
|
| 27 |
+ } else {
|
|
| 28 |
+ fmt.Fprintf(cli.out, "%s\n", name) |
|
| 29 |
+ } |
|
| 30 |
+ } |
|
| 31 |
+ return encounteredError |
|
| 32 |
+} |
| 0 | 33 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,39 @@ |
| 0 |
+package client |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "net/url" |
|
| 4 |
+ |
|
| 5 |
+ flag "github.com/docker/docker/pkg/mflag" |
|
| 6 |
+ "github.com/docker/docker/pkg/parsers" |
|
| 7 |
+ "github.com/docker/docker/registry" |
|
| 8 |
+ "github.com/docker/docker/utils" |
|
| 9 |
+) |
|
| 10 |
+ |
|
| 11 |
+func (cli *DockerCli) CmdTag(args ...string) error {
|
|
| 12 |
+ cmd := cli.Subcmd("tag", "IMAGE[:TAG] [REGISTRYHOST/][USERNAME/]NAME[:TAG]", "Tag an image into a repository", true)
|
|
| 13 |
+ force := cmd.Bool([]string{"f", "#force", "-force"}, false, "Force")
|
|
| 14 |
+ cmd.Require(flag.Exact, 2) |
|
| 15 |
+ |
|
| 16 |
+ utils.ParseFlags(cmd, args, true) |
|
| 17 |
+ |
|
| 18 |
+ var ( |
|
| 19 |
+ repository, tag = parsers.ParseRepositoryTag(cmd.Arg(1)) |
|
| 20 |
+ v = url.Values{}
|
|
| 21 |
+ ) |
|
| 22 |
+ |
|
| 23 |
+ //Check if the given image name can be resolved |
|
| 24 |
+ if err := registry.ValidateRepositoryName(repository); err != nil {
|
|
| 25 |
+ return err |
|
| 26 |
+ } |
|
| 27 |
+ v.Set("repo", repository)
|
|
| 28 |
+ v.Set("tag", tag)
|
|
| 29 |
+ |
|
| 30 |
+ if *force {
|
|
| 31 |
+ v.Set("force", "1")
|
|
| 32 |
+ } |
|
| 33 |
+ |
|
| 34 |
+ if _, _, err := readBody(cli.call("POST", "/images/"+cmd.Arg(0)+"/tag?"+v.Encode(), nil, false)); err != nil {
|
|
| 35 |
+ return err |
|
| 36 |
+ } |
|
| 37 |
+ return nil |
|
| 38 |
+} |
| 0 | 39 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,44 @@ |
| 0 |
+package client |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "fmt" |
|
| 4 |
+ "net/url" |
|
| 5 |
+ "strings" |
|
| 6 |
+ "text/tabwriter" |
|
| 7 |
+ |
|
| 8 |
+ "github.com/docker/docker/engine" |
|
| 9 |
+ flag "github.com/docker/docker/pkg/mflag" |
|
| 10 |
+ "github.com/docker/docker/utils" |
|
| 11 |
+) |
|
| 12 |
+ |
|
| 13 |
+func (cli *DockerCli) CmdTop(args ...string) error {
|
|
| 14 |
+ cmd := cli.Subcmd("top", "CONTAINER [ps OPTIONS]", "Display the running processes of a container", true)
|
|
| 15 |
+ cmd.Require(flag.Min, 1) |
|
| 16 |
+ |
|
| 17 |
+ utils.ParseFlags(cmd, args, true) |
|
| 18 |
+ |
|
| 19 |
+ val := url.Values{}
|
|
| 20 |
+ if cmd.NArg() > 1 {
|
|
| 21 |
+ val.Set("ps_args", strings.Join(cmd.Args()[1:], " "))
|
|
| 22 |
+ } |
|
| 23 |
+ |
|
| 24 |
+ stream, _, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/top?"+val.Encode(), nil, false)
|
|
| 25 |
+ if err != nil {
|
|
| 26 |
+ return err |
|
| 27 |
+ } |
|
| 28 |
+ var procs engine.Env |
|
| 29 |
+ if err := procs.Decode(stream); err != nil {
|
|
| 30 |
+ return err |
|
| 31 |
+ } |
|
| 32 |
+ w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0) |
|
| 33 |
+ fmt.Fprintln(w, strings.Join(procs.GetList("Titles"), "\t"))
|
|
| 34 |
+ processes := [][]string{}
|
|
| 35 |
+ if err := procs.GetJson("Processes", &processes); err != nil {
|
|
| 36 |
+ return err |
|
| 37 |
+ } |
|
| 38 |
+ for _, proc := range processes {
|
|
| 39 |
+ fmt.Fprintln(w, strings.Join(proc, "\t")) |
|
| 40 |
+ } |
|
| 41 |
+ w.Flush() |
|
| 42 |
+ return nil |
|
| 43 |
+} |
| 0 | 44 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,25 @@ |
| 0 |
+package client |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "fmt" |
|
| 4 |
+ |
|
| 5 |
+ flag "github.com/docker/docker/pkg/mflag" |
|
| 6 |
+ "github.com/docker/docker/utils" |
|
| 7 |
+) |
|
| 8 |
+ |
|
| 9 |
+func (cli *DockerCli) CmdUnpause(args ...string) error {
|
|
| 10 |
+ cmd := cli.Subcmd("unpause", "CONTAINER [CONTAINER...]", "Unpause all processes within a container", true)
|
|
| 11 |
+ cmd.Require(flag.Min, 1) |
|
| 12 |
+ utils.ParseFlags(cmd, args, false) |
|
| 13 |
+ |
|
| 14 |
+ var encounteredError error |
|
| 15 |
+ for _, name := range cmd.Args() {
|
|
| 16 |
+ if _, _, err := readBody(cli.call("POST", fmt.Sprintf("/containers/%s/unpause", name), nil, false)); err != nil {
|
|
| 17 |
+ fmt.Fprintf(cli.err, "%s\n", err) |
|
| 18 |
+ encounteredError = fmt.Errorf("Error: failed to unpause container named %s", name)
|
|
| 19 |
+ } else {
|
|
| 20 |
+ fmt.Fprintf(cli.out, "%s\n", name) |
|
| 21 |
+ } |
|
| 22 |
+ } |
|
| 23 |
+ return encounteredError |
|
| 24 |
+} |
| 0 | 25 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,56 @@ |
| 0 |
+package client |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "fmt" |
|
| 4 |
+ "runtime" |
|
| 5 |
+ |
|
| 6 |
+ log "github.com/Sirupsen/logrus" |
|
| 7 |
+ "github.com/docker/docker/api" |
|
| 8 |
+ "github.com/docker/docker/autogen/dockerversion" |
|
| 9 |
+ "github.com/docker/docker/engine" |
|
| 10 |
+ flag "github.com/docker/docker/pkg/mflag" |
|
| 11 |
+ "github.com/docker/docker/utils" |
|
| 12 |
+) |
|
| 13 |
+ |
|
| 14 |
+// 'docker version': show version information |
|
| 15 |
+func (cli *DockerCli) CmdVersion(args ...string) error {
|
|
| 16 |
+ cmd := cli.Subcmd("version", "", "Show the Docker version information.", true)
|
|
| 17 |
+ cmd.Require(flag.Exact, 0) |
|
| 18 |
+ |
|
| 19 |
+ utils.ParseFlags(cmd, args, false) |
|
| 20 |
+ |
|
| 21 |
+ if dockerversion.VERSION != "" {
|
|
| 22 |
+ fmt.Fprintf(cli.out, "Client version: %s\n", dockerversion.VERSION) |
|
| 23 |
+ } |
|
| 24 |
+ fmt.Fprintf(cli.out, "Client API version: %s\n", api.APIVERSION) |
|
| 25 |
+ fmt.Fprintf(cli.out, "Go version (client): %s\n", runtime.Version()) |
|
| 26 |
+ if dockerversion.GITCOMMIT != "" {
|
|
| 27 |
+ fmt.Fprintf(cli.out, "Git commit (client): %s\n", dockerversion.GITCOMMIT) |
|
| 28 |
+ } |
|
| 29 |
+ fmt.Fprintf(cli.out, "OS/Arch (client): %s/%s\n", runtime.GOOS, runtime.GOARCH) |
|
| 30 |
+ |
|
| 31 |
+ body, _, err := readBody(cli.call("GET", "/version", nil, false))
|
|
| 32 |
+ if err != nil {
|
|
| 33 |
+ return err |
|
| 34 |
+ } |
|
| 35 |
+ |
|
| 36 |
+ out := engine.NewOutput() |
|
| 37 |
+ remoteVersion, err := out.AddEnv() |
|
| 38 |
+ if err != nil {
|
|
| 39 |
+ log.Errorf("Error reading remote version: %s", err)
|
|
| 40 |
+ return err |
|
| 41 |
+ } |
|
| 42 |
+ if _, err := out.Write(body); err != nil {
|
|
| 43 |
+ log.Errorf("Error reading remote version: %s", err)
|
|
| 44 |
+ return err |
|
| 45 |
+ } |
|
| 46 |
+ out.Close() |
|
| 47 |
+ fmt.Fprintf(cli.out, "Server version: %s\n", remoteVersion.Get("Version"))
|
|
| 48 |
+ if apiVersion := remoteVersion.Get("ApiVersion"); apiVersion != "" {
|
|
| 49 |
+ fmt.Fprintf(cli.out, "Server API version: %s\n", apiVersion) |
|
| 50 |
+ } |
|
| 51 |
+ fmt.Fprintf(cli.out, "Go version (server): %s\n", remoteVersion.Get("GoVersion"))
|
|
| 52 |
+ fmt.Fprintf(cli.out, "Git commit (server): %s\n", remoteVersion.Get("GitCommit"))
|
|
| 53 |
+ fmt.Fprintf(cli.out, "OS/Arch (server): %s/%s\n", remoteVersion.Get("Os"), remoteVersion.Get("Arch"))
|
|
| 54 |
+ return nil |
|
| 55 |
+} |
| 0 | 56 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,28 @@ |
| 0 |
+package client |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "fmt" |
|
| 4 |
+ |
|
| 5 |
+ flag "github.com/docker/docker/pkg/mflag" |
|
| 6 |
+ "github.com/docker/docker/utils" |
|
| 7 |
+) |
|
| 8 |
+ |
|
| 9 |
+// 'docker wait': block until a container stops |
|
| 10 |
+func (cli *DockerCli) CmdWait(args ...string) error {
|
|
| 11 |
+ cmd := cli.Subcmd("wait", "CONTAINER [CONTAINER...]", "Block until a container stops, then print its exit code.", true)
|
|
| 12 |
+ cmd.Require(flag.Min, 1) |
|
| 13 |
+ |
|
| 14 |
+ utils.ParseFlags(cmd, args, true) |
|
| 15 |
+ |
|
| 16 |
+ var encounteredError error |
|
| 17 |
+ for _, name := range cmd.Args() {
|
|
| 18 |
+ status, err := waitForExit(cli, name) |
|
| 19 |
+ if err != nil {
|
|
| 20 |
+ fmt.Fprintf(cli.err, "%s\n", err) |
|
| 21 |
+ encounteredError = fmt.Errorf("Error: failed to wait one or more containers")
|
|
| 22 |
+ } else {
|
|
| 23 |
+ fmt.Fprintf(cli.out, "%d\n", status) |
|
| 24 |
+ } |
|
| 25 |
+ } |
|
| 26 |
+ return encounteredError |
|
| 27 |
+} |