diff --git a/api/client/commands.go b/api/client/commands.go index cb1dca81ba..06c369958a 100644 --- a/api/client/commands.go +++ b/api/client/commands.go @@ -818,6 +818,26 @@ func (cli *DockerCli) CmdPause(args ...string) error { return encounteredError } +func (cli *DockerCli) CmdRename(args ...string) error { + cmd := cli.Subcmd("rename", "OLD_NAME NEW_NAME", "Rename a container", true) + if err := cmd.Parse(args); err != nil { + return nil + } + + if cmd.NArg() != 2 { + cmd.Usage() + return nil + } + old_name := cmd.Arg(0) + new_name := cmd.Arg(1) + + if _, _, err := readBody(cli.call("POST", fmt.Sprintf("/containers/%s/rename?name=%s", old_name, new_name), nil, false)); err != nil { + fmt.Fprintf(cli.err, "%s\n", err) + return fmt.Errorf("Error: failed to rename container named %s", old_name) + } + return nil +} + func (cli *DockerCli) CmdInspect(args ...string) error { cmd := cli.Subcmd("inspect", "CONTAINER|IMAGE [CONTAINER|IMAGE...]", "Return low-level information on a container or image", true) tmplStr := cmd.String([]string{"f", "#format", "-format"}, "", "Format the output using the given go template.") diff --git a/api/server/server.go b/api/server/server.go index cfaa5f43ab..343c8389c0 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -739,6 +739,24 @@ func postContainersRestart(eng *engine.Engine, version version.Version, w http.R return nil } +func postContainerRename(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + if err := parseForm(r); err != nil { + return err + } + if vars == nil { + return fmt.Errorf("Missing parameter") + } + + newName := r.URL.Query().Get("name") + job := eng.Job("container_rename", vars["name"], newName) + job.Setenv("t", r.Form.Get("t")) + if err := job.Run(); err != nil { + return err + } + w.WriteHeader(http.StatusNoContent) + return nil +} + func deleteContainers(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := parseForm(r); err != nil { return err @@ -1311,6 +1329,7 @@ func createRouter(eng *engine.Engine, logging, enableCors bool, dockerVersion st "/containers/{name:.*}/exec": postContainerExecCreate, "/exec/{name:.*}/start": postContainerExecStart, "/exec/{name:.*}/resize": postContainerExecResize, + "/containers/{name:.*}/rename": postContainerRename, }, "DELETE": { "/containers/{name:.*}": deleteContainers, diff --git a/daemon/daemon.go b/daemon/daemon.go index 5972b4f87d..c0217eef56 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -113,6 +113,7 @@ func (daemon *Daemon) Install(eng *engine.Engine) error { "commit": daemon.ContainerCommit, "container_changes": daemon.ContainerChanges, "container_copy": daemon.ContainerCopy, + "container_rename": daemon.ContainerRename, "container_inspect": daemon.ContainerInspect, "containers": daemon.Containers, "create": daemon.ContainerCreate, diff --git a/daemon/rename.go b/daemon/rename.go new file mode 100644 index 0000000000..9b030aad0e --- /dev/null +++ b/daemon/rename.go @@ -0,0 +1,30 @@ +package daemon + +import ( + "github.com/docker/docker/engine" +) + +func (daemon *Daemon) ContainerRename(job *engine.Job) engine.Status { + if len(job.Args) != 2 { + return job.Errorf("usage: %s OLD_NAME NEW_NAME", job.Name) + } + old_name := job.Args[0] + new_name := job.Args[1] + + container := daemon.Get(old_name) + if container == nil { + return job.Errorf("No such container: %s", old_name) + } + + container.Lock() + defer container.Unlock() + if err := daemon.containerGraph.Delete(container.Name); err != nil { + return job.Errorf("Failed to delete container %q: %v", old_name, err) + } + if _, err := daemon.reserveName(container.ID, new_name); err != nil { + return job.Errorf("Error when allocating new name: %s", err) + } + container.Name = new_name + + return engine.StatusOK +} diff --git a/docker/flags.go b/docker/flags.go index d6c9f3c196..8fb85831e4 100644 --- a/docker/flags.go +++ b/docker/flags.go @@ -90,6 +90,7 @@ func init() { {"ps", "List containers"}, {"pull", "Pull an image or a repository from a Docker registry server"}, {"push", "Push an image or a repository to a Docker registry server"}, + {"rename", "Rename an existing container"}, {"restart", "Restart a running container"}, {"rm", "Remove one or more containers"}, {"rmi", "Remove one or more images"}, diff --git a/docs/man/docker-rename.1.md b/docs/man/docker-rename.1.md new file mode 100644 index 0000000000..f741a15b47 --- /dev/null +++ b/docs/man/docker-rename.1.md @@ -0,0 +1,13 @@ +% DOCKER(1) Docker User Manuals +% Docker Community +% OCTOBER 2014 +# NAME +docker-rename - Rename a container + +# SYNOPSIS +**docker rename** +OLD_NAME NEW_NAME + +# OPTIONS +There are no available options. + diff --git a/docs/sources/reference/api/docker_remote_api_v1.15.md b/docs/sources/reference/api/docker_remote_api_v1.15.md index 4d27a6150a..7edfa91016 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.15.md +++ b/docs/sources/reference/api/docker_remote_api_v1.15.md @@ -647,6 +647,27 @@ Status Codes: - **404** – no such container - **500** – server error +### Rename a container + +`POST /containers/(id)/rename/(new_name)` + +Rename the container `id` to a `new_name` + +**Example request**: + + POST /containers/e90e34656806/rename/new_name HTTP/1.1 + +**Example response**: + + HTTP/1.1 204 No Content + +Status Codes: + +- **204** – no error +- **404** – no such container +- **409** - conflict name already assigned +- **500** – server error + ### Pause a container `POST /containers/(id)/pause` diff --git a/docs/sources/reference/commandline/cli.md b/docs/sources/reference/commandline/cli.md index 2baf4699d6..877a19508c 100644 --- a/docs/sources/reference/commandline/cli.md +++ b/docs/sources/reference/commandline/cli.md @@ -1366,6 +1366,30 @@ just a specific mapping: $ sudo docker port test 7890 0.0.0.0:4321 +## pause + + Usage: docker pause CONTAINER + + Pause all processes within a container + +The `docker pause` command uses the cgroups freezer to suspend all processes in +a container. Traditionally when suspending a process the `SIGSTOP` signal is +used, which is observable by the process being suspended. With the cgroups freezer +the process is unaware, and unable to capture, that it is being suspended, +and subsequently resumed. + +See the +[cgroups freezer documentation](https://www.kernel.org/doc/Documentation/cgroups/freezer-subsystem.txt) +for further details. + +## rename + + Usage: docker rename OLD_NAME NEW_NAME + + rename a existing container to a NEW_NAME + +The `docker rename` command allows the container to be renamed to a different name. + ## ps Usage: docker ps [OPTIONS] diff --git a/integration-cli/docker_cli_links_test.go b/integration-cli/docker_cli_links_test.go index 49d46ed943..ad0638cf94 100644 --- a/integration-cli/docker_cli_links_test.go +++ b/integration-cli/docker_cli_links_test.go @@ -1,6 +1,7 @@ package main import ( + "github.com/docker/docker/pkg/iptables" "io/ioutil" "os" "os/exec" @@ -8,8 +9,6 @@ import ( "strings" "testing" "time" - - "github.com/docker/docker/pkg/iptables" ) func TestLinksEtcHostsRegularFile(t *testing.T) { @@ -76,6 +75,52 @@ func TestLinksPingLinkedContainers(t *testing.T) { logDone("links - ping linked container") } +func TestLinksPingLinkedContainersAfterRename(t *testing.T) { + out, _, _ := dockerCmd(t, "run", "-d", "--name", "container1", "busybox", "sleep", "10") + idA := stripTrailingCharacters(out) + out, _, _ = dockerCmd(t, "run", "-d", "--name", "container2", "busybox", "sleep", "10") + idB := stripTrailingCharacters(out) + dockerCmd(t, "rename", "container1", "container_new") + dockerCmd(t, "run", "--rm", "--link", "container_new:alias1", "--link", "container2:alias2", "busybox", "sh", "-c", "ping -c 1 alias1 -W 1 && ping -c 1 alias2 -W 1") + dockerCmd(t, "kill", idA) + dockerCmd(t, "kill", idB) + deleteAllContainers() + + logDone("links - ping linked container after rename") +} + +func TestLinksPingLinkedContainersOnRename(t *testing.T) { + var out string + out, _, _ = dockerCmd(t, "run", "-d", "--name", "container1", "busybox", "sleep", "10") + idA := stripTrailingCharacters(out) + if idA == "" { + t.Fatal(out, "id should not be nil") + } + out, _, _ = dockerCmd(t, "run", "-d", "--link", "container1:alias1", "--name", "container2", "busybox", "sleep", "10") + idB := stripTrailingCharacters(out) + if idB == "" { + t.Fatal(out, "id should not be nil") + } + + execCmd := exec.Command(dockerBinary, "exec", "container2", "ping", "-c", "1", "alias1", "-W", "1") + out, _, err := runCommandWithOutput(execCmd) + if err != nil { + t.Fatal(out, err) + } + + dockerCmd(t, "rename", "container1", "container_new") + + execCmd = exec.Command(dockerBinary, "exec", "container2", "ping", "-c", "1", "alias1", "-W", "1") + out, _, err = runCommandWithOutput(execCmd) + if err != nil { + t.Fatal(out, err) + } + + deleteAllContainers() + + logDone("links - ping linked container upon rename") +} + func TestLinksIpTablesRulesWhenLinkAndUnlink(t *testing.T) { dockerCmd(t, "run", "-d", "--name", "child", "--publish", "8080:80", "busybox", "sleep", "10") dockerCmd(t, "run", "-d", "--name", "parent", "--link", "child:http", "busybox", "sleep", "10") diff --git a/integration-cli/docker_cli_rename_test.go b/integration-cli/docker_cli_rename_test.go new file mode 100644 index 0000000000..3ba98e4e37 --- /dev/null +++ b/integration-cli/docker_cli_rename_test.go @@ -0,0 +1,99 @@ +package main + +import ( + "os/exec" + "strings" + "testing" +) + +func TestRenameStoppedContainer(t *testing.T) { + runCmd := exec.Command(dockerBinary, "run", "--name", "first_name", "-d", "busybox", "sh") + out, _, err := runCommandWithOutput(runCmd) + if err != nil { + t.Fatalf(out, err) + } + + cleanedContainerID := stripTrailingCharacters(out) + + runCmd = exec.Command(dockerBinary, "wait", cleanedContainerID) + out, _, err = runCommandWithOutput(runCmd) + if err != nil { + t.Fatalf(out, err) + } + + name, err := inspectField(cleanedContainerID, "Name") + + runCmd = exec.Command(dockerBinary, "rename", "first_name", "new_name") + out, _, err = runCommandWithOutput(runCmd) + if err != nil { + t.Fatalf(out, err) + } + + name, err = inspectField(cleanedContainerID, "Name") + if err != nil { + t.Fatal(err) + } + if name != "new_name" { + t.Fatal("Failed to rename container ", name) + } + deleteAllContainers() + + logDone("rename - stopped container") +} + +func TestRenameRunningContainer(t *testing.T) { + runCmd := exec.Command(dockerBinary, "run", "--name", "first_name", "-d", "busybox", "sh") + out, _, err := runCommandWithOutput(runCmd) + if err != nil { + t.Fatalf(out, err) + } + + cleanedContainerID := stripTrailingCharacters(out) + runCmd = exec.Command(dockerBinary, "rename", "first_name", "new_name") + out, _, err = runCommandWithOutput(runCmd) + if err != nil { + t.Fatalf(out, err) + } + + name, err := inspectField(cleanedContainerID, "Name") + if err != nil { + t.Fatal(err) + } + if name != "new_name" { + t.Fatal("Failed to rename container ") + } + deleteAllContainers() + + logDone("rename - running container") +} + +func TestRenameCheckNames(t *testing.T) { + runCmd := exec.Command(dockerBinary, "run", "--name", "first_name", "-d", "busybox", "sh") + out, _, err := runCommandWithOutput(runCmd) + if err != nil { + t.Fatalf(out, err) + } + + runCmd = exec.Command(dockerBinary, "rename", "first_name", "new_name") + out, _, err = runCommandWithOutput(runCmd) + if err != nil { + t.Fatalf(out, err) + } + + name, err := inspectField("new_name", "Name") + if err != nil { + t.Fatal(err) + } + if name != "new_name" { + t.Fatal("Failed to rename container ") + } + + name, err = inspectField("first_name", "Name") + if err == nil && !strings.Contains(err.Error(), "No such image or container: first_name") { + t.Fatal(err) + } + + deleteAllContainers() + + logDone("rename - running container") +}