Docker-DCO-1.1-Signed-off-by: Victor Vieux <victor.vieux@docker.com> (github: vieux)
| 1 | 1 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,102 @@ |
| 0 |
+package client |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "crypto/tls" |
|
| 4 |
+ "encoding/json" |
|
| 5 |
+ "fmt" |
|
| 6 |
+ "io" |
|
| 7 |
+ "os" |
|
| 8 |
+ "reflect" |
|
| 9 |
+ "strings" |
|
| 10 |
+ "text/template" |
|
| 11 |
+ |
|
| 12 |
+ flag "github.com/dotcloud/docker/pkg/mflag" |
|
| 13 |
+ "github.com/dotcloud/docker/pkg/term" |
|
| 14 |
+ "github.com/dotcloud/docker/registry" |
|
| 15 |
+) |
|
| 16 |
+ |
|
| 17 |
+var funcMap = template.FuncMap{
|
|
| 18 |
+ "json": func(v interface{}) string {
|
|
| 19 |
+ a, _ := json.Marshal(v) |
|
| 20 |
+ return string(a) |
|
| 21 |
+ }, |
|
| 22 |
+} |
|
| 23 |
+ |
|
| 24 |
+func (cli *DockerCli) getMethod(name string) (func(...string) error, bool) {
|
|
| 25 |
+ methodName := "Cmd" + strings.ToUpper(name[:1]) + strings.ToLower(name[1:]) |
|
| 26 |
+ method := reflect.ValueOf(cli).MethodByName(methodName) |
|
| 27 |
+ if !method.IsValid() {
|
|
| 28 |
+ return nil, false |
|
| 29 |
+ } |
|
| 30 |
+ return method.Interface().(func(...string) error), true |
|
| 31 |
+} |
|
| 32 |
+ |
|
| 33 |
+func (cli *DockerCli) ParseCommands(args ...string) error {
|
|
| 34 |
+ if len(args) > 0 {
|
|
| 35 |
+ method, exists := cli.getMethod(args[0]) |
|
| 36 |
+ if !exists {
|
|
| 37 |
+ fmt.Println("Error: Command not found:", args[0])
|
|
| 38 |
+ return cli.CmdHelp(args[1:]...) |
|
| 39 |
+ } |
|
| 40 |
+ return method(args[1:]...) |
|
| 41 |
+ } |
|
| 42 |
+ return cli.CmdHelp(args...) |
|
| 43 |
+} |
|
| 44 |
+ |
|
| 45 |
+func (cli *DockerCli) Subcmd(name, signature, description string) *flag.FlagSet {
|
|
| 46 |
+ flags := flag.NewFlagSet(name, flag.ContinueOnError) |
|
| 47 |
+ flags.Usage = func() {
|
|
| 48 |
+ fmt.Fprintf(cli.err, "\nUsage: docker %s %s\n\n%s\n\n", name, signature, description) |
|
| 49 |
+ flags.PrintDefaults() |
|
| 50 |
+ os.Exit(2) |
|
| 51 |
+ } |
|
| 52 |
+ return flags |
|
| 53 |
+} |
|
| 54 |
+ |
|
| 55 |
+func (cli *DockerCli) LoadConfigFile() (err error) {
|
|
| 56 |
+ cli.configFile, err = registry.LoadConfig(os.Getenv("HOME"))
|
|
| 57 |
+ if err != nil {
|
|
| 58 |
+ fmt.Fprintf(cli.err, "WARNING: %s\n", err) |
|
| 59 |
+ } |
|
| 60 |
+ return err |
|
| 61 |
+} |
|
| 62 |
+ |
|
| 63 |
+func NewDockerCli(in io.ReadCloser, out, err io.Writer, proto, addr string, tlsConfig *tls.Config) *DockerCli {
|
|
| 64 |
+ var ( |
|
| 65 |
+ isTerminal = false |
|
| 66 |
+ terminalFd uintptr |
|
| 67 |
+ ) |
|
| 68 |
+ |
|
| 69 |
+ if in != nil {
|
|
| 70 |
+ if file, ok := in.(*os.File); ok {
|
|
| 71 |
+ terminalFd = file.Fd() |
|
| 72 |
+ isTerminal = term.IsTerminal(terminalFd) |
|
| 73 |
+ } |
|
| 74 |
+ } |
|
| 75 |
+ |
|
| 76 |
+ if err == nil {
|
|
| 77 |
+ err = out |
|
| 78 |
+ } |
|
| 79 |
+ return &DockerCli{
|
|
| 80 |
+ proto: proto, |
|
| 81 |
+ addr: addr, |
|
| 82 |
+ in: in, |
|
| 83 |
+ out: out, |
|
| 84 |
+ err: err, |
|
| 85 |
+ isTerminal: isTerminal, |
|
| 86 |
+ terminalFd: terminalFd, |
|
| 87 |
+ tlsConfig: tlsConfig, |
|
| 88 |
+ } |
|
| 89 |
+} |
|
| 90 |
+ |
|
| 91 |
+type DockerCli struct {
|
|
| 92 |
+ proto string |
|
| 93 |
+ addr string |
|
| 94 |
+ configFile *registry.ConfigFile |
|
| 95 |
+ in io.ReadCloser |
|
| 96 |
+ out io.Writer |
|
| 97 |
+ err io.Writer |
|
| 98 |
+ isTerminal bool |
|
| 99 |
+ terminalFd uintptr |
|
| 100 |
+ tlsConfig *tls.Config |
|
| 101 |
+} |
| 0 | 102 |
deleted file mode 100644 |
| ... | ... |
@@ -1,2551 +0,0 @@ |
| 1 |
-package client |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- "bufio" |
|
| 5 |
- "bytes" |
|
| 6 |
- "crypto/tls" |
|
| 7 |
- "encoding/base64" |
|
| 8 |
- "encoding/json" |
|
| 9 |
- "errors" |
|
| 10 |
- "fmt" |
|
| 11 |
- "github.com/dotcloud/docker/api" |
|
| 12 |
- "github.com/dotcloud/docker/archive" |
|
| 13 |
- "github.com/dotcloud/docker/dockerversion" |
|
| 14 |
- "github.com/dotcloud/docker/engine" |
|
| 15 |
- "github.com/dotcloud/docker/nat" |
|
| 16 |
- flag "github.com/dotcloud/docker/pkg/mflag" |
|
| 17 |
- "github.com/dotcloud/docker/pkg/signal" |
|
| 18 |
- "github.com/dotcloud/docker/pkg/term" |
|
| 19 |
- "github.com/dotcloud/docker/registry" |
|
| 20 |
- "github.com/dotcloud/docker/runconfig" |
|
| 21 |
- "github.com/dotcloud/docker/utils" |
|
| 22 |
- "io" |
|
| 23 |
- "io/ioutil" |
|
| 24 |
- "net" |
|
| 25 |
- "net/http" |
|
| 26 |
- "net/http/httputil" |
|
| 27 |
- "net/url" |
|
| 28 |
- "os" |
|
| 29 |
- "os/exec" |
|
| 30 |
- gosignal "os/signal" |
|
| 31 |
- "path" |
|
| 32 |
- "reflect" |
|
| 33 |
- "regexp" |
|
| 34 |
- goruntime "runtime" |
|
| 35 |
- "strconv" |
|
| 36 |
- "strings" |
|
| 37 |
- "syscall" |
|
| 38 |
- "text/tabwriter" |
|
| 39 |
- "text/template" |
|
| 40 |
- "time" |
|
| 41 |
-) |
|
| 42 |
- |
|
| 43 |
-var funcMap = template.FuncMap{
|
|
| 44 |
- "json": func(v interface{}) string {
|
|
| 45 |
- a, _ := json.Marshal(v) |
|
| 46 |
- return string(a) |
|
| 47 |
- }, |
|
| 48 |
-} |
|
| 49 |
- |
|
| 50 |
-var ( |
|
| 51 |
- ErrConnectionRefused = errors.New("Cannot connect to the Docker daemon. Is 'docker -d' running on this host?")
|
|
| 52 |
-) |
|
| 53 |
- |
|
| 54 |
-func (cli *DockerCli) getMethod(name string) (func(...string) error, bool) {
|
|
| 55 |
- methodName := "Cmd" + strings.ToUpper(name[:1]) + strings.ToLower(name[1:]) |
|
| 56 |
- method := reflect.ValueOf(cli).MethodByName(methodName) |
|
| 57 |
- if !method.IsValid() {
|
|
| 58 |
- return nil, false |
|
| 59 |
- } |
|
| 60 |
- return method.Interface().(func(...string) error), true |
|
| 61 |
-} |
|
| 62 |
- |
|
| 63 |
-func (cli *DockerCli) ParseCommands(args ...string) error {
|
|
| 64 |
- if len(args) > 0 {
|
|
| 65 |
- method, exists := cli.getMethod(args[0]) |
|
| 66 |
- if !exists {
|
|
| 67 |
- fmt.Println("Error: Command not found:", args[0])
|
|
| 68 |
- return cli.CmdHelp(args[1:]...) |
|
| 69 |
- } |
|
| 70 |
- return method(args[1:]...) |
|
| 71 |
- } |
|
| 72 |
- return cli.CmdHelp(args...) |
|
| 73 |
-} |
|
| 74 |
- |
|
| 75 |
-func (cli *DockerCli) CmdHelp(args ...string) error {
|
|
| 76 |
- if len(args) > 0 {
|
|
| 77 |
- method, exists := cli.getMethod(args[0]) |
|
| 78 |
- if !exists {
|
|
| 79 |
- fmt.Fprintf(cli.err, "Error: Command not found: %s\n", args[0]) |
|
| 80 |
- } else {
|
|
| 81 |
- method("--help")
|
|
| 82 |
- return nil |
|
| 83 |
- } |
|
| 84 |
- } |
|
| 85 |
- help := fmt.Sprintf("Usage: docker [OPTIONS] COMMAND [arg...]\n -H=[unix://%s]: tcp://host:port to bind/connect to or unix://path/to/socket to use\n\nA self-sufficient runtime for linux containers.\n\nCommands:\n", api.DEFAULTUNIXSOCKET)
|
|
| 86 |
- for _, command := range [][]string{
|
|
| 87 |
- {"attach", "Attach to a running container"},
|
|
| 88 |
- {"build", "Build a container from a Dockerfile"},
|
|
| 89 |
- {"commit", "Create a new image from a container's changes"},
|
|
| 90 |
- {"cp", "Copy files/folders from the containers filesystem to the host path"},
|
|
| 91 |
- {"diff", "Inspect changes on a container's filesystem"},
|
|
| 92 |
- {"events", "Get real time events from the server"},
|
|
| 93 |
- {"export", "Stream the contents of a container as a tar archive"},
|
|
| 94 |
- {"history", "Show the history of an image"},
|
|
| 95 |
- {"images", "List images"},
|
|
| 96 |
- {"import", "Create a new filesystem image from the contents of a tarball"},
|
|
| 97 |
- {"info", "Display system-wide information"},
|
|
| 98 |
- {"insert", "Insert a file in an image"},
|
|
| 99 |
- {"inspect", "Return low-level information on a container"},
|
|
| 100 |
- {"kill", "Kill a running container"},
|
|
| 101 |
- {"load", "Load an image from a tar archive"},
|
|
| 102 |
- {"login", "Register or Login to the docker registry server"},
|
|
| 103 |
- {"logs", "Fetch the logs of a container"},
|
|
| 104 |
- {"port", "Lookup the public-facing port which is NAT-ed to PRIVATE_PORT"},
|
|
| 105 |
- {"ps", "List containers"},
|
|
| 106 |
- {"pull", "Pull an image or a repository from the docker registry server"},
|
|
| 107 |
- {"push", "Push an image or a repository to the docker registry server"},
|
|
| 108 |
- {"restart", "Restart a running container"},
|
|
| 109 |
- {"rm", "Remove one or more containers"},
|
|
| 110 |
- {"rmi", "Remove one or more images"},
|
|
| 111 |
- {"run", "Run a command in a new container"},
|
|
| 112 |
- {"save", "Save an image to a tar archive"},
|
|
| 113 |
- {"search", "Search for an image in the docker index"},
|
|
| 114 |
- {"start", "Start a stopped container"},
|
|
| 115 |
- {"stop", "Stop a running container"},
|
|
| 116 |
- {"tag", "Tag an image into a repository"},
|
|
| 117 |
- {"top", "Lookup the running processes of a container"},
|
|
| 118 |
- {"version", "Show the docker version information"},
|
|
| 119 |
- {"wait", "Block until a container stops, then print its exit code"},
|
|
| 120 |
- } {
|
|
| 121 |
- help += fmt.Sprintf(" %-10.10s%s\n", command[0], command[1])
|
|
| 122 |
- } |
|
| 123 |
- fmt.Fprintf(cli.err, "%s\n", help) |
|
| 124 |
- return nil |
|
| 125 |
-} |
|
| 126 |
- |
|
| 127 |
-func (cli *DockerCli) CmdInsert(args ...string) error {
|
|
| 128 |
- cmd := cli.Subcmd("insert", "IMAGE URL PATH", "Insert a file from URL in the IMAGE at PATH")
|
|
| 129 |
- if err := cmd.Parse(args); err != nil {
|
|
| 130 |
- return nil |
|
| 131 |
- } |
|
| 132 |
- if cmd.NArg() != 3 {
|
|
| 133 |
- cmd.Usage() |
|
| 134 |
- return nil |
|
| 135 |
- } |
|
| 136 |
- |
|
| 137 |
- v := url.Values{}
|
|
| 138 |
- v.Set("url", cmd.Arg(1))
|
|
| 139 |
- v.Set("path", cmd.Arg(2))
|
|
| 140 |
- |
|
| 141 |
- return cli.stream("POST", "/images/"+cmd.Arg(0)+"/insert?"+v.Encode(), nil, cli.out, nil)
|
|
| 142 |
-} |
|
| 143 |
- |
|
| 144 |
-func (cli *DockerCli) CmdBuild(args ...string) error {
|
|
| 145 |
- cmd := cli.Subcmd("build", "[OPTIONS] PATH | URL | -", "Build a new container image from the source code at PATH")
|
|
| 146 |
- tag := cmd.String([]string{"t", "-tag"}, "", "Repository name (and optionally a tag) to be applied to the resulting image in case of success")
|
|
| 147 |
- suppressOutput := cmd.Bool([]string{"q", "-quiet"}, false, "Suppress the verbose output generated by the containers")
|
|
| 148 |
- noCache := cmd.Bool([]string{"#no-cache", "-no-cache"}, false, "Do not use cache when building the image")
|
|
| 149 |
- rm := cmd.Bool([]string{"#rm", "-rm"}, true, "Remove intermediate containers after a successful build")
|
|
| 150 |
- if err := cmd.Parse(args); err != nil {
|
|
| 151 |
- return nil |
|
| 152 |
- } |
|
| 153 |
- if cmd.NArg() != 1 {
|
|
| 154 |
- cmd.Usage() |
|
| 155 |
- return nil |
|
| 156 |
- } |
|
| 157 |
- |
|
| 158 |
- var ( |
|
| 159 |
- context archive.Archive |
|
| 160 |
- isRemote bool |
|
| 161 |
- err error |
|
| 162 |
- ) |
|
| 163 |
- |
|
| 164 |
- _, err = exec.LookPath("git")
|
|
| 165 |
- hasGit := err == nil |
|
| 166 |
- if cmd.Arg(0) == "-" {
|
|
| 167 |
- // As a special case, 'docker build -' will build from an empty context with the |
|
| 168 |
- // contents of stdin as a Dockerfile |
|
| 169 |
- dockerfile, err := ioutil.ReadAll(cli.in) |
|
| 170 |
- if err != nil {
|
|
| 171 |
- return err |
|
| 172 |
- } |
|
| 173 |
- context, err = archive.Generate("Dockerfile", string(dockerfile))
|
|
| 174 |
- } else if utils.IsURL(cmd.Arg(0)) && (!utils.IsGIT(cmd.Arg(0)) || !hasGit) {
|
|
| 175 |
- isRemote = true |
|
| 176 |
- } else {
|
|
| 177 |
- root := cmd.Arg(0) |
|
| 178 |
- if utils.IsGIT(root) {
|
|
| 179 |
- remoteURL := cmd.Arg(0) |
|
| 180 |
- if !strings.HasPrefix(remoteURL, "git://") && !strings.HasPrefix(remoteURL, "git@") && !utils.IsURL(remoteURL) {
|
|
| 181 |
- remoteURL = "https://" + remoteURL |
|
| 182 |
- } |
|
| 183 |
- |
|
| 184 |
- root, err = ioutil.TempDir("", "docker-build-git")
|
|
| 185 |
- if err != nil {
|
|
| 186 |
- return err |
|
| 187 |
- } |
|
| 188 |
- defer os.RemoveAll(root) |
|
| 189 |
- |
|
| 190 |
- if output, err := exec.Command("git", "clone", "--recursive", remoteURL, root).CombinedOutput(); err != nil {
|
|
| 191 |
- return fmt.Errorf("Error trying to use git: %s (%s)", err, output)
|
|
| 192 |
- } |
|
| 193 |
- } |
|
| 194 |
- if _, err := os.Stat(root); err != nil {
|
|
| 195 |
- return err |
|
| 196 |
- } |
|
| 197 |
- filename := path.Join(root, "Dockerfile") |
|
| 198 |
- if _, err = os.Stat(filename); os.IsNotExist(err) {
|
|
| 199 |
- return fmt.Errorf("no Dockerfile found in %s", cmd.Arg(0))
|
|
| 200 |
- } |
|
| 201 |
- context, err = archive.Tar(root, archive.Uncompressed) |
|
| 202 |
- } |
|
| 203 |
- var body io.Reader |
|
| 204 |
- // Setup an upload progress bar |
|
| 205 |
- // FIXME: ProgressReader shouldn't be this annoying to use |
|
| 206 |
- if context != nil {
|
|
| 207 |
- sf := utils.NewStreamFormatter(false) |
|
| 208 |
- body = utils.ProgressReader(context, 0, cli.err, sf, true, "", "Uploading context") |
|
| 209 |
- } |
|
| 210 |
- // Upload the build context |
|
| 211 |
- v := &url.Values{}
|
|
| 212 |
- |
|
| 213 |
- //Check if the given image name can be resolved |
|
| 214 |
- if *tag != "" {
|
|
| 215 |
- repository, _ := utils.ParseRepositoryTag(*tag) |
|
| 216 |
- if _, _, err := registry.ResolveRepositoryName(repository); err != nil {
|
|
| 217 |
- return err |
|
| 218 |
- } |
|
| 219 |
- } |
|
| 220 |
- |
|
| 221 |
- v.Set("t", *tag)
|
|
| 222 |
- |
|
| 223 |
- if *suppressOutput {
|
|
| 224 |
- v.Set("q", "1")
|
|
| 225 |
- } |
|
| 226 |
- if isRemote {
|
|
| 227 |
- v.Set("remote", cmd.Arg(0))
|
|
| 228 |
- } |
|
| 229 |
- if *noCache {
|
|
| 230 |
- v.Set("nocache", "1")
|
|
| 231 |
- } |
|
| 232 |
- if *rm {
|
|
| 233 |
- v.Set("rm", "1")
|
|
| 234 |
- } |
|
| 235 |
- |
|
| 236 |
- cli.LoadConfigFile() |
|
| 237 |
- |
|
| 238 |
- headers := http.Header(make(map[string][]string)) |
|
| 239 |
- buf, err := json.Marshal(cli.configFile) |
|
| 240 |
- if err != nil {
|
|
| 241 |
- return err |
|
| 242 |
- } |
|
| 243 |
- headers.Add("X-Registry-Config", base64.URLEncoding.EncodeToString(buf))
|
|
| 244 |
- |
|
| 245 |
- if context != nil {
|
|
| 246 |
- headers.Set("Content-Type", "application/tar")
|
|
| 247 |
- } |
|
| 248 |
- err = cli.stream("POST", fmt.Sprintf("/build?%s", v.Encode()), body, cli.out, headers)
|
|
| 249 |
- if jerr, ok := err.(*utils.JSONError); ok {
|
|
| 250 |
- // If no error code is set, default to 1 |
|
| 251 |
- if jerr.Code == 0 {
|
|
| 252 |
- jerr.Code = 1 |
|
| 253 |
- } |
|
| 254 |
- return &utils.StatusError{Status: jerr.Message, StatusCode: jerr.Code}
|
|
| 255 |
- } |
|
| 256 |
- return err |
|
| 257 |
-} |
|
| 258 |
- |
|
| 259 |
-// 'docker login': login / register a user to registry service. |
|
| 260 |
-func (cli *DockerCli) CmdLogin(args ...string) error {
|
|
| 261 |
- cmd := cli.Subcmd("login", "[OPTIONS] [SERVER]", "Register or Login to a docker registry server, if no server is specified \""+registry.IndexServerAddress()+"\" is the default.")
|
|
| 262 |
- |
|
| 263 |
- var username, password, email string |
|
| 264 |
- |
|
| 265 |
- cmd.StringVar(&username, []string{"u", "-username"}, "", "Username")
|
|
| 266 |
- cmd.StringVar(&password, []string{"p", "-password"}, "", "Password")
|
|
| 267 |
- cmd.StringVar(&email, []string{"e", "-email"}, "", "Email")
|
|
| 268 |
- err := cmd.Parse(args) |
|
| 269 |
- if err != nil {
|
|
| 270 |
- return nil |
|
| 271 |
- } |
|
| 272 |
- serverAddress := registry.IndexServerAddress() |
|
| 273 |
- if len(cmd.Args()) > 0 {
|
|
| 274 |
- serverAddress = cmd.Arg(0) |
|
| 275 |
- } |
|
| 276 |
- |
|
| 277 |
- promptDefault := func(prompt string, configDefault string) {
|
|
| 278 |
- if configDefault == "" {
|
|
| 279 |
- fmt.Fprintf(cli.out, "%s: ", prompt) |
|
| 280 |
- } else {
|
|
| 281 |
- fmt.Fprintf(cli.out, "%s (%s): ", prompt, configDefault) |
|
| 282 |
- } |
|
| 283 |
- } |
|
| 284 |
- |
|
| 285 |
- readInput := func(in io.Reader, out io.Writer) string {
|
|
| 286 |
- reader := bufio.NewReader(in) |
|
| 287 |
- line, _, err := reader.ReadLine() |
|
| 288 |
- if err != nil {
|
|
| 289 |
- fmt.Fprintln(out, err.Error()) |
|
| 290 |
- os.Exit(1) |
|
| 291 |
- } |
|
| 292 |
- return string(line) |
|
| 293 |
- } |
|
| 294 |
- |
|
| 295 |
- cli.LoadConfigFile() |
|
| 296 |
- authconfig, ok := cli.configFile.Configs[serverAddress] |
|
| 297 |
- if !ok {
|
|
| 298 |
- authconfig = registry.AuthConfig{}
|
|
| 299 |
- } |
|
| 300 |
- |
|
| 301 |
- if username == "" {
|
|
| 302 |
- promptDefault("Username", authconfig.Username)
|
|
| 303 |
- username = readInput(cli.in, cli.out) |
|
| 304 |
- if username == "" {
|
|
| 305 |
- username = authconfig.Username |
|
| 306 |
- } |
|
| 307 |
- } |
|
| 308 |
- if username != authconfig.Username {
|
|
| 309 |
- if password == "" {
|
|
| 310 |
- oldState, _ := term.SaveState(cli.terminalFd) |
|
| 311 |
- fmt.Fprintf(cli.out, "Password: ") |
|
| 312 |
- term.DisableEcho(cli.terminalFd, oldState) |
|
| 313 |
- |
|
| 314 |
- password = readInput(cli.in, cli.out) |
|
| 315 |
- fmt.Fprint(cli.out, "\n") |
|
| 316 |
- |
|
| 317 |
- term.RestoreTerminal(cli.terminalFd, oldState) |
|
| 318 |
- if password == "" {
|
|
| 319 |
- return fmt.Errorf("Error : Password Required")
|
|
| 320 |
- } |
|
| 321 |
- } |
|
| 322 |
- |
|
| 323 |
- if email == "" {
|
|
| 324 |
- promptDefault("Email", authconfig.Email)
|
|
| 325 |
- email = readInput(cli.in, cli.out) |
|
| 326 |
- if email == "" {
|
|
| 327 |
- email = authconfig.Email |
|
| 328 |
- } |
|
| 329 |
- } |
|
| 330 |
- } else {
|
|
| 331 |
- password = authconfig.Password |
|
| 332 |
- email = authconfig.Email |
|
| 333 |
- } |
|
| 334 |
- authconfig.Username = username |
|
| 335 |
- authconfig.Password = password |
|
| 336 |
- authconfig.Email = email |
|
| 337 |
- authconfig.ServerAddress = serverAddress |
|
| 338 |
- cli.configFile.Configs[serverAddress] = authconfig |
|
| 339 |
- |
|
| 340 |
- stream, statusCode, err := cli.call("POST", "/auth", cli.configFile.Configs[serverAddress], false)
|
|
| 341 |
- if statusCode == 401 {
|
|
| 342 |
- delete(cli.configFile.Configs, serverAddress) |
|
| 343 |
- registry.SaveConfig(cli.configFile) |
|
| 344 |
- return err |
|
| 345 |
- } |
|
| 346 |
- if err != nil {
|
|
| 347 |
- return err |
|
| 348 |
- } |
|
| 349 |
- var out2 engine.Env |
|
| 350 |
- err = out2.Decode(stream) |
|
| 351 |
- if err != nil {
|
|
| 352 |
- cli.configFile, _ = registry.LoadConfig(os.Getenv("HOME"))
|
|
| 353 |
- return err |
|
| 354 |
- } |
|
| 355 |
- registry.SaveConfig(cli.configFile) |
|
| 356 |
- if out2.Get("Status") != "" {
|
|
| 357 |
- fmt.Fprintf(cli.out, "%s\n", out2.Get("Status"))
|
|
| 358 |
- } |
|
| 359 |
- return nil |
|
| 360 |
-} |
|
| 361 |
- |
|
| 362 |
-// 'docker wait': block until a container stops |
|
| 363 |
-func (cli *DockerCli) CmdWait(args ...string) error {
|
|
| 364 |
- cmd := cli.Subcmd("wait", "CONTAINER [CONTAINER...]", "Block until a container stops, then print its exit code.")
|
|
| 365 |
- if err := cmd.Parse(args); err != nil {
|
|
| 366 |
- return nil |
|
| 367 |
- } |
|
| 368 |
- if cmd.NArg() < 1 {
|
|
| 369 |
- cmd.Usage() |
|
| 370 |
- return nil |
|
| 371 |
- } |
|
| 372 |
- var encounteredError error |
|
| 373 |
- for _, name := range cmd.Args() {
|
|
| 374 |
- status, err := waitForExit(cli, name) |
|
| 375 |
- if err != nil {
|
|
| 376 |
- fmt.Fprintf(cli.err, "%s\n", err) |
|
| 377 |
- encounteredError = fmt.Errorf("Error: failed to wait one or more containers")
|
|
| 378 |
- } else {
|
|
| 379 |
- fmt.Fprintf(cli.out, "%d\n", status) |
|
| 380 |
- } |
|
| 381 |
- } |
|
| 382 |
- return encounteredError |
|
| 383 |
-} |
|
| 384 |
- |
|
| 385 |
-// 'docker version': show version information |
|
| 386 |
-func (cli *DockerCli) CmdVersion(args ...string) error {
|
|
| 387 |
- cmd := cli.Subcmd("version", "", "Show the docker version information.")
|
|
| 388 |
- if err := cmd.Parse(args); err != nil {
|
|
| 389 |
- return nil |
|
| 390 |
- } |
|
| 391 |
- |
|
| 392 |
- if cmd.NArg() > 0 {
|
|
| 393 |
- cmd.Usage() |
|
| 394 |
- return nil |
|
| 395 |
- } |
|
| 396 |
- if dockerversion.VERSION != "" {
|
|
| 397 |
- fmt.Fprintf(cli.out, "Client version: %s\n", dockerversion.VERSION) |
|
| 398 |
- } |
|
| 399 |
- fmt.Fprintf(cli.out, "Go version (client): %s\n", goruntime.Version()) |
|
| 400 |
- if dockerversion.GITCOMMIT != "" {
|
|
| 401 |
- fmt.Fprintf(cli.out, "Git commit (client): %s\n", dockerversion.GITCOMMIT) |
|
| 402 |
- } |
|
| 403 |
- |
|
| 404 |
- body, _, err := readBody(cli.call("GET", "/version", nil, false))
|
|
| 405 |
- if err != nil {
|
|
| 406 |
- return err |
|
| 407 |
- } |
|
| 408 |
- |
|
| 409 |
- out := engine.NewOutput() |
|
| 410 |
- remoteVersion, err := out.AddEnv() |
|
| 411 |
- if err != nil {
|
|
| 412 |
- utils.Errorf("Error reading remote version: %s\n", err)
|
|
| 413 |
- return err |
|
| 414 |
- } |
|
| 415 |
- if _, err := out.Write(body); err != nil {
|
|
| 416 |
- utils.Errorf("Error reading remote version: %s\n", err)
|
|
| 417 |
- return err |
|
| 418 |
- } |
|
| 419 |
- out.Close() |
|
| 420 |
- fmt.Fprintf(cli.out, "Server version: %s\n", remoteVersion.Get("Version"))
|
|
| 421 |
- fmt.Fprintf(cli.out, "Git commit (server): %s\n", remoteVersion.Get("GitCommit"))
|
|
| 422 |
- fmt.Fprintf(cli.out, "Go version (server): %s\n", remoteVersion.Get("GoVersion"))
|
|
| 423 |
- release := utils.GetReleaseVersion() |
|
| 424 |
- if release != "" {
|
|
| 425 |
- fmt.Fprintf(cli.out, "Last stable version: %s", release) |
|
| 426 |
- if (dockerversion.VERSION != "" || remoteVersion.Exists("Version")) && (strings.Trim(dockerversion.VERSION, "-dev") != release || strings.Trim(remoteVersion.Get("Version"), "-dev") != release) {
|
|
| 427 |
- fmt.Fprintf(cli.out, ", please update docker") |
|
| 428 |
- } |
|
| 429 |
- fmt.Fprintf(cli.out, "\n") |
|
| 430 |
- } |
|
| 431 |
- return nil |
|
| 432 |
-} |
|
| 433 |
- |
|
| 434 |
-// 'docker info': display system-wide information. |
|
| 435 |
-func (cli *DockerCli) CmdInfo(args ...string) error {
|
|
| 436 |
- cmd := cli.Subcmd("info", "", "Display system-wide information")
|
|
| 437 |
- if err := cmd.Parse(args); err != nil {
|
|
| 438 |
- return nil |
|
| 439 |
- } |
|
| 440 |
- if cmd.NArg() > 0 {
|
|
| 441 |
- cmd.Usage() |
|
| 442 |
- return nil |
|
| 443 |
- } |
|
| 444 |
- |
|
| 445 |
- body, _, err := readBody(cli.call("GET", "/info", nil, false))
|
|
| 446 |
- if err != nil {
|
|
| 447 |
- return err |
|
| 448 |
- } |
|
| 449 |
- |
|
| 450 |
- out := engine.NewOutput() |
|
| 451 |
- remoteInfo, err := out.AddEnv() |
|
| 452 |
- if err != nil {
|
|
| 453 |
- return err |
|
| 454 |
- } |
|
| 455 |
- |
|
| 456 |
- if _, err := out.Write(body); err != nil {
|
|
| 457 |
- utils.Errorf("Error reading remote info: %s\n", err)
|
|
| 458 |
- return err |
|
| 459 |
- } |
|
| 460 |
- out.Close() |
|
| 461 |
- |
|
| 462 |
- fmt.Fprintf(cli.out, "Containers: %d\n", remoteInfo.GetInt("Containers"))
|
|
| 463 |
- fmt.Fprintf(cli.out, "Images: %d\n", remoteInfo.GetInt("Images"))
|
|
| 464 |
- fmt.Fprintf(cli.out, "Storage Driver: %s\n", remoteInfo.Get("Driver"))
|
|
| 465 |
- var driverStatus [][2]string |
|
| 466 |
- if err := remoteInfo.GetJson("DriverStatus", &driverStatus); err != nil {
|
|
| 467 |
- return err |
|
| 468 |
- } |
|
| 469 |
- for _, pair := range driverStatus {
|
|
| 470 |
- fmt.Fprintf(cli.out, " %s: %s\n", pair[0], pair[1]) |
|
| 471 |
- } |
|
| 472 |
- fmt.Fprintf(cli.out, "Execution Driver: %s\n", remoteInfo.Get("ExecutionDriver"))
|
|
| 473 |
- fmt.Fprintf(cli.out, "Kernel Version: %s\n", remoteInfo.Get("KernelVersion"))
|
|
| 474 |
- |
|
| 475 |
- if remoteInfo.GetBool("Debug") || os.Getenv("DEBUG") != "" {
|
|
| 476 |
- fmt.Fprintf(cli.out, "Debug mode (server): %v\n", remoteInfo.GetBool("Debug"))
|
|
| 477 |
- fmt.Fprintf(cli.out, "Debug mode (client): %v\n", os.Getenv("DEBUG") != "")
|
|
| 478 |
- fmt.Fprintf(cli.out, "Fds: %d\n", remoteInfo.GetInt("NFd"))
|
|
| 479 |
- fmt.Fprintf(cli.out, "Goroutines: %d\n", remoteInfo.GetInt("NGoroutines"))
|
|
| 480 |
- fmt.Fprintf(cli.out, "EventsListeners: %d\n", remoteInfo.GetInt("NEventsListener"))
|
|
| 481 |
- |
|
| 482 |
- if initSha1 := remoteInfo.Get("InitSha1"); initSha1 != "" {
|
|
| 483 |
- fmt.Fprintf(cli.out, "Init SHA1: %s\n", initSha1) |
|
| 484 |
- } |
|
| 485 |
- if initPath := remoteInfo.Get("InitPath"); initPath != "" {
|
|
| 486 |
- fmt.Fprintf(cli.out, "Init Path: %s\n", initPath) |
|
| 487 |
- } |
|
| 488 |
- } |
|
| 489 |
- |
|
| 490 |
- if len(remoteInfo.GetList("IndexServerAddress")) != 0 {
|
|
| 491 |
- cli.LoadConfigFile() |
|
| 492 |
- u := cli.configFile.Configs[remoteInfo.Get("IndexServerAddress")].Username
|
|
| 493 |
- if len(u) > 0 {
|
|
| 494 |
- fmt.Fprintf(cli.out, "Username: %v\n", u) |
|
| 495 |
- fmt.Fprintf(cli.out, "Registry: %v\n", remoteInfo.GetList("IndexServerAddress"))
|
|
| 496 |
- } |
|
| 497 |
- } |
|
| 498 |
- if !remoteInfo.GetBool("MemoryLimit") {
|
|
| 499 |
- fmt.Fprintf(cli.err, "WARNING: No memory limit support\n") |
|
| 500 |
- } |
|
| 501 |
- if !remoteInfo.GetBool("SwapLimit") {
|
|
| 502 |
- fmt.Fprintf(cli.err, "WARNING: No swap limit support\n") |
|
| 503 |
- } |
|
| 504 |
- if !remoteInfo.GetBool("IPv4Forwarding") {
|
|
| 505 |
- fmt.Fprintf(cli.err, "WARNING: IPv4 forwarding is disabled.\n") |
|
| 506 |
- } |
|
| 507 |
- return nil |
|
| 508 |
-} |
|
| 509 |
- |
|
| 510 |
-func (cli *DockerCli) CmdStop(args ...string) error {
|
|
| 511 |
- cmd := cli.Subcmd("stop", "[OPTIONS] CONTAINER [CONTAINER...]", "Stop a running container (Send SIGTERM, and then SIGKILL after grace period)")
|
|
| 512 |
- nSeconds := cmd.Int([]string{"t", "-time"}, 10, "Number of seconds to wait for the container to stop before killing it.")
|
|
| 513 |
- if err := cmd.Parse(args); err != nil {
|
|
| 514 |
- return nil |
|
| 515 |
- } |
|
| 516 |
- if cmd.NArg() < 1 {
|
|
| 517 |
- cmd.Usage() |
|
| 518 |
- return nil |
|
| 519 |
- } |
|
| 520 |
- |
|
| 521 |
- v := url.Values{}
|
|
| 522 |
- v.Set("t", strconv.Itoa(*nSeconds))
|
|
| 523 |
- |
|
| 524 |
- var encounteredError error |
|
| 525 |
- for _, name := range cmd.Args() {
|
|
| 526 |
- _, _, err := readBody(cli.call("POST", "/containers/"+name+"/stop?"+v.Encode(), nil, false))
|
|
| 527 |
- if err != nil {
|
|
| 528 |
- fmt.Fprintf(cli.err, "%s\n", err) |
|
| 529 |
- encounteredError = fmt.Errorf("Error: failed to stop one or more containers")
|
|
| 530 |
- } else {
|
|
| 531 |
- fmt.Fprintf(cli.out, "%s\n", name) |
|
| 532 |
- } |
|
| 533 |
- } |
|
| 534 |
- return encounteredError |
|
| 535 |
-} |
|
| 536 |
- |
|
| 537 |
-func (cli *DockerCli) CmdRestart(args ...string) error {
|
|
| 538 |
- cmd := cli.Subcmd("restart", "[OPTIONS] CONTAINER [CONTAINER...]", "Restart a running container")
|
|
| 539 |
- nSeconds := cmd.Int([]string{"t", "-time"}, 10, "Number of seconds to try to stop for before killing the container. Once killed it will then be restarted. Default=10")
|
|
| 540 |
- if err := cmd.Parse(args); err != nil {
|
|
| 541 |
- return nil |
|
| 542 |
- } |
|
| 543 |
- if cmd.NArg() < 1 {
|
|
| 544 |
- cmd.Usage() |
|
| 545 |
- return nil |
|
| 546 |
- } |
|
| 547 |
- |
|
| 548 |
- v := url.Values{}
|
|
| 549 |
- v.Set("t", strconv.Itoa(*nSeconds))
|
|
| 550 |
- |
|
| 551 |
- var encounteredError error |
|
| 552 |
- for _, name := range cmd.Args() {
|
|
| 553 |
- _, _, err := readBody(cli.call("POST", "/containers/"+name+"/restart?"+v.Encode(), nil, false))
|
|
| 554 |
- if err != nil {
|
|
| 555 |
- fmt.Fprintf(cli.err, "%s\n", err) |
|
| 556 |
- encounteredError = fmt.Errorf("Error: failed to restart one or more containers")
|
|
| 557 |
- } else {
|
|
| 558 |
- fmt.Fprintf(cli.out, "%s\n", name) |
|
| 559 |
- } |
|
| 560 |
- } |
|
| 561 |
- return encounteredError |
|
| 562 |
-} |
|
| 563 |
- |
|
| 564 |
-func (cli *DockerCli) forwardAllSignals(cid string) chan os.Signal {
|
|
| 565 |
- sigc := make(chan os.Signal, 1) |
|
| 566 |
- signal.CatchAll(sigc) |
|
| 567 |
- go func() {
|
|
| 568 |
- for s := range sigc {
|
|
| 569 |
- if s == syscall.SIGCHLD {
|
|
| 570 |
- continue |
|
| 571 |
- } |
|
| 572 |
- var sig string |
|
| 573 |
- for sigStr, sigN := range signal.SignalMap {
|
|
| 574 |
- if sigN == s {
|
|
| 575 |
- sig = sigStr |
|
| 576 |
- break |
|
| 577 |
- } |
|
| 578 |
- } |
|
| 579 |
- if sig == "" {
|
|
| 580 |
- utils.Errorf("Unsupported signal: %d. Discarding.", s)
|
|
| 581 |
- } |
|
| 582 |
- if _, _, err := readBody(cli.call("POST", fmt.Sprintf("/containers/%s/kill?signal=%s", cid, sig), nil, false)); err != nil {
|
|
| 583 |
- utils.Debugf("Error sending signal: %s", err)
|
|
| 584 |
- } |
|
| 585 |
- } |
|
| 586 |
- }() |
|
| 587 |
- return sigc |
|
| 588 |
-} |
|
| 589 |
- |
|
| 590 |
-func (cli *DockerCli) CmdStart(args ...string) error {
|
|
| 591 |
- cmd := cli.Subcmd("start", "CONTAINER [CONTAINER...]", "Restart a stopped container")
|
|
| 592 |
- attach := cmd.Bool([]string{"a", "-attach"}, false, "Attach container's stdout/stderr and forward all signals to the process")
|
|
| 593 |
- openStdin := cmd.Bool([]string{"i", "-interactive"}, false, "Attach container's stdin")
|
|
| 594 |
- if err := cmd.Parse(args); err != nil {
|
|
| 595 |
- return nil |
|
| 596 |
- } |
|
| 597 |
- if cmd.NArg() < 1 {
|
|
| 598 |
- cmd.Usage() |
|
| 599 |
- return nil |
|
| 600 |
- } |
|
| 601 |
- |
|
| 602 |
- var cErr chan error |
|
| 603 |
- var tty bool |
|
| 604 |
- if *attach || *openStdin {
|
|
| 605 |
- if cmd.NArg() > 1 {
|
|
| 606 |
- return fmt.Errorf("You cannot start and attach multiple containers at once.")
|
|
| 607 |
- } |
|
| 608 |
- |
|
| 609 |
- body, _, err := readBody(cli.call("GET", "/containers/"+cmd.Arg(0)+"/json", nil, false))
|
|
| 610 |
- if err != nil {
|
|
| 611 |
- return err |
|
| 612 |
- } |
|
| 613 |
- |
|
| 614 |
- container := &api.Container{}
|
|
| 615 |
- err = json.Unmarshal(body, container) |
|
| 616 |
- if err != nil {
|
|
| 617 |
- return err |
|
| 618 |
- } |
|
| 619 |
- |
|
| 620 |
- tty = container.Config.Tty |
|
| 621 |
- |
|
| 622 |
- if !container.Config.Tty {
|
|
| 623 |
- sigc := cli.forwardAllSignals(cmd.Arg(0)) |
|
| 624 |
- defer signal.StopCatch(sigc) |
|
| 625 |
- } |
|
| 626 |
- |
|
| 627 |
- var in io.ReadCloser |
|
| 628 |
- |
|
| 629 |
- v := url.Values{}
|
|
| 630 |
- v.Set("stream", "1")
|
|
| 631 |
- if *openStdin && container.Config.OpenStdin {
|
|
| 632 |
- v.Set("stdin", "1")
|
|
| 633 |
- in = cli.in |
|
| 634 |
- } |
|
| 635 |
- v.Set("stdout", "1")
|
|
| 636 |
- v.Set("stderr", "1")
|
|
| 637 |
- |
|
| 638 |
- cErr = utils.Go(func() error {
|
|
| 639 |
- return cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), container.Config.Tty, in, cli.out, cli.err, nil)
|
|
| 640 |
- }) |
|
| 641 |
- } |
|
| 642 |
- |
|
| 643 |
- var encounteredError error |
|
| 644 |
- for _, name := range cmd.Args() {
|
|
| 645 |
- _, _, err := readBody(cli.call("POST", "/containers/"+name+"/start", nil, false))
|
|
| 646 |
- if err != nil {
|
|
| 647 |
- if !*attach || !*openStdin {
|
|
| 648 |
- fmt.Fprintf(cli.err, "%s\n", err) |
|
| 649 |
- encounteredError = fmt.Errorf("Error: failed to start one or more containers")
|
|
| 650 |
- } |
|
| 651 |
- } else {
|
|
| 652 |
- if !*attach || !*openStdin {
|
|
| 653 |
- fmt.Fprintf(cli.out, "%s\n", name) |
|
| 654 |
- } |
|
| 655 |
- } |
|
| 656 |
- } |
|
| 657 |
- if encounteredError != nil {
|
|
| 658 |
- if *openStdin || *attach {
|
|
| 659 |
- cli.in.Close() |
|
| 660 |
- <-cErr |
|
| 661 |
- } |
|
| 662 |
- return encounteredError |
|
| 663 |
- } |
|
| 664 |
- |
|
| 665 |
- if *openStdin || *attach {
|
|
| 666 |
- if tty && cli.isTerminal {
|
|
| 667 |
- if err := cli.monitorTtySize(cmd.Arg(0)); err != nil {
|
|
| 668 |
- utils.Errorf("Error monitoring TTY size: %s\n", err)
|
|
| 669 |
- } |
|
| 670 |
- } |
|
| 671 |
- return <-cErr |
|
| 672 |
- } |
|
| 673 |
- return nil |
|
| 674 |
-} |
|
| 675 |
- |
|
| 676 |
-func (cli *DockerCli) CmdInspect(args ...string) error {
|
|
| 677 |
- cmd := cli.Subcmd("inspect", "CONTAINER|IMAGE [CONTAINER|IMAGE...]", "Return low-level information on a container/image")
|
|
| 678 |
- tmplStr := cmd.String([]string{"f", "#format", "-format"}, "", "Format the output using the given go template.")
|
|
| 679 |
- if err := cmd.Parse(args); err != nil {
|
|
| 680 |
- return nil |
|
| 681 |
- } |
|
| 682 |
- if cmd.NArg() < 1 {
|
|
| 683 |
- cmd.Usage() |
|
| 684 |
- return nil |
|
| 685 |
- } |
|
| 686 |
- |
|
| 687 |
- var tmpl *template.Template |
|
| 688 |
- if *tmplStr != "" {
|
|
| 689 |
- var err error |
|
| 690 |
- if tmpl, err = template.New("").Funcs(funcMap).Parse(*tmplStr); err != nil {
|
|
| 691 |
- fmt.Fprintf(cli.err, "Template parsing error: %v\n", err) |
|
| 692 |
- return &utils.StatusError{StatusCode: 64,
|
|
| 693 |
- Status: "Template parsing error: " + err.Error()} |
|
| 694 |
- } |
|
| 695 |
- } |
|
| 696 |
- |
|
| 697 |
- indented := new(bytes.Buffer) |
|
| 698 |
- indented.WriteByte('[')
|
|
| 699 |
- status := 0 |
|
| 700 |
- |
|
| 701 |
- for _, name := range cmd.Args() {
|
|
| 702 |
- obj, _, err := readBody(cli.call("GET", "/containers/"+name+"/json", nil, false))
|
|
| 703 |
- if err != nil {
|
|
| 704 |
- obj, _, err = readBody(cli.call("GET", "/images/"+name+"/json", nil, false))
|
|
| 705 |
- if err != nil {
|
|
| 706 |
- if strings.Contains(err.Error(), "No such") {
|
|
| 707 |
- fmt.Fprintf(cli.err, "Error: No such image or container: %s\n", name) |
|
| 708 |
- } else {
|
|
| 709 |
- fmt.Fprintf(cli.err, "%s", err) |
|
| 710 |
- } |
|
| 711 |
- status = 1 |
|
| 712 |
- continue |
|
| 713 |
- } |
|
| 714 |
- } |
|
| 715 |
- |
|
| 716 |
- if tmpl == nil {
|
|
| 717 |
- if err = json.Indent(indented, obj, "", " "); err != nil {
|
|
| 718 |
- fmt.Fprintf(cli.err, "%s\n", err) |
|
| 719 |
- status = 1 |
|
| 720 |
- continue |
|
| 721 |
- } |
|
| 722 |
- } else {
|
|
| 723 |
- // Has template, will render |
|
| 724 |
- var value interface{}
|
|
| 725 |
- if err := json.Unmarshal(obj, &value); err != nil {
|
|
| 726 |
- fmt.Fprintf(cli.err, "%s\n", err) |
|
| 727 |
- status = 1 |
|
| 728 |
- continue |
|
| 729 |
- } |
|
| 730 |
- if err := tmpl.Execute(cli.out, value); err != nil {
|
|
| 731 |
- return err |
|
| 732 |
- } |
|
| 733 |
- cli.out.Write([]byte{'\n'})
|
|
| 734 |
- } |
|
| 735 |
- indented.WriteString(",")
|
|
| 736 |
- } |
|
| 737 |
- |
|
| 738 |
- if indented.Len() > 1 {
|
|
| 739 |
- // Remove trailing ',' |
|
| 740 |
- indented.Truncate(indented.Len() - 1) |
|
| 741 |
- } |
|
| 742 |
- indented.WriteByte(']')
|
|
| 743 |
- |
|
| 744 |
- if tmpl == nil {
|
|
| 745 |
- if _, err := io.Copy(cli.out, indented); err != nil {
|
|
| 746 |
- return err |
|
| 747 |
- } |
|
| 748 |
- } |
|
| 749 |
- |
|
| 750 |
- if status != 0 {
|
|
| 751 |
- return &utils.StatusError{StatusCode: status}
|
|
| 752 |
- } |
|
| 753 |
- return nil |
|
| 754 |
-} |
|
| 755 |
- |
|
| 756 |
-func (cli *DockerCli) CmdTop(args ...string) error {
|
|
| 757 |
- cmd := cli.Subcmd("top", "CONTAINER [ps OPTIONS]", "Lookup the running processes of a container")
|
|
| 758 |
- if err := cmd.Parse(args); err != nil {
|
|
| 759 |
- return nil |
|
| 760 |
- } |
|
| 761 |
- if cmd.NArg() == 0 {
|
|
| 762 |
- cmd.Usage() |
|
| 763 |
- return nil |
|
| 764 |
- } |
|
| 765 |
- val := url.Values{}
|
|
| 766 |
- if cmd.NArg() > 1 {
|
|
| 767 |
- val.Set("ps_args", strings.Join(cmd.Args()[1:], " "))
|
|
| 768 |
- } |
|
| 769 |
- |
|
| 770 |
- stream, _, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/top?"+val.Encode(), nil, false)
|
|
| 771 |
- if err != nil {
|
|
| 772 |
- return err |
|
| 773 |
- } |
|
| 774 |
- var procs engine.Env |
|
| 775 |
- if err := procs.Decode(stream); err != nil {
|
|
| 776 |
- return err |
|
| 777 |
- } |
|
| 778 |
- w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0) |
|
| 779 |
- fmt.Fprintln(w, strings.Join(procs.GetList("Titles"), "\t"))
|
|
| 780 |
- processes := [][]string{}
|
|
| 781 |
- if err := procs.GetJson("Processes", &processes); err != nil {
|
|
| 782 |
- return err |
|
| 783 |
- } |
|
| 784 |
- for _, proc := range processes {
|
|
| 785 |
- fmt.Fprintln(w, strings.Join(proc, "\t")) |
|
| 786 |
- } |
|
| 787 |
- w.Flush() |
|
| 788 |
- return nil |
|
| 789 |
-} |
|
| 790 |
- |
|
| 791 |
-func (cli *DockerCli) CmdPort(args ...string) error {
|
|
| 792 |
- cmd := cli.Subcmd("port", "CONTAINER PRIVATE_PORT", "Lookup the public-facing port which is NAT-ed to PRIVATE_PORT")
|
|
| 793 |
- if err := cmd.Parse(args); err != nil {
|
|
| 794 |
- return nil |
|
| 795 |
- } |
|
| 796 |
- if cmd.NArg() != 2 {
|
|
| 797 |
- cmd.Usage() |
|
| 798 |
- return nil |
|
| 799 |
- } |
|
| 800 |
- |
|
| 801 |
- var ( |
|
| 802 |
- port = cmd.Arg(1) |
|
| 803 |
- proto = "tcp" |
|
| 804 |
- parts = strings.SplitN(port, "/", 2) |
|
| 805 |
- container api.Container |
|
| 806 |
- ) |
|
| 807 |
- |
|
| 808 |
- if len(parts) == 2 && len(parts[1]) != 0 {
|
|
| 809 |
- port = parts[0] |
|
| 810 |
- proto = parts[1] |
|
| 811 |
- } |
|
| 812 |
- body, _, err := readBody(cli.call("GET", "/containers/"+cmd.Arg(0)+"/json", nil, false))
|
|
| 813 |
- if err != nil {
|
|
| 814 |
- return err |
|
| 815 |
- } |
|
| 816 |
- |
|
| 817 |
- err = json.Unmarshal(body, &container) |
|
| 818 |
- if err != nil {
|
|
| 819 |
- return err |
|
| 820 |
- } |
|
| 821 |
- |
|
| 822 |
- if frontends, exists := container.NetworkSettings.Ports[nat.Port(port+"/"+proto)]; exists && frontends != nil {
|
|
| 823 |
- for _, frontend := range frontends {
|
|
| 824 |
- fmt.Fprintf(cli.out, "%s:%s\n", frontend.HostIp, frontend.HostPort) |
|
| 825 |
- } |
|
| 826 |
- } else {
|
|
| 827 |
- return fmt.Errorf("Error: No public port '%s' published for %s", cmd.Arg(1), cmd.Arg(0))
|
|
| 828 |
- } |
|
| 829 |
- return nil |
|
| 830 |
-} |
|
| 831 |
- |
|
| 832 |
-// 'docker rmi IMAGE' removes all images with the name IMAGE |
|
| 833 |
-func (cli *DockerCli) CmdRmi(args ...string) error {
|
|
| 834 |
- var ( |
|
| 835 |
- cmd = cli.Subcmd("rmi", "IMAGE [IMAGE...]", "Remove one or more images")
|
|
| 836 |
- force = cmd.Bool([]string{"f", "-force"}, false, "Force")
|
|
| 837 |
- noprune = cmd.Bool([]string{"-no-prune"}, false, "Do not delete untagged parents")
|
|
| 838 |
- ) |
|
| 839 |
- if err := cmd.Parse(args); err != nil {
|
|
| 840 |
- return nil |
|
| 841 |
- } |
|
| 842 |
- if cmd.NArg() < 1 {
|
|
| 843 |
- cmd.Usage() |
|
| 844 |
- return nil |
|
| 845 |
- } |
|
| 846 |
- |
|
| 847 |
- v := url.Values{}
|
|
| 848 |
- if *force {
|
|
| 849 |
- v.Set("force", "1")
|
|
| 850 |
- } |
|
| 851 |
- if *noprune {
|
|
| 852 |
- v.Set("noprune", "1")
|
|
| 853 |
- } |
|
| 854 |
- |
|
| 855 |
- var encounteredError error |
|
| 856 |
- for _, name := range cmd.Args() {
|
|
| 857 |
- body, _, err := readBody(cli.call("DELETE", "/images/"+name+"?"+v.Encode(), nil, false))
|
|
| 858 |
- if err != nil {
|
|
| 859 |
- fmt.Fprintf(cli.err, "%s\n", err) |
|
| 860 |
- encounteredError = fmt.Errorf("Error: failed to remove one or more images")
|
|
| 861 |
- } else {
|
|
| 862 |
- outs := engine.NewTable("Created", 0)
|
|
| 863 |
- if _, err := outs.ReadListFrom(body); err != nil {
|
|
| 864 |
- fmt.Fprintf(cli.err, "%s\n", err) |
|
| 865 |
- encounteredError = fmt.Errorf("Error: failed to remove one or more images")
|
|
| 866 |
- continue |
|
| 867 |
- } |
|
| 868 |
- for _, out := range outs.Data {
|
|
| 869 |
- if out.Get("Deleted") != "" {
|
|
| 870 |
- fmt.Fprintf(cli.out, "Deleted: %s\n", out.Get("Deleted"))
|
|
| 871 |
- } else {
|
|
| 872 |
- fmt.Fprintf(cli.out, "Untagged: %s\n", out.Get("Untagged"))
|
|
| 873 |
- } |
|
| 874 |
- } |
|
| 875 |
- } |
|
| 876 |
- } |
|
| 877 |
- return encounteredError |
|
| 878 |
-} |
|
| 879 |
- |
|
| 880 |
-func (cli *DockerCli) CmdHistory(args ...string) error {
|
|
| 881 |
- cmd := cli.Subcmd("history", "[OPTIONS] IMAGE", "Show the history of an image")
|
|
| 882 |
- quiet := cmd.Bool([]string{"q", "-quiet"}, false, "Only show numeric IDs")
|
|
| 883 |
- noTrunc := cmd.Bool([]string{"#notrunc", "-no-trunc"}, false, "Don't truncate output")
|
|
| 884 |
- |
|
| 885 |
- if err := cmd.Parse(args); err != nil {
|
|
| 886 |
- return nil |
|
| 887 |
- } |
|
| 888 |
- if cmd.NArg() != 1 {
|
|
| 889 |
- cmd.Usage() |
|
| 890 |
- return nil |
|
| 891 |
- } |
|
| 892 |
- |
|
| 893 |
- body, _, err := readBody(cli.call("GET", "/images/"+cmd.Arg(0)+"/history", nil, false))
|
|
| 894 |
- if err != nil {
|
|
| 895 |
- return err |
|
| 896 |
- } |
|
| 897 |
- |
|
| 898 |
- outs := engine.NewTable("Created", 0)
|
|
| 899 |
- if _, err := outs.ReadListFrom(body); err != nil {
|
|
| 900 |
- return err |
|
| 901 |
- } |
|
| 902 |
- |
|
| 903 |
- w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0) |
|
| 904 |
- if !*quiet {
|
|
| 905 |
- fmt.Fprintln(w, "IMAGE\tCREATED\tCREATED BY\tSIZE") |
|
| 906 |
- } |
|
| 907 |
- |
|
| 908 |
- for _, out := range outs.Data {
|
|
| 909 |
- outID := out.Get("Id")
|
|
| 910 |
- if !*quiet {
|
|
| 911 |
- if *noTrunc {
|
|
| 912 |
- fmt.Fprintf(w, "%s\t", outID) |
|
| 913 |
- } else {
|
|
| 914 |
- fmt.Fprintf(w, "%s\t", utils.TruncateID(outID)) |
|
| 915 |
- } |
|
| 916 |
- |
|
| 917 |
- fmt.Fprintf(w, "%s ago\t", utils.HumanDuration(time.Now().UTC().Sub(time.Unix(out.GetInt64("Created"), 0))))
|
|
| 918 |
- |
|
| 919 |
- if *noTrunc {
|
|
| 920 |
- fmt.Fprintf(w, "%s\t", out.Get("CreatedBy"))
|
|
| 921 |
- } else {
|
|
| 922 |
- fmt.Fprintf(w, "%s\t", utils.Trunc(out.Get("CreatedBy"), 45))
|
|
| 923 |
- } |
|
| 924 |
- fmt.Fprintf(w, "%s\n", utils.HumanSize(out.GetInt64("Size")))
|
|
| 925 |
- } else {
|
|
| 926 |
- if *noTrunc {
|
|
| 927 |
- fmt.Fprintln(w, outID) |
|
| 928 |
- } else {
|
|
| 929 |
- fmt.Fprintln(w, utils.TruncateID(outID)) |
|
| 930 |
- } |
|
| 931 |
- } |
|
| 932 |
- } |
|
| 933 |
- w.Flush() |
|
| 934 |
- return nil |
|
| 935 |
-} |
|
| 936 |
- |
|
| 937 |
-func (cli *DockerCli) CmdRm(args ...string) error {
|
|
| 938 |
- cmd := cli.Subcmd("rm", "[OPTIONS] CONTAINER [CONTAINER...]", "Remove one or more containers")
|
|
| 939 |
- v := cmd.Bool([]string{"v", "-volumes"}, false, "Remove the volumes associated to the container")
|
|
| 940 |
- link := cmd.Bool([]string{"l", "#link", "-link"}, false, "Remove the specified link and not the underlying container")
|
|
| 941 |
- force := cmd.Bool([]string{"f", "-force"}, false, "Force removal of running container")
|
|
| 942 |
- |
|
| 943 |
- if err := cmd.Parse(args); err != nil {
|
|
| 944 |
- return nil |
|
| 945 |
- } |
|
| 946 |
- if cmd.NArg() < 1 {
|
|
| 947 |
- cmd.Usage() |
|
| 948 |
- return nil |
|
| 949 |
- } |
|
| 950 |
- val := url.Values{}
|
|
| 951 |
- if *v {
|
|
| 952 |
- val.Set("v", "1")
|
|
| 953 |
- } |
|
| 954 |
- if *link {
|
|
| 955 |
- val.Set("link", "1")
|
|
| 956 |
- } |
|
| 957 |
- if *force {
|
|
| 958 |
- val.Set("force", "1")
|
|
| 959 |
- } |
|
| 960 |
- |
|
| 961 |
- var encounteredError error |
|
| 962 |
- for _, name := range cmd.Args() {
|
|
| 963 |
- _, _, err := readBody(cli.call("DELETE", "/containers/"+name+"?"+val.Encode(), nil, false))
|
|
| 964 |
- if err != nil {
|
|
| 965 |
- fmt.Fprintf(cli.err, "%s\n", err) |
|
| 966 |
- encounteredError = fmt.Errorf("Error: failed to remove one or more containers")
|
|
| 967 |
- } else {
|
|
| 968 |
- fmt.Fprintf(cli.out, "%s\n", name) |
|
| 969 |
- } |
|
| 970 |
- } |
|
| 971 |
- return encounteredError |
|
| 972 |
-} |
|
| 973 |
- |
|
| 974 |
-// 'docker kill NAME' kills a running container |
|
| 975 |
-func (cli *DockerCli) CmdKill(args ...string) error {
|
|
| 976 |
- cmd := cli.Subcmd("kill", "[OPTIONS] CONTAINER [CONTAINER...]", "Kill a running container (send SIGKILL, or specified signal)")
|
|
| 977 |
- signal := cmd.String([]string{"s", "-signal"}, "KILL", "Signal to send to the container")
|
|
| 978 |
- |
|
| 979 |
- if err := cmd.Parse(args); err != nil {
|
|
| 980 |
- return nil |
|
| 981 |
- } |
|
| 982 |
- if cmd.NArg() < 1 {
|
|
| 983 |
- cmd.Usage() |
|
| 984 |
- return nil |
|
| 985 |
- } |
|
| 986 |
- |
|
| 987 |
- var encounteredError error |
|
| 988 |
- for _, name := range cmd.Args() {
|
|
| 989 |
- if _, _, err := readBody(cli.call("POST", fmt.Sprintf("/containers/%s/kill?signal=%s", name, *signal), nil, false)); err != nil {
|
|
| 990 |
- fmt.Fprintf(cli.err, "%s\n", err) |
|
| 991 |
- encounteredError = fmt.Errorf("Error: failed to kill one or more containers")
|
|
| 992 |
- } else {
|
|
| 993 |
- fmt.Fprintf(cli.out, "%s\n", name) |
|
| 994 |
- } |
|
| 995 |
- } |
|
| 996 |
- return encounteredError |
|
| 997 |
-} |
|
| 998 |
- |
|
| 999 |
-func (cli *DockerCli) CmdImport(args ...string) error {
|
|
| 1000 |
- cmd := cli.Subcmd("import", "URL|- [REPOSITORY[:TAG]]", "Create an empty filesystem image and import the contents of the tarball (.tar, .tar.gz, .tgz, .bzip, .tar.xz, .txz) into it, then optionally tag it.")
|
|
| 1001 |
- |
|
| 1002 |
- if err := cmd.Parse(args); err != nil {
|
|
| 1003 |
- return nil |
|
| 1004 |
- } |
|
| 1005 |
- if cmd.NArg() < 1 {
|
|
| 1006 |
- cmd.Usage() |
|
| 1007 |
- return nil |
|
| 1008 |
- } |
|
| 1009 |
- |
|
| 1010 |
- var src, repository, tag string |
|
| 1011 |
- |
|
| 1012 |
- if cmd.NArg() == 3 {
|
|
| 1013 |
- fmt.Fprintf(cli.err, "[DEPRECATED] The format 'URL|- [REPOSITORY [TAG]]' as been deprecated. Please use URL|- [REPOSITORY[:TAG]]\n") |
|
| 1014 |
- src, repository, tag = cmd.Arg(0), cmd.Arg(1), cmd.Arg(2) |
|
| 1015 |
- } else {
|
|
| 1016 |
- src = cmd.Arg(0) |
|
| 1017 |
- repository, tag = utils.ParseRepositoryTag(cmd.Arg(1)) |
|
| 1018 |
- } |
|
| 1019 |
- v := url.Values{}
|
|
| 1020 |
- |
|
| 1021 |
- if repository != "" {
|
|
| 1022 |
- //Check if the given image name can be resolved |
|
| 1023 |
- if _, _, err := registry.ResolveRepositoryName(repository); err != nil {
|
|
| 1024 |
- return err |
|
| 1025 |
- } |
|
| 1026 |
- } |
|
| 1027 |
- |
|
| 1028 |
- v.Set("repo", repository)
|
|
| 1029 |
- v.Set("tag", tag)
|
|
| 1030 |
- v.Set("fromSrc", src)
|
|
| 1031 |
- |
|
| 1032 |
- var in io.Reader |
|
| 1033 |
- |
|
| 1034 |
- if src == "-" {
|
|
| 1035 |
- in = cli.in |
|
| 1036 |
- } |
|
| 1037 |
- |
|
| 1038 |
- return cli.stream("POST", "/images/create?"+v.Encode(), in, cli.out, nil)
|
|
| 1039 |
-} |
|
| 1040 |
- |
|
| 1041 |
-func (cli *DockerCli) CmdPush(args ...string) error {
|
|
| 1042 |
- cmd := cli.Subcmd("push", "NAME", "Push an image or a repository to the registry")
|
|
| 1043 |
- if err := cmd.Parse(args); err != nil {
|
|
| 1044 |
- return nil |
|
| 1045 |
- } |
|
| 1046 |
- name := cmd.Arg(0) |
|
| 1047 |
- |
|
| 1048 |
- if name == "" {
|
|
| 1049 |
- cmd.Usage() |
|
| 1050 |
- return nil |
|
| 1051 |
- } |
|
| 1052 |
- |
|
| 1053 |
- cli.LoadConfigFile() |
|
| 1054 |
- |
|
| 1055 |
- // Resolve the Repository name from fqn to hostname + name |
|
| 1056 |
- hostname, _, err := registry.ResolveRepositoryName(name) |
|
| 1057 |
- if err != nil {
|
|
| 1058 |
- return err |
|
| 1059 |
- } |
|
| 1060 |
- // Resolve the Auth config relevant for this server |
|
| 1061 |
- authConfig := cli.configFile.ResolveAuthConfig(hostname) |
|
| 1062 |
- // If we're not using a custom registry, we know the restrictions |
|
| 1063 |
- // applied to repository names and can warn the user in advance. |
|
| 1064 |
- // Custom repositories can have different rules, and we must also |
|
| 1065 |
- // allow pushing by image ID. |
|
| 1066 |
- if len(strings.SplitN(name, "/", 2)) == 1 {
|
|
| 1067 |
- username := cli.configFile.Configs[registry.IndexServerAddress()].Username |
|
| 1068 |
- if username == "" {
|
|
| 1069 |
- username = "<user>" |
|
| 1070 |
- } |
|
| 1071 |
- return fmt.Errorf("You cannot push a \"root\" repository. Please rename your repository in <user>/<repo> (ex: %s/%s)", username, name)
|
|
| 1072 |
- } |
|
| 1073 |
- |
|
| 1074 |
- v := url.Values{}
|
|
| 1075 |
- push := func(authConfig registry.AuthConfig) error {
|
|
| 1076 |
- buf, err := json.Marshal(authConfig) |
|
| 1077 |
- if err != nil {
|
|
| 1078 |
- return err |
|
| 1079 |
- } |
|
| 1080 |
- registryAuthHeader := []string{
|
|
| 1081 |
- base64.URLEncoding.EncodeToString(buf), |
|
| 1082 |
- } |
|
| 1083 |
- |
|
| 1084 |
- return cli.stream("POST", "/images/"+name+"/push?"+v.Encode(), nil, cli.out, map[string][]string{
|
|
| 1085 |
- "X-Registry-Auth": registryAuthHeader, |
|
| 1086 |
- }) |
|
| 1087 |
- } |
|
| 1088 |
- |
|
| 1089 |
- if err := push(authConfig); err != nil {
|
|
| 1090 |
- if strings.Contains(err.Error(), "Status 401") {
|
|
| 1091 |
- fmt.Fprintln(cli.out, "\nPlease login prior to push:") |
|
| 1092 |
- if err := cli.CmdLogin(hostname); err != nil {
|
|
| 1093 |
- return err |
|
| 1094 |
- } |
|
| 1095 |
- authConfig := cli.configFile.ResolveAuthConfig(hostname) |
|
| 1096 |
- return push(authConfig) |
|
| 1097 |
- } |
|
| 1098 |
- return err |
|
| 1099 |
- } |
|
| 1100 |
- return nil |
|
| 1101 |
-} |
|
| 1102 |
- |
|
| 1103 |
-func (cli *DockerCli) CmdPull(args ...string) error {
|
|
| 1104 |
- cmd := cli.Subcmd("pull", "NAME[:TAG]", "Pull an image or a repository from the registry")
|
|
| 1105 |
- tag := cmd.String([]string{"#t", "#-tag"}, "", "Download tagged image in repository")
|
|
| 1106 |
- if err := cmd.Parse(args); err != nil {
|
|
| 1107 |
- return nil |
|
| 1108 |
- } |
|
| 1109 |
- |
|
| 1110 |
- if cmd.NArg() != 1 {
|
|
| 1111 |
- cmd.Usage() |
|
| 1112 |
- return nil |
|
| 1113 |
- } |
|
| 1114 |
- |
|
| 1115 |
- remote, parsedTag := utils.ParseRepositoryTag(cmd.Arg(0)) |
|
| 1116 |
- if *tag == "" {
|
|
| 1117 |
- *tag = parsedTag |
|
| 1118 |
- } |
|
| 1119 |
- |
|
| 1120 |
- // Resolve the Repository name from fqn to hostname + name |
|
| 1121 |
- hostname, _, err := registry.ResolveRepositoryName(remote) |
|
| 1122 |
- if err != nil {
|
|
| 1123 |
- return err |
|
| 1124 |
- } |
|
| 1125 |
- |
|
| 1126 |
- cli.LoadConfigFile() |
|
| 1127 |
- |
|
| 1128 |
- // Resolve the Auth config relevant for this server |
|
| 1129 |
- authConfig := cli.configFile.ResolveAuthConfig(hostname) |
|
| 1130 |
- v := url.Values{}
|
|
| 1131 |
- v.Set("fromImage", remote)
|
|
| 1132 |
- v.Set("tag", *tag)
|
|
| 1133 |
- |
|
| 1134 |
- pull := func(authConfig registry.AuthConfig) error {
|
|
| 1135 |
- buf, err := json.Marshal(authConfig) |
|
| 1136 |
- if err != nil {
|
|
| 1137 |
- return err |
|
| 1138 |
- } |
|
| 1139 |
- registryAuthHeader := []string{
|
|
| 1140 |
- base64.URLEncoding.EncodeToString(buf), |
|
| 1141 |
- } |
|
| 1142 |
- |
|
| 1143 |
- return cli.stream("POST", "/images/create?"+v.Encode(), nil, cli.out, map[string][]string{
|
|
| 1144 |
- "X-Registry-Auth": registryAuthHeader, |
|
| 1145 |
- }) |
|
| 1146 |
- } |
|
| 1147 |
- |
|
| 1148 |
- if err := pull(authConfig); err != nil {
|
|
| 1149 |
- if strings.Contains(err.Error(), "Status 401") {
|
|
| 1150 |
- fmt.Fprintln(cli.out, "\nPlease login prior to pull:") |
|
| 1151 |
- if err := cli.CmdLogin(hostname); err != nil {
|
|
| 1152 |
- return err |
|
| 1153 |
- } |
|
| 1154 |
- authConfig := cli.configFile.ResolveAuthConfig(hostname) |
|
| 1155 |
- return pull(authConfig) |
|
| 1156 |
- } |
|
| 1157 |
- return err |
|
| 1158 |
- } |
|
| 1159 |
- |
|
| 1160 |
- return nil |
|
| 1161 |
-} |
|
| 1162 |
- |
|
| 1163 |
-func (cli *DockerCli) CmdImages(args ...string) error {
|
|
| 1164 |
- cmd := cli.Subcmd("images", "[OPTIONS] [NAME]", "List images")
|
|
| 1165 |
- quiet := cmd.Bool([]string{"q", "-quiet"}, false, "Only show numeric IDs")
|
|
| 1166 |
- all := cmd.Bool([]string{"a", "-all"}, false, "Show all images (by default filter out the intermediate images used to build)")
|
|
| 1167 |
- noTrunc := cmd.Bool([]string{"#notrunc", "-no-trunc"}, false, "Don't truncate output")
|
|
| 1168 |
- flViz := cmd.Bool([]string{"v", "#viz", "-viz"}, false, "Output graph in graphviz format")
|
|
| 1169 |
- flTree := cmd.Bool([]string{"t", "#tree", "-tree"}, false, "Output graph in tree format")
|
|
| 1170 |
- |
|
| 1171 |
- if err := cmd.Parse(args); err != nil {
|
|
| 1172 |
- return nil |
|
| 1173 |
- } |
|
| 1174 |
- if cmd.NArg() > 1 {
|
|
| 1175 |
- cmd.Usage() |
|
| 1176 |
- return nil |
|
| 1177 |
- } |
|
| 1178 |
- |
|
| 1179 |
- filter := cmd.Arg(0) |
|
| 1180 |
- |
|
| 1181 |
- if *flViz || *flTree {
|
|
| 1182 |
- body, _, err := readBody(cli.call("GET", "/images/json?all=1", nil, false))
|
|
| 1183 |
- if err != nil {
|
|
| 1184 |
- return err |
|
| 1185 |
- } |
|
| 1186 |
- |
|
| 1187 |
- outs := engine.NewTable("Created", 0)
|
|
| 1188 |
- if _, err := outs.ReadListFrom(body); err != nil {
|
|
| 1189 |
- return err |
|
| 1190 |
- } |
|
| 1191 |
- |
|
| 1192 |
- var ( |
|
| 1193 |
- printNode func(cli *DockerCli, noTrunc bool, image *engine.Env, prefix string) |
|
| 1194 |
- startImage *engine.Env |
|
| 1195 |
- |
|
| 1196 |
- roots = engine.NewTable("Created", outs.Len())
|
|
| 1197 |
- byParent = make(map[string]*engine.Table) |
|
| 1198 |
- ) |
|
| 1199 |
- |
|
| 1200 |
- for _, image := range outs.Data {
|
|
| 1201 |
- if image.Get("ParentId") == "" {
|
|
| 1202 |
- roots.Add(image) |
|
| 1203 |
- } else {
|
|
| 1204 |
- if children, exists := byParent[image.Get("ParentId")]; exists {
|
|
| 1205 |
- children.Add(image) |
|
| 1206 |
- } else {
|
|
| 1207 |
- byParent[image.Get("ParentId")] = engine.NewTable("Created", 1)
|
|
| 1208 |
- byParent[image.Get("ParentId")].Add(image)
|
|
| 1209 |
- } |
|
| 1210 |
- } |
|
| 1211 |
- |
|
| 1212 |
- if filter != "" {
|
|
| 1213 |
- if filter == image.Get("Id") || filter == utils.TruncateID(image.Get("Id")) {
|
|
| 1214 |
- startImage = image |
|
| 1215 |
- } |
|
| 1216 |
- |
|
| 1217 |
- for _, repotag := range image.GetList("RepoTags") {
|
|
| 1218 |
- if repotag == filter {
|
|
| 1219 |
- startImage = image |
|
| 1220 |
- } |
|
| 1221 |
- } |
|
| 1222 |
- } |
|
| 1223 |
- } |
|
| 1224 |
- |
|
| 1225 |
- if *flViz {
|
|
| 1226 |
- fmt.Fprintf(cli.out, "digraph docker {\n")
|
|
| 1227 |
- printNode = (*DockerCli).printVizNode |
|
| 1228 |
- } else {
|
|
| 1229 |
- printNode = (*DockerCli).printTreeNode |
|
| 1230 |
- } |
|
| 1231 |
- |
|
| 1232 |
- if startImage != nil {
|
|
| 1233 |
- root := engine.NewTable("Created", 1)
|
|
| 1234 |
- root.Add(startImage) |
|
| 1235 |
- cli.WalkTree(*noTrunc, root, byParent, "", printNode) |
|
| 1236 |
- } else if filter == "" {
|
|
| 1237 |
- cli.WalkTree(*noTrunc, roots, byParent, "", printNode) |
|
| 1238 |
- } |
|
| 1239 |
- if *flViz {
|
|
| 1240 |
- fmt.Fprintf(cli.out, " base [style=invisible]\n}\n") |
|
| 1241 |
- } |
|
| 1242 |
- } else {
|
|
| 1243 |
- v := url.Values{}
|
|
| 1244 |
- if cmd.NArg() == 1 {
|
|
| 1245 |
- v.Set("filter", filter)
|
|
| 1246 |
- } |
|
| 1247 |
- if *all {
|
|
| 1248 |
- v.Set("all", "1")
|
|
| 1249 |
- } |
|
| 1250 |
- |
|
| 1251 |
- body, _, err := readBody(cli.call("GET", "/images/json?"+v.Encode(), nil, false))
|
|
| 1252 |
- |
|
| 1253 |
- if err != nil {
|
|
| 1254 |
- return err |
|
| 1255 |
- } |
|
| 1256 |
- |
|
| 1257 |
- outs := engine.NewTable("Created", 0)
|
|
| 1258 |
- if _, err := outs.ReadListFrom(body); err != nil {
|
|
| 1259 |
- return err |
|
| 1260 |
- } |
|
| 1261 |
- |
|
| 1262 |
- w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0) |
|
| 1263 |
- if !*quiet {
|
|
| 1264 |
- fmt.Fprintln(w, "REPOSITORY\tTAG\tIMAGE ID\tCREATED\tVIRTUAL SIZE") |
|
| 1265 |
- } |
|
| 1266 |
- |
|
| 1267 |
- for _, out := range outs.Data {
|
|
| 1268 |
- for _, repotag := range out.GetList("RepoTags") {
|
|
| 1269 |
- |
|
| 1270 |
- repo, tag := utils.ParseRepositoryTag(repotag) |
|
| 1271 |
- outID := out.Get("Id")
|
|
| 1272 |
- if !*noTrunc {
|
|
| 1273 |
- outID = utils.TruncateID(outID) |
|
| 1274 |
- } |
|
| 1275 |
- |
|
| 1276 |
- if !*quiet {
|
|
| 1277 |
- fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\n", repo, tag, outID, utils.HumanDuration(time.Now().UTC().Sub(time.Unix(out.GetInt64("Created"), 0))), utils.HumanSize(out.GetInt64("VirtualSize")))
|
|
| 1278 |
- } else {
|
|
| 1279 |
- fmt.Fprintln(w, outID) |
|
| 1280 |
- } |
|
| 1281 |
- } |
|
| 1282 |
- } |
|
| 1283 |
- |
|
| 1284 |
- if !*quiet {
|
|
| 1285 |
- w.Flush() |
|
| 1286 |
- } |
|
| 1287 |
- } |
|
| 1288 |
- return nil |
|
| 1289 |
-} |
|
| 1290 |
- |
|
| 1291 |
-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)) {
|
|
| 1292 |
- length := images.Len() |
|
| 1293 |
- if length > 1 {
|
|
| 1294 |
- for index, image := range images.Data {
|
|
| 1295 |
- if index+1 == length {
|
|
| 1296 |
- printNode(cli, noTrunc, image, prefix+"└─") |
|
| 1297 |
- if subimages, exists := byParent[image.Get("Id")]; exists {
|
|
| 1298 |
- cli.WalkTree(noTrunc, subimages, byParent, prefix+" ", printNode) |
|
| 1299 |
- } |
|
| 1300 |
- } else {
|
|
| 1301 |
- printNode(cli, noTrunc, image, prefix+"\u251C─") |
|
| 1302 |
- if subimages, exists := byParent[image.Get("Id")]; exists {
|
|
| 1303 |
- cli.WalkTree(noTrunc, subimages, byParent, prefix+"\u2502 ", printNode) |
|
| 1304 |
- } |
|
| 1305 |
- } |
|
| 1306 |
- } |
|
| 1307 |
- } else {
|
|
| 1308 |
- for _, image := range images.Data {
|
|
| 1309 |
- printNode(cli, noTrunc, image, prefix+"└─") |
|
| 1310 |
- if subimages, exists := byParent[image.Get("Id")]; exists {
|
|
| 1311 |
- cli.WalkTree(noTrunc, subimages, byParent, prefix+" ", printNode) |
|
| 1312 |
- } |
|
| 1313 |
- } |
|
| 1314 |
- } |
|
| 1315 |
-} |
|
| 1316 |
- |
|
| 1317 |
-func (cli *DockerCli) printVizNode(noTrunc bool, image *engine.Env, prefix string) {
|
|
| 1318 |
- var ( |
|
| 1319 |
- imageID string |
|
| 1320 |
- parentID string |
|
| 1321 |
- ) |
|
| 1322 |
- if noTrunc {
|
|
| 1323 |
- imageID = image.Get("Id")
|
|
| 1324 |
- parentID = image.Get("ParentId")
|
|
| 1325 |
- } else {
|
|
| 1326 |
- imageID = utils.TruncateID(image.Get("Id"))
|
|
| 1327 |
- parentID = utils.TruncateID(image.Get("ParentId"))
|
|
| 1328 |
- } |
|
| 1329 |
- if parentID == "" {
|
|
| 1330 |
- fmt.Fprintf(cli.out, " base -> \"%s\" [style=invis]\n", imageID) |
|
| 1331 |
- } else {
|
|
| 1332 |
- fmt.Fprintf(cli.out, " \"%s\" -> \"%s\"\n", parentID, imageID) |
|
| 1333 |
- } |
|
| 1334 |
- if image.GetList("RepoTags")[0] != "<none>:<none>" {
|
|
| 1335 |
- fmt.Fprintf(cli.out, " \"%s\" [label=\"%s\\n%s\",shape=box,fillcolor=\"paleturquoise\",style=\"filled,rounded\"];\n", |
|
| 1336 |
- imageID, imageID, strings.Join(image.GetList("RepoTags"), "\\n"))
|
|
| 1337 |
- } |
|
| 1338 |
-} |
|
| 1339 |
- |
|
| 1340 |
-func (cli *DockerCli) printTreeNode(noTrunc bool, image *engine.Env, prefix string) {
|
|
| 1341 |
- var imageID string |
|
| 1342 |
- if noTrunc {
|
|
| 1343 |
- imageID = image.Get("Id")
|
|
| 1344 |
- } else {
|
|
| 1345 |
- imageID = utils.TruncateID(image.Get("Id"))
|
|
| 1346 |
- } |
|
| 1347 |
- |
|
| 1348 |
- fmt.Fprintf(cli.out, "%s%s Virtual Size: %s", prefix, imageID, utils.HumanSize(image.GetInt64("VirtualSize")))
|
|
| 1349 |
- if image.GetList("RepoTags")[0] != "<none>:<none>" {
|
|
| 1350 |
- fmt.Fprintf(cli.out, " Tags: %s\n", strings.Join(image.GetList("RepoTags"), ", "))
|
|
| 1351 |
- } else {
|
|
| 1352 |
- fmt.Fprint(cli.out, "\n") |
|
| 1353 |
- } |
|
| 1354 |
-} |
|
| 1355 |
- |
|
| 1356 |
-func (cli *DockerCli) CmdPs(args ...string) error {
|
|
| 1357 |
- cmd := cli.Subcmd("ps", "[OPTIONS]", "List containers")
|
|
| 1358 |
- quiet := cmd.Bool([]string{"q", "-quiet"}, false, "Only display numeric IDs")
|
|
| 1359 |
- size := cmd.Bool([]string{"s", "-size"}, false, "Display sizes")
|
|
| 1360 |
- all := cmd.Bool([]string{"a", "-all"}, false, "Show all containers. Only running containers are shown by default.")
|
|
| 1361 |
- noTrunc := cmd.Bool([]string{"#notrunc", "-no-trunc"}, false, "Don't truncate output")
|
|
| 1362 |
- nLatest := cmd.Bool([]string{"l", "-latest"}, false, "Show only the latest created container, include non-running ones.")
|
|
| 1363 |
- since := cmd.String([]string{"#sinceId", "#-since-id", "-since"}, "", "Show only containers created since Id or Name, include non-running ones.")
|
|
| 1364 |
- before := cmd.String([]string{"#beforeId", "#-before-id", "-before"}, "", "Show only container created before Id or Name, include non-running ones.")
|
|
| 1365 |
- last := cmd.Int([]string{"n"}, -1, "Show n last created containers, include non-running ones.")
|
|
| 1366 |
- |
|
| 1367 |
- if err := cmd.Parse(args); err != nil {
|
|
| 1368 |
- return nil |
|
| 1369 |
- } |
|
| 1370 |
- v := url.Values{}
|
|
| 1371 |
- if *last == -1 && *nLatest {
|
|
| 1372 |
- *last = 1 |
|
| 1373 |
- } |
|
| 1374 |
- if *all {
|
|
| 1375 |
- v.Set("all", "1")
|
|
| 1376 |
- } |
|
| 1377 |
- if *last != -1 {
|
|
| 1378 |
- v.Set("limit", strconv.Itoa(*last))
|
|
| 1379 |
- } |
|
| 1380 |
- if *since != "" {
|
|
| 1381 |
- v.Set("since", *since)
|
|
| 1382 |
- } |
|
| 1383 |
- if *before != "" {
|
|
| 1384 |
- v.Set("before", *before)
|
|
| 1385 |
- } |
|
| 1386 |
- if *size {
|
|
| 1387 |
- v.Set("size", "1")
|
|
| 1388 |
- } |
|
| 1389 |
- |
|
| 1390 |
- body, _, err := readBody(cli.call("GET", "/containers/json?"+v.Encode(), nil, false))
|
|
| 1391 |
- if err != nil {
|
|
| 1392 |
- return err |
|
| 1393 |
- } |
|
| 1394 |
- |
|
| 1395 |
- outs := engine.NewTable("Created", 0)
|
|
| 1396 |
- if _, err := outs.ReadListFrom(body); err != nil {
|
|
| 1397 |
- return err |
|
| 1398 |
- } |
|
| 1399 |
- w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0) |
|
| 1400 |
- if !*quiet {
|
|
| 1401 |
- fmt.Fprint(w, "CONTAINER ID\tIMAGE\tCOMMAND\tCREATED\tSTATUS\tPORTS\tNAMES") |
|
| 1402 |
- if *size {
|
|
| 1403 |
- fmt.Fprintln(w, "\tSIZE") |
|
| 1404 |
- } else {
|
|
| 1405 |
- fmt.Fprint(w, "\n") |
|
| 1406 |
- } |
|
| 1407 |
- } |
|
| 1408 |
- |
|
| 1409 |
- for _, out := range outs.Data {
|
|
| 1410 |
- var ( |
|
| 1411 |
- outID = out.Get("Id")
|
|
| 1412 |
- outNames = out.GetList("Names")
|
|
| 1413 |
- ) |
|
| 1414 |
- |
|
| 1415 |
- if !*noTrunc {
|
|
| 1416 |
- outID = utils.TruncateID(outID) |
|
| 1417 |
- } |
|
| 1418 |
- |
|
| 1419 |
- // Remove the leading / from the names |
|
| 1420 |
- for i := 0; i < len(outNames); i++ {
|
|
| 1421 |
- outNames[i] = outNames[i][1:] |
|
| 1422 |
- } |
|
| 1423 |
- |
|
| 1424 |
- if !*quiet {
|
|
| 1425 |
- var ( |
|
| 1426 |
- outCommand = out.Get("Command")
|
|
| 1427 |
- ports = engine.NewTable("", 0)
|
|
| 1428 |
- ) |
|
| 1429 |
- if !*noTrunc {
|
|
| 1430 |
- outCommand = utils.Trunc(outCommand, 20) |
|
| 1431 |
- } |
|
| 1432 |
- ports.ReadListFrom([]byte(out.Get("Ports")))
|
|
| 1433 |
- fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\t%s\t%s\t", outID, out.Get("Image"), outCommand, utils.HumanDuration(time.Now().UTC().Sub(time.Unix(out.GetInt64("Created"), 0))), out.Get("Status"), api.DisplayablePorts(ports), strings.Join(outNames, ","))
|
|
| 1434 |
- if *size {
|
|
| 1435 |
- if out.GetInt("SizeRootFs") > 0 {
|
|
| 1436 |
- fmt.Fprintf(w, "%s (virtual %s)\n", utils.HumanSize(out.GetInt64("SizeRw")), utils.HumanSize(out.GetInt64("SizeRootFs")))
|
|
| 1437 |
- } else {
|
|
| 1438 |
- fmt.Fprintf(w, "%s\n", utils.HumanSize(out.GetInt64("SizeRw")))
|
|
| 1439 |
- } |
|
| 1440 |
- } else {
|
|
| 1441 |
- fmt.Fprint(w, "\n") |
|
| 1442 |
- } |
|
| 1443 |
- } else {
|
|
| 1444 |
- fmt.Fprintln(w, outID) |
|
| 1445 |
- } |
|
| 1446 |
- } |
|
| 1447 |
- |
|
| 1448 |
- if !*quiet {
|
|
| 1449 |
- w.Flush() |
|
| 1450 |
- } |
|
| 1451 |
- return nil |
|
| 1452 |
-} |
|
| 1453 |
- |
|
| 1454 |
-func (cli *DockerCli) CmdCommit(args ...string) error {
|
|
| 1455 |
- cmd := cli.Subcmd("commit", "[OPTIONS] CONTAINER [REPOSITORY[:TAG]]", "Create a new image from a container's changes")
|
|
| 1456 |
- flComment := cmd.String([]string{"m", "-message"}, "", "Commit message")
|
|
| 1457 |
- flAuthor := cmd.String([]string{"a", "#author", "-author"}, "", "Author (eg. \"John Hannibal Smith <hannibal@a-team.com>\"")
|
|
| 1458 |
- flConfig := cmd.String([]string{"#run", "-run"}, "", "Config automatically applied when the image is run. "+`(ex: --run='{"Cmd": ["cat", "/world"], "PortSpecs": ["22"]}')`)
|
|
| 1459 |
- if err := cmd.Parse(args); err != nil {
|
|
| 1460 |
- return nil |
|
| 1461 |
- } |
|
| 1462 |
- |
|
| 1463 |
- var name, repository, tag string |
|
| 1464 |
- |
|
| 1465 |
- if cmd.NArg() == 3 {
|
|
| 1466 |
- fmt.Fprintf(cli.err, "[DEPRECATED] The format 'CONTAINER [REPOSITORY [TAG]]' as been deprecated. Please use CONTAINER [REPOSITORY[:TAG]]\n") |
|
| 1467 |
- name, repository, tag = cmd.Arg(0), cmd.Arg(1), cmd.Arg(2) |
|
| 1468 |
- } else {
|
|
| 1469 |
- name = cmd.Arg(0) |
|
| 1470 |
- repository, tag = utils.ParseRepositoryTag(cmd.Arg(1)) |
|
| 1471 |
- } |
|
| 1472 |
- |
|
| 1473 |
- if name == "" {
|
|
| 1474 |
- cmd.Usage() |
|
| 1475 |
- return nil |
|
| 1476 |
- } |
|
| 1477 |
- |
|
| 1478 |
- //Check if the given image name can be resolved |
|
| 1479 |
- if repository != "" {
|
|
| 1480 |
- if _, _, err := registry.ResolveRepositoryName(repository); err != nil {
|
|
| 1481 |
- return err |
|
| 1482 |
- } |
|
| 1483 |
- } |
|
| 1484 |
- |
|
| 1485 |
- v := url.Values{}
|
|
| 1486 |
- v.Set("container", name)
|
|
| 1487 |
- v.Set("repo", repository)
|
|
| 1488 |
- v.Set("tag", tag)
|
|
| 1489 |
- v.Set("comment", *flComment)
|
|
| 1490 |
- v.Set("author", *flAuthor)
|
|
| 1491 |
- var ( |
|
| 1492 |
- config *runconfig.Config |
|
| 1493 |
- env engine.Env |
|
| 1494 |
- ) |
|
| 1495 |
- if *flConfig != "" {
|
|
| 1496 |
- config = &runconfig.Config{}
|
|
| 1497 |
- if err := json.Unmarshal([]byte(*flConfig), config); err != nil {
|
|
| 1498 |
- return err |
|
| 1499 |
- } |
|
| 1500 |
- } |
|
| 1501 |
- stream, _, err := cli.call("POST", "/commit?"+v.Encode(), config, false)
|
|
| 1502 |
- if err != nil {
|
|
| 1503 |
- return err |
|
| 1504 |
- } |
|
| 1505 |
- if err := env.Decode(stream); err != nil {
|
|
| 1506 |
- return err |
|
| 1507 |
- } |
|
| 1508 |
- |
|
| 1509 |
- fmt.Fprintf(cli.out, "%s\n", env.Get("Id"))
|
|
| 1510 |
- return nil |
|
| 1511 |
-} |
|
| 1512 |
- |
|
| 1513 |
-func (cli *DockerCli) CmdEvents(args ...string) error {
|
|
| 1514 |
- cmd := cli.Subcmd("events", "[OPTIONS]", "Get real time events from the server")
|
|
| 1515 |
- since := cmd.String([]string{"#since", "-since"}, "", "Show previously created events and then stream.")
|
|
| 1516 |
- if err := cmd.Parse(args); err != nil {
|
|
| 1517 |
- return nil |
|
| 1518 |
- } |
|
| 1519 |
- |
|
| 1520 |
- if cmd.NArg() != 0 {
|
|
| 1521 |
- cmd.Usage() |
|
| 1522 |
- return nil |
|
| 1523 |
- } |
|
| 1524 |
- |
|
| 1525 |
- v := url.Values{}
|
|
| 1526 |
- if *since != "" {
|
|
| 1527 |
- loc := time.FixedZone(time.Now().Zone()) |
|
| 1528 |
- format := "2006-01-02 15:04:05 -0700 MST" |
|
| 1529 |
- if len(*since) < len(format) {
|
|
| 1530 |
- format = format[:len(*since)] |
|
| 1531 |
- } |
|
| 1532 |
- |
|
| 1533 |
- if t, err := time.ParseInLocation(format, *since, loc); err == nil {
|
|
| 1534 |
- v.Set("since", strconv.FormatInt(t.Unix(), 10))
|
|
| 1535 |
- } else {
|
|
| 1536 |
- v.Set("since", *since)
|
|
| 1537 |
- } |
|
| 1538 |
- } |
|
| 1539 |
- |
|
| 1540 |
- if err := cli.stream("GET", "/events?"+v.Encode(), nil, cli.out, nil); err != nil {
|
|
| 1541 |
- return err |
|
| 1542 |
- } |
|
| 1543 |
- return nil |
|
| 1544 |
-} |
|
| 1545 |
- |
|
| 1546 |
-func (cli *DockerCli) CmdExport(args ...string) error {
|
|
| 1547 |
- cmd := cli.Subcmd("export", "CONTAINER", "Export the contents of a filesystem as a tar archive to STDOUT")
|
|
| 1548 |
- if err := cmd.Parse(args); err != nil {
|
|
| 1549 |
- return nil |
|
| 1550 |
- } |
|
| 1551 |
- |
|
| 1552 |
- if cmd.NArg() != 1 {
|
|
| 1553 |
- cmd.Usage() |
|
| 1554 |
- return nil |
|
| 1555 |
- } |
|
| 1556 |
- |
|
| 1557 |
- if err := cli.stream("GET", "/containers/"+cmd.Arg(0)+"/export", nil, cli.out, nil); err != nil {
|
|
| 1558 |
- return err |
|
| 1559 |
- } |
|
| 1560 |
- return nil |
|
| 1561 |
-} |
|
| 1562 |
- |
|
| 1563 |
-func (cli *DockerCli) CmdDiff(args ...string) error {
|
|
| 1564 |
- cmd := cli.Subcmd("diff", "CONTAINER", "Inspect changes on a container's filesystem")
|
|
| 1565 |
- if err := cmd.Parse(args); err != nil {
|
|
| 1566 |
- return nil |
|
| 1567 |
- } |
|
| 1568 |
- if cmd.NArg() != 1 {
|
|
| 1569 |
- cmd.Usage() |
|
| 1570 |
- return nil |
|
| 1571 |
- } |
|
| 1572 |
- |
|
| 1573 |
- body, _, err := readBody(cli.call("GET", "/containers/"+cmd.Arg(0)+"/changes", nil, false))
|
|
| 1574 |
- |
|
| 1575 |
- if err != nil {
|
|
| 1576 |
- return err |
|
| 1577 |
- } |
|
| 1578 |
- |
|
| 1579 |
- outs := engine.NewTable("", 0)
|
|
| 1580 |
- if _, err := outs.ReadListFrom(body); err != nil {
|
|
| 1581 |
- return err |
|
| 1582 |
- } |
|
| 1583 |
- for _, change := range outs.Data {
|
|
| 1584 |
- var kind string |
|
| 1585 |
- switch change.GetInt("Kind") {
|
|
| 1586 |
- case archive.ChangeModify: |
|
| 1587 |
- kind = "C" |
|
| 1588 |
- case archive.ChangeAdd: |
|
| 1589 |
- kind = "A" |
|
| 1590 |
- case archive.ChangeDelete: |
|
| 1591 |
- kind = "D" |
|
| 1592 |
- } |
|
| 1593 |
- fmt.Fprintf(cli.out, "%s %s\n", kind, change.Get("Path"))
|
|
| 1594 |
- } |
|
| 1595 |
- return nil |
|
| 1596 |
-} |
|
| 1597 |
- |
|
| 1598 |
-func (cli *DockerCli) CmdLogs(args ...string) error {
|
|
| 1599 |
- cmd := cli.Subcmd("logs", "CONTAINER", "Fetch the logs of a container")
|
|
| 1600 |
- follow := cmd.Bool([]string{"f", "-follow"}, false, "Follow log output")
|
|
| 1601 |
- if err := cmd.Parse(args); err != nil {
|
|
| 1602 |
- return nil |
|
| 1603 |
- } |
|
| 1604 |
- if cmd.NArg() != 1 {
|
|
| 1605 |
- cmd.Usage() |
|
| 1606 |
- return nil |
|
| 1607 |
- } |
|
| 1608 |
- name := cmd.Arg(0) |
|
| 1609 |
- body, _, err := readBody(cli.call("GET", "/containers/"+name+"/json", nil, false))
|
|
| 1610 |
- if err != nil {
|
|
| 1611 |
- return err |
|
| 1612 |
- } |
|
| 1613 |
- |
|
| 1614 |
- container := &api.Container{}
|
|
| 1615 |
- err = json.Unmarshal(body, container) |
|
| 1616 |
- if err != nil {
|
|
| 1617 |
- return err |
|
| 1618 |
- } |
|
| 1619 |
- |
|
| 1620 |
- v := url.Values{}
|
|
| 1621 |
- v.Set("logs", "1")
|
|
| 1622 |
- v.Set("stdout", "1")
|
|
| 1623 |
- v.Set("stderr", "1")
|
|
| 1624 |
- if *follow && container.State.Running {
|
|
| 1625 |
- v.Set("stream", "1")
|
|
| 1626 |
- } |
|
| 1627 |
- |
|
| 1628 |
- if err := cli.hijack("POST", "/containers/"+name+"/attach?"+v.Encode(), container.Config.Tty, nil, cli.out, cli.err, nil); err != nil {
|
|
| 1629 |
- return err |
|
| 1630 |
- } |
|
| 1631 |
- return nil |
|
| 1632 |
-} |
|
| 1633 |
- |
|
| 1634 |
-func (cli *DockerCli) CmdAttach(args ...string) error {
|
|
| 1635 |
- cmd := cli.Subcmd("attach", "[OPTIONS] CONTAINER", "Attach to a running container")
|
|
| 1636 |
- noStdin := cmd.Bool([]string{"#nostdin", "-no-stdin"}, false, "Do not attach stdin")
|
|
| 1637 |
- proxy := cmd.Bool([]string{"#sig-proxy", "-sig-proxy"}, true, "Proxify all received signal to the process (even in non-tty mode)")
|
|
| 1638 |
- if err := cmd.Parse(args); err != nil {
|
|
| 1639 |
- return nil |
|
| 1640 |
- } |
|
| 1641 |
- if cmd.NArg() != 1 {
|
|
| 1642 |
- cmd.Usage() |
|
| 1643 |
- return nil |
|
| 1644 |
- } |
|
| 1645 |
- name := cmd.Arg(0) |
|
| 1646 |
- body, _, err := readBody(cli.call("GET", "/containers/"+name+"/json", nil, false))
|
|
| 1647 |
- if err != nil {
|
|
| 1648 |
- return err |
|
| 1649 |
- } |
|
| 1650 |
- |
|
| 1651 |
- container := &api.Container{}
|
|
| 1652 |
- err = json.Unmarshal(body, container) |
|
| 1653 |
- if err != nil {
|
|
| 1654 |
- return err |
|
| 1655 |
- } |
|
| 1656 |
- |
|
| 1657 |
- if !container.State.Running {
|
|
| 1658 |
- return fmt.Errorf("You cannot attach to a stopped container, start it first")
|
|
| 1659 |
- } |
|
| 1660 |
- |
|
| 1661 |
- if container.Config.Tty && cli.isTerminal {
|
|
| 1662 |
- if err := cli.monitorTtySize(cmd.Arg(0)); err != nil {
|
|
| 1663 |
- utils.Debugf("Error monitoring TTY size: %s", err)
|
|
| 1664 |
- } |
|
| 1665 |
- } |
|
| 1666 |
- |
|
| 1667 |
- var in io.ReadCloser |
|
| 1668 |
- |
|
| 1669 |
- v := url.Values{}
|
|
| 1670 |
- v.Set("stream", "1")
|
|
| 1671 |
- if !*noStdin && container.Config.OpenStdin {
|
|
| 1672 |
- v.Set("stdin", "1")
|
|
| 1673 |
- in = cli.in |
|
| 1674 |
- } |
|
| 1675 |
- v.Set("stdout", "1")
|
|
| 1676 |
- v.Set("stderr", "1")
|
|
| 1677 |
- |
|
| 1678 |
- if *proxy && !container.Config.Tty {
|
|
| 1679 |
- sigc := cli.forwardAllSignals(cmd.Arg(0)) |
|
| 1680 |
- defer signal.StopCatch(sigc) |
|
| 1681 |
- } |
|
| 1682 |
- |
|
| 1683 |
- if err := cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), container.Config.Tty, in, cli.out, cli.err, nil); err != nil {
|
|
| 1684 |
- return err |
|
| 1685 |
- } |
|
| 1686 |
- |
|
| 1687 |
- _, status, err := getExitCode(cli, cmd.Arg(0)) |
|
| 1688 |
- if err != nil {
|
|
| 1689 |
- return err |
|
| 1690 |
- } |
|
| 1691 |
- if status != 0 {
|
|
| 1692 |
- return &utils.StatusError{StatusCode: status}
|
|
| 1693 |
- } |
|
| 1694 |
- |
|
| 1695 |
- return nil |
|
| 1696 |
-} |
|
| 1697 |
- |
|
| 1698 |
-func (cli *DockerCli) CmdSearch(args ...string) error {
|
|
| 1699 |
- cmd := cli.Subcmd("search", "TERM", "Search the docker index for images")
|
|
| 1700 |
- noTrunc := cmd.Bool([]string{"#notrunc", "-no-trunc"}, false, "Don't truncate output")
|
|
| 1701 |
- trusted := cmd.Bool([]string{"t", "#trusted", "-trusted"}, false, "Only show trusted builds")
|
|
| 1702 |
- stars := cmd.Int([]string{"s", "#stars", "-stars"}, 0, "Only displays with at least xxx stars")
|
|
| 1703 |
- if err := cmd.Parse(args); err != nil {
|
|
| 1704 |
- return nil |
|
| 1705 |
- } |
|
| 1706 |
- if cmd.NArg() != 1 {
|
|
| 1707 |
- cmd.Usage() |
|
| 1708 |
- return nil |
|
| 1709 |
- } |
|
| 1710 |
- |
|
| 1711 |
- v := url.Values{}
|
|
| 1712 |
- v.Set("term", cmd.Arg(0))
|
|
| 1713 |
- |
|
| 1714 |
- body, _, err := readBody(cli.call("GET", "/images/search?"+v.Encode(), nil, true))
|
|
| 1715 |
- |
|
| 1716 |
- if err != nil {
|
|
| 1717 |
- return err |
|
| 1718 |
- } |
|
| 1719 |
- outs := engine.NewTable("star_count", 0)
|
|
| 1720 |
- if _, err := outs.ReadListFrom(body); err != nil {
|
|
| 1721 |
- return err |
|
| 1722 |
- } |
|
| 1723 |
- w := tabwriter.NewWriter(cli.out, 10, 1, 3, ' ', 0) |
|
| 1724 |
- fmt.Fprintf(w, "NAME\tDESCRIPTION\tSTARS\tOFFICIAL\tTRUSTED\n") |
|
| 1725 |
- for _, out := range outs.Data {
|
|
| 1726 |
- if (*trusted && !out.GetBool("is_trusted")) || (*stars > out.GetInt("star_count")) {
|
|
| 1727 |
- continue |
|
| 1728 |
- } |
|
| 1729 |
- desc := strings.Replace(out.Get("description"), "\n", " ", -1)
|
|
| 1730 |
- desc = strings.Replace(desc, "\r", " ", -1) |
|
| 1731 |
- if !*noTrunc && len(desc) > 45 {
|
|
| 1732 |
- desc = utils.Trunc(desc, 42) + "..." |
|
| 1733 |
- } |
|
| 1734 |
- fmt.Fprintf(w, "%s\t%s\t%d\t", out.Get("name"), desc, out.GetInt("star_count"))
|
|
| 1735 |
- if out.GetBool("is_official") {
|
|
| 1736 |
- fmt.Fprint(w, "[OK]") |
|
| 1737 |
- |
|
| 1738 |
- } |
|
| 1739 |
- fmt.Fprint(w, "\t") |
|
| 1740 |
- if out.GetBool("is_trusted") {
|
|
| 1741 |
- fmt.Fprint(w, "[OK]") |
|
| 1742 |
- } |
|
| 1743 |
- fmt.Fprint(w, "\n") |
|
| 1744 |
- } |
|
| 1745 |
- w.Flush() |
|
| 1746 |
- return nil |
|
| 1747 |
-} |
|
| 1748 |
- |
|
| 1749 |
-// Ports type - Used to parse multiple -p flags |
|
| 1750 |
-type ports []int |
|
| 1751 |
- |
|
| 1752 |
-func (cli *DockerCli) CmdTag(args ...string) error {
|
|
| 1753 |
- cmd := cli.Subcmd("tag", "[OPTIONS] IMAGE [REGISTRYHOST/][USERNAME/]NAME[:TAG]", "Tag an image into a repository")
|
|
| 1754 |
- force := cmd.Bool([]string{"f", "#force", "-force"}, false, "Force")
|
|
| 1755 |
- if err := cmd.Parse(args); err != nil {
|
|
| 1756 |
- return nil |
|
| 1757 |
- } |
|
| 1758 |
- if cmd.NArg() != 2 && cmd.NArg() != 3 {
|
|
| 1759 |
- cmd.Usage() |
|
| 1760 |
- return nil |
|
| 1761 |
- } |
|
| 1762 |
- |
|
| 1763 |
- var repository, tag string |
|
| 1764 |
- |
|
| 1765 |
- if cmd.NArg() == 3 {
|
|
| 1766 |
- fmt.Fprintf(cli.err, "[DEPRECATED] The format 'IMAGE [REPOSITORY [TAG]]' as been deprecated. Please use IMAGE [REGISTRYHOST/][USERNAME/]NAME[:TAG]]\n") |
|
| 1767 |
- repository, tag = cmd.Arg(1), cmd.Arg(2) |
|
| 1768 |
- } else {
|
|
| 1769 |
- repository, tag = utils.ParseRepositoryTag(cmd.Arg(1)) |
|
| 1770 |
- } |
|
| 1771 |
- |
|
| 1772 |
- v := url.Values{}
|
|
| 1773 |
- |
|
| 1774 |
- //Check if the given image name can be resolved |
|
| 1775 |
- if _, _, err := registry.ResolveRepositoryName(repository); err != nil {
|
|
| 1776 |
- return err |
|
| 1777 |
- } |
|
| 1778 |
- v.Set("repo", repository)
|
|
| 1779 |
- v.Set("tag", tag)
|
|
| 1780 |
- |
|
| 1781 |
- if *force {
|
|
| 1782 |
- v.Set("force", "1")
|
|
| 1783 |
- } |
|
| 1784 |
- |
|
| 1785 |
- if _, _, err := readBody(cli.call("POST", "/images/"+cmd.Arg(0)+"/tag?"+v.Encode(), nil, false)); err != nil {
|
|
| 1786 |
- return err |
|
| 1787 |
- } |
|
| 1788 |
- return nil |
|
| 1789 |
-} |
|
| 1790 |
- |
|
| 1791 |
-func (cli *DockerCli) CmdRun(args ...string) error {
|
|
| 1792 |
- // FIXME: just use runconfig.Parse already |
|
| 1793 |
- config, hostConfig, cmd, err := runconfig.ParseSubcommand(cli.Subcmd("run", "[OPTIONS] IMAGE [COMMAND] [ARG...]", "Run a command in a new container"), args, nil)
|
|
| 1794 |
- if err != nil {
|
|
| 1795 |
- return err |
|
| 1796 |
- } |
|
| 1797 |
- if config.Image == "" {
|
|
| 1798 |
- cmd.Usage() |
|
| 1799 |
- return nil |
|
| 1800 |
- } |
|
| 1801 |
- |
|
| 1802 |
- // Retrieve relevant client-side config |
|
| 1803 |
- var ( |
|
| 1804 |
- flName = cmd.Lookup("name")
|
|
| 1805 |
- flRm = cmd.Lookup("rm")
|
|
| 1806 |
- flSigProxy = cmd.Lookup("sig-proxy")
|
|
| 1807 |
- autoRemove, _ = strconv.ParseBool(flRm.Value.String()) |
|
| 1808 |
- sigProxy, _ = strconv.ParseBool(flSigProxy.Value.String()) |
|
| 1809 |
- ) |
|
| 1810 |
- |
|
| 1811 |
- // Disable sigProxy in case on TTY |
|
| 1812 |
- if config.Tty {
|
|
| 1813 |
- sigProxy = false |
|
| 1814 |
- } |
|
| 1815 |
- |
|
| 1816 |
- var containerIDFile io.WriteCloser |
|
| 1817 |
- if len(hostConfig.ContainerIDFile) > 0 {
|
|
| 1818 |
- if _, err := os.Stat(hostConfig.ContainerIDFile); err == nil {
|
|
| 1819 |
- return fmt.Errorf("Container ID file found, make sure the other container isn't running or delete %s", hostConfig.ContainerIDFile)
|
|
| 1820 |
- } |
|
| 1821 |
- if containerIDFile, err = os.Create(hostConfig.ContainerIDFile); err != nil {
|
|
| 1822 |
- return fmt.Errorf("Failed to create the container ID file: %s", err)
|
|
| 1823 |
- } |
|
| 1824 |
- defer func() {
|
|
| 1825 |
- containerIDFile.Close() |
|
| 1826 |
- var ( |
|
| 1827 |
- cidFileInfo os.FileInfo |
|
| 1828 |
- err error |
|
| 1829 |
- ) |
|
| 1830 |
- if cidFileInfo, err = os.Stat(hostConfig.ContainerIDFile); err != nil {
|
|
| 1831 |
- return |
|
| 1832 |
- } |
|
| 1833 |
- if cidFileInfo.Size() == 0 {
|
|
| 1834 |
- if err := os.Remove(hostConfig.ContainerIDFile); err != nil {
|
|
| 1835 |
- fmt.Printf("failed to remove CID file '%s': %s \n", hostConfig.ContainerIDFile, err)
|
|
| 1836 |
- } |
|
| 1837 |
- } |
|
| 1838 |
- }() |
|
| 1839 |
- } |
|
| 1840 |
- |
|
| 1841 |
- containerValues := url.Values{}
|
|
| 1842 |
- if name := flName.Value.String(); name != "" {
|
|
| 1843 |
- containerValues.Set("name", name)
|
|
| 1844 |
- } |
|
| 1845 |
- |
|
| 1846 |
- //create the container |
|
| 1847 |
- stream, statusCode, err := cli.call("POST", "/containers/create?"+containerValues.Encode(), config, false)
|
|
| 1848 |
- //if image not found try to pull it |
|
| 1849 |
- if statusCode == 404 {
|
|
| 1850 |
- fmt.Fprintf(cli.err, "Unable to find image '%s' locally\n", config.Image) |
|
| 1851 |
- |
|
| 1852 |
- v := url.Values{}
|
|
| 1853 |
- repos, tag := utils.ParseRepositoryTag(config.Image) |
|
| 1854 |
- v.Set("fromImage", repos)
|
|
| 1855 |
- v.Set("tag", tag)
|
|
| 1856 |
- |
|
| 1857 |
- // Resolve the Repository name from fqn to hostname + name |
|
| 1858 |
- hostname, _, err := registry.ResolveRepositoryName(repos) |
|
| 1859 |
- if err != nil {
|
|
| 1860 |
- return err |
|
| 1861 |
- } |
|
| 1862 |
- |
|
| 1863 |
- // Load the auth config file, to be able to pull the image |
|
| 1864 |
- cli.LoadConfigFile() |
|
| 1865 |
- |
|
| 1866 |
- // Resolve the Auth config relevant for this server |
|
| 1867 |
- authConfig := cli.configFile.ResolveAuthConfig(hostname) |
|
| 1868 |
- buf, err := json.Marshal(authConfig) |
|
| 1869 |
- if err != nil {
|
|
| 1870 |
- return err |
|
| 1871 |
- } |
|
| 1872 |
- |
|
| 1873 |
- registryAuthHeader := []string{
|
|
| 1874 |
- base64.URLEncoding.EncodeToString(buf), |
|
| 1875 |
- } |
|
| 1876 |
- if err = cli.stream("POST", "/images/create?"+v.Encode(), nil, cli.err, map[string][]string{"X-Registry-Auth": registryAuthHeader}); err != nil {
|
|
| 1877 |
- return err |
|
| 1878 |
- } |
|
| 1879 |
- if stream, _, err = cli.call("POST", "/containers/create?"+containerValues.Encode(), config, false); err != nil {
|
|
| 1880 |
- return err |
|
| 1881 |
- } |
|
| 1882 |
- } else if err != nil {
|
|
| 1883 |
- return err |
|
| 1884 |
- } |
|
| 1885 |
- |
|
| 1886 |
- var runResult engine.Env |
|
| 1887 |
- if err := runResult.Decode(stream); err != nil {
|
|
| 1888 |
- return err |
|
| 1889 |
- } |
|
| 1890 |
- |
|
| 1891 |
- for _, warning := range runResult.GetList("Warnings") {
|
|
| 1892 |
- fmt.Fprintf(cli.err, "WARNING: %s\n", warning) |
|
| 1893 |
- } |
|
| 1894 |
- |
|
| 1895 |
- if len(hostConfig.ContainerIDFile) > 0 {
|
|
| 1896 |
- if _, err = containerIDFile.Write([]byte(runResult.Get("Id"))); err != nil {
|
|
| 1897 |
- return fmt.Errorf("Failed to write the container ID to the file: %s", err)
|
|
| 1898 |
- } |
|
| 1899 |
- } |
|
| 1900 |
- |
|
| 1901 |
- if sigProxy {
|
|
| 1902 |
- sigc := cli.forwardAllSignals(runResult.Get("Id"))
|
|
| 1903 |
- defer signal.StopCatch(sigc) |
|
| 1904 |
- } |
|
| 1905 |
- |
|
| 1906 |
- var ( |
|
| 1907 |
- waitDisplayId chan struct{}
|
|
| 1908 |
- errCh chan error |
|
| 1909 |
- ) |
|
| 1910 |
- |
|
| 1911 |
- if !config.AttachStdout && !config.AttachStderr {
|
|
| 1912 |
- // Make this asynchrone in order to let the client write to stdin before having to read the ID |
|
| 1913 |
- waitDisplayId = make(chan struct{})
|
|
| 1914 |
- go func() {
|
|
| 1915 |
- defer close(waitDisplayId) |
|
| 1916 |
- fmt.Fprintf(cli.out, "%s\n", runResult.Get("Id"))
|
|
| 1917 |
- }() |
|
| 1918 |
- } |
|
| 1919 |
- |
|
| 1920 |
- // We need to instanciate the chan because the select needs it. It can |
|
| 1921 |
- // be closed but can't be uninitialized. |
|
| 1922 |
- hijacked := make(chan io.Closer) |
|
| 1923 |
- |
|
| 1924 |
- // Block the return until the chan gets closed |
|
| 1925 |
- defer func() {
|
|
| 1926 |
- utils.Debugf("End of CmdRun(), Waiting for hijack to finish.")
|
|
| 1927 |
- if _, ok := <-hijacked; ok {
|
|
| 1928 |
- utils.Errorf("Hijack did not finish (chan still open)")
|
|
| 1929 |
- } |
|
| 1930 |
- }() |
|
| 1931 |
- |
|
| 1932 |
- if config.AttachStdin || config.AttachStdout || config.AttachStderr {
|
|
| 1933 |
- var ( |
|
| 1934 |
- out, stderr io.Writer |
|
| 1935 |
- in io.ReadCloser |
|
| 1936 |
- v = url.Values{}
|
|
| 1937 |
- ) |
|
| 1938 |
- v.Set("stream", "1")
|
|
| 1939 |
- |
|
| 1940 |
- if config.AttachStdin {
|
|
| 1941 |
- v.Set("stdin", "1")
|
|
| 1942 |
- in = cli.in |
|
| 1943 |
- } |
|
| 1944 |
- if config.AttachStdout {
|
|
| 1945 |
- v.Set("stdout", "1")
|
|
| 1946 |
- out = cli.out |
|
| 1947 |
- } |
|
| 1948 |
- if config.AttachStderr {
|
|
| 1949 |
- v.Set("stderr", "1")
|
|
| 1950 |
- if config.Tty {
|
|
| 1951 |
- stderr = cli.out |
|
| 1952 |
- } else {
|
|
| 1953 |
- stderr = cli.err |
|
| 1954 |
- } |
|
| 1955 |
- } |
|
| 1956 |
- |
|
| 1957 |
- errCh = utils.Go(func() error {
|
|
| 1958 |
- return cli.hijack("POST", "/containers/"+runResult.Get("Id")+"/attach?"+v.Encode(), config.Tty, in, out, stderr, hijacked)
|
|
| 1959 |
- }) |
|
| 1960 |
- } else {
|
|
| 1961 |
- close(hijacked) |
|
| 1962 |
- } |
|
| 1963 |
- |
|
| 1964 |
- // Acknowledge the hijack before starting |
|
| 1965 |
- select {
|
|
| 1966 |
- case closer := <-hijacked: |
|
| 1967 |
- // Make sure that hijack gets closed when returning. (result |
|
| 1968 |
- // in closing hijack chan and freeing server's goroutines. |
|
| 1969 |
- if closer != nil {
|
|
| 1970 |
- defer closer.Close() |
|
| 1971 |
- } |
|
| 1972 |
- case err := <-errCh: |
|
| 1973 |
- if err != nil {
|
|
| 1974 |
- utils.Debugf("Error hijack: %s", err)
|
|
| 1975 |
- return err |
|
| 1976 |
- } |
|
| 1977 |
- } |
|
| 1978 |
- |
|
| 1979 |
- //start the container |
|
| 1980 |
- if _, _, err = readBody(cli.call("POST", "/containers/"+runResult.Get("Id")+"/start", hostConfig, false)); err != nil {
|
|
| 1981 |
- return err |
|
| 1982 |
- } |
|
| 1983 |
- |
|
| 1984 |
- if (config.AttachStdin || config.AttachStdout || config.AttachStderr) && config.Tty && cli.isTerminal {
|
|
| 1985 |
- if err := cli.monitorTtySize(runResult.Get("Id")); err != nil {
|
|
| 1986 |
- utils.Errorf("Error monitoring TTY size: %s\n", err)
|
|
| 1987 |
- } |
|
| 1988 |
- } |
|
| 1989 |
- |
|
| 1990 |
- if errCh != nil {
|
|
| 1991 |
- if err := <-errCh; err != nil {
|
|
| 1992 |
- utils.Debugf("Error hijack: %s", err)
|
|
| 1993 |
- return err |
|
| 1994 |
- } |
|
| 1995 |
- } |
|
| 1996 |
- |
|
| 1997 |
- // Detached mode: wait for the id to be displayed and return. |
|
| 1998 |
- if !config.AttachStdout && !config.AttachStderr {
|
|
| 1999 |
- // Detached mode |
|
| 2000 |
- <-waitDisplayId |
|
| 2001 |
- return nil |
|
| 2002 |
- } |
|
| 2003 |
- |
|
| 2004 |
- var status int |
|
| 2005 |
- |
|
| 2006 |
- // Attached mode |
|
| 2007 |
- if autoRemove {
|
|
| 2008 |
- // Autoremove: wait for the container to finish, retrieve |
|
| 2009 |
- // the exit code and remove the container |
|
| 2010 |
- if _, _, err := readBody(cli.call("POST", "/containers/"+runResult.Get("Id")+"/wait", nil, false)); err != nil {
|
|
| 2011 |
- return err |
|
| 2012 |
- } |
|
| 2013 |
- if _, status, err = getExitCode(cli, runResult.Get("Id")); err != nil {
|
|
| 2014 |
- return err |
|
| 2015 |
- } |
|
| 2016 |
- if _, _, err := readBody(cli.call("DELETE", "/containers/"+runResult.Get("Id")+"?v=1", nil, false)); err != nil {
|
|
| 2017 |
- return err |
|
| 2018 |
- } |
|
| 2019 |
- } else {
|
|
| 2020 |
- if !config.Tty {
|
|
| 2021 |
- // In non-tty mode, we can't dettach, so we know we need to wait. |
|
| 2022 |
- if status, err = waitForExit(cli, runResult.Get("Id")); err != nil {
|
|
| 2023 |
- return err |
|
| 2024 |
- } |
|
| 2025 |
- } else {
|
|
| 2026 |
- // In TTY mode, there is a race. If the process dies too slowly, the state can be update after the getExitCode call |
|
| 2027 |
- // and result in a wrong exit code. |
|
| 2028 |
- // No Autoremove: Simply retrieve the exit code |
|
| 2029 |
- if _, status, err = getExitCode(cli, runResult.Get("Id")); err != nil {
|
|
| 2030 |
- return err |
|
| 2031 |
- } |
|
| 2032 |
- } |
|
| 2033 |
- } |
|
| 2034 |
- if status != 0 {
|
|
| 2035 |
- return &utils.StatusError{StatusCode: status}
|
|
| 2036 |
- } |
|
| 2037 |
- return nil |
|
| 2038 |
-} |
|
| 2039 |
- |
|
| 2040 |
-func (cli *DockerCli) CmdCp(args ...string) error {
|
|
| 2041 |
- cmd := cli.Subcmd("cp", "CONTAINER:PATH HOSTPATH", "Copy files/folders from the PATH to the HOSTPATH")
|
|
| 2042 |
- if err := cmd.Parse(args); err != nil {
|
|
| 2043 |
- return nil |
|
| 2044 |
- } |
|
| 2045 |
- |
|
| 2046 |
- if cmd.NArg() != 2 {
|
|
| 2047 |
- cmd.Usage() |
|
| 2048 |
- return nil |
|
| 2049 |
- } |
|
| 2050 |
- |
|
| 2051 |
- var copyData engine.Env |
|
| 2052 |
- info := strings.Split(cmd.Arg(0), ":") |
|
| 2053 |
- |
|
| 2054 |
- if len(info) != 2 {
|
|
| 2055 |
- return fmt.Errorf("Error: Path not specified")
|
|
| 2056 |
- } |
|
| 2057 |
- |
|
| 2058 |
- copyData.Set("Resource", info[1])
|
|
| 2059 |
- copyData.Set("HostPath", cmd.Arg(1))
|
|
| 2060 |
- |
|
| 2061 |
- stream, statusCode, err := cli.call("POST", "/containers/"+info[0]+"/copy", copyData, false)
|
|
| 2062 |
- if stream != nil {
|
|
| 2063 |
- defer stream.Close() |
|
| 2064 |
- } |
|
| 2065 |
- if statusCode == 404 {
|
|
| 2066 |
- return fmt.Errorf("No such container: %v", info[0])
|
|
| 2067 |
- } |
|
| 2068 |
- if err != nil {
|
|
| 2069 |
- return err |
|
| 2070 |
- } |
|
| 2071 |
- |
|
| 2072 |
- if statusCode == 200 {
|
|
| 2073 |
- if err := archive.Untar(stream, copyData.Get("HostPath"), nil); err != nil {
|
|
| 2074 |
- return err |
|
| 2075 |
- } |
|
| 2076 |
- } |
|
| 2077 |
- return nil |
|
| 2078 |
-} |
|
| 2079 |
- |
|
| 2080 |
-func (cli *DockerCli) CmdSave(args ...string) error {
|
|
| 2081 |
- cmd := cli.Subcmd("save", "IMAGE", "Save an image to a tar archive (streamed to stdout by default)")
|
|
| 2082 |
- outfile := cmd.String([]string{"o", "-output"}, "", "Write to an file, instead of STDOUT")
|
|
| 2083 |
- |
|
| 2084 |
- if err := cmd.Parse(args); err != nil {
|
|
| 2085 |
- return err |
|
| 2086 |
- } |
|
| 2087 |
- |
|
| 2088 |
- if cmd.NArg() != 1 {
|
|
| 2089 |
- cmd.Usage() |
|
| 2090 |
- return nil |
|
| 2091 |
- } |
|
| 2092 |
- |
|
| 2093 |
- var ( |
|
| 2094 |
- output io.Writer = cli.out |
|
| 2095 |
- err error |
|
| 2096 |
- ) |
|
| 2097 |
- if *outfile != "" {
|
|
| 2098 |
- output, err = os.Create(*outfile) |
|
| 2099 |
- if err != nil {
|
|
| 2100 |
- return err |
|
| 2101 |
- } |
|
| 2102 |
- } |
|
| 2103 |
- image := cmd.Arg(0) |
|
| 2104 |
- if err := cli.stream("GET", "/images/"+image+"/get", nil, output, nil); err != nil {
|
|
| 2105 |
- return err |
|
| 2106 |
- } |
|
| 2107 |
- return nil |
|
| 2108 |
-} |
|
| 2109 |
- |
|
| 2110 |
-func (cli *DockerCli) CmdLoad(args ...string) error {
|
|
| 2111 |
- cmd := cli.Subcmd("load", "", "Load an image from a tar archive on STDIN")
|
|
| 2112 |
- infile := cmd.String([]string{"i", "-input"}, "", "Read from a tar archive file, instead of STDIN")
|
|
| 2113 |
- |
|
| 2114 |
- if err := cmd.Parse(args); err != nil {
|
|
| 2115 |
- return err |
|
| 2116 |
- } |
|
| 2117 |
- |
|
| 2118 |
- if cmd.NArg() != 0 {
|
|
| 2119 |
- cmd.Usage() |
|
| 2120 |
- return nil |
|
| 2121 |
- } |
|
| 2122 |
- |
|
| 2123 |
- var ( |
|
| 2124 |
- input io.Reader = cli.in |
|
| 2125 |
- err error |
|
| 2126 |
- ) |
|
| 2127 |
- if *infile != "" {
|
|
| 2128 |
- input, err = os.Open(*infile) |
|
| 2129 |
- if err != nil {
|
|
| 2130 |
- return err |
|
| 2131 |
- } |
|
| 2132 |
- } |
|
| 2133 |
- if err := cli.stream("POST", "/images/load", input, cli.out, nil); err != nil {
|
|
| 2134 |
- return err |
|
| 2135 |
- } |
|
| 2136 |
- return nil |
|
| 2137 |
-} |
|
| 2138 |
- |
|
| 2139 |
-func (cli *DockerCli) dial() (net.Conn, error) {
|
|
| 2140 |
- if cli.tlsConfig != nil && cli.proto != "unix" {
|
|
| 2141 |
- return tls.Dial(cli.proto, cli.addr, cli.tlsConfig) |
|
| 2142 |
- } |
|
| 2143 |
- return net.Dial(cli.proto, cli.addr) |
|
| 2144 |
-} |
|
| 2145 |
- |
|
| 2146 |
-func (cli *DockerCli) call(method, path string, data interface{}, passAuthInfo bool) (io.ReadCloser, int, error) {
|
|
| 2147 |
- params := bytes.NewBuffer(nil) |
|
| 2148 |
- if data != nil {
|
|
| 2149 |
- if env, ok := data.(engine.Env); ok {
|
|
| 2150 |
- if err := env.Encode(params); err != nil {
|
|
| 2151 |
- return nil, -1, err |
|
| 2152 |
- } |
|
| 2153 |
- } else {
|
|
| 2154 |
- buf, err := json.Marshal(data) |
|
| 2155 |
- if err != nil {
|
|
| 2156 |
- return nil, -1, err |
|
| 2157 |
- } |
|
| 2158 |
- if _, err := params.Write(buf); err != nil {
|
|
| 2159 |
- return nil, -1, err |
|
| 2160 |
- } |
|
| 2161 |
- } |
|
| 2162 |
- } |
|
| 2163 |
- // fixme: refactor client to support redirect |
|
| 2164 |
- re := regexp.MustCompile("/+")
|
|
| 2165 |
- path = re.ReplaceAllString(path, "/") |
|
| 2166 |
- |
|
| 2167 |
- req, err := http.NewRequest(method, fmt.Sprintf("/v%s%s", api.APIVERSION, path), params)
|
|
| 2168 |
- if err != nil {
|
|
| 2169 |
- return nil, -1, err |
|
| 2170 |
- } |
|
| 2171 |
- if passAuthInfo {
|
|
| 2172 |
- cli.LoadConfigFile() |
|
| 2173 |
- // Resolve the Auth config relevant for this server |
|
| 2174 |
- authConfig := cli.configFile.ResolveAuthConfig(registry.IndexServerAddress()) |
|
| 2175 |
- getHeaders := func(authConfig registry.AuthConfig) (map[string][]string, error) {
|
|
| 2176 |
- buf, err := json.Marshal(authConfig) |
|
| 2177 |
- if err != nil {
|
|
| 2178 |
- return nil, err |
|
| 2179 |
- } |
|
| 2180 |
- registryAuthHeader := []string{
|
|
| 2181 |
- base64.URLEncoding.EncodeToString(buf), |
|
| 2182 |
- } |
|
| 2183 |
- return map[string][]string{"X-Registry-Auth": registryAuthHeader}, nil
|
|
| 2184 |
- } |
|
| 2185 |
- if headers, err := getHeaders(authConfig); err == nil && headers != nil {
|
|
| 2186 |
- for k, v := range headers {
|
|
| 2187 |
- req.Header[k] = v |
|
| 2188 |
- } |
|
| 2189 |
- } |
|
| 2190 |
- } |
|
| 2191 |
- req.Header.Set("User-Agent", "Docker-Client/"+dockerversion.VERSION)
|
|
| 2192 |
- req.Host = cli.addr |
|
| 2193 |
- if data != nil {
|
|
| 2194 |
- req.Header.Set("Content-Type", "application/json")
|
|
| 2195 |
- } else if method == "POST" {
|
|
| 2196 |
- req.Header.Set("Content-Type", "plain/text")
|
|
| 2197 |
- } |
|
| 2198 |
- dial, err := cli.dial() |
|
| 2199 |
- if err != nil {
|
|
| 2200 |
- if strings.Contains(err.Error(), "connection refused") {
|
|
| 2201 |
- return nil, -1, ErrConnectionRefused |
|
| 2202 |
- } |
|
| 2203 |
- return nil, -1, err |
|
| 2204 |
- } |
|
| 2205 |
- clientconn := httputil.NewClientConn(dial, nil) |
|
| 2206 |
- resp, err := clientconn.Do(req) |
|
| 2207 |
- if err != nil {
|
|
| 2208 |
- clientconn.Close() |
|
| 2209 |
- if strings.Contains(err.Error(), "connection refused") {
|
|
| 2210 |
- return nil, -1, ErrConnectionRefused |
|
| 2211 |
- } |
|
| 2212 |
- return nil, -1, err |
|
| 2213 |
- } |
|
| 2214 |
- |
|
| 2215 |
- if resp.StatusCode < 200 || resp.StatusCode >= 400 {
|
|
| 2216 |
- body, err := ioutil.ReadAll(resp.Body) |
|
| 2217 |
- if err != nil {
|
|
| 2218 |
- return nil, -1, err |
|
| 2219 |
- } |
|
| 2220 |
- if len(body) == 0 {
|
|
| 2221 |
- return nil, resp.StatusCode, fmt.Errorf("Error: request returned %s for API route and version %s, check if the server supports the requested API version", http.StatusText(resp.StatusCode), req.URL)
|
|
| 2222 |
- } |
|
| 2223 |
- return nil, resp.StatusCode, fmt.Errorf("Error: %s", bytes.TrimSpace(body))
|
|
| 2224 |
- } |
|
| 2225 |
- |
|
| 2226 |
- wrapper := utils.NewReadCloserWrapper(resp.Body, func() error {
|
|
| 2227 |
- if resp != nil && resp.Body != nil {
|
|
| 2228 |
- resp.Body.Close() |
|
| 2229 |
- } |
|
| 2230 |
- return clientconn.Close() |
|
| 2231 |
- }) |
|
| 2232 |
- return wrapper, resp.StatusCode, nil |
|
| 2233 |
-} |
|
| 2234 |
- |
|
| 2235 |
-func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer, headers map[string][]string) error {
|
|
| 2236 |
- if (method == "POST" || method == "PUT") && in == nil {
|
|
| 2237 |
- in = bytes.NewReader([]byte{})
|
|
| 2238 |
- } |
|
| 2239 |
- |
|
| 2240 |
- // fixme: refactor client to support redirect |
|
| 2241 |
- re := regexp.MustCompile("/+")
|
|
| 2242 |
- path = re.ReplaceAllString(path, "/") |
|
| 2243 |
- |
|
| 2244 |
- req, err := http.NewRequest(method, fmt.Sprintf("/v%s%s", api.APIVERSION, path), in)
|
|
| 2245 |
- if err != nil {
|
|
| 2246 |
- return err |
|
| 2247 |
- } |
|
| 2248 |
- req.Header.Set("User-Agent", "Docker-Client/"+dockerversion.VERSION)
|
|
| 2249 |
- req.Host = cli.addr |
|
| 2250 |
- if method == "POST" {
|
|
| 2251 |
- req.Header.Set("Content-Type", "plain/text")
|
|
| 2252 |
- } |
|
| 2253 |
- |
|
| 2254 |
- if headers != nil {
|
|
| 2255 |
- for k, v := range headers {
|
|
| 2256 |
- req.Header[k] = v |
|
| 2257 |
- } |
|
| 2258 |
- } |
|
| 2259 |
- |
|
| 2260 |
- dial, err := cli.dial() |
|
| 2261 |
- if err != nil {
|
|
| 2262 |
- if strings.Contains(err.Error(), "connection refused") {
|
|
| 2263 |
- return fmt.Errorf("Cannot connect to the Docker daemon. Is 'docker -d' running on this host?")
|
|
| 2264 |
- } |
|
| 2265 |
- return err |
|
| 2266 |
- } |
|
| 2267 |
- clientconn := httputil.NewClientConn(dial, nil) |
|
| 2268 |
- resp, err := clientconn.Do(req) |
|
| 2269 |
- defer clientconn.Close() |
|
| 2270 |
- if err != nil {
|
|
| 2271 |
- if strings.Contains(err.Error(), "connection refused") {
|
|
| 2272 |
- return fmt.Errorf("Cannot connect to the Docker daemon. Is 'docker -d' running on this host?")
|
|
| 2273 |
- } |
|
| 2274 |
- return err |
|
| 2275 |
- } |
|
| 2276 |
- defer resp.Body.Close() |
|
| 2277 |
- |
|
| 2278 |
- if resp.StatusCode < 200 || resp.StatusCode >= 400 {
|
|
| 2279 |
- body, err := ioutil.ReadAll(resp.Body) |
|
| 2280 |
- if err != nil {
|
|
| 2281 |
- return err |
|
| 2282 |
- } |
|
| 2283 |
- if len(body) == 0 {
|
|
| 2284 |
- return fmt.Errorf("Error :%s", http.StatusText(resp.StatusCode))
|
|
| 2285 |
- } |
|
| 2286 |
- return fmt.Errorf("Error: %s", bytes.TrimSpace(body))
|
|
| 2287 |
- } |
|
| 2288 |
- |
|
| 2289 |
- if api.MatchesContentType(resp.Header.Get("Content-Type"), "application/json") {
|
|
| 2290 |
- return utils.DisplayJSONMessagesStream(resp.Body, out, cli.terminalFd, cli.isTerminal) |
|
| 2291 |
- } |
|
| 2292 |
- if _, err := io.Copy(out, resp.Body); err != nil {
|
|
| 2293 |
- return err |
|
| 2294 |
- } |
|
| 2295 |
- return nil |
|
| 2296 |
-} |
|
| 2297 |
- |
|
| 2298 |
-func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.ReadCloser, stdout, stderr io.Writer, started chan io.Closer) error {
|
|
| 2299 |
- defer func() {
|
|
| 2300 |
- if started != nil {
|
|
| 2301 |
- close(started) |
|
| 2302 |
- } |
|
| 2303 |
- }() |
|
| 2304 |
- // fixme: refactor client to support redirect |
|
| 2305 |
- re := regexp.MustCompile("/+")
|
|
| 2306 |
- path = re.ReplaceAllString(path, "/") |
|
| 2307 |
- |
|
| 2308 |
- req, err := http.NewRequest(method, fmt.Sprintf("/v%s%s", api.APIVERSION, path), nil)
|
|
| 2309 |
- if err != nil {
|
|
| 2310 |
- return err |
|
| 2311 |
- } |
|
| 2312 |
- req.Header.Set("User-Agent", "Docker-Client/"+dockerversion.VERSION)
|
|
| 2313 |
- req.Header.Set("Content-Type", "plain/text")
|
|
| 2314 |
- req.Host = cli.addr |
|
| 2315 |
- |
|
| 2316 |
- dial, err := cli.dial() |
|
| 2317 |
- if err != nil {
|
|
| 2318 |
- if strings.Contains(err.Error(), "connection refused") {
|
|
| 2319 |
- return fmt.Errorf("Cannot connect to the Docker daemon. Is 'docker -d' running on this host?")
|
|
| 2320 |
- } |
|
| 2321 |
- return err |
|
| 2322 |
- } |
|
| 2323 |
- clientconn := httputil.NewClientConn(dial, nil) |
|
| 2324 |
- defer clientconn.Close() |
|
| 2325 |
- |
|
| 2326 |
- // Server hijacks the connection, error 'connection closed' expected |
|
| 2327 |
- clientconn.Do(req) |
|
| 2328 |
- |
|
| 2329 |
- rwc, br := clientconn.Hijack() |
|
| 2330 |
- defer rwc.Close() |
|
| 2331 |
- |
|
| 2332 |
- if started != nil {
|
|
| 2333 |
- started <- rwc |
|
| 2334 |
- } |
|
| 2335 |
- |
|
| 2336 |
- var receiveStdout chan error |
|
| 2337 |
- |
|
| 2338 |
- var oldState *term.State |
|
| 2339 |
- |
|
| 2340 |
- if in != nil && setRawTerminal && cli.isTerminal && os.Getenv("NORAW") == "" {
|
|
| 2341 |
- oldState, err = term.SetRawTerminal(cli.terminalFd) |
|
| 2342 |
- if err != nil {
|
|
| 2343 |
- return err |
|
| 2344 |
- } |
|
| 2345 |
- defer term.RestoreTerminal(cli.terminalFd, oldState) |
|
| 2346 |
- } |
|
| 2347 |
- |
|
| 2348 |
- if stdout != nil || stderr != nil {
|
|
| 2349 |
- receiveStdout = utils.Go(func() (err error) {
|
|
| 2350 |
- defer func() {
|
|
| 2351 |
- if in != nil {
|
|
| 2352 |
- if setRawTerminal && cli.isTerminal {
|
|
| 2353 |
- term.RestoreTerminal(cli.terminalFd, oldState) |
|
| 2354 |
- } |
|
| 2355 |
- // For some reason this Close call blocks on darwin.. |
|
| 2356 |
- // As the client exists right after, simply discard the close |
|
| 2357 |
- // until we find a better solution. |
|
| 2358 |
- if goruntime.GOOS != "darwin" {
|
|
| 2359 |
- in.Close() |
|
| 2360 |
- } |
|
| 2361 |
- } |
|
| 2362 |
- }() |
|
| 2363 |
- |
|
| 2364 |
- // When TTY is ON, use regular copy |
|
| 2365 |
- if setRawTerminal {
|
|
| 2366 |
- _, err = io.Copy(stdout, br) |
|
| 2367 |
- } else {
|
|
| 2368 |
- _, err = utils.StdCopy(stdout, stderr, br) |
|
| 2369 |
- } |
|
| 2370 |
- utils.Debugf("[hijack] End of stdout")
|
|
| 2371 |
- return err |
|
| 2372 |
- }) |
|
| 2373 |
- } |
|
| 2374 |
- |
|
| 2375 |
- sendStdin := utils.Go(func() error {
|
|
| 2376 |
- if in != nil {
|
|
| 2377 |
- io.Copy(rwc, in) |
|
| 2378 |
- utils.Debugf("[hijack] End of stdin")
|
|
| 2379 |
- } |
|
| 2380 |
- if tcpc, ok := rwc.(*net.TCPConn); ok {
|
|
| 2381 |
- if err := tcpc.CloseWrite(); err != nil {
|
|
| 2382 |
- utils.Debugf("Couldn't send EOF: %s\n", err)
|
|
| 2383 |
- } |
|
| 2384 |
- } else if unixc, ok := rwc.(*net.UnixConn); ok {
|
|
| 2385 |
- if err := unixc.CloseWrite(); err != nil {
|
|
| 2386 |
- utils.Debugf("Couldn't send EOF: %s\n", err)
|
|
| 2387 |
- } |
|
| 2388 |
- } |
|
| 2389 |
- // Discard errors due to pipe interruption |
|
| 2390 |
- return nil |
|
| 2391 |
- }) |
|
| 2392 |
- |
|
| 2393 |
- if stdout != nil || stderr != nil {
|
|
| 2394 |
- if err := <-receiveStdout; err != nil {
|
|
| 2395 |
- utils.Debugf("Error receiveStdout: %s", err)
|
|
| 2396 |
- return err |
|
| 2397 |
- } |
|
| 2398 |
- } |
|
| 2399 |
- |
|
| 2400 |
- if !cli.isTerminal {
|
|
| 2401 |
- if err := <-sendStdin; err != nil {
|
|
| 2402 |
- utils.Debugf("Error sendStdin: %s", err)
|
|
| 2403 |
- return err |
|
| 2404 |
- } |
|
| 2405 |
- } |
|
| 2406 |
- return nil |
|
| 2407 |
- |
|
| 2408 |
-} |
|
| 2409 |
- |
|
| 2410 |
-func (cli *DockerCli) getTtySize() (int, int) {
|
|
| 2411 |
- if !cli.isTerminal {
|
|
| 2412 |
- return 0, 0 |
|
| 2413 |
- } |
|
| 2414 |
- ws, err := term.GetWinsize(cli.terminalFd) |
|
| 2415 |
- if err != nil {
|
|
| 2416 |
- utils.Debugf("Error getting size: %s", err)
|
|
| 2417 |
- if ws == nil {
|
|
| 2418 |
- return 0, 0 |
|
| 2419 |
- } |
|
| 2420 |
- } |
|
| 2421 |
- return int(ws.Height), int(ws.Width) |
|
| 2422 |
-} |
|
| 2423 |
- |
|
| 2424 |
-func (cli *DockerCli) resizeTty(id string) {
|
|
| 2425 |
- height, width := cli.getTtySize() |
|
| 2426 |
- if height == 0 && width == 0 {
|
|
| 2427 |
- return |
|
| 2428 |
- } |
|
| 2429 |
- v := url.Values{}
|
|
| 2430 |
- v.Set("h", strconv.Itoa(height))
|
|
| 2431 |
- v.Set("w", strconv.Itoa(width))
|
|
| 2432 |
- if _, _, err := readBody(cli.call("POST", "/containers/"+id+"/resize?"+v.Encode(), nil, false)); err != nil {
|
|
| 2433 |
- utils.Debugf("Error resize: %s", err)
|
|
| 2434 |
- } |
|
| 2435 |
-} |
|
| 2436 |
- |
|
| 2437 |
-func (cli *DockerCli) monitorTtySize(id string) error {
|
|
| 2438 |
- cli.resizeTty(id) |
|
| 2439 |
- |
|
| 2440 |
- sigchan := make(chan os.Signal, 1) |
|
| 2441 |
- gosignal.Notify(sigchan, syscall.SIGWINCH) |
|
| 2442 |
- go func() {
|
|
| 2443 |
- for _ = range sigchan {
|
|
| 2444 |
- cli.resizeTty(id) |
|
| 2445 |
- } |
|
| 2446 |
- }() |
|
| 2447 |
- return nil |
|
| 2448 |
-} |
|
| 2449 |
- |
|
| 2450 |
-func (cli *DockerCli) Subcmd(name, signature, description string) *flag.FlagSet {
|
|
| 2451 |
- flags := flag.NewFlagSet(name, flag.ContinueOnError) |
|
| 2452 |
- flags.Usage = func() {
|
|
| 2453 |
- fmt.Fprintf(cli.err, "\nUsage: docker %s %s\n\n%s\n\n", name, signature, description) |
|
| 2454 |
- flags.PrintDefaults() |
|
| 2455 |
- os.Exit(2) |
|
| 2456 |
- } |
|
| 2457 |
- return flags |
|
| 2458 |
-} |
|
| 2459 |
- |
|
| 2460 |
-func (cli *DockerCli) LoadConfigFile() (err error) {
|
|
| 2461 |
- cli.configFile, err = registry.LoadConfig(os.Getenv("HOME"))
|
|
| 2462 |
- if err != nil {
|
|
| 2463 |
- fmt.Fprintf(cli.err, "WARNING: %s\n", err) |
|
| 2464 |
- } |
|
| 2465 |
- return err |
|
| 2466 |
-} |
|
| 2467 |
- |
|
| 2468 |
-func waitForExit(cli *DockerCli, containerId string) (int, error) {
|
|
| 2469 |
- stream, _, err := cli.call("POST", "/containers/"+containerId+"/wait", nil, false)
|
|
| 2470 |
- if err != nil {
|
|
| 2471 |
- return -1, err |
|
| 2472 |
- } |
|
| 2473 |
- |
|
| 2474 |
- var out engine.Env |
|
| 2475 |
- if err := out.Decode(stream); err != nil {
|
|
| 2476 |
- return -1, err |
|
| 2477 |
- } |
|
| 2478 |
- return out.GetInt("StatusCode"), nil
|
|
| 2479 |
-} |
|
| 2480 |
- |
|
| 2481 |
-// getExitCode perform an inspect on the container. It returns |
|
| 2482 |
-// the running state and the exit code. |
|
| 2483 |
-func getExitCode(cli *DockerCli, containerId string) (bool, int, error) {
|
|
| 2484 |
- body, _, err := readBody(cli.call("GET", "/containers/"+containerId+"/json", nil, false))
|
|
| 2485 |
- if err != nil {
|
|
| 2486 |
- // If we can't connect, then the daemon probably died. |
|
| 2487 |
- if err != ErrConnectionRefused {
|
|
| 2488 |
- return false, -1, err |
|
| 2489 |
- } |
|
| 2490 |
- return false, -1, nil |
|
| 2491 |
- } |
|
| 2492 |
- c := &api.Container{}
|
|
| 2493 |
- if err := json.Unmarshal(body, c); err != nil {
|
|
| 2494 |
- return false, -1, err |
|
| 2495 |
- } |
|
| 2496 |
- return c.State.Running, c.State.ExitCode, nil |
|
| 2497 |
-} |
|
| 2498 |
- |
|
| 2499 |
-func readBody(stream io.ReadCloser, statusCode int, err error) ([]byte, int, error) {
|
|
| 2500 |
- if stream != nil {
|
|
| 2501 |
- defer stream.Close() |
|
| 2502 |
- } |
|
| 2503 |
- if err != nil {
|
|
| 2504 |
- return nil, statusCode, err |
|
| 2505 |
- } |
|
| 2506 |
- body, err := ioutil.ReadAll(stream) |
|
| 2507 |
- if err != nil {
|
|
| 2508 |
- return nil, -1, err |
|
| 2509 |
- } |
|
| 2510 |
- return body, statusCode, nil |
|
| 2511 |
-} |
|
| 2512 |
- |
|
| 2513 |
-func NewDockerCli(in io.ReadCloser, out, err io.Writer, proto, addr string, tlsConfig *tls.Config) *DockerCli {
|
|
| 2514 |
- var ( |
|
| 2515 |
- isTerminal = false |
|
| 2516 |
- terminalFd uintptr |
|
| 2517 |
- ) |
|
| 2518 |
- |
|
| 2519 |
- if in != nil {
|
|
| 2520 |
- if file, ok := in.(*os.File); ok {
|
|
| 2521 |
- terminalFd = file.Fd() |
|
| 2522 |
- isTerminal = term.IsTerminal(terminalFd) |
|
| 2523 |
- } |
|
| 2524 |
- } |
|
| 2525 |
- |
|
| 2526 |
- if err == nil {
|
|
| 2527 |
- err = out |
|
| 2528 |
- } |
|
| 2529 |
- return &DockerCli{
|
|
| 2530 |
- proto: proto, |
|
| 2531 |
- addr: addr, |
|
| 2532 |
- in: in, |
|
| 2533 |
- out: out, |
|
| 2534 |
- err: err, |
|
| 2535 |
- isTerminal: isTerminal, |
|
| 2536 |
- terminalFd: terminalFd, |
|
| 2537 |
- tlsConfig: tlsConfig, |
|
| 2538 |
- } |
|
| 2539 |
-} |
|
| 2540 |
- |
|
| 2541 |
-type DockerCli struct {
|
|
| 2542 |
- proto string |
|
| 2543 |
- addr string |
|
| 2544 |
- configFile *registry.ConfigFile |
|
| 2545 |
- in io.ReadCloser |
|
| 2546 |
- out io.Writer |
|
| 2547 |
- err io.Writer |
|
| 2548 |
- isTerminal bool |
|
| 2549 |
- terminalFd uintptr |
|
| 2550 |
- tlsConfig *tls.Config |
|
| 2551 |
-} |
| 2552 | 1 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,2098 @@ |
| 0 |
+package client |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "bufio" |
|
| 4 |
+ "bytes" |
|
| 5 |
+ "encoding/base64" |
|
| 6 |
+ "encoding/json" |
|
| 7 |
+ "fmt" |
|
| 8 |
+ "io" |
|
| 9 |
+ "io/ioutil" |
|
| 10 |
+ "net/http" |
|
| 11 |
+ "net/url" |
|
| 12 |
+ "os" |
|
| 13 |
+ "os/exec" |
|
| 14 |
+ "path" |
|
| 15 |
+ goruntime "runtime" |
|
| 16 |
+ "strconv" |
|
| 17 |
+ "strings" |
|
| 18 |
+ "syscall" |
|
| 19 |
+ "text/tabwriter" |
|
| 20 |
+ "text/template" |
|
| 21 |
+ "time" |
|
| 22 |
+ |
|
| 23 |
+ "github.com/dotcloud/docker/api" |
|
| 24 |
+ "github.com/dotcloud/docker/archive" |
|
| 25 |
+ "github.com/dotcloud/docker/dockerversion" |
|
| 26 |
+ "github.com/dotcloud/docker/engine" |
|
| 27 |
+ "github.com/dotcloud/docker/nat" |
|
| 28 |
+ "github.com/dotcloud/docker/pkg/signal" |
|
| 29 |
+ "github.com/dotcloud/docker/pkg/term" |
|
| 30 |
+ "github.com/dotcloud/docker/registry" |
|
| 31 |
+ "github.com/dotcloud/docker/runconfig" |
|
| 32 |
+ "github.com/dotcloud/docker/utils" |
|
| 33 |
+) |
|
| 34 |
+ |
|
| 35 |
+func (cli *DockerCli) CmdHelp(args ...string) error {
|
|
| 36 |
+ if len(args) > 0 {
|
|
| 37 |
+ method, exists := cli.getMethod(args[0]) |
|
| 38 |
+ if !exists {
|
|
| 39 |
+ fmt.Fprintf(cli.err, "Error: Command not found: %s\n", args[0]) |
|
| 40 |
+ } else {
|
|
| 41 |
+ method("--help")
|
|
| 42 |
+ return nil |
|
| 43 |
+ } |
|
| 44 |
+ } |
|
| 45 |
+ help := fmt.Sprintf("Usage: docker [OPTIONS] COMMAND [arg...]\n -H=[unix://%s]: tcp://host:port to bind/connect to or unix://path/to/socket to use\n\nA self-sufficient runtime for linux containers.\n\nCommands:\n", api.DEFAULTUNIXSOCKET)
|
|
| 46 |
+ for _, command := range [][]string{
|
|
| 47 |
+ {"attach", "Attach to a running container"},
|
|
| 48 |
+ {"build", "Build a container from a Dockerfile"},
|
|
| 49 |
+ {"commit", "Create a new image from a container's changes"},
|
|
| 50 |
+ {"cp", "Copy files/folders from the containers filesystem to the host path"},
|
|
| 51 |
+ {"diff", "Inspect changes on a container's filesystem"},
|
|
| 52 |
+ {"events", "Get real time events from the server"},
|
|
| 53 |
+ {"export", "Stream the contents of a container as a tar archive"},
|
|
| 54 |
+ {"history", "Show the history of an image"},
|
|
| 55 |
+ {"images", "List images"},
|
|
| 56 |
+ {"import", "Create a new filesystem image from the contents of a tarball"},
|
|
| 57 |
+ {"info", "Display system-wide information"},
|
|
| 58 |
+ {"insert", "Insert a file in an image"},
|
|
| 59 |
+ {"inspect", "Return low-level information on a container"},
|
|
| 60 |
+ {"kill", "Kill a running container"},
|
|
| 61 |
+ {"load", "Load an image from a tar archive"},
|
|
| 62 |
+ {"login", "Register or Login to the docker registry server"},
|
|
| 63 |
+ {"logs", "Fetch the logs of a container"},
|
|
| 64 |
+ {"port", "Lookup the public-facing port which is NAT-ed to PRIVATE_PORT"},
|
|
| 65 |
+ {"ps", "List containers"},
|
|
| 66 |
+ {"pull", "Pull an image or a repository from the docker registry server"},
|
|
| 67 |
+ {"push", "Push an image or a repository to the docker registry server"},
|
|
| 68 |
+ {"restart", "Restart a running container"},
|
|
| 69 |
+ {"rm", "Remove one or more containers"},
|
|
| 70 |
+ {"rmi", "Remove one or more images"},
|
|
| 71 |
+ {"run", "Run a command in a new container"},
|
|
| 72 |
+ {"save", "Save an image to a tar archive"},
|
|
| 73 |
+ {"search", "Search for an image in the docker index"},
|
|
| 74 |
+ {"start", "Start a stopped container"},
|
|
| 75 |
+ {"stop", "Stop a running container"},
|
|
| 76 |
+ {"tag", "Tag an image into a repository"},
|
|
| 77 |
+ {"top", "Lookup the running processes of a container"},
|
|
| 78 |
+ {"version", "Show the docker version information"},
|
|
| 79 |
+ {"wait", "Block until a container stops, then print its exit code"},
|
|
| 80 |
+ } {
|
|
| 81 |
+ help += fmt.Sprintf(" %-10.10s%s\n", command[0], command[1])
|
|
| 82 |
+ } |
|
| 83 |
+ fmt.Fprintf(cli.err, "%s\n", help) |
|
| 84 |
+ return nil |
|
| 85 |
+} |
|
| 86 |
+ |
|
| 87 |
+func (cli *DockerCli) CmdInsert(args ...string) error {
|
|
| 88 |
+ cmd := cli.Subcmd("insert", "IMAGE URL PATH", "Insert a file from URL in the IMAGE at PATH")
|
|
| 89 |
+ if err := cmd.Parse(args); err != nil {
|
|
| 90 |
+ return nil |
|
| 91 |
+ } |
|
| 92 |
+ if cmd.NArg() != 3 {
|
|
| 93 |
+ cmd.Usage() |
|
| 94 |
+ return nil |
|
| 95 |
+ } |
|
| 96 |
+ |
|
| 97 |
+ v := url.Values{}
|
|
| 98 |
+ v.Set("url", cmd.Arg(1))
|
|
| 99 |
+ v.Set("path", cmd.Arg(2))
|
|
| 100 |
+ |
|
| 101 |
+ return cli.stream("POST", "/images/"+cmd.Arg(0)+"/insert?"+v.Encode(), nil, cli.out, nil)
|
|
| 102 |
+} |
|
| 103 |
+ |
|
| 104 |
+func (cli *DockerCli) CmdBuild(args ...string) error {
|
|
| 105 |
+ cmd := cli.Subcmd("build", "[OPTIONS] PATH | URL | -", "Build a new container image from the source code at PATH")
|
|
| 106 |
+ tag := cmd.String([]string{"t", "-tag"}, "", "Repository name (and optionally a tag) to be applied to the resulting image in case of success")
|
|
| 107 |
+ suppressOutput := cmd.Bool([]string{"q", "-quiet"}, false, "Suppress the verbose output generated by the containers")
|
|
| 108 |
+ noCache := cmd.Bool([]string{"#no-cache", "-no-cache"}, false, "Do not use cache when building the image")
|
|
| 109 |
+ rm := cmd.Bool([]string{"#rm", "-rm"}, true, "Remove intermediate containers after a successful build")
|
|
| 110 |
+ if err := cmd.Parse(args); err != nil {
|
|
| 111 |
+ return nil |
|
| 112 |
+ } |
|
| 113 |
+ if cmd.NArg() != 1 {
|
|
| 114 |
+ cmd.Usage() |
|
| 115 |
+ return nil |
|
| 116 |
+ } |
|
| 117 |
+ |
|
| 118 |
+ var ( |
|
| 119 |
+ context archive.Archive |
|
| 120 |
+ isRemote bool |
|
| 121 |
+ err error |
|
| 122 |
+ ) |
|
| 123 |
+ |
|
| 124 |
+ _, err = exec.LookPath("git")
|
|
| 125 |
+ hasGit := err == nil |
|
| 126 |
+ if cmd.Arg(0) == "-" {
|
|
| 127 |
+ // As a special case, 'docker build -' will build from an empty context with the |
|
| 128 |
+ // contents of stdin as a Dockerfile |
|
| 129 |
+ dockerfile, err := ioutil.ReadAll(cli.in) |
|
| 130 |
+ if err != nil {
|
|
| 131 |
+ return err |
|
| 132 |
+ } |
|
| 133 |
+ context, err = archive.Generate("Dockerfile", string(dockerfile))
|
|
| 134 |
+ } else if utils.IsURL(cmd.Arg(0)) && (!utils.IsGIT(cmd.Arg(0)) || !hasGit) {
|
|
| 135 |
+ isRemote = true |
|
| 136 |
+ } else {
|
|
| 137 |
+ root := cmd.Arg(0) |
|
| 138 |
+ if utils.IsGIT(root) {
|
|
| 139 |
+ remoteURL := cmd.Arg(0) |
|
| 140 |
+ if !strings.HasPrefix(remoteURL, "git://") && !strings.HasPrefix(remoteURL, "git@") && !utils.IsURL(remoteURL) {
|
|
| 141 |
+ remoteURL = "https://" + remoteURL |
|
| 142 |
+ } |
|
| 143 |
+ |
|
| 144 |
+ root, err = ioutil.TempDir("", "docker-build-git")
|
|
| 145 |
+ if err != nil {
|
|
| 146 |
+ return err |
|
| 147 |
+ } |
|
| 148 |
+ defer os.RemoveAll(root) |
|
| 149 |
+ |
|
| 150 |
+ if output, err := exec.Command("git", "clone", "--recursive", remoteURL, root).CombinedOutput(); err != nil {
|
|
| 151 |
+ return fmt.Errorf("Error trying to use git: %s (%s)", err, output)
|
|
| 152 |
+ } |
|
| 153 |
+ } |
|
| 154 |
+ if _, err := os.Stat(root); err != nil {
|
|
| 155 |
+ return err |
|
| 156 |
+ } |
|
| 157 |
+ filename := path.Join(root, "Dockerfile") |
|
| 158 |
+ if _, err = os.Stat(filename); os.IsNotExist(err) {
|
|
| 159 |
+ return fmt.Errorf("no Dockerfile found in %s", cmd.Arg(0))
|
|
| 160 |
+ } |
|
| 161 |
+ context, err = archive.Tar(root, archive.Uncompressed) |
|
| 162 |
+ } |
|
| 163 |
+ var body io.Reader |
|
| 164 |
+ // Setup an upload progress bar |
|
| 165 |
+ // FIXME: ProgressReader shouldn't be this annoying to use |
|
| 166 |
+ if context != nil {
|
|
| 167 |
+ sf := utils.NewStreamFormatter(false) |
|
| 168 |
+ body = utils.ProgressReader(context, 0, cli.err, sf, true, "", "Uploading context") |
|
| 169 |
+ } |
|
| 170 |
+ // Upload the build context |
|
| 171 |
+ v := &url.Values{}
|
|
| 172 |
+ |
|
| 173 |
+ //Check if the given image name can be resolved |
|
| 174 |
+ if *tag != "" {
|
|
| 175 |
+ repository, _ := utils.ParseRepositoryTag(*tag) |
|
| 176 |
+ if _, _, err := registry.ResolveRepositoryName(repository); err != nil {
|
|
| 177 |
+ return err |
|
| 178 |
+ } |
|
| 179 |
+ } |
|
| 180 |
+ |
|
| 181 |
+ v.Set("t", *tag)
|
|
| 182 |
+ |
|
| 183 |
+ if *suppressOutput {
|
|
| 184 |
+ v.Set("q", "1")
|
|
| 185 |
+ } |
|
| 186 |
+ if isRemote {
|
|
| 187 |
+ v.Set("remote", cmd.Arg(0))
|
|
| 188 |
+ } |
|
| 189 |
+ if *noCache {
|
|
| 190 |
+ v.Set("nocache", "1")
|
|
| 191 |
+ } |
|
| 192 |
+ if *rm {
|
|
| 193 |
+ v.Set("rm", "1")
|
|
| 194 |
+ } |
|
| 195 |
+ |
|
| 196 |
+ cli.LoadConfigFile() |
|
| 197 |
+ |
|
| 198 |
+ headers := http.Header(make(map[string][]string)) |
|
| 199 |
+ buf, err := json.Marshal(cli.configFile) |
|
| 200 |
+ if err != nil {
|
|
| 201 |
+ return err |
|
| 202 |
+ } |
|
| 203 |
+ headers.Add("X-Registry-Config", base64.URLEncoding.EncodeToString(buf))
|
|
| 204 |
+ |
|
| 205 |
+ if context != nil {
|
|
| 206 |
+ headers.Set("Content-Type", "application/tar")
|
|
| 207 |
+ } |
|
| 208 |
+ err = cli.stream("POST", fmt.Sprintf("/build?%s", v.Encode()), body, cli.out, headers)
|
|
| 209 |
+ if jerr, ok := err.(*utils.JSONError); ok {
|
|
| 210 |
+ // If no error code is set, default to 1 |
|
| 211 |
+ if jerr.Code == 0 {
|
|
| 212 |
+ jerr.Code = 1 |
|
| 213 |
+ } |
|
| 214 |
+ return &utils.StatusError{Status: jerr.Message, StatusCode: jerr.Code}
|
|
| 215 |
+ } |
|
| 216 |
+ return err |
|
| 217 |
+} |
|
| 218 |
+ |
|
| 219 |
+// 'docker login': login / register a user to registry service. |
|
| 220 |
+func (cli *DockerCli) CmdLogin(args ...string) error {
|
|
| 221 |
+ cmd := cli.Subcmd("login", "[OPTIONS] [SERVER]", "Register or Login to a docker registry server, if no server is specified \""+registry.IndexServerAddress()+"\" is the default.")
|
|
| 222 |
+ |
|
| 223 |
+ var username, password, email string |
|
| 224 |
+ |
|
| 225 |
+ cmd.StringVar(&username, []string{"u", "-username"}, "", "Username")
|
|
| 226 |
+ cmd.StringVar(&password, []string{"p", "-password"}, "", "Password")
|
|
| 227 |
+ cmd.StringVar(&email, []string{"e", "-email"}, "", "Email")
|
|
| 228 |
+ err := cmd.Parse(args) |
|
| 229 |
+ if err != nil {
|
|
| 230 |
+ return nil |
|
| 231 |
+ } |
|
| 232 |
+ serverAddress := registry.IndexServerAddress() |
|
| 233 |
+ if len(cmd.Args()) > 0 {
|
|
| 234 |
+ serverAddress = cmd.Arg(0) |
|
| 235 |
+ } |
|
| 236 |
+ |
|
| 237 |
+ promptDefault := func(prompt string, configDefault string) {
|
|
| 238 |
+ if configDefault == "" {
|
|
| 239 |
+ fmt.Fprintf(cli.out, "%s: ", prompt) |
|
| 240 |
+ } else {
|
|
| 241 |
+ fmt.Fprintf(cli.out, "%s (%s): ", prompt, configDefault) |
|
| 242 |
+ } |
|
| 243 |
+ } |
|
| 244 |
+ |
|
| 245 |
+ readInput := func(in io.Reader, out io.Writer) string {
|
|
| 246 |
+ reader := bufio.NewReader(in) |
|
| 247 |
+ line, _, err := reader.ReadLine() |
|
| 248 |
+ if err != nil {
|
|
| 249 |
+ fmt.Fprintln(out, err.Error()) |
|
| 250 |
+ os.Exit(1) |
|
| 251 |
+ } |
|
| 252 |
+ return string(line) |
|
| 253 |
+ } |
|
| 254 |
+ |
|
| 255 |
+ cli.LoadConfigFile() |
|
| 256 |
+ authconfig, ok := cli.configFile.Configs[serverAddress] |
|
| 257 |
+ if !ok {
|
|
| 258 |
+ authconfig = registry.AuthConfig{}
|
|
| 259 |
+ } |
|
| 260 |
+ |
|
| 261 |
+ if username == "" {
|
|
| 262 |
+ promptDefault("Username", authconfig.Username)
|
|
| 263 |
+ username = readInput(cli.in, cli.out) |
|
| 264 |
+ if username == "" {
|
|
| 265 |
+ username = authconfig.Username |
|
| 266 |
+ } |
|
| 267 |
+ } |
|
| 268 |
+ if username != authconfig.Username {
|
|
| 269 |
+ if password == "" {
|
|
| 270 |
+ oldState, _ := term.SaveState(cli.terminalFd) |
|
| 271 |
+ fmt.Fprintf(cli.out, "Password: ") |
|
| 272 |
+ term.DisableEcho(cli.terminalFd, oldState) |
|
| 273 |
+ |
|
| 274 |
+ password = readInput(cli.in, cli.out) |
|
| 275 |
+ fmt.Fprint(cli.out, "\n") |
|
| 276 |
+ |
|
| 277 |
+ term.RestoreTerminal(cli.terminalFd, oldState) |
|
| 278 |
+ if password == "" {
|
|
| 279 |
+ return fmt.Errorf("Error : Password Required")
|
|
| 280 |
+ } |
|
| 281 |
+ } |
|
| 282 |
+ |
|
| 283 |
+ if email == "" {
|
|
| 284 |
+ promptDefault("Email", authconfig.Email)
|
|
| 285 |
+ email = readInput(cli.in, cli.out) |
|
| 286 |
+ if email == "" {
|
|
| 287 |
+ email = authconfig.Email |
|
| 288 |
+ } |
|
| 289 |
+ } |
|
| 290 |
+ } else {
|
|
| 291 |
+ password = authconfig.Password |
|
| 292 |
+ email = authconfig.Email |
|
| 293 |
+ } |
|
| 294 |
+ authconfig.Username = username |
|
| 295 |
+ authconfig.Password = password |
|
| 296 |
+ authconfig.Email = email |
|
| 297 |
+ authconfig.ServerAddress = serverAddress |
|
| 298 |
+ cli.configFile.Configs[serverAddress] = authconfig |
|
| 299 |
+ |
|
| 300 |
+ stream, statusCode, err := cli.call("POST", "/auth", cli.configFile.Configs[serverAddress], false)
|
|
| 301 |
+ if statusCode == 401 {
|
|
| 302 |
+ delete(cli.configFile.Configs, serverAddress) |
|
| 303 |
+ registry.SaveConfig(cli.configFile) |
|
| 304 |
+ return err |
|
| 305 |
+ } |
|
| 306 |
+ if err != nil {
|
|
| 307 |
+ return err |
|
| 308 |
+ } |
|
| 309 |
+ var out2 engine.Env |
|
| 310 |
+ err = out2.Decode(stream) |
|
| 311 |
+ if err != nil {
|
|
| 312 |
+ cli.configFile, _ = registry.LoadConfig(os.Getenv("HOME"))
|
|
| 313 |
+ return err |
|
| 314 |
+ } |
|
| 315 |
+ registry.SaveConfig(cli.configFile) |
|
| 316 |
+ if out2.Get("Status") != "" {
|
|
| 317 |
+ fmt.Fprintf(cli.out, "%s\n", out2.Get("Status"))
|
|
| 318 |
+ } |
|
| 319 |
+ return nil |
|
| 320 |
+} |
|
| 321 |
+ |
|
| 322 |
+// 'docker wait': block until a container stops |
|
| 323 |
+func (cli *DockerCli) CmdWait(args ...string) error {
|
|
| 324 |
+ cmd := cli.Subcmd("wait", "CONTAINER [CONTAINER...]", "Block until a container stops, then print its exit code.")
|
|
| 325 |
+ if err := cmd.Parse(args); err != nil {
|
|
| 326 |
+ return nil |
|
| 327 |
+ } |
|
| 328 |
+ if cmd.NArg() < 1 {
|
|
| 329 |
+ cmd.Usage() |
|
| 330 |
+ return nil |
|
| 331 |
+ } |
|
| 332 |
+ var encounteredError error |
|
| 333 |
+ for _, name := range cmd.Args() {
|
|
| 334 |
+ status, err := waitForExit(cli, name) |
|
| 335 |
+ if err != nil {
|
|
| 336 |
+ fmt.Fprintf(cli.err, "%s\n", err) |
|
| 337 |
+ encounteredError = fmt.Errorf("Error: failed to wait one or more containers")
|
|
| 338 |
+ } else {
|
|
| 339 |
+ fmt.Fprintf(cli.out, "%d\n", status) |
|
| 340 |
+ } |
|
| 341 |
+ } |
|
| 342 |
+ return encounteredError |
|
| 343 |
+} |
|
| 344 |
+ |
|
| 345 |
+// 'docker version': show version information |
|
| 346 |
+func (cli *DockerCli) CmdVersion(args ...string) error {
|
|
| 347 |
+ cmd := cli.Subcmd("version", "", "Show the docker version information.")
|
|
| 348 |
+ if err := cmd.Parse(args); err != nil {
|
|
| 349 |
+ return nil |
|
| 350 |
+ } |
|
| 351 |
+ |
|
| 352 |
+ if cmd.NArg() > 0 {
|
|
| 353 |
+ cmd.Usage() |
|
| 354 |
+ return nil |
|
| 355 |
+ } |
|
| 356 |
+ if dockerversion.VERSION != "" {
|
|
| 357 |
+ fmt.Fprintf(cli.out, "Client version: %s\n", dockerversion.VERSION) |
|
| 358 |
+ } |
|
| 359 |
+ fmt.Fprintf(cli.out, "Go version (client): %s\n", goruntime.Version()) |
|
| 360 |
+ if dockerversion.GITCOMMIT != "" {
|
|
| 361 |
+ fmt.Fprintf(cli.out, "Git commit (client): %s\n", dockerversion.GITCOMMIT) |
|
| 362 |
+ } |
|
| 363 |
+ |
|
| 364 |
+ body, _, err := readBody(cli.call("GET", "/version", nil, false))
|
|
| 365 |
+ if err != nil {
|
|
| 366 |
+ return err |
|
| 367 |
+ } |
|
| 368 |
+ |
|
| 369 |
+ out := engine.NewOutput() |
|
| 370 |
+ remoteVersion, err := out.AddEnv() |
|
| 371 |
+ if err != nil {
|
|
| 372 |
+ utils.Errorf("Error reading remote version: %s\n", err)
|
|
| 373 |
+ return err |
|
| 374 |
+ } |
|
| 375 |
+ if _, err := out.Write(body); err != nil {
|
|
| 376 |
+ utils.Errorf("Error reading remote version: %s\n", err)
|
|
| 377 |
+ return err |
|
| 378 |
+ } |
|
| 379 |
+ out.Close() |
|
| 380 |
+ fmt.Fprintf(cli.out, "Server version: %s\n", remoteVersion.Get("Version"))
|
|
| 381 |
+ fmt.Fprintf(cli.out, "Git commit (server): %s\n", remoteVersion.Get("GitCommit"))
|
|
| 382 |
+ fmt.Fprintf(cli.out, "Go version (server): %s\n", remoteVersion.Get("GoVersion"))
|
|
| 383 |
+ release := utils.GetReleaseVersion() |
|
| 384 |
+ if release != "" {
|
|
| 385 |
+ fmt.Fprintf(cli.out, "Last stable version: %s", release) |
|
| 386 |
+ if (dockerversion.VERSION != "" || remoteVersion.Exists("Version")) && (strings.Trim(dockerversion.VERSION, "-dev") != release || strings.Trim(remoteVersion.Get("Version"), "-dev") != release) {
|
|
| 387 |
+ fmt.Fprintf(cli.out, ", please update docker") |
|
| 388 |
+ } |
|
| 389 |
+ fmt.Fprintf(cli.out, "\n") |
|
| 390 |
+ } |
|
| 391 |
+ return nil |
|
| 392 |
+} |
|
| 393 |
+ |
|
| 394 |
+// 'docker info': display system-wide information. |
|
| 395 |
+func (cli *DockerCli) CmdInfo(args ...string) error {
|
|
| 396 |
+ cmd := cli.Subcmd("info", "", "Display system-wide information")
|
|
| 397 |
+ if err := cmd.Parse(args); err != nil {
|
|
| 398 |
+ return nil |
|
| 399 |
+ } |
|
| 400 |
+ if cmd.NArg() > 0 {
|
|
| 401 |
+ cmd.Usage() |
|
| 402 |
+ return nil |
|
| 403 |
+ } |
|
| 404 |
+ |
|
| 405 |
+ body, _, err := readBody(cli.call("GET", "/info", nil, false))
|
|
| 406 |
+ if err != nil {
|
|
| 407 |
+ return err |
|
| 408 |
+ } |
|
| 409 |
+ |
|
| 410 |
+ out := engine.NewOutput() |
|
| 411 |
+ remoteInfo, err := out.AddEnv() |
|
| 412 |
+ if err != nil {
|
|
| 413 |
+ return err |
|
| 414 |
+ } |
|
| 415 |
+ |
|
| 416 |
+ if _, err := out.Write(body); err != nil {
|
|
| 417 |
+ utils.Errorf("Error reading remote info: %s\n", err)
|
|
| 418 |
+ return err |
|
| 419 |
+ } |
|
| 420 |
+ out.Close() |
|
| 421 |
+ |
|
| 422 |
+ fmt.Fprintf(cli.out, "Containers: %d\n", remoteInfo.GetInt("Containers"))
|
|
| 423 |
+ fmt.Fprintf(cli.out, "Images: %d\n", remoteInfo.GetInt("Images"))
|
|
| 424 |
+ fmt.Fprintf(cli.out, "Storage Driver: %s\n", remoteInfo.Get("Driver"))
|
|
| 425 |
+ var driverStatus [][2]string |
|
| 426 |
+ if err := remoteInfo.GetJson("DriverStatus", &driverStatus); err != nil {
|
|
| 427 |
+ return err |
|
| 428 |
+ } |
|
| 429 |
+ for _, pair := range driverStatus {
|
|
| 430 |
+ fmt.Fprintf(cli.out, " %s: %s\n", pair[0], pair[1]) |
|
| 431 |
+ } |
|
| 432 |
+ fmt.Fprintf(cli.out, "Execution Driver: %s\n", remoteInfo.Get("ExecutionDriver"))
|
|
| 433 |
+ fmt.Fprintf(cli.out, "Kernel Version: %s\n", remoteInfo.Get("KernelVersion"))
|
|
| 434 |
+ |
|
| 435 |
+ if remoteInfo.GetBool("Debug") || os.Getenv("DEBUG") != "" {
|
|
| 436 |
+ fmt.Fprintf(cli.out, "Debug mode (server): %v\n", remoteInfo.GetBool("Debug"))
|
|
| 437 |
+ fmt.Fprintf(cli.out, "Debug mode (client): %v\n", os.Getenv("DEBUG") != "")
|
|
| 438 |
+ fmt.Fprintf(cli.out, "Fds: %d\n", remoteInfo.GetInt("NFd"))
|
|
| 439 |
+ fmt.Fprintf(cli.out, "Goroutines: %d\n", remoteInfo.GetInt("NGoroutines"))
|
|
| 440 |
+ fmt.Fprintf(cli.out, "EventsListeners: %d\n", remoteInfo.GetInt("NEventsListener"))
|
|
| 441 |
+ |
|
| 442 |
+ if initSha1 := remoteInfo.Get("InitSha1"); initSha1 != "" {
|
|
| 443 |
+ fmt.Fprintf(cli.out, "Init SHA1: %s\n", initSha1) |
|
| 444 |
+ } |
|
| 445 |
+ if initPath := remoteInfo.Get("InitPath"); initPath != "" {
|
|
| 446 |
+ fmt.Fprintf(cli.out, "Init Path: %s\n", initPath) |
|
| 447 |
+ } |
|
| 448 |
+ } |
|
| 449 |
+ |
|
| 450 |
+ if len(remoteInfo.GetList("IndexServerAddress")) != 0 {
|
|
| 451 |
+ cli.LoadConfigFile() |
|
| 452 |
+ u := cli.configFile.Configs[remoteInfo.Get("IndexServerAddress")].Username
|
|
| 453 |
+ if len(u) > 0 {
|
|
| 454 |
+ fmt.Fprintf(cli.out, "Username: %v\n", u) |
|
| 455 |
+ fmt.Fprintf(cli.out, "Registry: %v\n", remoteInfo.GetList("IndexServerAddress"))
|
|
| 456 |
+ } |
|
| 457 |
+ } |
|
| 458 |
+ if !remoteInfo.GetBool("MemoryLimit") {
|
|
| 459 |
+ fmt.Fprintf(cli.err, "WARNING: No memory limit support\n") |
|
| 460 |
+ } |
|
| 461 |
+ if !remoteInfo.GetBool("SwapLimit") {
|
|
| 462 |
+ fmt.Fprintf(cli.err, "WARNING: No swap limit support\n") |
|
| 463 |
+ } |
|
| 464 |
+ if !remoteInfo.GetBool("IPv4Forwarding") {
|
|
| 465 |
+ fmt.Fprintf(cli.err, "WARNING: IPv4 forwarding is disabled.\n") |
|
| 466 |
+ } |
|
| 467 |
+ return nil |
|
| 468 |
+} |
|
| 469 |
+ |
|
| 470 |
+func (cli *DockerCli) CmdStop(args ...string) error {
|
|
| 471 |
+ cmd := cli.Subcmd("stop", "[OPTIONS] CONTAINER [CONTAINER...]", "Stop a running container (Send SIGTERM, and then SIGKILL after grace period)")
|
|
| 472 |
+ nSeconds := cmd.Int([]string{"t", "-time"}, 10, "Number of seconds to wait for the container to stop before killing it.")
|
|
| 473 |
+ if err := cmd.Parse(args); err != nil {
|
|
| 474 |
+ return nil |
|
| 475 |
+ } |
|
| 476 |
+ if cmd.NArg() < 1 {
|
|
| 477 |
+ cmd.Usage() |
|
| 478 |
+ return nil |
|
| 479 |
+ } |
|
| 480 |
+ |
|
| 481 |
+ v := url.Values{}
|
|
| 482 |
+ v.Set("t", strconv.Itoa(*nSeconds))
|
|
| 483 |
+ |
|
| 484 |
+ var encounteredError error |
|
| 485 |
+ for _, name := range cmd.Args() {
|
|
| 486 |
+ _, _, err := readBody(cli.call("POST", "/containers/"+name+"/stop?"+v.Encode(), nil, false))
|
|
| 487 |
+ if err != nil {
|
|
| 488 |
+ fmt.Fprintf(cli.err, "%s\n", err) |
|
| 489 |
+ encounteredError = fmt.Errorf("Error: failed to stop one or more containers")
|
|
| 490 |
+ } else {
|
|
| 491 |
+ fmt.Fprintf(cli.out, "%s\n", name) |
|
| 492 |
+ } |
|
| 493 |
+ } |
|
| 494 |
+ return encounteredError |
|
| 495 |
+} |
|
| 496 |
+ |
|
| 497 |
+func (cli *DockerCli) CmdRestart(args ...string) error {
|
|
| 498 |
+ cmd := cli.Subcmd("restart", "[OPTIONS] CONTAINER [CONTAINER...]", "Restart a running container")
|
|
| 499 |
+ nSeconds := cmd.Int([]string{"t", "-time"}, 10, "Number of seconds to try to stop for before killing the container. Once killed it will then be restarted. Default=10")
|
|
| 500 |
+ if err := cmd.Parse(args); err != nil {
|
|
| 501 |
+ return nil |
|
| 502 |
+ } |
|
| 503 |
+ if cmd.NArg() < 1 {
|
|
| 504 |
+ cmd.Usage() |
|
| 505 |
+ return nil |
|
| 506 |
+ } |
|
| 507 |
+ |
|
| 508 |
+ v := url.Values{}
|
|
| 509 |
+ v.Set("t", strconv.Itoa(*nSeconds))
|
|
| 510 |
+ |
|
| 511 |
+ var encounteredError error |
|
| 512 |
+ for _, name := range cmd.Args() {
|
|
| 513 |
+ _, _, err := readBody(cli.call("POST", "/containers/"+name+"/restart?"+v.Encode(), nil, false))
|
|
| 514 |
+ if err != nil {
|
|
| 515 |
+ fmt.Fprintf(cli.err, "%s\n", err) |
|
| 516 |
+ encounteredError = fmt.Errorf("Error: failed to restart one or more containers")
|
|
| 517 |
+ } else {
|
|
| 518 |
+ fmt.Fprintf(cli.out, "%s\n", name) |
|
| 519 |
+ } |
|
| 520 |
+ } |
|
| 521 |
+ return encounteredError |
|
| 522 |
+} |
|
| 523 |
+ |
|
| 524 |
+func (cli *DockerCli) forwardAllSignals(cid string) chan os.Signal {
|
|
| 525 |
+ sigc := make(chan os.Signal, 1) |
|
| 526 |
+ signal.CatchAll(sigc) |
|
| 527 |
+ go func() {
|
|
| 528 |
+ for s := range sigc {
|
|
| 529 |
+ if s == syscall.SIGCHLD {
|
|
| 530 |
+ continue |
|
| 531 |
+ } |
|
| 532 |
+ var sig string |
|
| 533 |
+ for sigStr, sigN := range signal.SignalMap {
|
|
| 534 |
+ if sigN == s {
|
|
| 535 |
+ sig = sigStr |
|
| 536 |
+ break |
|
| 537 |
+ } |
|
| 538 |
+ } |
|
| 539 |
+ if sig == "" {
|
|
| 540 |
+ utils.Errorf("Unsupported signal: %d. Discarding.", s)
|
|
| 541 |
+ } |
|
| 542 |
+ if _, _, err := readBody(cli.call("POST", fmt.Sprintf("/containers/%s/kill?signal=%s", cid, sig), nil, false)); err != nil {
|
|
| 543 |
+ utils.Debugf("Error sending signal: %s", err)
|
|
| 544 |
+ } |
|
| 545 |
+ } |
|
| 546 |
+ }() |
|
| 547 |
+ return sigc |
|
| 548 |
+} |
|
| 549 |
+ |
|
| 550 |
+func (cli *DockerCli) CmdStart(args ...string) error {
|
|
| 551 |
+ cmd := cli.Subcmd("start", "CONTAINER [CONTAINER...]", "Restart a stopped container")
|
|
| 552 |
+ attach := cmd.Bool([]string{"a", "-attach"}, false, "Attach container's stdout/stderr and forward all signals to the process")
|
|
| 553 |
+ openStdin := cmd.Bool([]string{"i", "-interactive"}, false, "Attach container's stdin")
|
|
| 554 |
+ if err := cmd.Parse(args); err != nil {
|
|
| 555 |
+ return nil |
|
| 556 |
+ } |
|
| 557 |
+ if cmd.NArg() < 1 {
|
|
| 558 |
+ cmd.Usage() |
|
| 559 |
+ return nil |
|
| 560 |
+ } |
|
| 561 |
+ |
|
| 562 |
+ var cErr chan error |
|
| 563 |
+ var tty bool |
|
| 564 |
+ if *attach || *openStdin {
|
|
| 565 |
+ if cmd.NArg() > 1 {
|
|
| 566 |
+ return fmt.Errorf("You cannot start and attach multiple containers at once.")
|
|
| 567 |
+ } |
|
| 568 |
+ |
|
| 569 |
+ body, _, err := readBody(cli.call("GET", "/containers/"+cmd.Arg(0)+"/json", nil, false))
|
|
| 570 |
+ if err != nil {
|
|
| 571 |
+ return err |
|
| 572 |
+ } |
|
| 573 |
+ |
|
| 574 |
+ container := &api.Container{}
|
|
| 575 |
+ err = json.Unmarshal(body, container) |
|
| 576 |
+ if err != nil {
|
|
| 577 |
+ return err |
|
| 578 |
+ } |
|
| 579 |
+ |
|
| 580 |
+ tty = container.Config.Tty |
|
| 581 |
+ |
|
| 582 |
+ if !container.Config.Tty {
|
|
| 583 |
+ sigc := cli.forwardAllSignals(cmd.Arg(0)) |
|
| 584 |
+ defer signal.StopCatch(sigc) |
|
| 585 |
+ } |
|
| 586 |
+ |
|
| 587 |
+ var in io.ReadCloser |
|
| 588 |
+ |
|
| 589 |
+ v := url.Values{}
|
|
| 590 |
+ v.Set("stream", "1")
|
|
| 591 |
+ if *openStdin && container.Config.OpenStdin {
|
|
| 592 |
+ v.Set("stdin", "1")
|
|
| 593 |
+ in = cli.in |
|
| 594 |
+ } |
|
| 595 |
+ v.Set("stdout", "1")
|
|
| 596 |
+ v.Set("stderr", "1")
|
|
| 597 |
+ |
|
| 598 |
+ cErr = utils.Go(func() error {
|
|
| 599 |
+ return cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), container.Config.Tty, in, cli.out, cli.err, nil)
|
|
| 600 |
+ }) |
|
| 601 |
+ } |
|
| 602 |
+ |
|
| 603 |
+ var encounteredError error |
|
| 604 |
+ for _, name := range cmd.Args() {
|
|
| 605 |
+ _, _, err := readBody(cli.call("POST", "/containers/"+name+"/start", nil, false))
|
|
| 606 |
+ if err != nil {
|
|
| 607 |
+ if !*attach || !*openStdin {
|
|
| 608 |
+ fmt.Fprintf(cli.err, "%s\n", err) |
|
| 609 |
+ encounteredError = fmt.Errorf("Error: failed to start one or more containers")
|
|
| 610 |
+ } |
|
| 611 |
+ } else {
|
|
| 612 |
+ if !*attach || !*openStdin {
|
|
| 613 |
+ fmt.Fprintf(cli.out, "%s\n", name) |
|
| 614 |
+ } |
|
| 615 |
+ } |
|
| 616 |
+ } |
|
| 617 |
+ if encounteredError != nil {
|
|
| 618 |
+ if *openStdin || *attach {
|
|
| 619 |
+ cli.in.Close() |
|
| 620 |
+ <-cErr |
|
| 621 |
+ } |
|
| 622 |
+ return encounteredError |
|
| 623 |
+ } |
|
| 624 |
+ |
|
| 625 |
+ if *openStdin || *attach {
|
|
| 626 |
+ if tty && cli.isTerminal {
|
|
| 627 |
+ if err := cli.monitorTtySize(cmd.Arg(0)); err != nil {
|
|
| 628 |
+ utils.Errorf("Error monitoring TTY size: %s\n", err)
|
|
| 629 |
+ } |
|
| 630 |
+ } |
|
| 631 |
+ return <-cErr |
|
| 632 |
+ } |
|
| 633 |
+ return nil |
|
| 634 |
+} |
|
| 635 |
+ |
|
| 636 |
+func (cli *DockerCli) CmdInspect(args ...string) error {
|
|
| 637 |
+ cmd := cli.Subcmd("inspect", "CONTAINER|IMAGE [CONTAINER|IMAGE...]", "Return low-level information on a container/image")
|
|
| 638 |
+ tmplStr := cmd.String([]string{"f", "#format", "-format"}, "", "Format the output using the given go template.")
|
|
| 639 |
+ if err := cmd.Parse(args); err != nil {
|
|
| 640 |
+ return nil |
|
| 641 |
+ } |
|
| 642 |
+ if cmd.NArg() < 1 {
|
|
| 643 |
+ cmd.Usage() |
|
| 644 |
+ return nil |
|
| 645 |
+ } |
|
| 646 |
+ |
|
| 647 |
+ var tmpl *template.Template |
|
| 648 |
+ if *tmplStr != "" {
|
|
| 649 |
+ var err error |
|
| 650 |
+ if tmpl, err = template.New("").Funcs(funcMap).Parse(*tmplStr); err != nil {
|
|
| 651 |
+ fmt.Fprintf(cli.err, "Template parsing error: %v\n", err) |
|
| 652 |
+ return &utils.StatusError{StatusCode: 64,
|
|
| 653 |
+ Status: "Template parsing error: " + err.Error()} |
|
| 654 |
+ } |
|
| 655 |
+ } |
|
| 656 |
+ |
|
| 657 |
+ indented := new(bytes.Buffer) |
|
| 658 |
+ indented.WriteByte('[')
|
|
| 659 |
+ status := 0 |
|
| 660 |
+ |
|
| 661 |
+ for _, name := range cmd.Args() {
|
|
| 662 |
+ obj, _, err := readBody(cli.call("GET", "/containers/"+name+"/json", nil, false))
|
|
| 663 |
+ if err != nil {
|
|
| 664 |
+ obj, _, err = readBody(cli.call("GET", "/images/"+name+"/json", nil, false))
|
|
| 665 |
+ if err != nil {
|
|
| 666 |
+ if strings.Contains(err.Error(), "No such") {
|
|
| 667 |
+ fmt.Fprintf(cli.err, "Error: No such image or container: %s\n", name) |
|
| 668 |
+ } else {
|
|
| 669 |
+ fmt.Fprintf(cli.err, "%s", err) |
|
| 670 |
+ } |
|
| 671 |
+ status = 1 |
|
| 672 |
+ continue |
|
| 673 |
+ } |
|
| 674 |
+ } |
|
| 675 |
+ |
|
| 676 |
+ if tmpl == nil {
|
|
| 677 |
+ if err = json.Indent(indented, obj, "", " "); err != nil {
|
|
| 678 |
+ fmt.Fprintf(cli.err, "%s\n", err) |
|
| 679 |
+ status = 1 |
|
| 680 |
+ continue |
|
| 681 |
+ } |
|
| 682 |
+ } else {
|
|
| 683 |
+ // Has template, will render |
|
| 684 |
+ var value interface{}
|
|
| 685 |
+ if err := json.Unmarshal(obj, &value); err != nil {
|
|
| 686 |
+ fmt.Fprintf(cli.err, "%s\n", err) |
|
| 687 |
+ status = 1 |
|
| 688 |
+ continue |
|
| 689 |
+ } |
|
| 690 |
+ if err := tmpl.Execute(cli.out, value); err != nil {
|
|
| 691 |
+ return err |
|
| 692 |
+ } |
|
| 693 |
+ cli.out.Write([]byte{'\n'})
|
|
| 694 |
+ } |
|
| 695 |
+ indented.WriteString(",")
|
|
| 696 |
+ } |
|
| 697 |
+ |
|
| 698 |
+ if indented.Len() > 1 {
|
|
| 699 |
+ // Remove trailing ',' |
|
| 700 |
+ indented.Truncate(indented.Len() - 1) |
|
| 701 |
+ } |
|
| 702 |
+ indented.WriteByte(']')
|
|
| 703 |
+ |
|
| 704 |
+ if tmpl == nil {
|
|
| 705 |
+ if _, err := io.Copy(cli.out, indented); err != nil {
|
|
| 706 |
+ return err |
|
| 707 |
+ } |
|
| 708 |
+ } |
|
| 709 |
+ |
|
| 710 |
+ if status != 0 {
|
|
| 711 |
+ return &utils.StatusError{StatusCode: status}
|
|
| 712 |
+ } |
|
| 713 |
+ return nil |
|
| 714 |
+} |
|
| 715 |
+ |
|
| 716 |
+func (cli *DockerCli) CmdTop(args ...string) error {
|
|
| 717 |
+ cmd := cli.Subcmd("top", "CONTAINER [ps OPTIONS]", "Lookup the running processes of a container")
|
|
| 718 |
+ if err := cmd.Parse(args); err != nil {
|
|
| 719 |
+ return nil |
|
| 720 |
+ } |
|
| 721 |
+ if cmd.NArg() == 0 {
|
|
| 722 |
+ cmd.Usage() |
|
| 723 |
+ return nil |
|
| 724 |
+ } |
|
| 725 |
+ val := url.Values{}
|
|
| 726 |
+ if cmd.NArg() > 1 {
|
|
| 727 |
+ val.Set("ps_args", strings.Join(cmd.Args()[1:], " "))
|
|
| 728 |
+ } |
|
| 729 |
+ |
|
| 730 |
+ stream, _, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/top?"+val.Encode(), nil, false)
|
|
| 731 |
+ if err != nil {
|
|
| 732 |
+ return err |
|
| 733 |
+ } |
|
| 734 |
+ var procs engine.Env |
|
| 735 |
+ if err := procs.Decode(stream); err != nil {
|
|
| 736 |
+ return err |
|
| 737 |
+ } |
|
| 738 |
+ w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0) |
|
| 739 |
+ fmt.Fprintln(w, strings.Join(procs.GetList("Titles"), "\t"))
|
|
| 740 |
+ processes := [][]string{}
|
|
| 741 |
+ if err := procs.GetJson("Processes", &processes); err != nil {
|
|
| 742 |
+ return err |
|
| 743 |
+ } |
|
| 744 |
+ for _, proc := range processes {
|
|
| 745 |
+ fmt.Fprintln(w, strings.Join(proc, "\t")) |
|
| 746 |
+ } |
|
| 747 |
+ w.Flush() |
|
| 748 |
+ return nil |
|
| 749 |
+} |
|
| 750 |
+ |
|
| 751 |
+func (cli *DockerCli) CmdPort(args ...string) error {
|
|
| 752 |
+ cmd := cli.Subcmd("port", "CONTAINER PRIVATE_PORT", "Lookup the public-facing port which is NAT-ed to PRIVATE_PORT")
|
|
| 753 |
+ if err := cmd.Parse(args); err != nil {
|
|
| 754 |
+ return nil |
|
| 755 |
+ } |
|
| 756 |
+ if cmd.NArg() != 2 {
|
|
| 757 |
+ cmd.Usage() |
|
| 758 |
+ return nil |
|
| 759 |
+ } |
|
| 760 |
+ |
|
| 761 |
+ var ( |
|
| 762 |
+ port = cmd.Arg(1) |
|
| 763 |
+ proto = "tcp" |
|
| 764 |
+ parts = strings.SplitN(port, "/", 2) |
|
| 765 |
+ container api.Container |
|
| 766 |
+ ) |
|
| 767 |
+ |
|
| 768 |
+ if len(parts) == 2 && len(parts[1]) != 0 {
|
|
| 769 |
+ port = parts[0] |
|
| 770 |
+ proto = parts[1] |
|
| 771 |
+ } |
|
| 772 |
+ body, _, err := readBody(cli.call("GET", "/containers/"+cmd.Arg(0)+"/json", nil, false))
|
|
| 773 |
+ if err != nil {
|
|
| 774 |
+ return err |
|
| 775 |
+ } |
|
| 776 |
+ |
|
| 777 |
+ err = json.Unmarshal(body, &container) |
|
| 778 |
+ if err != nil {
|
|
| 779 |
+ return err |
|
| 780 |
+ } |
|
| 781 |
+ |
|
| 782 |
+ if frontends, exists := container.NetworkSettings.Ports[nat.Port(port+"/"+proto)]; exists && frontends != nil {
|
|
| 783 |
+ for _, frontend := range frontends {
|
|
| 784 |
+ fmt.Fprintf(cli.out, "%s:%s\n", frontend.HostIp, frontend.HostPort) |
|
| 785 |
+ } |
|
| 786 |
+ } else {
|
|
| 787 |
+ return fmt.Errorf("Error: No public port '%s' published for %s", cmd.Arg(1), cmd.Arg(0))
|
|
| 788 |
+ } |
|
| 789 |
+ return nil |
|
| 790 |
+} |
|
| 791 |
+ |
|
| 792 |
+// 'docker rmi IMAGE' removes all images with the name IMAGE |
|
| 793 |
+func (cli *DockerCli) CmdRmi(args ...string) error {
|
|
| 794 |
+ var ( |
|
| 795 |
+ cmd = cli.Subcmd("rmi", "IMAGE [IMAGE...]", "Remove one or more images")
|
|
| 796 |
+ force = cmd.Bool([]string{"f", "-force"}, false, "Force")
|
|
| 797 |
+ noprune = cmd.Bool([]string{"-no-prune"}, false, "Do not delete untagged parents")
|
|
| 798 |
+ ) |
|
| 799 |
+ if err := cmd.Parse(args); err != nil {
|
|
| 800 |
+ return nil |
|
| 801 |
+ } |
|
| 802 |
+ if cmd.NArg() < 1 {
|
|
| 803 |
+ cmd.Usage() |
|
| 804 |
+ return nil |
|
| 805 |
+ } |
|
| 806 |
+ |
|
| 807 |
+ v := url.Values{}
|
|
| 808 |
+ if *force {
|
|
| 809 |
+ v.Set("force", "1")
|
|
| 810 |
+ } |
|
| 811 |
+ if *noprune {
|
|
| 812 |
+ v.Set("noprune", "1")
|
|
| 813 |
+ } |
|
| 814 |
+ |
|
| 815 |
+ var encounteredError error |
|
| 816 |
+ for _, name := range cmd.Args() {
|
|
| 817 |
+ body, _, err := readBody(cli.call("DELETE", "/images/"+name+"?"+v.Encode(), nil, false))
|
|
| 818 |
+ if err != nil {
|
|
| 819 |
+ fmt.Fprintf(cli.err, "%s\n", err) |
|
| 820 |
+ encounteredError = fmt.Errorf("Error: failed to remove one or more images")
|
|
| 821 |
+ } else {
|
|
| 822 |
+ outs := engine.NewTable("Created", 0)
|
|
| 823 |
+ if _, err := outs.ReadListFrom(body); err != nil {
|
|
| 824 |
+ fmt.Fprintf(cli.err, "%s\n", err) |
|
| 825 |
+ encounteredError = fmt.Errorf("Error: failed to remove one or more images")
|
|
| 826 |
+ continue |
|
| 827 |
+ } |
|
| 828 |
+ for _, out := range outs.Data {
|
|
| 829 |
+ if out.Get("Deleted") != "" {
|
|
| 830 |
+ fmt.Fprintf(cli.out, "Deleted: %s\n", out.Get("Deleted"))
|
|
| 831 |
+ } else {
|
|
| 832 |
+ fmt.Fprintf(cli.out, "Untagged: %s\n", out.Get("Untagged"))
|
|
| 833 |
+ } |
|
| 834 |
+ } |
|
| 835 |
+ } |
|
| 836 |
+ } |
|
| 837 |
+ return encounteredError |
|
| 838 |
+} |
|
| 839 |
+ |
|
| 840 |
+func (cli *DockerCli) CmdHistory(args ...string) error {
|
|
| 841 |
+ cmd := cli.Subcmd("history", "[OPTIONS] IMAGE", "Show the history of an image")
|
|
| 842 |
+ quiet := cmd.Bool([]string{"q", "-quiet"}, false, "Only show numeric IDs")
|
|
| 843 |
+ noTrunc := cmd.Bool([]string{"#notrunc", "-no-trunc"}, false, "Don't truncate output")
|
|
| 844 |
+ |
|
| 845 |
+ if err := cmd.Parse(args); err != nil {
|
|
| 846 |
+ return nil |
|
| 847 |
+ } |
|
| 848 |
+ if cmd.NArg() != 1 {
|
|
| 849 |
+ cmd.Usage() |
|
| 850 |
+ return nil |
|
| 851 |
+ } |
|
| 852 |
+ |
|
| 853 |
+ body, _, err := readBody(cli.call("GET", "/images/"+cmd.Arg(0)+"/history", nil, false))
|
|
| 854 |
+ if err != nil {
|
|
| 855 |
+ return err |
|
| 856 |
+ } |
|
| 857 |
+ |
|
| 858 |
+ outs := engine.NewTable("Created", 0)
|
|
| 859 |
+ if _, err := outs.ReadListFrom(body); err != nil {
|
|
| 860 |
+ return err |
|
| 861 |
+ } |
|
| 862 |
+ |
|
| 863 |
+ w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0) |
|
| 864 |
+ if !*quiet {
|
|
| 865 |
+ fmt.Fprintln(w, "IMAGE\tCREATED\tCREATED BY\tSIZE") |
|
| 866 |
+ } |
|
| 867 |
+ |
|
| 868 |
+ for _, out := range outs.Data {
|
|
| 869 |
+ outID := out.Get("Id")
|
|
| 870 |
+ if !*quiet {
|
|
| 871 |
+ if *noTrunc {
|
|
| 872 |
+ fmt.Fprintf(w, "%s\t", outID) |
|
| 873 |
+ } else {
|
|
| 874 |
+ fmt.Fprintf(w, "%s\t", utils.TruncateID(outID)) |
|
| 875 |
+ } |
|
| 876 |
+ |
|
| 877 |
+ fmt.Fprintf(w, "%s ago\t", utils.HumanDuration(time.Now().UTC().Sub(time.Unix(out.GetInt64("Created"), 0))))
|
|
| 878 |
+ |
|
| 879 |
+ if *noTrunc {
|
|
| 880 |
+ fmt.Fprintf(w, "%s\t", out.Get("CreatedBy"))
|
|
| 881 |
+ } else {
|
|
| 882 |
+ fmt.Fprintf(w, "%s\t", utils.Trunc(out.Get("CreatedBy"), 45))
|
|
| 883 |
+ } |
|
| 884 |
+ fmt.Fprintf(w, "%s\n", utils.HumanSize(out.GetInt64("Size")))
|
|
| 885 |
+ } else {
|
|
| 886 |
+ if *noTrunc {
|
|
| 887 |
+ fmt.Fprintln(w, outID) |
|
| 888 |
+ } else {
|
|
| 889 |
+ fmt.Fprintln(w, utils.TruncateID(outID)) |
|
| 890 |
+ } |
|
| 891 |
+ } |
|
| 892 |
+ } |
|
| 893 |
+ w.Flush() |
|
| 894 |
+ return nil |
|
| 895 |
+} |
|
| 896 |
+ |
|
| 897 |
+func (cli *DockerCli) CmdRm(args ...string) error {
|
|
| 898 |
+ cmd := cli.Subcmd("rm", "[OPTIONS] CONTAINER [CONTAINER...]", "Remove one or more containers")
|
|
| 899 |
+ v := cmd.Bool([]string{"v", "-volumes"}, false, "Remove the volumes associated to the container")
|
|
| 900 |
+ link := cmd.Bool([]string{"l", "#link", "-link"}, false, "Remove the specified link and not the underlying container")
|
|
| 901 |
+ force := cmd.Bool([]string{"f", "-force"}, false, "Force removal of running container")
|
|
| 902 |
+ |
|
| 903 |
+ if err := cmd.Parse(args); err != nil {
|
|
| 904 |
+ return nil |
|
| 905 |
+ } |
|
| 906 |
+ if cmd.NArg() < 1 {
|
|
| 907 |
+ cmd.Usage() |
|
| 908 |
+ return nil |
|
| 909 |
+ } |
|
| 910 |
+ val := url.Values{}
|
|
| 911 |
+ if *v {
|
|
| 912 |
+ val.Set("v", "1")
|
|
| 913 |
+ } |
|
| 914 |
+ if *link {
|
|
| 915 |
+ val.Set("link", "1")
|
|
| 916 |
+ } |
|
| 917 |
+ if *force {
|
|
| 918 |
+ val.Set("force", "1")
|
|
| 919 |
+ } |
|
| 920 |
+ |
|
| 921 |
+ var encounteredError error |
|
| 922 |
+ for _, name := range cmd.Args() {
|
|
| 923 |
+ _, _, err := readBody(cli.call("DELETE", "/containers/"+name+"?"+val.Encode(), nil, false))
|
|
| 924 |
+ if err != nil {
|
|
| 925 |
+ fmt.Fprintf(cli.err, "%s\n", err) |
|
| 926 |
+ encounteredError = fmt.Errorf("Error: failed to remove one or more containers")
|
|
| 927 |
+ } else {
|
|
| 928 |
+ fmt.Fprintf(cli.out, "%s\n", name) |
|
| 929 |
+ } |
|
| 930 |
+ } |
|
| 931 |
+ return encounteredError |
|
| 932 |
+} |
|
| 933 |
+ |
|
| 934 |
+// 'docker kill NAME' kills a running container |
|
| 935 |
+func (cli *DockerCli) CmdKill(args ...string) error {
|
|
| 936 |
+ cmd := cli.Subcmd("kill", "[OPTIONS] CONTAINER [CONTAINER...]", "Kill a running container (send SIGKILL, or specified signal)")
|
|
| 937 |
+ signal := cmd.String([]string{"s", "-signal"}, "KILL", "Signal to send to the container")
|
|
| 938 |
+ |
|
| 939 |
+ if err := cmd.Parse(args); err != nil {
|
|
| 940 |
+ return nil |
|
| 941 |
+ } |
|
| 942 |
+ if cmd.NArg() < 1 {
|
|
| 943 |
+ cmd.Usage() |
|
| 944 |
+ return nil |
|
| 945 |
+ } |
|
| 946 |
+ |
|
| 947 |
+ var encounteredError error |
|
| 948 |
+ for _, name := range cmd.Args() {
|
|
| 949 |
+ if _, _, err := readBody(cli.call("POST", fmt.Sprintf("/containers/%s/kill?signal=%s", name, *signal), nil, false)); err != nil {
|
|
| 950 |
+ fmt.Fprintf(cli.err, "%s\n", err) |
|
| 951 |
+ encounteredError = fmt.Errorf("Error: failed to kill one or more containers")
|
|
| 952 |
+ } else {
|
|
| 953 |
+ fmt.Fprintf(cli.out, "%s\n", name) |
|
| 954 |
+ } |
|
| 955 |
+ } |
|
| 956 |
+ return encounteredError |
|
| 957 |
+} |
|
| 958 |
+ |
|
| 959 |
+func (cli *DockerCli) CmdImport(args ...string) error {
|
|
| 960 |
+ cmd := cli.Subcmd("import", "URL|- [REPOSITORY[:TAG]]", "Create an empty filesystem image and import the contents of the tarball (.tar, .tar.gz, .tgz, .bzip, .tar.xz, .txz) into it, then optionally tag it.")
|
|
| 961 |
+ |
|
| 962 |
+ if err := cmd.Parse(args); err != nil {
|
|
| 963 |
+ return nil |
|
| 964 |
+ } |
|
| 965 |
+ if cmd.NArg() < 1 {
|
|
| 966 |
+ cmd.Usage() |
|
| 967 |
+ return nil |
|
| 968 |
+ } |
|
| 969 |
+ |
|
| 970 |
+ var src, repository, tag string |
|
| 971 |
+ |
|
| 972 |
+ if cmd.NArg() == 3 {
|
|
| 973 |
+ fmt.Fprintf(cli.err, "[DEPRECATED] The format 'URL|- [REPOSITORY [TAG]]' as been deprecated. Please use URL|- [REPOSITORY[:TAG]]\n") |
|
| 974 |
+ src, repository, tag = cmd.Arg(0), cmd.Arg(1), cmd.Arg(2) |
|
| 975 |
+ } else {
|
|
| 976 |
+ src = cmd.Arg(0) |
|
| 977 |
+ repository, tag = utils.ParseRepositoryTag(cmd.Arg(1)) |
|
| 978 |
+ } |
|
| 979 |
+ v := url.Values{}
|
|
| 980 |
+ |
|
| 981 |
+ if repository != "" {
|
|
| 982 |
+ //Check if the given image name can be resolved |
|
| 983 |
+ if _, _, err := registry.ResolveRepositoryName(repository); err != nil {
|
|
| 984 |
+ return err |
|
| 985 |
+ } |
|
| 986 |
+ } |
|
| 987 |
+ |
|
| 988 |
+ v.Set("repo", repository)
|
|
| 989 |
+ v.Set("tag", tag)
|
|
| 990 |
+ v.Set("fromSrc", src)
|
|
| 991 |
+ |
|
| 992 |
+ var in io.Reader |
|
| 993 |
+ |
|
| 994 |
+ if src == "-" {
|
|
| 995 |
+ in = cli.in |
|
| 996 |
+ } |
|
| 997 |
+ |
|
| 998 |
+ return cli.stream("POST", "/images/create?"+v.Encode(), in, cli.out, nil)
|
|
| 999 |
+} |
|
| 1000 |
+ |
|
| 1001 |
+func (cli *DockerCli) CmdPush(args ...string) error {
|
|
| 1002 |
+ cmd := cli.Subcmd("push", "NAME", "Push an image or a repository to the registry")
|
|
| 1003 |
+ if err := cmd.Parse(args); err != nil {
|
|
| 1004 |
+ return nil |
|
| 1005 |
+ } |
|
| 1006 |
+ name := cmd.Arg(0) |
|
| 1007 |
+ |
|
| 1008 |
+ if name == "" {
|
|
| 1009 |
+ cmd.Usage() |
|
| 1010 |
+ return nil |
|
| 1011 |
+ } |
|
| 1012 |
+ |
|
| 1013 |
+ cli.LoadConfigFile() |
|
| 1014 |
+ |
|
| 1015 |
+ // Resolve the Repository name from fqn to hostname + name |
|
| 1016 |
+ hostname, _, err := registry.ResolveRepositoryName(name) |
|
| 1017 |
+ if err != nil {
|
|
| 1018 |
+ return err |
|
| 1019 |
+ } |
|
| 1020 |
+ // Resolve the Auth config relevant for this server |
|
| 1021 |
+ authConfig := cli.configFile.ResolveAuthConfig(hostname) |
|
| 1022 |
+ // If we're not using a custom registry, we know the restrictions |
|
| 1023 |
+ // applied to repository names and can warn the user in advance. |
|
| 1024 |
+ // Custom repositories can have different rules, and we must also |
|
| 1025 |
+ // allow pushing by image ID. |
|
| 1026 |
+ if len(strings.SplitN(name, "/", 2)) == 1 {
|
|
| 1027 |
+ username := cli.configFile.Configs[registry.IndexServerAddress()].Username |
|
| 1028 |
+ if username == "" {
|
|
| 1029 |
+ username = "<user>" |
|
| 1030 |
+ } |
|
| 1031 |
+ return fmt.Errorf("You cannot push a \"root\" repository. Please rename your repository in <user>/<repo> (ex: %s/%s)", username, name)
|
|
| 1032 |
+ } |
|
| 1033 |
+ |
|
| 1034 |
+ v := url.Values{}
|
|
| 1035 |
+ push := func(authConfig registry.AuthConfig) error {
|
|
| 1036 |
+ buf, err := json.Marshal(authConfig) |
|
| 1037 |
+ if err != nil {
|
|
| 1038 |
+ return err |
|
| 1039 |
+ } |
|
| 1040 |
+ registryAuthHeader := []string{
|
|
| 1041 |
+ base64.URLEncoding.EncodeToString(buf), |
|
| 1042 |
+ } |
|
| 1043 |
+ |
|
| 1044 |
+ return cli.stream("POST", "/images/"+name+"/push?"+v.Encode(), nil, cli.out, map[string][]string{
|
|
| 1045 |
+ "X-Registry-Auth": registryAuthHeader, |
|
| 1046 |
+ }) |
|
| 1047 |
+ } |
|
| 1048 |
+ |
|
| 1049 |
+ if err := push(authConfig); err != nil {
|
|
| 1050 |
+ if strings.Contains(err.Error(), "Status 401") {
|
|
| 1051 |
+ fmt.Fprintln(cli.out, "\nPlease login prior to push:") |
|
| 1052 |
+ if err := cli.CmdLogin(hostname); err != nil {
|
|
| 1053 |
+ return err |
|
| 1054 |
+ } |
|
| 1055 |
+ authConfig := cli.configFile.ResolveAuthConfig(hostname) |
|
| 1056 |
+ return push(authConfig) |
|
| 1057 |
+ } |
|
| 1058 |
+ return err |
|
| 1059 |
+ } |
|
| 1060 |
+ return nil |
|
| 1061 |
+} |
|
| 1062 |
+ |
|
| 1063 |
+func (cli *DockerCli) CmdPull(args ...string) error {
|
|
| 1064 |
+ cmd := cli.Subcmd("pull", "NAME[:TAG]", "Pull an image or a repository from the registry")
|
|
| 1065 |
+ tag := cmd.String([]string{"#t", "#-tag"}, "", "Download tagged image in repository")
|
|
| 1066 |
+ if err := cmd.Parse(args); err != nil {
|
|
| 1067 |
+ return nil |
|
| 1068 |
+ } |
|
| 1069 |
+ |
|
| 1070 |
+ if cmd.NArg() != 1 {
|
|
| 1071 |
+ cmd.Usage() |
|
| 1072 |
+ return nil |
|
| 1073 |
+ } |
|
| 1074 |
+ |
|
| 1075 |
+ remote, parsedTag := utils.ParseRepositoryTag(cmd.Arg(0)) |
|
| 1076 |
+ if *tag == "" {
|
|
| 1077 |
+ *tag = parsedTag |
|
| 1078 |
+ } |
|
| 1079 |
+ |
|
| 1080 |
+ // Resolve the Repository name from fqn to hostname + name |
|
| 1081 |
+ hostname, _, err := registry.ResolveRepositoryName(remote) |
|
| 1082 |
+ if err != nil {
|
|
| 1083 |
+ return err |
|
| 1084 |
+ } |
|
| 1085 |
+ |
|
| 1086 |
+ cli.LoadConfigFile() |
|
| 1087 |
+ |
|
| 1088 |
+ // Resolve the Auth config relevant for this server |
|
| 1089 |
+ authConfig := cli.configFile.ResolveAuthConfig(hostname) |
|
| 1090 |
+ v := url.Values{}
|
|
| 1091 |
+ v.Set("fromImage", remote)
|
|
| 1092 |
+ v.Set("tag", *tag)
|
|
| 1093 |
+ |
|
| 1094 |
+ pull := func(authConfig registry.AuthConfig) error {
|
|
| 1095 |
+ buf, err := json.Marshal(authConfig) |
|
| 1096 |
+ if err != nil {
|
|
| 1097 |
+ return err |
|
| 1098 |
+ } |
|
| 1099 |
+ registryAuthHeader := []string{
|
|
| 1100 |
+ base64.URLEncoding.EncodeToString(buf), |
|
| 1101 |
+ } |
|
| 1102 |
+ |
|
| 1103 |
+ return cli.stream("POST", "/images/create?"+v.Encode(), nil, cli.out, map[string][]string{
|
|
| 1104 |
+ "X-Registry-Auth": registryAuthHeader, |
|
| 1105 |
+ }) |
|
| 1106 |
+ } |
|
| 1107 |
+ |
|
| 1108 |
+ if err := pull(authConfig); err != nil {
|
|
| 1109 |
+ if strings.Contains(err.Error(), "Status 401") {
|
|
| 1110 |
+ fmt.Fprintln(cli.out, "\nPlease login prior to pull:") |
|
| 1111 |
+ if err := cli.CmdLogin(hostname); err != nil {
|
|
| 1112 |
+ return err |
|
| 1113 |
+ } |
|
| 1114 |
+ authConfig := cli.configFile.ResolveAuthConfig(hostname) |
|
| 1115 |
+ return pull(authConfig) |
|
| 1116 |
+ } |
|
| 1117 |
+ return err |
|
| 1118 |
+ } |
|
| 1119 |
+ |
|
| 1120 |
+ return nil |
|
| 1121 |
+} |
|
| 1122 |
+ |
|
| 1123 |
+func (cli *DockerCli) CmdImages(args ...string) error {
|
|
| 1124 |
+ cmd := cli.Subcmd("images", "[OPTIONS] [NAME]", "List images")
|
|
| 1125 |
+ quiet := cmd.Bool([]string{"q", "-quiet"}, false, "Only show numeric IDs")
|
|
| 1126 |
+ all := cmd.Bool([]string{"a", "-all"}, false, "Show all images (by default filter out the intermediate images used to build)")
|
|
| 1127 |
+ noTrunc := cmd.Bool([]string{"#notrunc", "-no-trunc"}, false, "Don't truncate output")
|
|
| 1128 |
+ flViz := cmd.Bool([]string{"v", "#viz", "-viz"}, false, "Output graph in graphviz format")
|
|
| 1129 |
+ flTree := cmd.Bool([]string{"t", "#tree", "-tree"}, false, "Output graph in tree format")
|
|
| 1130 |
+ |
|
| 1131 |
+ if err := cmd.Parse(args); err != nil {
|
|
| 1132 |
+ return nil |
|
| 1133 |
+ } |
|
| 1134 |
+ if cmd.NArg() > 1 {
|
|
| 1135 |
+ cmd.Usage() |
|
| 1136 |
+ return nil |
|
| 1137 |
+ } |
|
| 1138 |
+ |
|
| 1139 |
+ filter := cmd.Arg(0) |
|
| 1140 |
+ |
|
| 1141 |
+ if *flViz || *flTree {
|
|
| 1142 |
+ body, _, err := readBody(cli.call("GET", "/images/json?all=1", nil, false))
|
|
| 1143 |
+ if err != nil {
|
|
| 1144 |
+ return err |
|
| 1145 |
+ } |
|
| 1146 |
+ |
|
| 1147 |
+ outs := engine.NewTable("Created", 0)
|
|
| 1148 |
+ if _, err := outs.ReadListFrom(body); err != nil {
|
|
| 1149 |
+ return err |
|
| 1150 |
+ } |
|
| 1151 |
+ |
|
| 1152 |
+ var ( |
|
| 1153 |
+ printNode func(cli *DockerCli, noTrunc bool, image *engine.Env, prefix string) |
|
| 1154 |
+ startImage *engine.Env |
|
| 1155 |
+ |
|
| 1156 |
+ roots = engine.NewTable("Created", outs.Len())
|
|
| 1157 |
+ byParent = make(map[string]*engine.Table) |
|
| 1158 |
+ ) |
|
| 1159 |
+ |
|
| 1160 |
+ for _, image := range outs.Data {
|
|
| 1161 |
+ if image.Get("ParentId") == "" {
|
|
| 1162 |
+ roots.Add(image) |
|
| 1163 |
+ } else {
|
|
| 1164 |
+ if children, exists := byParent[image.Get("ParentId")]; exists {
|
|
| 1165 |
+ children.Add(image) |
|
| 1166 |
+ } else {
|
|
| 1167 |
+ byParent[image.Get("ParentId")] = engine.NewTable("Created", 1)
|
|
| 1168 |
+ byParent[image.Get("ParentId")].Add(image)
|
|
| 1169 |
+ } |
|
| 1170 |
+ } |
|
| 1171 |
+ |
|
| 1172 |
+ if filter != "" {
|
|
| 1173 |
+ if filter == image.Get("Id") || filter == utils.TruncateID(image.Get("Id")) {
|
|
| 1174 |
+ startImage = image |
|
| 1175 |
+ } |
|
| 1176 |
+ |
|
| 1177 |
+ for _, repotag := range image.GetList("RepoTags") {
|
|
| 1178 |
+ if repotag == filter {
|
|
| 1179 |
+ startImage = image |
|
| 1180 |
+ } |
|
| 1181 |
+ } |
|
| 1182 |
+ } |
|
| 1183 |
+ } |
|
| 1184 |
+ |
|
| 1185 |
+ if *flViz {
|
|
| 1186 |
+ fmt.Fprintf(cli.out, "digraph docker {\n")
|
|
| 1187 |
+ printNode = (*DockerCli).printVizNode |
|
| 1188 |
+ } else {
|
|
| 1189 |
+ printNode = (*DockerCli).printTreeNode |
|
| 1190 |
+ } |
|
| 1191 |
+ |
|
| 1192 |
+ if startImage != nil {
|
|
| 1193 |
+ root := engine.NewTable("Created", 1)
|
|
| 1194 |
+ root.Add(startImage) |
|
| 1195 |
+ cli.WalkTree(*noTrunc, root, byParent, "", printNode) |
|
| 1196 |
+ } else if filter == "" {
|
|
| 1197 |
+ cli.WalkTree(*noTrunc, roots, byParent, "", printNode) |
|
| 1198 |
+ } |
|
| 1199 |
+ if *flViz {
|
|
| 1200 |
+ fmt.Fprintf(cli.out, " base [style=invisible]\n}\n") |
|
| 1201 |
+ } |
|
| 1202 |
+ } else {
|
|
| 1203 |
+ v := url.Values{}
|
|
| 1204 |
+ if cmd.NArg() == 1 {
|
|
| 1205 |
+ v.Set("filter", filter)
|
|
| 1206 |
+ } |
|
| 1207 |
+ if *all {
|
|
| 1208 |
+ v.Set("all", "1")
|
|
| 1209 |
+ } |
|
| 1210 |
+ |
|
| 1211 |
+ body, _, err := readBody(cli.call("GET", "/images/json?"+v.Encode(), nil, false))
|
|
| 1212 |
+ |
|
| 1213 |
+ if err != nil {
|
|
| 1214 |
+ return err |
|
| 1215 |
+ } |
|
| 1216 |
+ |
|
| 1217 |
+ outs := engine.NewTable("Created", 0)
|
|
| 1218 |
+ if _, err := outs.ReadListFrom(body); err != nil {
|
|
| 1219 |
+ return err |
|
| 1220 |
+ } |
|
| 1221 |
+ |
|
| 1222 |
+ w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0) |
|
| 1223 |
+ if !*quiet {
|
|
| 1224 |
+ fmt.Fprintln(w, "REPOSITORY\tTAG\tIMAGE ID\tCREATED\tVIRTUAL SIZE") |
|
| 1225 |
+ } |
|
| 1226 |
+ |
|
| 1227 |
+ for _, out := range outs.Data {
|
|
| 1228 |
+ for _, repotag := range out.GetList("RepoTags") {
|
|
| 1229 |
+ |
|
| 1230 |
+ repo, tag := utils.ParseRepositoryTag(repotag) |
|
| 1231 |
+ outID := out.Get("Id")
|
|
| 1232 |
+ if !*noTrunc {
|
|
| 1233 |
+ outID = utils.TruncateID(outID) |
|
| 1234 |
+ } |
|
| 1235 |
+ |
|
| 1236 |
+ if !*quiet {
|
|
| 1237 |
+ fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\n", repo, tag, outID, utils.HumanDuration(time.Now().UTC().Sub(time.Unix(out.GetInt64("Created"), 0))), utils.HumanSize(out.GetInt64("VirtualSize")))
|
|
| 1238 |
+ } else {
|
|
| 1239 |
+ fmt.Fprintln(w, outID) |
|
| 1240 |
+ } |
|
| 1241 |
+ } |
|
| 1242 |
+ } |
|
| 1243 |
+ |
|
| 1244 |
+ if !*quiet {
|
|
| 1245 |
+ w.Flush() |
|
| 1246 |
+ } |
|
| 1247 |
+ } |
|
| 1248 |
+ return nil |
|
| 1249 |
+} |
|
| 1250 |
+ |
|
| 1251 |
+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)) {
|
|
| 1252 |
+ length := images.Len() |
|
| 1253 |
+ if length > 1 {
|
|
| 1254 |
+ for index, image := range images.Data {
|
|
| 1255 |
+ if index+1 == length {
|
|
| 1256 |
+ printNode(cli, noTrunc, image, prefix+"└─") |
|
| 1257 |
+ if subimages, exists := byParent[image.Get("Id")]; exists {
|
|
| 1258 |
+ cli.WalkTree(noTrunc, subimages, byParent, prefix+" ", printNode) |
|
| 1259 |
+ } |
|
| 1260 |
+ } else {
|
|
| 1261 |
+ printNode(cli, noTrunc, image, prefix+"\u251C─") |
|
| 1262 |
+ if subimages, exists := byParent[image.Get("Id")]; exists {
|
|
| 1263 |
+ cli.WalkTree(noTrunc, subimages, byParent, prefix+"\u2502 ", printNode) |
|
| 1264 |
+ } |
|
| 1265 |
+ } |
|
| 1266 |
+ } |
|
| 1267 |
+ } else {
|
|
| 1268 |
+ for _, image := range images.Data {
|
|
| 1269 |
+ printNode(cli, noTrunc, image, prefix+"└─") |
|
| 1270 |
+ if subimages, exists := byParent[image.Get("Id")]; exists {
|
|
| 1271 |
+ cli.WalkTree(noTrunc, subimages, byParent, prefix+" ", printNode) |
|
| 1272 |
+ } |
|
| 1273 |
+ } |
|
| 1274 |
+ } |
|
| 1275 |
+} |
|
| 1276 |
+ |
|
| 1277 |
+func (cli *DockerCli) printVizNode(noTrunc bool, image *engine.Env, prefix string) {
|
|
| 1278 |
+ var ( |
|
| 1279 |
+ imageID string |
|
| 1280 |
+ parentID string |
|
| 1281 |
+ ) |
|
| 1282 |
+ if noTrunc {
|
|
| 1283 |
+ imageID = image.Get("Id")
|
|
| 1284 |
+ parentID = image.Get("ParentId")
|
|
| 1285 |
+ } else {
|
|
| 1286 |
+ imageID = utils.TruncateID(image.Get("Id"))
|
|
| 1287 |
+ parentID = utils.TruncateID(image.Get("ParentId"))
|
|
| 1288 |
+ } |
|
| 1289 |
+ if parentID == "" {
|
|
| 1290 |
+ fmt.Fprintf(cli.out, " base -> \"%s\" [style=invis]\n", imageID) |
|
| 1291 |
+ } else {
|
|
| 1292 |
+ fmt.Fprintf(cli.out, " \"%s\" -> \"%s\"\n", parentID, imageID) |
|
| 1293 |
+ } |
|
| 1294 |
+ if image.GetList("RepoTags")[0] != "<none>:<none>" {
|
|
| 1295 |
+ fmt.Fprintf(cli.out, " \"%s\" [label=\"%s\\n%s\",shape=box,fillcolor=\"paleturquoise\",style=\"filled,rounded\"];\n", |
|
| 1296 |
+ imageID, imageID, strings.Join(image.GetList("RepoTags"), "\\n"))
|
|
| 1297 |
+ } |
|
| 1298 |
+} |
|
| 1299 |
+ |
|
| 1300 |
+func (cli *DockerCli) printTreeNode(noTrunc bool, image *engine.Env, prefix string) {
|
|
| 1301 |
+ var imageID string |
|
| 1302 |
+ if noTrunc {
|
|
| 1303 |
+ imageID = image.Get("Id")
|
|
| 1304 |
+ } else {
|
|
| 1305 |
+ imageID = utils.TruncateID(image.Get("Id"))
|
|
| 1306 |
+ } |
|
| 1307 |
+ |
|
| 1308 |
+ fmt.Fprintf(cli.out, "%s%s Virtual Size: %s", prefix, imageID, utils.HumanSize(image.GetInt64("VirtualSize")))
|
|
| 1309 |
+ if image.GetList("RepoTags")[0] != "<none>:<none>" {
|
|
| 1310 |
+ fmt.Fprintf(cli.out, " Tags: %s\n", strings.Join(image.GetList("RepoTags"), ", "))
|
|
| 1311 |
+ } else {
|
|
| 1312 |
+ fmt.Fprint(cli.out, "\n") |
|
| 1313 |
+ } |
|
| 1314 |
+} |
|
| 1315 |
+ |
|
| 1316 |
+func (cli *DockerCli) CmdPs(args ...string) error {
|
|
| 1317 |
+ cmd := cli.Subcmd("ps", "[OPTIONS]", "List containers")
|
|
| 1318 |
+ quiet := cmd.Bool([]string{"q", "-quiet"}, false, "Only display numeric IDs")
|
|
| 1319 |
+ size := cmd.Bool([]string{"s", "-size"}, false, "Display sizes")
|
|
| 1320 |
+ all := cmd.Bool([]string{"a", "-all"}, false, "Show all containers. Only running containers are shown by default.")
|
|
| 1321 |
+ noTrunc := cmd.Bool([]string{"#notrunc", "-no-trunc"}, false, "Don't truncate output")
|
|
| 1322 |
+ nLatest := cmd.Bool([]string{"l", "-latest"}, false, "Show only the latest created container, include non-running ones.")
|
|
| 1323 |
+ since := cmd.String([]string{"#sinceId", "#-since-id", "-since"}, "", "Show only containers created since Id or Name, include non-running ones.")
|
|
| 1324 |
+ before := cmd.String([]string{"#beforeId", "#-before-id", "-before"}, "", "Show only container created before Id or Name, include non-running ones.")
|
|
| 1325 |
+ last := cmd.Int([]string{"n"}, -1, "Show n last created containers, include non-running ones.")
|
|
| 1326 |
+ |
|
| 1327 |
+ if err := cmd.Parse(args); err != nil {
|
|
| 1328 |
+ return nil |
|
| 1329 |
+ } |
|
| 1330 |
+ v := url.Values{}
|
|
| 1331 |
+ if *last == -1 && *nLatest {
|
|
| 1332 |
+ *last = 1 |
|
| 1333 |
+ } |
|
| 1334 |
+ if *all {
|
|
| 1335 |
+ v.Set("all", "1")
|
|
| 1336 |
+ } |
|
| 1337 |
+ if *last != -1 {
|
|
| 1338 |
+ v.Set("limit", strconv.Itoa(*last))
|
|
| 1339 |
+ } |
|
| 1340 |
+ if *since != "" {
|
|
| 1341 |
+ v.Set("since", *since)
|
|
| 1342 |
+ } |
|
| 1343 |
+ if *before != "" {
|
|
| 1344 |
+ v.Set("before", *before)
|
|
| 1345 |
+ } |
|
| 1346 |
+ if *size {
|
|
| 1347 |
+ v.Set("size", "1")
|
|
| 1348 |
+ } |
|
| 1349 |
+ |
|
| 1350 |
+ body, _, err := readBody(cli.call("GET", "/containers/json?"+v.Encode(), nil, false))
|
|
| 1351 |
+ if err != nil {
|
|
| 1352 |
+ return err |
|
| 1353 |
+ } |
|
| 1354 |
+ |
|
| 1355 |
+ outs := engine.NewTable("Created", 0)
|
|
| 1356 |
+ if _, err := outs.ReadListFrom(body); err != nil {
|
|
| 1357 |
+ return err |
|
| 1358 |
+ } |
|
| 1359 |
+ w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0) |
|
| 1360 |
+ if !*quiet {
|
|
| 1361 |
+ fmt.Fprint(w, "CONTAINER ID\tIMAGE\tCOMMAND\tCREATED\tSTATUS\tPORTS\tNAMES") |
|
| 1362 |
+ if *size {
|
|
| 1363 |
+ fmt.Fprintln(w, "\tSIZE") |
|
| 1364 |
+ } else {
|
|
| 1365 |
+ fmt.Fprint(w, "\n") |
|
| 1366 |
+ } |
|
| 1367 |
+ } |
|
| 1368 |
+ |
|
| 1369 |
+ for _, out := range outs.Data {
|
|
| 1370 |
+ var ( |
|
| 1371 |
+ outID = out.Get("Id")
|
|
| 1372 |
+ outNames = out.GetList("Names")
|
|
| 1373 |
+ ) |
|
| 1374 |
+ |
|
| 1375 |
+ if !*noTrunc {
|
|
| 1376 |
+ outID = utils.TruncateID(outID) |
|
| 1377 |
+ } |
|
| 1378 |
+ |
|
| 1379 |
+ // Remove the leading / from the names |
|
| 1380 |
+ for i := 0; i < len(outNames); i++ {
|
|
| 1381 |
+ outNames[i] = outNames[i][1:] |
|
| 1382 |
+ } |
|
| 1383 |
+ |
|
| 1384 |
+ if !*quiet {
|
|
| 1385 |
+ var ( |
|
| 1386 |
+ outCommand = out.Get("Command")
|
|
| 1387 |
+ ports = engine.NewTable("", 0)
|
|
| 1388 |
+ ) |
|
| 1389 |
+ if !*noTrunc {
|
|
| 1390 |
+ outCommand = utils.Trunc(outCommand, 20) |
|
| 1391 |
+ } |
|
| 1392 |
+ ports.ReadListFrom([]byte(out.Get("Ports")))
|
|
| 1393 |
+ fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\t%s\t%s\t", outID, out.Get("Image"), outCommand, utils.HumanDuration(time.Now().UTC().Sub(time.Unix(out.GetInt64("Created"), 0))), out.Get("Status"), api.DisplayablePorts(ports), strings.Join(outNames, ","))
|
|
| 1394 |
+ if *size {
|
|
| 1395 |
+ if out.GetInt("SizeRootFs") > 0 {
|
|
| 1396 |
+ fmt.Fprintf(w, "%s (virtual %s)\n", utils.HumanSize(out.GetInt64("SizeRw")), utils.HumanSize(out.GetInt64("SizeRootFs")))
|
|
| 1397 |
+ } else {
|
|
| 1398 |
+ fmt.Fprintf(w, "%s\n", utils.HumanSize(out.GetInt64("SizeRw")))
|
|
| 1399 |
+ } |
|
| 1400 |
+ } else {
|
|
| 1401 |
+ fmt.Fprint(w, "\n") |
|
| 1402 |
+ } |
|
| 1403 |
+ } else {
|
|
| 1404 |
+ fmt.Fprintln(w, outID) |
|
| 1405 |
+ } |
|
| 1406 |
+ } |
|
| 1407 |
+ |
|
| 1408 |
+ if !*quiet {
|
|
| 1409 |
+ w.Flush() |
|
| 1410 |
+ } |
|
| 1411 |
+ return nil |
|
| 1412 |
+} |
|
| 1413 |
+ |
|
| 1414 |
+func (cli *DockerCli) CmdCommit(args ...string) error {
|
|
| 1415 |
+ cmd := cli.Subcmd("commit", "[OPTIONS] CONTAINER [REPOSITORY[:TAG]]", "Create a new image from a container's changes")
|
|
| 1416 |
+ flComment := cmd.String([]string{"m", "-message"}, "", "Commit message")
|
|
| 1417 |
+ flAuthor := cmd.String([]string{"a", "#author", "-author"}, "", "Author (eg. \"John Hannibal Smith <hannibal@a-team.com>\"")
|
|
| 1418 |
+ flConfig := cmd.String([]string{"#run", "-run"}, "", "Config automatically applied when the image is run. "+`(ex: --run='{"Cmd": ["cat", "/world"], "PortSpecs": ["22"]}')`)
|
|
| 1419 |
+ if err := cmd.Parse(args); err != nil {
|
|
| 1420 |
+ return nil |
|
| 1421 |
+ } |
|
| 1422 |
+ |
|
| 1423 |
+ var name, repository, tag string |
|
| 1424 |
+ |
|
| 1425 |
+ if cmd.NArg() == 3 {
|
|
| 1426 |
+ fmt.Fprintf(cli.err, "[DEPRECATED] The format 'CONTAINER [REPOSITORY [TAG]]' as been deprecated. Please use CONTAINER [REPOSITORY[:TAG]]\n") |
|
| 1427 |
+ name, repository, tag = cmd.Arg(0), cmd.Arg(1), cmd.Arg(2) |
|
| 1428 |
+ } else {
|
|
| 1429 |
+ name = cmd.Arg(0) |
|
| 1430 |
+ repository, tag = utils.ParseRepositoryTag(cmd.Arg(1)) |
|
| 1431 |
+ } |
|
| 1432 |
+ |
|
| 1433 |
+ if name == "" {
|
|
| 1434 |
+ cmd.Usage() |
|
| 1435 |
+ return nil |
|
| 1436 |
+ } |
|
| 1437 |
+ |
|
| 1438 |
+ //Check if the given image name can be resolved |
|
| 1439 |
+ if repository != "" {
|
|
| 1440 |
+ if _, _, err := registry.ResolveRepositoryName(repository); err != nil {
|
|
| 1441 |
+ return err |
|
| 1442 |
+ } |
|
| 1443 |
+ } |
|
| 1444 |
+ |
|
| 1445 |
+ v := url.Values{}
|
|
| 1446 |
+ v.Set("container", name)
|
|
| 1447 |
+ v.Set("repo", repository)
|
|
| 1448 |
+ v.Set("tag", tag)
|
|
| 1449 |
+ v.Set("comment", *flComment)
|
|
| 1450 |
+ v.Set("author", *flAuthor)
|
|
| 1451 |
+ var ( |
|
| 1452 |
+ config *runconfig.Config |
|
| 1453 |
+ env engine.Env |
|
| 1454 |
+ ) |
|
| 1455 |
+ if *flConfig != "" {
|
|
| 1456 |
+ config = &runconfig.Config{}
|
|
| 1457 |
+ if err := json.Unmarshal([]byte(*flConfig), config); err != nil {
|
|
| 1458 |
+ return err |
|
| 1459 |
+ } |
|
| 1460 |
+ } |
|
| 1461 |
+ stream, _, err := cli.call("POST", "/commit?"+v.Encode(), config, false)
|
|
| 1462 |
+ if err != nil {
|
|
| 1463 |
+ return err |
|
| 1464 |
+ } |
|
| 1465 |
+ if err := env.Decode(stream); err != nil {
|
|
| 1466 |
+ return err |
|
| 1467 |
+ } |
|
| 1468 |
+ |
|
| 1469 |
+ fmt.Fprintf(cli.out, "%s\n", env.Get("Id"))
|
|
| 1470 |
+ return nil |
|
| 1471 |
+} |
|
| 1472 |
+ |
|
| 1473 |
+func (cli *DockerCli) CmdEvents(args ...string) error {
|
|
| 1474 |
+ cmd := cli.Subcmd("events", "[OPTIONS]", "Get real time events from the server")
|
|
| 1475 |
+ since := cmd.String([]string{"#since", "-since"}, "", "Show previously created events and then stream.")
|
|
| 1476 |
+ if err := cmd.Parse(args); err != nil {
|
|
| 1477 |
+ return nil |
|
| 1478 |
+ } |
|
| 1479 |
+ |
|
| 1480 |
+ if cmd.NArg() != 0 {
|
|
| 1481 |
+ cmd.Usage() |
|
| 1482 |
+ return nil |
|
| 1483 |
+ } |
|
| 1484 |
+ |
|
| 1485 |
+ v := url.Values{}
|
|
| 1486 |
+ if *since != "" {
|
|
| 1487 |
+ loc := time.FixedZone(time.Now().Zone()) |
|
| 1488 |
+ format := "2006-01-02 15:04:05 -0700 MST" |
|
| 1489 |
+ if len(*since) < len(format) {
|
|
| 1490 |
+ format = format[:len(*since)] |
|
| 1491 |
+ } |
|
| 1492 |
+ |
|
| 1493 |
+ if t, err := time.ParseInLocation(format, *since, loc); err == nil {
|
|
| 1494 |
+ v.Set("since", strconv.FormatInt(t.Unix(), 10))
|
|
| 1495 |
+ } else {
|
|
| 1496 |
+ v.Set("since", *since)
|
|
| 1497 |
+ } |
|
| 1498 |
+ } |
|
| 1499 |
+ |
|
| 1500 |
+ if err := cli.stream("GET", "/events?"+v.Encode(), nil, cli.out, nil); err != nil {
|
|
| 1501 |
+ return err |
|
| 1502 |
+ } |
|
| 1503 |
+ return nil |
|
| 1504 |
+} |
|
| 1505 |
+ |
|
| 1506 |
+func (cli *DockerCli) CmdExport(args ...string) error {
|
|
| 1507 |
+ cmd := cli.Subcmd("export", "CONTAINER", "Export the contents of a filesystem as a tar archive to STDOUT")
|
|
| 1508 |
+ if err := cmd.Parse(args); err != nil {
|
|
| 1509 |
+ return nil |
|
| 1510 |
+ } |
|
| 1511 |
+ |
|
| 1512 |
+ if cmd.NArg() != 1 {
|
|
| 1513 |
+ cmd.Usage() |
|
| 1514 |
+ return nil |
|
| 1515 |
+ } |
|
| 1516 |
+ |
|
| 1517 |
+ if err := cli.stream("GET", "/containers/"+cmd.Arg(0)+"/export", nil, cli.out, nil); err != nil {
|
|
| 1518 |
+ return err |
|
| 1519 |
+ } |
|
| 1520 |
+ return nil |
|
| 1521 |
+} |
|
| 1522 |
+ |
|
| 1523 |
+func (cli *DockerCli) CmdDiff(args ...string) error {
|
|
| 1524 |
+ cmd := cli.Subcmd("diff", "CONTAINER", "Inspect changes on a container's filesystem")
|
|
| 1525 |
+ if err := cmd.Parse(args); err != nil {
|
|
| 1526 |
+ return nil |
|
| 1527 |
+ } |
|
| 1528 |
+ if cmd.NArg() != 1 {
|
|
| 1529 |
+ cmd.Usage() |
|
| 1530 |
+ return nil |
|
| 1531 |
+ } |
|
| 1532 |
+ |
|
| 1533 |
+ body, _, err := readBody(cli.call("GET", "/containers/"+cmd.Arg(0)+"/changes", nil, false))
|
|
| 1534 |
+ |
|
| 1535 |
+ if err != nil {
|
|
| 1536 |
+ return err |
|
| 1537 |
+ } |
|
| 1538 |
+ |
|
| 1539 |
+ outs := engine.NewTable("", 0)
|
|
| 1540 |
+ if _, err := outs.ReadListFrom(body); err != nil {
|
|
| 1541 |
+ return err |
|
| 1542 |
+ } |
|
| 1543 |
+ for _, change := range outs.Data {
|
|
| 1544 |
+ var kind string |
|
| 1545 |
+ switch change.GetInt("Kind") {
|
|
| 1546 |
+ case archive.ChangeModify: |
|
| 1547 |
+ kind = "C" |
|
| 1548 |
+ case archive.ChangeAdd: |
|
| 1549 |
+ kind = "A" |
|
| 1550 |
+ case archive.ChangeDelete: |
|
| 1551 |
+ kind = "D" |
|
| 1552 |
+ } |
|
| 1553 |
+ fmt.Fprintf(cli.out, "%s %s\n", kind, change.Get("Path"))
|
|
| 1554 |
+ } |
|
| 1555 |
+ return nil |
|
| 1556 |
+} |
|
| 1557 |
+ |
|
| 1558 |
+func (cli *DockerCli) CmdLogs(args ...string) error {
|
|
| 1559 |
+ cmd := cli.Subcmd("logs", "CONTAINER", "Fetch the logs of a container")
|
|
| 1560 |
+ follow := cmd.Bool([]string{"f", "-follow"}, false, "Follow log output")
|
|
| 1561 |
+ if err := cmd.Parse(args); err != nil {
|
|
| 1562 |
+ return nil |
|
| 1563 |
+ } |
|
| 1564 |
+ if cmd.NArg() != 1 {
|
|
| 1565 |
+ cmd.Usage() |
|
| 1566 |
+ return nil |
|
| 1567 |
+ } |
|
| 1568 |
+ name := cmd.Arg(0) |
|
| 1569 |
+ body, _, err := readBody(cli.call("GET", "/containers/"+name+"/json", nil, false))
|
|
| 1570 |
+ if err != nil {
|
|
| 1571 |
+ return err |
|
| 1572 |
+ } |
|
| 1573 |
+ |
|
| 1574 |
+ container := &api.Container{}
|
|
| 1575 |
+ err = json.Unmarshal(body, container) |
|
| 1576 |
+ if err != nil {
|
|
| 1577 |
+ return err |
|
| 1578 |
+ } |
|
| 1579 |
+ |
|
| 1580 |
+ v := url.Values{}
|
|
| 1581 |
+ v.Set("logs", "1")
|
|
| 1582 |
+ v.Set("stdout", "1")
|
|
| 1583 |
+ v.Set("stderr", "1")
|
|
| 1584 |
+ if *follow && container.State.Running {
|
|
| 1585 |
+ v.Set("stream", "1")
|
|
| 1586 |
+ } |
|
| 1587 |
+ |
|
| 1588 |
+ if err := cli.hijack("POST", "/containers/"+name+"/attach?"+v.Encode(), container.Config.Tty, nil, cli.out, cli.err, nil); err != nil {
|
|
| 1589 |
+ return err |
|
| 1590 |
+ } |
|
| 1591 |
+ return nil |
|
| 1592 |
+} |
|
| 1593 |
+ |
|
| 1594 |
+func (cli *DockerCli) CmdAttach(args ...string) error {
|
|
| 1595 |
+ cmd := cli.Subcmd("attach", "[OPTIONS] CONTAINER", "Attach to a running container")
|
|
| 1596 |
+ noStdin := cmd.Bool([]string{"#nostdin", "-no-stdin"}, false, "Do not attach stdin")
|
|
| 1597 |
+ proxy := cmd.Bool([]string{"#sig-proxy", "-sig-proxy"}, true, "Proxify all received signal to the process (even in non-tty mode)")
|
|
| 1598 |
+ if err := cmd.Parse(args); err != nil {
|
|
| 1599 |
+ return nil |
|
| 1600 |
+ } |
|
| 1601 |
+ if cmd.NArg() != 1 {
|
|
| 1602 |
+ cmd.Usage() |
|
| 1603 |
+ return nil |
|
| 1604 |
+ } |
|
| 1605 |
+ name := cmd.Arg(0) |
|
| 1606 |
+ body, _, err := readBody(cli.call("GET", "/containers/"+name+"/json", nil, false))
|
|
| 1607 |
+ if err != nil {
|
|
| 1608 |
+ return err |
|
| 1609 |
+ } |
|
| 1610 |
+ |
|
| 1611 |
+ container := &api.Container{}
|
|
| 1612 |
+ err = json.Unmarshal(body, container) |
|
| 1613 |
+ if err != nil {
|
|
| 1614 |
+ return err |
|
| 1615 |
+ } |
|
| 1616 |
+ |
|
| 1617 |
+ if !container.State.Running {
|
|
| 1618 |
+ return fmt.Errorf("You cannot attach to a stopped container, start it first")
|
|
| 1619 |
+ } |
|
| 1620 |
+ |
|
| 1621 |
+ if container.Config.Tty && cli.isTerminal {
|
|
| 1622 |
+ if err := cli.monitorTtySize(cmd.Arg(0)); err != nil {
|
|
| 1623 |
+ utils.Debugf("Error monitoring TTY size: %s", err)
|
|
| 1624 |
+ } |
|
| 1625 |
+ } |
|
| 1626 |
+ |
|
| 1627 |
+ var in io.ReadCloser |
|
| 1628 |
+ |
|
| 1629 |
+ v := url.Values{}
|
|
| 1630 |
+ v.Set("stream", "1")
|
|
| 1631 |
+ if !*noStdin && container.Config.OpenStdin {
|
|
| 1632 |
+ v.Set("stdin", "1")
|
|
| 1633 |
+ in = cli.in |
|
| 1634 |
+ } |
|
| 1635 |
+ v.Set("stdout", "1")
|
|
| 1636 |
+ v.Set("stderr", "1")
|
|
| 1637 |
+ |
|
| 1638 |
+ if *proxy && !container.Config.Tty {
|
|
| 1639 |
+ sigc := cli.forwardAllSignals(cmd.Arg(0)) |
|
| 1640 |
+ defer signal.StopCatch(sigc) |
|
| 1641 |
+ } |
|
| 1642 |
+ |
|
| 1643 |
+ if err := cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), container.Config.Tty, in, cli.out, cli.err, nil); err != nil {
|
|
| 1644 |
+ return err |
|
| 1645 |
+ } |
|
| 1646 |
+ |
|
| 1647 |
+ _, status, err := getExitCode(cli, cmd.Arg(0)) |
|
| 1648 |
+ if err != nil {
|
|
| 1649 |
+ return err |
|
| 1650 |
+ } |
|
| 1651 |
+ if status != 0 {
|
|
| 1652 |
+ return &utils.StatusError{StatusCode: status}
|
|
| 1653 |
+ } |
|
| 1654 |
+ |
|
| 1655 |
+ return nil |
|
| 1656 |
+} |
|
| 1657 |
+ |
|
| 1658 |
+func (cli *DockerCli) CmdSearch(args ...string) error {
|
|
| 1659 |
+ cmd := cli.Subcmd("search", "TERM", "Search the docker index for images")
|
|
| 1660 |
+ noTrunc := cmd.Bool([]string{"#notrunc", "-no-trunc"}, false, "Don't truncate output")
|
|
| 1661 |
+ trusted := cmd.Bool([]string{"t", "#trusted", "-trusted"}, false, "Only show trusted builds")
|
|
| 1662 |
+ stars := cmd.Int([]string{"s", "#stars", "-stars"}, 0, "Only displays with at least xxx stars")
|
|
| 1663 |
+ if err := cmd.Parse(args); err != nil {
|
|
| 1664 |
+ return nil |
|
| 1665 |
+ } |
|
| 1666 |
+ if cmd.NArg() != 1 {
|
|
| 1667 |
+ cmd.Usage() |
|
| 1668 |
+ return nil |
|
| 1669 |
+ } |
|
| 1670 |
+ |
|
| 1671 |
+ v := url.Values{}
|
|
| 1672 |
+ v.Set("term", cmd.Arg(0))
|
|
| 1673 |
+ |
|
| 1674 |
+ body, _, err := readBody(cli.call("GET", "/images/search?"+v.Encode(), nil, true))
|
|
| 1675 |
+ |
|
| 1676 |
+ if err != nil {
|
|
| 1677 |
+ return err |
|
| 1678 |
+ } |
|
| 1679 |
+ outs := engine.NewTable("star_count", 0)
|
|
| 1680 |
+ if _, err := outs.ReadListFrom(body); err != nil {
|
|
| 1681 |
+ return err |
|
| 1682 |
+ } |
|
| 1683 |
+ w := tabwriter.NewWriter(cli.out, 10, 1, 3, ' ', 0) |
|
| 1684 |
+ fmt.Fprintf(w, "NAME\tDESCRIPTION\tSTARS\tOFFICIAL\tTRUSTED\n") |
|
| 1685 |
+ for _, out := range outs.Data {
|
|
| 1686 |
+ if (*trusted && !out.GetBool("is_trusted")) || (*stars > out.GetInt("star_count")) {
|
|
| 1687 |
+ continue |
|
| 1688 |
+ } |
|
| 1689 |
+ desc := strings.Replace(out.Get("description"), "\n", " ", -1)
|
|
| 1690 |
+ desc = strings.Replace(desc, "\r", " ", -1) |
|
| 1691 |
+ if !*noTrunc && len(desc) > 45 {
|
|
| 1692 |
+ desc = utils.Trunc(desc, 42) + "..." |
|
| 1693 |
+ } |
|
| 1694 |
+ fmt.Fprintf(w, "%s\t%s\t%d\t", out.Get("name"), desc, out.GetInt("star_count"))
|
|
| 1695 |
+ if out.GetBool("is_official") {
|
|
| 1696 |
+ fmt.Fprint(w, "[OK]") |
|
| 1697 |
+ |
|
| 1698 |
+ } |
|
| 1699 |
+ fmt.Fprint(w, "\t") |
|
| 1700 |
+ if out.GetBool("is_trusted") {
|
|
| 1701 |
+ fmt.Fprint(w, "[OK]") |
|
| 1702 |
+ } |
|
| 1703 |
+ fmt.Fprint(w, "\n") |
|
| 1704 |
+ } |
|
| 1705 |
+ w.Flush() |
|
| 1706 |
+ return nil |
|
| 1707 |
+} |
|
| 1708 |
+ |
|
| 1709 |
+// Ports type - Used to parse multiple -p flags |
|
| 1710 |
+type ports []int |
|
| 1711 |
+ |
|
| 1712 |
+func (cli *DockerCli) CmdTag(args ...string) error {
|
|
| 1713 |
+ cmd := cli.Subcmd("tag", "[OPTIONS] IMAGE [REGISTRYHOST/][USERNAME/]NAME[:TAG]", "Tag an image into a repository")
|
|
| 1714 |
+ force := cmd.Bool([]string{"f", "#force", "-force"}, false, "Force")
|
|
| 1715 |
+ if err := cmd.Parse(args); err != nil {
|
|
| 1716 |
+ return nil |
|
| 1717 |
+ } |
|
| 1718 |
+ if cmd.NArg() != 2 && cmd.NArg() != 3 {
|
|
| 1719 |
+ cmd.Usage() |
|
| 1720 |
+ return nil |
|
| 1721 |
+ } |
|
| 1722 |
+ |
|
| 1723 |
+ var repository, tag string |
|
| 1724 |
+ |
|
| 1725 |
+ if cmd.NArg() == 3 {
|
|
| 1726 |
+ fmt.Fprintf(cli.err, "[DEPRECATED] The format 'IMAGE [REPOSITORY [TAG]]' as been deprecated. Please use IMAGE [REGISTRYHOST/][USERNAME/]NAME[:TAG]]\n") |
|
| 1727 |
+ repository, tag = cmd.Arg(1), cmd.Arg(2) |
|
| 1728 |
+ } else {
|
|
| 1729 |
+ repository, tag = utils.ParseRepositoryTag(cmd.Arg(1)) |
|
| 1730 |
+ } |
|
| 1731 |
+ |
|
| 1732 |
+ v := url.Values{}
|
|
| 1733 |
+ |
|
| 1734 |
+ //Check if the given image name can be resolved |
|
| 1735 |
+ if _, _, err := registry.ResolveRepositoryName(repository); err != nil {
|
|
| 1736 |
+ return err |
|
| 1737 |
+ } |
|
| 1738 |
+ v.Set("repo", repository)
|
|
| 1739 |
+ v.Set("tag", tag)
|
|
| 1740 |
+ |
|
| 1741 |
+ if *force {
|
|
| 1742 |
+ v.Set("force", "1")
|
|
| 1743 |
+ } |
|
| 1744 |
+ |
|
| 1745 |
+ if _, _, err := readBody(cli.call("POST", "/images/"+cmd.Arg(0)+"/tag?"+v.Encode(), nil, false)); err != nil {
|
|
| 1746 |
+ return err |
|
| 1747 |
+ } |
|
| 1748 |
+ return nil |
|
| 1749 |
+} |
|
| 1750 |
+ |
|
| 1751 |
+func (cli *DockerCli) CmdRun(args ...string) error {
|
|
| 1752 |
+ // FIXME: just use runconfig.Parse already |
|
| 1753 |
+ config, hostConfig, cmd, err := runconfig.ParseSubcommand(cli.Subcmd("run", "[OPTIONS] IMAGE [COMMAND] [ARG...]", "Run a command in a new container"), args, nil)
|
|
| 1754 |
+ if err != nil {
|
|
| 1755 |
+ return err |
|
| 1756 |
+ } |
|
| 1757 |
+ if config.Image == "" {
|
|
| 1758 |
+ cmd.Usage() |
|
| 1759 |
+ return nil |
|
| 1760 |
+ } |
|
| 1761 |
+ |
|
| 1762 |
+ // Retrieve relevant client-side config |
|
| 1763 |
+ var ( |
|
| 1764 |
+ flName = cmd.Lookup("name")
|
|
| 1765 |
+ flRm = cmd.Lookup("rm")
|
|
| 1766 |
+ flSigProxy = cmd.Lookup("sig-proxy")
|
|
| 1767 |
+ autoRemove, _ = strconv.ParseBool(flRm.Value.String()) |
|
| 1768 |
+ sigProxy, _ = strconv.ParseBool(flSigProxy.Value.String()) |
|
| 1769 |
+ ) |
|
| 1770 |
+ |
|
| 1771 |
+ // Disable sigProxy in case on TTY |
|
| 1772 |
+ if config.Tty {
|
|
| 1773 |
+ sigProxy = false |
|
| 1774 |
+ } |
|
| 1775 |
+ |
|
| 1776 |
+ var containerIDFile io.WriteCloser |
|
| 1777 |
+ if len(hostConfig.ContainerIDFile) > 0 {
|
|
| 1778 |
+ if _, err := os.Stat(hostConfig.ContainerIDFile); err == nil {
|
|
| 1779 |
+ return fmt.Errorf("Container ID file found, make sure the other container isn't running or delete %s", hostConfig.ContainerIDFile)
|
|
| 1780 |
+ } |
|
| 1781 |
+ if containerIDFile, err = os.Create(hostConfig.ContainerIDFile); err != nil {
|
|
| 1782 |
+ return fmt.Errorf("Failed to create the container ID file: %s", err)
|
|
| 1783 |
+ } |
|
| 1784 |
+ defer func() {
|
|
| 1785 |
+ containerIDFile.Close() |
|
| 1786 |
+ var ( |
|
| 1787 |
+ cidFileInfo os.FileInfo |
|
| 1788 |
+ err error |
|
| 1789 |
+ ) |
|
| 1790 |
+ if cidFileInfo, err = os.Stat(hostConfig.ContainerIDFile); err != nil {
|
|
| 1791 |
+ return |
|
| 1792 |
+ } |
|
| 1793 |
+ if cidFileInfo.Size() == 0 {
|
|
| 1794 |
+ if err := os.Remove(hostConfig.ContainerIDFile); err != nil {
|
|
| 1795 |
+ fmt.Printf("failed to remove CID file '%s': %s \n", hostConfig.ContainerIDFile, err)
|
|
| 1796 |
+ } |
|
| 1797 |
+ } |
|
| 1798 |
+ }() |
|
| 1799 |
+ } |
|
| 1800 |
+ |
|
| 1801 |
+ containerValues := url.Values{}
|
|
| 1802 |
+ if name := flName.Value.String(); name != "" {
|
|
| 1803 |
+ containerValues.Set("name", name)
|
|
| 1804 |
+ } |
|
| 1805 |
+ |
|
| 1806 |
+ //create the container |
|
| 1807 |
+ stream, statusCode, err := cli.call("POST", "/containers/create?"+containerValues.Encode(), config, false)
|
|
| 1808 |
+ //if image not found try to pull it |
|
| 1809 |
+ if statusCode == 404 {
|
|
| 1810 |
+ fmt.Fprintf(cli.err, "Unable to find image '%s' locally\n", config.Image) |
|
| 1811 |
+ |
|
| 1812 |
+ v := url.Values{}
|
|
| 1813 |
+ repos, tag := utils.ParseRepositoryTag(config.Image) |
|
| 1814 |
+ v.Set("fromImage", repos)
|
|
| 1815 |
+ v.Set("tag", tag)
|
|
| 1816 |
+ |
|
| 1817 |
+ // Resolve the Repository name from fqn to hostname + name |
|
| 1818 |
+ hostname, _, err := registry.ResolveRepositoryName(repos) |
|
| 1819 |
+ if err != nil {
|
|
| 1820 |
+ return err |
|
| 1821 |
+ } |
|
| 1822 |
+ |
|
| 1823 |
+ // Load the auth config file, to be able to pull the image |
|
| 1824 |
+ cli.LoadConfigFile() |
|
| 1825 |
+ |
|
| 1826 |
+ // Resolve the Auth config relevant for this server |
|
| 1827 |
+ authConfig := cli.configFile.ResolveAuthConfig(hostname) |
|
| 1828 |
+ buf, err := json.Marshal(authConfig) |
|
| 1829 |
+ if err != nil {
|
|
| 1830 |
+ return err |
|
| 1831 |
+ } |
|
| 1832 |
+ |
|
| 1833 |
+ registryAuthHeader := []string{
|
|
| 1834 |
+ base64.URLEncoding.EncodeToString(buf), |
|
| 1835 |
+ } |
|
| 1836 |
+ if err = cli.stream("POST", "/images/create?"+v.Encode(), nil, cli.err, map[string][]string{"X-Registry-Auth": registryAuthHeader}); err != nil {
|
|
| 1837 |
+ return err |
|
| 1838 |
+ } |
|
| 1839 |
+ if stream, _, err = cli.call("POST", "/containers/create?"+containerValues.Encode(), config, false); err != nil {
|
|
| 1840 |
+ return err |
|
| 1841 |
+ } |
|
| 1842 |
+ } else if err != nil {
|
|
| 1843 |
+ return err |
|
| 1844 |
+ } |
|
| 1845 |
+ |
|
| 1846 |
+ var runResult engine.Env |
|
| 1847 |
+ if err := runResult.Decode(stream); err != nil {
|
|
| 1848 |
+ return err |
|
| 1849 |
+ } |
|
| 1850 |
+ |
|
| 1851 |
+ for _, warning := range runResult.GetList("Warnings") {
|
|
| 1852 |
+ fmt.Fprintf(cli.err, "WARNING: %s\n", warning) |
|
| 1853 |
+ } |
|
| 1854 |
+ |
|
| 1855 |
+ if len(hostConfig.ContainerIDFile) > 0 {
|
|
| 1856 |
+ if _, err = containerIDFile.Write([]byte(runResult.Get("Id"))); err != nil {
|
|
| 1857 |
+ return fmt.Errorf("Failed to write the container ID to the file: %s", err)
|
|
| 1858 |
+ } |
|
| 1859 |
+ } |
|
| 1860 |
+ |
|
| 1861 |
+ if sigProxy {
|
|
| 1862 |
+ sigc := cli.forwardAllSignals(runResult.Get("Id"))
|
|
| 1863 |
+ defer signal.StopCatch(sigc) |
|
| 1864 |
+ } |
|
| 1865 |
+ |
|
| 1866 |
+ var ( |
|
| 1867 |
+ waitDisplayId chan struct{}
|
|
| 1868 |
+ errCh chan error |
|
| 1869 |
+ ) |
|
| 1870 |
+ |
|
| 1871 |
+ if !config.AttachStdout && !config.AttachStderr {
|
|
| 1872 |
+ // Make this asynchrone in order to let the client write to stdin before having to read the ID |
|
| 1873 |
+ waitDisplayId = make(chan struct{})
|
|
| 1874 |
+ go func() {
|
|
| 1875 |
+ defer close(waitDisplayId) |
|
| 1876 |
+ fmt.Fprintf(cli.out, "%s\n", runResult.Get("Id"))
|
|
| 1877 |
+ }() |
|
| 1878 |
+ } |
|
| 1879 |
+ |
|
| 1880 |
+ // We need to instanciate the chan because the select needs it. It can |
|
| 1881 |
+ // be closed but can't be uninitialized. |
|
| 1882 |
+ hijacked := make(chan io.Closer) |
|
| 1883 |
+ |
|
| 1884 |
+ // Block the return until the chan gets closed |
|
| 1885 |
+ defer func() {
|
|
| 1886 |
+ utils.Debugf("End of CmdRun(), Waiting for hijack to finish.")
|
|
| 1887 |
+ if _, ok := <-hijacked; ok {
|
|
| 1888 |
+ utils.Errorf("Hijack did not finish (chan still open)")
|
|
| 1889 |
+ } |
|
| 1890 |
+ }() |
|
| 1891 |
+ |
|
| 1892 |
+ if config.AttachStdin || config.AttachStdout || config.AttachStderr {
|
|
| 1893 |
+ var ( |
|
| 1894 |
+ out, stderr io.Writer |
|
| 1895 |
+ in io.ReadCloser |
|
| 1896 |
+ v = url.Values{}
|
|
| 1897 |
+ ) |
|
| 1898 |
+ v.Set("stream", "1")
|
|
| 1899 |
+ |
|
| 1900 |
+ if config.AttachStdin {
|
|
| 1901 |
+ v.Set("stdin", "1")
|
|
| 1902 |
+ in = cli.in |
|
| 1903 |
+ } |
|
| 1904 |
+ if config.AttachStdout {
|
|
| 1905 |
+ v.Set("stdout", "1")
|
|
| 1906 |
+ out = cli.out |
|
| 1907 |
+ } |
|
| 1908 |
+ if config.AttachStderr {
|
|
| 1909 |
+ v.Set("stderr", "1")
|
|
| 1910 |
+ if config.Tty {
|
|
| 1911 |
+ stderr = cli.out |
|
| 1912 |
+ } else {
|
|
| 1913 |
+ stderr = cli.err |
|
| 1914 |
+ } |
|
| 1915 |
+ } |
|
| 1916 |
+ |
|
| 1917 |
+ errCh = utils.Go(func() error {
|
|
| 1918 |
+ return cli.hijack("POST", "/containers/"+runResult.Get("Id")+"/attach?"+v.Encode(), config.Tty, in, out, stderr, hijacked)
|
|
| 1919 |
+ }) |
|
| 1920 |
+ } else {
|
|
| 1921 |
+ close(hijacked) |
|
| 1922 |
+ } |
|
| 1923 |
+ |
|
| 1924 |
+ // Acknowledge the hijack before starting |
|
| 1925 |
+ select {
|
|
| 1926 |
+ case closer := <-hijacked: |
|
| 1927 |
+ // Make sure that hijack gets closed when returning. (result |
|
| 1928 |
+ // in closing hijack chan and freeing server's goroutines. |
|
| 1929 |
+ if closer != nil {
|
|
| 1930 |
+ defer closer.Close() |
|
| 1931 |
+ } |
|
| 1932 |
+ case err := <-errCh: |
|
| 1933 |
+ if err != nil {
|
|
| 1934 |
+ utils.Debugf("Error hijack: %s", err)
|
|
| 1935 |
+ return err |
|
| 1936 |
+ } |
|
| 1937 |
+ } |
|
| 1938 |
+ |
|
| 1939 |
+ //start the container |
|
| 1940 |
+ if _, _, err = readBody(cli.call("POST", "/containers/"+runResult.Get("Id")+"/start", hostConfig, false)); err != nil {
|
|
| 1941 |
+ return err |
|
| 1942 |
+ } |
|
| 1943 |
+ |
|
| 1944 |
+ if (config.AttachStdin || config.AttachStdout || config.AttachStderr) && config.Tty && cli.isTerminal {
|
|
| 1945 |
+ if err := cli.monitorTtySize(runResult.Get("Id")); err != nil {
|
|
| 1946 |
+ utils.Errorf("Error monitoring TTY size: %s\n", err)
|
|
| 1947 |
+ } |
|
| 1948 |
+ } |
|
| 1949 |
+ |
|
| 1950 |
+ if errCh != nil {
|
|
| 1951 |
+ if err := <-errCh; err != nil {
|
|
| 1952 |
+ utils.Debugf("Error hijack: %s", err)
|
|
| 1953 |
+ return err |
|
| 1954 |
+ } |
|
| 1955 |
+ } |
|
| 1956 |
+ |
|
| 1957 |
+ // Detached mode: wait for the id to be displayed and return. |
|
| 1958 |
+ if !config.AttachStdout && !config.AttachStderr {
|
|
| 1959 |
+ // Detached mode |
|
| 1960 |
+ <-waitDisplayId |
|
| 1961 |
+ return nil |
|
| 1962 |
+ } |
|
| 1963 |
+ |
|
| 1964 |
+ var status int |
|
| 1965 |
+ |
|
| 1966 |
+ // Attached mode |
|
| 1967 |
+ if autoRemove {
|
|
| 1968 |
+ // Autoremove: wait for the container to finish, retrieve |
|
| 1969 |
+ // the exit code and remove the container |
|
| 1970 |
+ if _, _, err := readBody(cli.call("POST", "/containers/"+runResult.Get("Id")+"/wait", nil, false)); err != nil {
|
|
| 1971 |
+ return err |
|
| 1972 |
+ } |
|
| 1973 |
+ if _, status, err = getExitCode(cli, runResult.Get("Id")); err != nil {
|
|
| 1974 |
+ return err |
|
| 1975 |
+ } |
|
| 1976 |
+ if _, _, err := readBody(cli.call("DELETE", "/containers/"+runResult.Get("Id")+"?v=1", nil, false)); err != nil {
|
|
| 1977 |
+ return err |
|
| 1978 |
+ } |
|
| 1979 |
+ } else {
|
|
| 1980 |
+ if !config.Tty {
|
|
| 1981 |
+ // In non-tty mode, we can't dettach, so we know we need to wait. |
|
| 1982 |
+ if status, err = waitForExit(cli, runResult.Get("Id")); err != nil {
|
|
| 1983 |
+ return err |
|
| 1984 |
+ } |
|
| 1985 |
+ } else {
|
|
| 1986 |
+ // In TTY mode, there is a race. If the process dies too slowly, the state can be update after the getExitCode call |
|
| 1987 |
+ // and result in a wrong exit code. |
|
| 1988 |
+ // No Autoremove: Simply retrieve the exit code |
|
| 1989 |
+ if _, status, err = getExitCode(cli, runResult.Get("Id")); err != nil {
|
|
| 1990 |
+ return err |
|
| 1991 |
+ } |
|
| 1992 |
+ } |
|
| 1993 |
+ } |
|
| 1994 |
+ if status != 0 {
|
|
| 1995 |
+ return &utils.StatusError{StatusCode: status}
|
|
| 1996 |
+ } |
|
| 1997 |
+ return nil |
|
| 1998 |
+} |
|
| 1999 |
+ |
|
| 2000 |
+func (cli *DockerCli) CmdCp(args ...string) error {
|
|
| 2001 |
+ cmd := cli.Subcmd("cp", "CONTAINER:PATH HOSTPATH", "Copy files/folders from the PATH to the HOSTPATH")
|
|
| 2002 |
+ if err := cmd.Parse(args); err != nil {
|
|
| 2003 |
+ return nil |
|
| 2004 |
+ } |
|
| 2005 |
+ |
|
| 2006 |
+ if cmd.NArg() != 2 {
|
|
| 2007 |
+ cmd.Usage() |
|
| 2008 |
+ return nil |
|
| 2009 |
+ } |
|
| 2010 |
+ |
|
| 2011 |
+ var copyData engine.Env |
|
| 2012 |
+ info := strings.Split(cmd.Arg(0), ":") |
|
| 2013 |
+ |
|
| 2014 |
+ if len(info) != 2 {
|
|
| 2015 |
+ return fmt.Errorf("Error: Path not specified")
|
|
| 2016 |
+ } |
|
| 2017 |
+ |
|
| 2018 |
+ copyData.Set("Resource", info[1])
|
|
| 2019 |
+ copyData.Set("HostPath", cmd.Arg(1))
|
|
| 2020 |
+ |
|
| 2021 |
+ stream, statusCode, err := cli.call("POST", "/containers/"+info[0]+"/copy", copyData, false)
|
|
| 2022 |
+ if stream != nil {
|
|
| 2023 |
+ defer stream.Close() |
|
| 2024 |
+ } |
|
| 2025 |
+ if statusCode == 404 {
|
|
| 2026 |
+ return fmt.Errorf("No such container: %v", info[0])
|
|
| 2027 |
+ } |
|
| 2028 |
+ if err != nil {
|
|
| 2029 |
+ return err |
|
| 2030 |
+ } |
|
| 2031 |
+ |
|
| 2032 |
+ if statusCode == 200 {
|
|
| 2033 |
+ if err := archive.Untar(stream, copyData.Get("HostPath"), nil); err != nil {
|
|
| 2034 |
+ return err |
|
| 2035 |
+ } |
|
| 2036 |
+ } |
|
| 2037 |
+ return nil |
|
| 2038 |
+} |
|
| 2039 |
+ |
|
| 2040 |
+func (cli *DockerCli) CmdSave(args ...string) error {
|
|
| 2041 |
+ cmd := cli.Subcmd("save", "IMAGE", "Save an image to a tar archive (streamed to stdout by default)")
|
|
| 2042 |
+ outfile := cmd.String([]string{"o", "-output"}, "", "Write to an file, instead of STDOUT")
|
|
| 2043 |
+ |
|
| 2044 |
+ if err := cmd.Parse(args); err != nil {
|
|
| 2045 |
+ return err |
|
| 2046 |
+ } |
|
| 2047 |
+ |
|
| 2048 |
+ if cmd.NArg() != 1 {
|
|
| 2049 |
+ cmd.Usage() |
|
| 2050 |
+ return nil |
|
| 2051 |
+ } |
|
| 2052 |
+ |
|
| 2053 |
+ var ( |
|
| 2054 |
+ output io.Writer = cli.out |
|
| 2055 |
+ err error |
|
| 2056 |
+ ) |
|
| 2057 |
+ if *outfile != "" {
|
|
| 2058 |
+ output, err = os.Create(*outfile) |
|
| 2059 |
+ if err != nil {
|
|
| 2060 |
+ return err |
|
| 2061 |
+ } |
|
| 2062 |
+ } |
|
| 2063 |
+ image := cmd.Arg(0) |
|
| 2064 |
+ if err := cli.stream("GET", "/images/"+image+"/get", nil, output, nil); err != nil {
|
|
| 2065 |
+ return err |
|
| 2066 |
+ } |
|
| 2067 |
+ return nil |
|
| 2068 |
+} |
|
| 2069 |
+ |
|
| 2070 |
+func (cli *DockerCli) CmdLoad(args ...string) error {
|
|
| 2071 |
+ cmd := cli.Subcmd("load", "", "Load an image from a tar archive on STDIN")
|
|
| 2072 |
+ infile := cmd.String([]string{"i", "-input"}, "", "Read from a tar archive file, instead of STDIN")
|
|
| 2073 |
+ |
|
| 2074 |
+ if err := cmd.Parse(args); err != nil {
|
|
| 2075 |
+ return err |
|
| 2076 |
+ } |
|
| 2077 |
+ |
|
| 2078 |
+ if cmd.NArg() != 0 {
|
|
| 2079 |
+ cmd.Usage() |
|
| 2080 |
+ return nil |
|
| 2081 |
+ } |
|
| 2082 |
+ |
|
| 2083 |
+ var ( |
|
| 2084 |
+ input io.Reader = cli.in |
|
| 2085 |
+ err error |
|
| 2086 |
+ ) |
|
| 2087 |
+ if *infile != "" {
|
|
| 2088 |
+ input, err = os.Open(*infile) |
|
| 2089 |
+ if err != nil {
|
|
| 2090 |
+ return err |
|
| 2091 |
+ } |
|
| 2092 |
+ } |
|
| 2093 |
+ if err := cli.stream("POST", "/images/load", input, cli.out, nil); err != nil {
|
|
| 2094 |
+ return err |
|
| 2095 |
+ } |
|
| 2096 |
+ return nil |
|
| 2097 |
+} |
| 0 | 2098 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,390 @@ |
| 0 |
+package client |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "bytes" |
|
| 4 |
+ "crypto/tls" |
|
| 5 |
+ "encoding/base64" |
|
| 6 |
+ "encoding/json" |
|
| 7 |
+ "errors" |
|
| 8 |
+ "fmt" |
|
| 9 |
+ "io" |
|
| 10 |
+ "io/ioutil" |
|
| 11 |
+ "net" |
|
| 12 |
+ "net/http" |
|
| 13 |
+ "net/http/httputil" |
|
| 14 |
+ "net/url" |
|
| 15 |
+ "os" |
|
| 16 |
+ gosignal "os/signal" |
|
| 17 |
+ "regexp" |
|
| 18 |
+ goruntime "runtime" |
|
| 19 |
+ "strconv" |
|
| 20 |
+ "strings" |
|
| 21 |
+ "syscall" |
|
| 22 |
+ |
|
| 23 |
+ "github.com/dotcloud/docker/api" |
|
| 24 |
+ "github.com/dotcloud/docker/dockerversion" |
|
| 25 |
+ "github.com/dotcloud/docker/engine" |
|
| 26 |
+ "github.com/dotcloud/docker/pkg/term" |
|
| 27 |
+ "github.com/dotcloud/docker/registry" |
|
| 28 |
+ "github.com/dotcloud/docker/utils" |
|
| 29 |
+) |
|
| 30 |
+ |
|
| 31 |
+var ( |
|
| 32 |
+ ErrConnectionRefused = errors.New("Cannot connect to the Docker daemon. Is 'docker -d' running on this host?")
|
|
| 33 |
+) |
|
| 34 |
+ |
|
| 35 |
+func (cli *DockerCli) dial() (net.Conn, error) {
|
|
| 36 |
+ if cli.tlsConfig != nil && cli.proto != "unix" {
|
|
| 37 |
+ return tls.Dial(cli.proto, cli.addr, cli.tlsConfig) |
|
| 38 |
+ } |
|
| 39 |
+ return net.Dial(cli.proto, cli.addr) |
|
| 40 |
+} |
|
| 41 |
+ |
|
| 42 |
+func (cli *DockerCli) call(method, path string, data interface{}, passAuthInfo bool) (io.ReadCloser, int, error) {
|
|
| 43 |
+ params := bytes.NewBuffer(nil) |
|
| 44 |
+ if data != nil {
|
|
| 45 |
+ if env, ok := data.(engine.Env); ok {
|
|
| 46 |
+ if err := env.Encode(params); err != nil {
|
|
| 47 |
+ return nil, -1, err |
|
| 48 |
+ } |
|
| 49 |
+ } else {
|
|
| 50 |
+ buf, err := json.Marshal(data) |
|
| 51 |
+ if err != nil {
|
|
| 52 |
+ return nil, -1, err |
|
| 53 |
+ } |
|
| 54 |
+ if _, err := params.Write(buf); err != nil {
|
|
| 55 |
+ return nil, -1, err |
|
| 56 |
+ } |
|
| 57 |
+ } |
|
| 58 |
+ } |
|
| 59 |
+ // fixme: refactor client to support redirect |
|
| 60 |
+ re := regexp.MustCompile("/+")
|
|
| 61 |
+ path = re.ReplaceAllString(path, "/") |
|
| 62 |
+ |
|
| 63 |
+ req, err := http.NewRequest(method, fmt.Sprintf("/v%s%s", api.APIVERSION, path), params)
|
|
| 64 |
+ if err != nil {
|
|
| 65 |
+ return nil, -1, err |
|
| 66 |
+ } |
|
| 67 |
+ if passAuthInfo {
|
|
| 68 |
+ cli.LoadConfigFile() |
|
| 69 |
+ // Resolve the Auth config relevant for this server |
|
| 70 |
+ authConfig := cli.configFile.ResolveAuthConfig(registry.IndexServerAddress()) |
|
| 71 |
+ getHeaders := func(authConfig registry.AuthConfig) (map[string][]string, error) {
|
|
| 72 |
+ buf, err := json.Marshal(authConfig) |
|
| 73 |
+ if err != nil {
|
|
| 74 |
+ return nil, err |
|
| 75 |
+ } |
|
| 76 |
+ registryAuthHeader := []string{
|
|
| 77 |
+ base64.URLEncoding.EncodeToString(buf), |
|
| 78 |
+ } |
|
| 79 |
+ return map[string][]string{"X-Registry-Auth": registryAuthHeader}, nil
|
|
| 80 |
+ } |
|
| 81 |
+ if headers, err := getHeaders(authConfig); err == nil && headers != nil {
|
|
| 82 |
+ for k, v := range headers {
|
|
| 83 |
+ req.Header[k] = v |
|
| 84 |
+ } |
|
| 85 |
+ } |
|
| 86 |
+ } |
|
| 87 |
+ req.Header.Set("User-Agent", "Docker-Client/"+dockerversion.VERSION)
|
|
| 88 |
+ req.Host = cli.addr |
|
| 89 |
+ if data != nil {
|
|
| 90 |
+ req.Header.Set("Content-Type", "application/json")
|
|
| 91 |
+ } else if method == "POST" {
|
|
| 92 |
+ req.Header.Set("Content-Type", "plain/text")
|
|
| 93 |
+ } |
|
| 94 |
+ dial, err := cli.dial() |
|
| 95 |
+ if err != nil {
|
|
| 96 |
+ if strings.Contains(err.Error(), "connection refused") {
|
|
| 97 |
+ return nil, -1, ErrConnectionRefused |
|
| 98 |
+ } |
|
| 99 |
+ return nil, -1, err |
|
| 100 |
+ } |
|
| 101 |
+ clientconn := httputil.NewClientConn(dial, nil) |
|
| 102 |
+ resp, err := clientconn.Do(req) |
|
| 103 |
+ if err != nil {
|
|
| 104 |
+ clientconn.Close() |
|
| 105 |
+ if strings.Contains(err.Error(), "connection refused") {
|
|
| 106 |
+ return nil, -1, ErrConnectionRefused |
|
| 107 |
+ } |
|
| 108 |
+ return nil, -1, err |
|
| 109 |
+ } |
|
| 110 |
+ |
|
| 111 |
+ if resp.StatusCode < 200 || resp.StatusCode >= 400 {
|
|
| 112 |
+ body, err := ioutil.ReadAll(resp.Body) |
|
| 113 |
+ if err != nil {
|
|
| 114 |
+ return nil, -1, err |
|
| 115 |
+ } |
|
| 116 |
+ if len(body) == 0 {
|
|
| 117 |
+ return nil, resp.StatusCode, fmt.Errorf("Error: request returned %s for API route and version %s, check if the server supports the requested API version", http.StatusText(resp.StatusCode), req.URL)
|
|
| 118 |
+ } |
|
| 119 |
+ return nil, resp.StatusCode, fmt.Errorf("Error: %s", bytes.TrimSpace(body))
|
|
| 120 |
+ } |
|
| 121 |
+ |
|
| 122 |
+ wrapper := utils.NewReadCloserWrapper(resp.Body, func() error {
|
|
| 123 |
+ if resp != nil && resp.Body != nil {
|
|
| 124 |
+ resp.Body.Close() |
|
| 125 |
+ } |
|
| 126 |
+ return clientconn.Close() |
|
| 127 |
+ }) |
|
| 128 |
+ return wrapper, resp.StatusCode, nil |
|
| 129 |
+} |
|
| 130 |
+ |
|
| 131 |
+func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer, headers map[string][]string) error {
|
|
| 132 |
+ if (method == "POST" || method == "PUT") && in == nil {
|
|
| 133 |
+ in = bytes.NewReader([]byte{})
|
|
| 134 |
+ } |
|
| 135 |
+ |
|
| 136 |
+ // fixme: refactor client to support redirect |
|
| 137 |
+ re := regexp.MustCompile("/+")
|
|
| 138 |
+ path = re.ReplaceAllString(path, "/") |
|
| 139 |
+ |
|
| 140 |
+ req, err := http.NewRequest(method, fmt.Sprintf("/v%s%s", api.APIVERSION, path), in)
|
|
| 141 |
+ if err != nil {
|
|
| 142 |
+ return err |
|
| 143 |
+ } |
|
| 144 |
+ req.Header.Set("User-Agent", "Docker-Client/"+dockerversion.VERSION)
|
|
| 145 |
+ req.Host = cli.addr |
|
| 146 |
+ if method == "POST" {
|
|
| 147 |
+ req.Header.Set("Content-Type", "plain/text")
|
|
| 148 |
+ } |
|
| 149 |
+ |
|
| 150 |
+ if headers != nil {
|
|
| 151 |
+ for k, v := range headers {
|
|
| 152 |
+ req.Header[k] = v |
|
| 153 |
+ } |
|
| 154 |
+ } |
|
| 155 |
+ |
|
| 156 |
+ dial, err := cli.dial() |
|
| 157 |
+ if err != nil {
|
|
| 158 |
+ if strings.Contains(err.Error(), "connection refused") {
|
|
| 159 |
+ return fmt.Errorf("Cannot connect to the Docker daemon. Is 'docker -d' running on this host?")
|
|
| 160 |
+ } |
|
| 161 |
+ return err |
|
| 162 |
+ } |
|
| 163 |
+ clientconn := httputil.NewClientConn(dial, nil) |
|
| 164 |
+ resp, err := clientconn.Do(req) |
|
| 165 |
+ defer clientconn.Close() |
|
| 166 |
+ if err != nil {
|
|
| 167 |
+ if strings.Contains(err.Error(), "connection refused") {
|
|
| 168 |
+ return fmt.Errorf("Cannot connect to the Docker daemon. Is 'docker -d' running on this host?")
|
|
| 169 |
+ } |
|
| 170 |
+ return err |
|
| 171 |
+ } |
|
| 172 |
+ defer resp.Body.Close() |
|
| 173 |
+ |
|
| 174 |
+ if resp.StatusCode < 200 || resp.StatusCode >= 400 {
|
|
| 175 |
+ body, err := ioutil.ReadAll(resp.Body) |
|
| 176 |
+ if err != nil {
|
|
| 177 |
+ return err |
|
| 178 |
+ } |
|
| 179 |
+ if len(body) == 0 {
|
|
| 180 |
+ return fmt.Errorf("Error :%s", http.StatusText(resp.StatusCode))
|
|
| 181 |
+ } |
|
| 182 |
+ return fmt.Errorf("Error: %s", bytes.TrimSpace(body))
|
|
| 183 |
+ } |
|
| 184 |
+ |
|
| 185 |
+ if api.MatchesContentType(resp.Header.Get("Content-Type"), "application/json") {
|
|
| 186 |
+ return utils.DisplayJSONMessagesStream(resp.Body, out, cli.terminalFd, cli.isTerminal) |
|
| 187 |
+ } |
|
| 188 |
+ if _, err := io.Copy(out, resp.Body); err != nil {
|
|
| 189 |
+ return err |
|
| 190 |
+ } |
|
| 191 |
+ return nil |
|
| 192 |
+} |
|
| 193 |
+ |
|
| 194 |
+func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.ReadCloser, stdout, stderr io.Writer, started chan io.Closer) error {
|
|
| 195 |
+ defer func() {
|
|
| 196 |
+ if started != nil {
|
|
| 197 |
+ close(started) |
|
| 198 |
+ } |
|
| 199 |
+ }() |
|
| 200 |
+ // fixme: refactor client to support redirect |
|
| 201 |
+ re := regexp.MustCompile("/+")
|
|
| 202 |
+ path = re.ReplaceAllString(path, "/") |
|
| 203 |
+ |
|
| 204 |
+ req, err := http.NewRequest(method, fmt.Sprintf("/v%s%s", api.APIVERSION, path), nil)
|
|
| 205 |
+ if err != nil {
|
|
| 206 |
+ return err |
|
| 207 |
+ } |
|
| 208 |
+ req.Header.Set("User-Agent", "Docker-Client/"+dockerversion.VERSION)
|
|
| 209 |
+ req.Header.Set("Content-Type", "plain/text")
|
|
| 210 |
+ req.Host = cli.addr |
|
| 211 |
+ |
|
| 212 |
+ dial, err := cli.dial() |
|
| 213 |
+ if err != nil {
|
|
| 214 |
+ if strings.Contains(err.Error(), "connection refused") {
|
|
| 215 |
+ return fmt.Errorf("Cannot connect to the Docker daemon. Is 'docker -d' running on this host?")
|
|
| 216 |
+ } |
|
| 217 |
+ return err |
|
| 218 |
+ } |
|
| 219 |
+ clientconn := httputil.NewClientConn(dial, nil) |
|
| 220 |
+ defer clientconn.Close() |
|
| 221 |
+ |
|
| 222 |
+ // Server hijacks the connection, error 'connection closed' expected |
|
| 223 |
+ clientconn.Do(req) |
|
| 224 |
+ |
|
| 225 |
+ rwc, br := clientconn.Hijack() |
|
| 226 |
+ defer rwc.Close() |
|
| 227 |
+ |
|
| 228 |
+ if started != nil {
|
|
| 229 |
+ started <- rwc |
|
| 230 |
+ } |
|
| 231 |
+ |
|
| 232 |
+ var receiveStdout chan error |
|
| 233 |
+ |
|
| 234 |
+ var oldState *term.State |
|
| 235 |
+ |
|
| 236 |
+ if in != nil && setRawTerminal && cli.isTerminal && os.Getenv("NORAW") == "" {
|
|
| 237 |
+ oldState, err = term.SetRawTerminal(cli.terminalFd) |
|
| 238 |
+ if err != nil {
|
|
| 239 |
+ return err |
|
| 240 |
+ } |
|
| 241 |
+ defer term.RestoreTerminal(cli.terminalFd, oldState) |
|
| 242 |
+ } |
|
| 243 |
+ |
|
| 244 |
+ if stdout != nil || stderr != nil {
|
|
| 245 |
+ receiveStdout = utils.Go(func() (err error) {
|
|
| 246 |
+ defer func() {
|
|
| 247 |
+ if in != nil {
|
|
| 248 |
+ if setRawTerminal && cli.isTerminal {
|
|
| 249 |
+ term.RestoreTerminal(cli.terminalFd, oldState) |
|
| 250 |
+ } |
|
| 251 |
+ // For some reason this Close call blocks on darwin.. |
|
| 252 |
+ // As the client exists right after, simply discard the close |
|
| 253 |
+ // until we find a better solution. |
|
| 254 |
+ if goruntime.GOOS != "darwin" {
|
|
| 255 |
+ in.Close() |
|
| 256 |
+ } |
|
| 257 |
+ } |
|
| 258 |
+ }() |
|
| 259 |
+ |
|
| 260 |
+ // When TTY is ON, use regular copy |
|
| 261 |
+ if setRawTerminal {
|
|
| 262 |
+ _, err = io.Copy(stdout, br) |
|
| 263 |
+ } else {
|
|
| 264 |
+ _, err = utils.StdCopy(stdout, stderr, br) |
|
| 265 |
+ } |
|
| 266 |
+ utils.Debugf("[hijack] End of stdout")
|
|
| 267 |
+ return err |
|
| 268 |
+ }) |
|
| 269 |
+ } |
|
| 270 |
+ |
|
| 271 |
+ sendStdin := utils.Go(func() error {
|
|
| 272 |
+ if in != nil {
|
|
| 273 |
+ io.Copy(rwc, in) |
|
| 274 |
+ utils.Debugf("[hijack] End of stdin")
|
|
| 275 |
+ } |
|
| 276 |
+ if tcpc, ok := rwc.(*net.TCPConn); ok {
|
|
| 277 |
+ if err := tcpc.CloseWrite(); err != nil {
|
|
| 278 |
+ utils.Errorf("Couldn't send EOF: %s\n", err)
|
|
| 279 |
+ } |
|
| 280 |
+ } else if unixc, ok := rwc.(*net.UnixConn); ok {
|
|
| 281 |
+ if err := unixc.CloseWrite(); err != nil {
|
|
| 282 |
+ utils.Errorf("Couldn't send EOF: %s\n", err)
|
|
| 283 |
+ } |
|
| 284 |
+ } |
|
| 285 |
+ // Discard errors due to pipe interruption |
|
| 286 |
+ return nil |
|
| 287 |
+ }) |
|
| 288 |
+ |
|
| 289 |
+ if stdout != nil || stderr != nil {
|
|
| 290 |
+ if err := <-receiveStdout; err != nil {
|
|
| 291 |
+ utils.Errorf("Error receiveStdout: %s", err)
|
|
| 292 |
+ return err |
|
| 293 |
+ } |
|
| 294 |
+ } |
|
| 295 |
+ |
|
| 296 |
+ if !cli.isTerminal {
|
|
| 297 |
+ if err := <-sendStdin; err != nil {
|
|
| 298 |
+ utils.Errorf("Error sendStdin: %s", err)
|
|
| 299 |
+ return err |
|
| 300 |
+ } |
|
| 301 |
+ } |
|
| 302 |
+ return nil |
|
| 303 |
+ |
|
| 304 |
+} |
|
| 305 |
+ |
|
| 306 |
+func (cli *DockerCli) resizeTty(id string) {
|
|
| 307 |
+ height, width := cli.getTtySize() |
|
| 308 |
+ if height == 0 && width == 0 {
|
|
| 309 |
+ return |
|
| 310 |
+ } |
|
| 311 |
+ v := url.Values{}
|
|
| 312 |
+ v.Set("h", strconv.Itoa(height))
|
|
| 313 |
+ v.Set("w", strconv.Itoa(width))
|
|
| 314 |
+ if _, _, err := readBody(cli.call("POST", "/containers/"+id+"/resize?"+v.Encode(), nil, false)); err != nil {
|
|
| 315 |
+ utils.Errorf("Error resize: %s", err)
|
|
| 316 |
+ } |
|
| 317 |
+} |
|
| 318 |
+ |
|
| 319 |
+func waitForExit(cli *DockerCli, containerId string) (int, error) {
|
|
| 320 |
+ stream, _, err := cli.call("POST", "/containers/"+containerId+"/wait", nil, false)
|
|
| 321 |
+ if err != nil {
|
|
| 322 |
+ return -1, err |
|
| 323 |
+ } |
|
| 324 |
+ |
|
| 325 |
+ var out engine.Env |
|
| 326 |
+ if err := out.Decode(stream); err != nil {
|
|
| 327 |
+ return -1, err |
|
| 328 |
+ } |
|
| 329 |
+ return out.GetInt("StatusCode"), nil
|
|
| 330 |
+} |
|
| 331 |
+ |
|
| 332 |
+// getExitCode perform an inspect on the container. It returns |
|
| 333 |
+// the running state and the exit code. |
|
| 334 |
+func getExitCode(cli *DockerCli, containerId string) (bool, int, error) {
|
|
| 335 |
+ body, _, err := readBody(cli.call("GET", "/containers/"+containerId+"/json", nil, false))
|
|
| 336 |
+ if err != nil {
|
|
| 337 |
+ // If we can't connect, then the daemon probably died. |
|
| 338 |
+ if err != ErrConnectionRefused {
|
|
| 339 |
+ return false, -1, err |
|
| 340 |
+ } |
|
| 341 |
+ return false, -1, nil |
|
| 342 |
+ } |
|
| 343 |
+ c := &api.Container{}
|
|
| 344 |
+ if err := json.Unmarshal(body, c); err != nil {
|
|
| 345 |
+ return false, -1, err |
|
| 346 |
+ } |
|
| 347 |
+ return c.State.Running, c.State.ExitCode, nil |
|
| 348 |
+} |
|
| 349 |
+ |
|
| 350 |
+func (cli *DockerCli) monitorTtySize(id string) error {
|
|
| 351 |
+ cli.resizeTty(id) |
|
| 352 |
+ |
|
| 353 |
+ sigchan := make(chan os.Signal, 1) |
|
| 354 |
+ gosignal.Notify(sigchan, syscall.SIGWINCH) |
|
| 355 |
+ go func() {
|
|
| 356 |
+ for _ = range sigchan {
|
|
| 357 |
+ cli.resizeTty(id) |
|
| 358 |
+ } |
|
| 359 |
+ }() |
|
| 360 |
+ return nil |
|
| 361 |
+} |
|
| 362 |
+ |
|
| 363 |
+func (cli *DockerCli) getTtySize() (int, int) {
|
|
| 364 |
+ if !cli.isTerminal {
|
|
| 365 |
+ return 0, 0 |
|
| 366 |
+ } |
|
| 367 |
+ ws, err := term.GetWinsize(cli.terminalFd) |
|
| 368 |
+ if err != nil {
|
|
| 369 |
+ utils.Errorf("Error getting size: %s", err)
|
|
| 370 |
+ if ws == nil {
|
|
| 371 |
+ return 0, 0 |
|
| 372 |
+ } |
|
| 373 |
+ } |
|
| 374 |
+ return int(ws.Height), int(ws.Width) |
|
| 375 |
+} |
|
| 376 |
+ |
|
| 377 |
+func readBody(stream io.ReadCloser, statusCode int, err error) ([]byte, int, error) {
|
|
| 378 |
+ if stream != nil {
|
|
| 379 |
+ defer stream.Close() |
|
| 380 |
+ } |
|
| 381 |
+ if err != nil {
|
|
| 382 |
+ return nil, statusCode, err |
|
| 383 |
+ } |
|
| 384 |
+ body, err := ioutil.ReadAll(stream) |
|
| 385 |
+ if err != nil {
|
|
| 386 |
+ return nil, -1, err |
|
| 387 |
+ } |
|
| 388 |
+ return body, statusCode, nil |
|
| 389 |
+} |