summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMatthew Heon <mheon@redhat.com>2021-01-12 14:29:27 -0500
committerMatthew Heon <mheon@redhat.com>2021-01-14 18:29:28 -0500
commit997de2f8e9e5453a99108bde012aa6c41d7323ec (patch)
tree499660321cf95f726636fcdd3dd4a8afbb86e2f5
parent2b7793b6121d336a285fb7b9a7612c221cbf63d2 (diff)
downloadpodman-997de2f8e9e5453a99108bde012aa6c41d7323ec.tar.gz
podman-997de2f8e9e5453a99108bde012aa6c41d7323ec.tar.bz2
podman-997de2f8e9e5453a99108bde012aa6c41d7323ec.zip
Initial implementation of renaming containers
Basic theory: We remove the container, but *only from the DB*. We leave it in c/storage, we leave the lock allocated, we leave it running (if it is). Then we create an identical container with an altered name, and add that back to the database. Theoretically we now have a renamed container. The advantage of this approach is that it doesn't just apply to rename - we can use this to make *any* configuration change to a container that does not alter its container ID. Potential problems are numerous. This process is *THOROUGHLY* non-atomic at present - if you `kill -9` Podman mid-rename things will be in a bad place, for example. Also, we can't rename containers that can't be removed normally - IE, containers with dependencies (pod infra containers, for example). The largest potential improvement will be to move the majority of the work into the DB, with a `RecreateContainer()` method - that will add atomicity, and let us remove the container without worrying about depencies and similar issues. Potential problems: long-running processes that edit the DB and may have an older version of the configuration around. Most notable example is `podman run --rm` - the removal command needed to be manually edited to avoid this one. This begins to get at the heart of me not wanting to do this in the first place... This provides CLI and API implementations for frontend, but no tunnel implementation. It will be added in a future release (just held back for time now - we need this in 3.0 and are running low on time). This is honestly kind of horrifying, but I think it will work. Signed-off-by: Matthew Heon <mheon@redhat.com>
-rw-r--r--README.md14
-rw-r--r--cmd/podman/containers/rename.go56
-rw-r--r--docs/source/Commands.rst2
-rw-r--r--docs/source/managecontainers.rst2
-rw-r--r--docs/source/markdown/podman-container.1.md3
-rw-r--r--docs/source/markdown/podman-rename.1.md38
-rw-r--r--docs/source/markdown/podman.1.md3
-rw-r--r--libpod/runtime_ctr.go149
-rw-r--r--pkg/api/handlers/compat/containers.go31
-rw-r--r--pkg/api/server/register_containers.go62
-rw-r--r--pkg/domain/entities/containers.go6
-rw-r--r--pkg/domain/entities/engine_container.go1
-rw-r--r--pkg/domain/infra/abi/containers.go16
-rw-r--r--pkg/domain/infra/tunnel/containers.go5
-rw-r--r--test/e2e/rename_test.go93
-rw-r--r--test/python/docker/test_containers.py8
16 files changed, 459 insertions, 30 deletions
diff --git a/README.md b/README.md
index 89dd012c7..4dd34d366 100644
--- a/README.md
+++ b/README.md
@@ -71,20 +71,6 @@ A little configuration by an administrator is required before rootless Podman ca
See [Skopeo](https://github.com/containers/skopeo/) for those tasks.
* Support for the Kubernetes CRI interface for container management.
The [CRI-O](https://github.com/cri-o/cri-o) daemon specializes in that.
-* Supporting `docker-compose`. We believe that Kubernetes is the defacto
- standard for composing Pods and for orchestrating containers, making
- Kubernetes YAML a defacto standard file format. Hence, Podman allows the
- creation and execution of Pods from a Kubernetes YAML file (see
- [podman-play-kube](https://github.com/containers/podman/blob/master/docs/source/markdown/podman-play-kube.1.md)).
- Podman can also generate Kubernetes YAML based on a container or Pod (see
- [podman-generate-kube](https://github.com/containers/podman/blob/master/docs/source/markdown/podman-generate-kube.1.md)),
- which allows for an easy transition from a local development environment
- to a production Kubernetes cluster. If Kubernetes does not fit your requirements,
- there are other third-party tools that support the docker-compose format such as
- [kompose](https://github.com/kubernetes/kompose/) and
- [podman-compose](https://github.com/muayyad-alsadi/podman-compose)
- that might be appropriate for your environment. This situation may change with
- the addition of the REST API.
## OCI Projects Plans
diff --git a/cmd/podman/containers/rename.go b/cmd/podman/containers/rename.go
new file mode 100644
index 000000000..9c94e6272
--- /dev/null
+++ b/cmd/podman/containers/rename.go
@@ -0,0 +1,56 @@
+package containers
+
+import (
+ "github.com/containers/podman/v2/cmd/podman/common"
+ "github.com/containers/podman/v2/cmd/podman/registry"
+ "github.com/containers/podman/v2/pkg/domain/entities"
+ "github.com/pkg/errors"
+ "github.com/spf13/cobra"
+)
+
+var (
+ renameDescription = "The podman rename command allows you to rename an existing container"
+ renameCommand = &cobra.Command{
+ Use: "rename CONTAINER NAME",
+ Short: "Rename an existing container",
+ Long: renameDescription,
+ RunE: rename,
+ Args: cobra.ExactArgs(2),
+ ValidArgsFunction: common.AutocompletePortCommand,
+ Example: "podman rename containerA newName",
+ }
+
+ containerRenameCommand = &cobra.Command{
+ Use: renameCommand.Use,
+ Short: renameCommand.Short,
+ Long: renameCommand.Long,
+ RunE: renameCommand.RunE,
+ Args: renameCommand.Args,
+ ValidArgsFunction: renameCommand.ValidArgsFunction,
+ Example: "podman container rename containerA newName",
+ }
+)
+
+func init() {
+ // TODO: Once bindings are done, add this to TunnelMode
+ registry.Commands = append(registry.Commands, registry.CliCommand{
+ Mode: []entities.EngineMode{entities.ABIMode},
+ Command: renameCommand,
+ })
+
+ registry.Commands = append(registry.Commands, registry.CliCommand{
+ Mode: []entities.EngineMode{entities.ABIMode},
+ Command: containerRenameCommand,
+ Parent: containerCmd,
+ })
+}
+
+func rename(cmd *cobra.Command, args []string) error {
+ if len(args) > 2 {
+ return errors.Errorf("must provide at least two arguments to rename")
+ }
+ renameOpts := entities.ContainerRenameOptions{
+ NewName: args[1],
+ }
+ return registry.ContainerEngine().ContainerRename(registry.GetContext(), args[0], renameOpts)
+}
diff --git a/docs/source/Commands.rst b/docs/source/Commands.rst
index cd5d894da..563462377 100644
--- a/docs/source/Commands.rst
+++ b/docs/source/Commands.rst
@@ -75,6 +75,8 @@ Commands
:doc:`push <markdown/podman-push.1>` Push an image to a specified destination
+:doc:`rename <markdown/podman-rename.1>` Rename an existing container
+
:doc:`restart <markdown/podman-restart.1>` Restart one or more containers
:doc:`rm <markdown/podman-rm.1>` Remove one or more containers
diff --git a/docs/source/managecontainers.rst b/docs/source/managecontainers.rst
index 9926f9996..9b3978f25 100644
--- a/docs/source/managecontainers.rst
+++ b/docs/source/managecontainers.rst
@@ -41,6 +41,8 @@ Manage Containers
:doc:`ps <markdown/podman-ps.1>` List containers
+:doc:`rename <markdown/podman-rename.1>` Rename an existing container
+
:doc:`restart <markdown/podman-restart.1>` Restart one or more containers
:doc:`restore <markdown/podman-container-restore.1>` Restores one or more containers from a checkpoint
diff --git a/docs/source/markdown/podman-container.1.md b/docs/source/markdown/podman-container.1.md
index 9da5db601..e85d69c59 100644
--- a/docs/source/markdown/podman-container.1.md
+++ b/docs/source/markdown/podman-container.1.md
@@ -33,6 +33,7 @@ The container command allows you to manage containers
| port | [podman-port(1)](podman-port.1.md) | List port mappings for the container. |
| prune | [podman-container-prune(1)](podman-container-prune.1.md)| Remove all stopped containers from local storage. |
| ps | [podman-ps(1)](podman-ps.1.md) | Prints out information about containers. |
+| rename | [podman-rename(1)](podman-rename.1.md) | Rename an existing container. |
| restart | [podman-restart(1)](podman-restart.1.md) | Restart one or more containers. |
| restore | [podman-container-restore(1)](podman-container-restore.1.md) | Restores one or more containers from a checkpoint. |
| rm | [podman-rm(1)](podman-rm.1.md) | Remove one or more containers. |
@@ -42,7 +43,7 @@ The container command allows you to manage containers
| stats | [podman-stats(1)](podman-stats.1.md) | Display a live stream of one or more container's resource usage statistics. |
| stop | [podman-stop(1)](podman-stop.1.md) | Stop one or more running containers. |
| top | [podman-top(1)](podman-top.1.md) | Display the running processes of a container. |
-| unmount | [podman-unmount(1)](podman-unmount.1.md) | Unmount a working container's root filesystem.(Alias unmount) |
+| unmount | [podman-unmount(1)](podman-unmount.1.md) | Unmount a working container's root filesystem.(Alias unmount) |
| unpause | [podman-unpause(1)](podman-unpause.1.md) | Unpause one or more containers. |
| wait | [podman-wait(1)](podman-wait.1.md) | Wait on one or more containers to stop and print their exit codes. |
diff --git a/docs/source/markdown/podman-rename.1.md b/docs/source/markdown/podman-rename.1.md
new file mode 100644
index 000000000..fdb0dac89
--- /dev/null
+++ b/docs/source/markdown/podman-rename.1.md
@@ -0,0 +1,38 @@
+% podman-rename(1)
+
+## NAME
+podman\-rename - Rename an existing container
+
+## SYNOPSIS
+**podman rename** *container* *newname*
+
+**podman container rename** *container* *newname*
+
+## DESCRIPTION
+Rename changes the name of an existing container.
+The old name will be freed, and will be available for use.
+This command can be run on containers in any state.
+However, running containers may not fully receive the effects until they are restarted - for example, a running container may still use the old name in its logs.
+At present, only containers are supported; pods and volumes cannot be renamed.
+
+## OPTIONS
+
+## EXAMPLES
+
+```
+# Rename a container by name
+$ podman rename oldContainer aNewName
+```
+
+```
+# Rename a container by ID
+$ podman rename 717716c00a6b testcontainer
+```
+
+```
+# Use the container rename alias
+$ podman container rename 6e7514b47180 databaseCtr
+```
+
+## SEE ALSO
+podman(1), podman-create(1), podman-run(1)
diff --git a/docs/source/markdown/podman.1.md b/docs/source/markdown/podman.1.md
index 42054d075..67484c3ec 100644
--- a/docs/source/markdown/podman.1.md
+++ b/docs/source/markdown/podman.1.md
@@ -247,6 +247,7 @@ the exit codes follow the `chroot` standard, see below:
| [podman-ps(1)](podman-ps.1.md) | Prints out information about containers. |
| [podman-pull(1)](podman-pull.1.md) | Pull an image from a registry. |
| [podman-push(1)](podman-push.1.md) | Push an image from local storage to elsewhere. |
+| [podman-rename(1)](podman-rename.1.md) | Rename an existing container. |
| [podman-restart(1)](podman-restart.1.md) | Restart one or more containers. |
| [podman-rm(1)](podman-rm.1.md) | Remove one or more containers. |
| [podman-rmi(1)](podman-rmi.1.md) | Removes one or more locally stored images. |
@@ -259,7 +260,7 @@ the exit codes follow the `chroot` standard, see below:
| [podman-system(1)](podman-system.1.md) | Manage podman. |
| [podman-tag(1)](podman-tag.1.md) | Add an additional name to a local image. |
| [podman-top(1)](podman-top.1.md) | Display the running processes of a container. |
-| [podman-unmount(1)](podman-unmount.1.md) | Unmount a working container's root filesystem. |
+| [podman-unmount(1)](podman-unmount.1.md) | Unmount a working container's root filesystem. |
| [podman-unpause(1)](podman-unpause.1.md) | Unpause one or more containers. |
| [podman-unshare(1)](podman-unshare.1.md) | Run a command inside of a modified user namespace. |
| [podman-untag(1)](podman-untag.1.md) | Removes one or more names from a locally-stored image. |
diff --git a/libpod/runtime_ctr.go b/libpod/runtime_ctr.go
index f22e48746..d2bcd8db3 100644
--- a/libpod/runtime_ctr.go
+++ b/libpod/runtime_ctr.go
@@ -72,6 +72,140 @@ func (r *Runtime) RestoreContainer(ctx context.Context, rSpec *spec.Spec, config
return r.setupContainer(ctx, ctr)
}
+// RenameContainer renames the given container.
+// The given container object will be rendered unusable, and a new, renamed
+// Container will be returned.
+func (r *Runtime) RenameContainer(ctx context.Context, ctr *Container, newName string) (*Container, error) {
+ ctr.lock.Lock()
+ defer ctr.lock.Unlock()
+
+ if err := ctr.syncContainer(); err != nil {
+ return nil, err
+ }
+
+ if newName == "" || !define.NameRegex.MatchString(newName) {
+ return nil, define.RegexError
+ }
+
+ // Check if the name is available.
+ // This is *100% NOT ATOMIC* so any failures in-flight will do
+ // *VERY BAD THINGS* to the state. So we have to try and catch all we
+ // can before starting.
+ if _, err := r.state.LookupContainerID(newName); err == nil {
+ return nil, errors.Wrapf(define.ErrCtrExists, "name %s is already in use by another container", newName)
+ }
+ if _, err := r.state.LookupPod(newName); err == nil {
+ return nil, errors.Wrapf(define.ErrPodExists, "name %s is already in use by another pod", newName)
+ }
+
+ // TODO: Investigate if it is possible to remove this limitation.
+ depCtrs, err := r.state.ContainerInUse(ctr)
+ if err != nil {
+ return nil, err
+ }
+ if len(depCtrs) > 0 {
+ return nil, errors.Wrapf(define.ErrCtrExists, "cannot rename container %s as it is in use by other containers: %v", ctr.ID(), strings.Join(depCtrs, ","))
+ }
+
+ // We need to pull an updated config, in case another rename fired and
+ // the config was re-written.
+ newConf, err := r.state.GetContainerConfig(ctr.ID())
+ if err != nil {
+ return nil, errors.Wrapf(err, "error retrieving container %s configuration from DB to remove", ctr.ID())
+ }
+ ctr.config = newConf
+
+ // TODO: This is going to fail if we have active exec sessions, too.
+ // Investigate fixing that at a later date.
+
+ var pod *Pod
+ if ctr.config.Pod != "" {
+ tmpPod, err := r.state.Pod(ctr.config.Pod)
+ if err != nil {
+ return nil, errors.Wrapf(err, "error retrieving container %s pod", ctr.ID())
+ }
+ pod = tmpPod
+ // Lock pod to ensure it's not removed while we're working
+ pod.lock.Lock()
+ defer pod.lock.Unlock()
+ }
+
+ // Lock all volumes to ensure they are not removed while we're working
+ volsLocked := make(map[string]bool)
+ for _, namedVol := range ctr.config.NamedVolumes {
+ if volsLocked[namedVol.Name] {
+ continue
+ }
+ vol, err := r.state.Volume(namedVol.Name)
+ if err != nil {
+ return nil, errors.Wrapf(err, "error retrieving volume used by container %s", ctr.ID())
+ }
+
+ volsLocked[vol.Name()] = true
+ vol.lock.Lock()
+ defer vol.lock.Unlock()
+ }
+
+ logrus.Infof("Going to rename container %s from %q to %q", ctr.ID(), ctr.Name(), newName)
+
+ // Step 1: remove the old container.
+ if pod != nil {
+ if err := r.state.RemoveContainerFromPod(pod, ctr); err != nil {
+ return nil, errors.Wrapf(err, "error renaming container %s", ctr.ID())
+ }
+ } else {
+ if err := r.state.RemoveContainer(ctr); err != nil {
+ return nil, errors.Wrapf(err, "error renaming container %s", ctr.ID())
+ }
+ }
+
+ // Step 2: Make a new container based on the old one.
+ // TODO: Should we deep-copy the container config and state, to be safe?
+ newCtr := new(Container)
+ newCtr.config = ctr.config
+ newCtr.state = ctr.state
+ newCtr.lock = ctr.lock
+ newCtr.ociRuntime = ctr.ociRuntime
+ newCtr.runtime = r
+ newCtr.rootlessSlirpSyncR = ctr.rootlessSlirpSyncR
+ newCtr.rootlessSlirpSyncW = ctr.rootlessSlirpSyncW
+ newCtr.rootlessPortSyncR = ctr.rootlessPortSyncR
+ newCtr.rootlessPortSyncW = ctr.rootlessPortSyncW
+
+ newCtr.valid = true
+ newCtr.config.Name = newName
+
+ // Step 3: Add that new container to the DB
+ if pod != nil {
+ if err := r.state.AddContainerToPod(pod, newCtr); err != nil {
+ return nil, errors.Wrapf(err, "error renaming container %s", newCtr.ID())
+ }
+ } else {
+ if err := r.state.AddContainer(newCtr); err != nil {
+ return nil, errors.Wrapf(err, "error renaming container %s", newCtr.ID())
+ }
+ }
+
+ // Step 4: Save the new container, to force the state to be written to
+ // the DB. This may not be necessary, depending on DB implementation,
+ // but let's do it to be safe.
+ if err := newCtr.save(); err != nil {
+ return nil, err
+ }
+
+ // Step 5: rename the container in c/storage.
+ // This can fail if the name is already in use by a non-Podman
+ // container. This puts us in a bad spot - we've already renamed the
+ // container in Podman. We can swap the order, but then we have the
+ // opposite problem. Atomicity is a real problem here, with no easy
+ // solution.
+ if err := r.store.SetNames(newCtr.ID(), []string{newCtr.Name()}); err != nil {
+ return nil, err
+ }
+
+ return newCtr, nil
+}
+
func (r *Runtime) initContainerVariables(rSpec *spec.Spec, config *ContainerConfig) (*Container, error) {
if rSpec == nil {
return nil, errors.Wrapf(define.ErrInvalidArg, "must provide a valid runtime spec to create container")
@@ -393,7 +527,7 @@ func (r *Runtime) RemoveContainer(ctx context.Context, c *Container, force bool,
// removePod is used only when removing pods. It instructs Podman to ignore
// infra container protections, and *not* remove from the database (as pod
// remove will handle that).
-func (r *Runtime) removeContainer(ctx context.Context, c *Container, force bool, removeVolume bool, removePod bool) error {
+func (r *Runtime) removeContainer(ctx context.Context, c *Container, force, removeVolume, removePod bool) error {
span, _ := opentracing.StartSpanFromContext(ctx, "removeContainer")
span.SetTag("type", "runtime")
defer span.Finish()
@@ -406,6 +540,18 @@ func (r *Runtime) removeContainer(ctx context.Context, c *Container, force bool,
}
}
+ // We need to refresh container config from the DB, to ensure that any
+ // changes (e.g. a rename) are picked up before we start removing.
+ // Since HasContainer above succeeded, we can safely assume the
+ // container exists.
+ // This is *very iffy* but it should be OK because the container won't
+ // exist once we're done.
+ newConf, err := r.state.GetContainerConfig(c.ID())
+ if err != nil {
+ return errors.Wrapf(err, "error retrieving container %s configuration from DB to remove", c.ID())
+ }
+ c.config = newConf
+
logrus.Debugf("Removing container %s", c.ID())
// We need to lock the pod before we lock the container.
@@ -413,7 +559,6 @@ func (r *Runtime) removeContainer(ctx context.Context, c *Container, force bool,
// Don't need to do this in pod removal case - we're evicting the entire
// pod.
var pod *Pod
- var err error
runtime := c.runtime
if c.config.Pod != "" && !removePod {
pod, err = r.state.Pod(c.config.Pod)
diff --git a/pkg/api/handlers/compat/containers.go b/pkg/api/handlers/compat/containers.go
index 0f89c859e..6e1945db1 100644
--- a/pkg/api/handlers/compat/containers.go
+++ b/pkg/api/handlers/compat/containers.go
@@ -465,3 +465,34 @@ func formatCapabilities(slice []string) {
slice[i] = strings.TrimPrefix(slice[i], "CAP_")
}
}
+
+func RenameContainer(w http.ResponseWriter, r *http.Request) {
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+ decoder := r.Context().Value("decoder").(*schema.Decoder)
+
+ name := utils.GetName(r)
+ query := struct {
+ Name string `schema:"name"`
+ }{}
+ if err := decoder.Decode(&query, r.URL.Query()); err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String()))
+ return
+ }
+
+ ctr, err := runtime.LookupContainer(name)
+ if err != nil {
+ utils.ContainerNotFound(w, name, err)
+ return
+ }
+
+ if _, err := runtime.RenameContainer(r.Context(), ctr, query.Name); err != nil {
+ if errors.Cause(err) == define.ErrPodExists || errors.Cause(err) == define.ErrCtrExists {
+ utils.Error(w, "Something went wrong.", http.StatusConflict, err)
+ return
+ }
+ utils.InternalServerError(w, err)
+ return
+ }
+
+ utils.WriteResponse(w, http.StatusNoContent, nil)
+}
diff --git a/pkg/api/server/register_containers.go b/pkg/api/server/register_containers.go
index b80dea545..74a04b2e6 100644
--- a/pkg/api/server/register_containers.go
+++ b/pkg/api/server/register_containers.go
@@ -291,9 +291,6 @@ func (s *APIServer) registerContainersHandlers(r *mux.Router) error {
r.HandleFunc(VersionedPath("/containers/{name}/pause"), s.APIHandler(compat.PauseContainer)).Methods(http.MethodPost)
// Added non version path to URI to support docker non versioned paths
r.HandleFunc("/containers/{name}/pause", s.APIHandler(compat.PauseContainer)).Methods(http.MethodPost)
- r.HandleFunc(VersionedPath("/containers/{name}/rename"), s.APIHandler(compat.UnsupportedHandler)).Methods(http.MethodPost)
- // Added non version path to URI to support docker non versioned paths
- r.HandleFunc("/containers/{name}/rename", s.APIHandler(compat.UnsupportedHandler)).Methods(http.MethodPost)
// swagger:operation POST /containers/{name}/restart compat restartContainer
// ---
// tags:
@@ -610,6 +607,36 @@ func (s *APIServer) registerContainersHandlers(r *mux.Router) error {
// $ref: "#/responses/InternalError"
r.HandleFunc(VersionedPath("/containers/{name}/export"), s.APIHandler(compat.ExportContainer)).Methods(http.MethodGet)
r.HandleFunc("/containers/{name}/export", s.APIHandler(compat.ExportContainer)).Methods(http.MethodGet)
+ // swagger:operation POST /containers/{name}/rename compat renameContainer
+ // ---
+ // tags:
+ // - containers (compat)
+ // summary: Rename an existing container
+ // description: Change the name of an existing container.
+ // parameters:
+ // - in: path
+ // name: name
+ // type: string
+ // required: true
+ // description: Full or partial ID or full name of the container to rename
+ // - in: query
+ // name: name
+ // type: string
+ // required: true
+ // description: New name for the container
+ // produces:
+ // - application/json
+ // responses:
+ // 204:
+ // description: no error
+ // 404:
+ // $ref: "#/responses/NoSuchContainer"
+ // 409:
+ // $ref: "#/responses/ConflictError"
+ // 500:
+ // $ref: "#/responses/InternalError"
+ r.HandleFunc(VersionedPath("/containers/{name}/rename"), s.APIHandler(compat.RenameContainer)).Methods(http.MethodPost)
+ r.HandleFunc("/containers/{name}/rename", s.APIHandler(compat.RenameContainer)).Methods(http.MethodPost)
/*
libpod endpoints
@@ -1463,5 +1490,34 @@ func (s *APIServer) registerContainersHandlers(r *mux.Router) error {
// 500:
// $ref: "#/responses/InternalError"
r.HandleFunc(VersionedPath("/libpod/containers/{name}/init"), s.APIHandler(libpod.InitContainer)).Methods(http.MethodPost)
+ // swagger:operation POST /libpod/containers/{name}/rename libpod libpodRenameContainer
+ // ---
+ // tags:
+ // - containers
+ // summary: Rename an existing container
+ // description: Change the name of an existing container.
+ // parameters:
+ // - in: path
+ // name: name
+ // type: string
+ // required: true
+ // description: Full or partial ID or full name of the container to rename
+ // - in: query
+ // name: name
+ // type: string
+ // required: true
+ // description: New name for the container
+ // produces:
+ // - application/json
+ // responses:
+ // 204:
+ // description: no error
+ // 404:
+ // $ref: "#/responses/NoSuchContainer"
+ // 409:
+ // $ref: "#/responses/ConflictError"
+ // 500:
+ // $ref: "#/responses/InternalError"
+ r.HandleFunc(VersionedPath("/libpod/containers/{name}/rename"), s.APIHandler(compat.RenameContainer)).Methods(http.MethodPost)
return nil
}
diff --git a/pkg/domain/entities/containers.go b/pkg/domain/entities/containers.go
index 96687b1de..d8576c101 100644
--- a/pkg/domain/entities/containers.go
+++ b/pkg/domain/entities/containers.go
@@ -434,3 +434,9 @@ type ContainerStatsReport struct {
// Results, set when there is no error.
Stats []define.ContainerStats
}
+
+// ContainerRenameOptions describes input options for renaming a container.
+type ContainerRenameOptions struct {
+ // NewName is the new name that will be given to the container.
+ NewName string
+}
diff --git a/pkg/domain/entities/engine_container.go b/pkg/domain/entities/engine_container.go
index 7d38a97f2..d2552770c 100644
--- a/pkg/domain/entities/engine_container.go
+++ b/pkg/domain/entities/engine_container.go
@@ -37,6 +37,7 @@ type ContainerEngine interface {
ContainerPause(ctx context.Context, namesOrIds []string, options PauseUnPauseOptions) ([]*PauseUnpauseReport, error)
ContainerPort(ctx context.Context, nameOrID string, options ContainerPortOptions) ([]*ContainerPortReport, error)
ContainerPrune(ctx context.Context, options ContainerPruneOptions) ([]*reports.PruneReport, error)
+ ContainerRename(ctr context.Context, nameOrID string, options ContainerRenameOptions) error
ContainerRestart(ctx context.Context, namesOrIds []string, options RestartOptions) ([]*RestartReport, error)
ContainerRestore(ctx context.Context, namesOrIds []string, options RestoreOptions) ([]*RestoreReport, error)
ContainerRm(ctx context.Context, namesOrIds []string, options RmOptions) ([]*RmReport, error)
diff --git a/pkg/domain/infra/abi/containers.go b/pkg/domain/infra/abi/containers.go
index b5f5a0e91..a8f4d44a8 100644
--- a/pkg/domain/infra/abi/containers.go
+++ b/pkg/domain/infra/abi/containers.go
@@ -902,7 +902,7 @@ func (ic *ContainerEngine) ContainerRun(ctx context.Context, opts entities.Conta
if err := ic.Libpod.RemoveContainer(ctx, ctr, false, true); err != nil {
if errors.Cause(err) == define.ErrNoSuchCtr ||
errors.Cause(err) == define.ErrCtrRemoved {
- logrus.Warnf("Container %s does not exist: %v", ctr.ID(), err)
+ logrus.Infof("Container %s was already removed, skipping --rm", ctr.ID())
} else {
logrus.Errorf("Error removing container %s: %v", ctr.ID(), err)
}
@@ -1312,3 +1312,17 @@ func (ic *ContainerEngine) ShouldRestart(ctx context.Context, nameOrID string) (
return &entities.BoolReport{Value: ctr.ShouldRestart(ctx)}, nil
}
+
+// ContainerRename renames the given container.
+func (ic *ContainerEngine) ContainerRename(ctx context.Context, nameOrID string, opts entities.ContainerRenameOptions) error {
+ ctr, err := ic.Libpod.LookupContainer(nameOrID)
+ if err != nil {
+ return err
+ }
+
+ if _, err := ic.Libpod.RenameContainer(ctx, ctr, opts.NewName); err != nil {
+ return err
+ }
+
+ return nil
+}
diff --git a/pkg/domain/infra/tunnel/containers.go b/pkg/domain/infra/tunnel/containers.go
index 49bcdec98..8aab4a9cd 100644
--- a/pkg/domain/infra/tunnel/containers.go
+++ b/pkg/domain/infra/tunnel/containers.go
@@ -820,3 +820,8 @@ func (ic *ContainerEngine) ContainerStats(ctx context.Context, namesOrIds []stri
func (ic *ContainerEngine) ShouldRestart(_ context.Context, id string) (bool, error) {
return containers.ShouldRestart(ic.ClientCtx, id, nil)
}
+
+// ContainerRename renames the given container.
+func (ic *ContainerEngine) ContainerRename(ctx context.Context, nameOrID string, opts entities.ContainerRenameOptions) error {
+ return errors.Errorf("NOT YET IMPLEMENTED")
+}
diff --git a/test/e2e/rename_test.go b/test/e2e/rename_test.go
new file mode 100644
index 000000000..324e6a54a
--- /dev/null
+++ b/test/e2e/rename_test.go
@@ -0,0 +1,93 @@
+package integration
+
+import (
+ "fmt"
+ "os"
+
+ . "github.com/containers/podman/v2/test/utils"
+ . "github.com/onsi/ginkgo"
+ . "github.com/onsi/gomega"
+)
+
+var _ = Describe("podman rename", func() {
+ var (
+ tempdir string
+ err error
+ podmanTest *PodmanTestIntegration
+ )
+
+ BeforeEach(func() {
+ SkipIfRemote("Rename not yet implemented by podman-remote")
+ tempdir, err = CreateTempDirInTempDir()
+ if err != nil {
+ os.Exit(1)
+ }
+ podmanTest = PodmanTestCreate(tempdir)
+ podmanTest.Setup()
+ podmanTest.SeedImages()
+ })
+
+ AfterEach(func() {
+ podmanTest.Cleanup()
+ f := CurrentGinkgoTestDescription()
+ processTestResult(f)
+
+ })
+
+ It("podman rename on non-existent container", func() {
+ session := podmanTest.Podman([]string{"rename", "doesNotExist", "aNewName"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Not(Equal(0)))
+ })
+
+ It("Podman rename on existing container with bad name", func() {
+ ctrName := "testCtr"
+ ctr := podmanTest.Podman([]string{"create", "--name", ctrName, ALPINE, "top"})
+ ctr.WaitWithDefaultTimeout()
+ Expect(ctr.ExitCode()).To(Equal(0))
+
+ newName := "invalid<>:char"
+ rename := podmanTest.Podman([]string{"rename", ctrName, newName})
+ rename.WaitWithDefaultTimeout()
+ Expect(rename.ExitCode()).To(Not(Equal(0)))
+
+ ps := podmanTest.Podman([]string{"ps", "-aq", "--filter", fmt.Sprintf("name=%s", ctrName), "--format", "{{ .Names }}"})
+ ps.WaitWithDefaultTimeout()
+ Expect(ps.ExitCode()).To(Equal(0))
+ Expect(ps.OutputToString()).To(ContainSubstring(ctrName))
+ })
+
+ It("Successfully rename a created container", func() {
+ ctrName := "testCtr"
+ ctr := podmanTest.Podman([]string{"create", "--name", ctrName, ALPINE, "top"})
+ ctr.WaitWithDefaultTimeout()
+ Expect(ctr.ExitCode()).To(Equal(0))
+
+ newName := "aNewName"
+ rename := podmanTest.Podman([]string{"rename", ctrName, newName})
+ rename.WaitWithDefaultTimeout()
+ Expect(rename.ExitCode()).To(Equal(0))
+
+ ps := podmanTest.Podman([]string{"ps", "-aq", "--filter", fmt.Sprintf("name=%s", newName), "--format", "{{ .Names }}"})
+ ps.WaitWithDefaultTimeout()
+ Expect(ps.ExitCode()).To(Equal(0))
+ Expect(ps.OutputToString()).To(ContainSubstring(newName))
+ })
+
+ It("Successfully rename a running container", func() {
+ ctrName := "testCtr"
+ ctr := podmanTest.Podman([]string{"run", "-d", "--name", ctrName, ALPINE, "top"})
+ ctr.WaitWithDefaultTimeout()
+ Expect(ctr.ExitCode()).To(Equal(0))
+
+ newName := "aNewName"
+ rename := podmanTest.Podman([]string{"rename", ctrName, newName})
+ rename.WaitWithDefaultTimeout()
+ Expect(rename.ExitCode()).To(Equal(0))
+
+ ps := podmanTest.Podman([]string{"ps", "-aq", "--filter", fmt.Sprintf("name=%s", newName), "--format", "{{ .Names }}"})
+ ps.WaitWithDefaultTimeout()
+ Expect(ps.ExitCode()).To(Equal(0))
+ Expect(ps.OutputToString()).To(ContainSubstring(newName))
+ })
+})
diff --git a/test/python/docker/test_containers.py b/test/python/docker/test_containers.py
index 5a9f761a6..01e049ed4 100644
--- a/test/python/docker/test_containers.py
+++ b/test/python/docker/test_containers.py
@@ -179,11 +179,3 @@ class TestContainers(unittest.TestCase):
filters = {"name": "top"}
ctnrs = self.client.containers.list(all=True, filters=filters)
self.assertEqual(len(ctnrs), 1)
-
- def test_rename_container(self):
- top = self.client.containers.get(TestContainers.topContainerId)
-
- # rename bogus container
- with self.assertRaises(errors.APIError) as error:
- top.rename(name="newname")
- self.assertEqual(error.exception.response.status_code, 404)