diff options
author | OpenShift Merge Robot <openshift-merge-robot@users.noreply.github.com> | 2021-01-14 22:03:01 -0500 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-01-14 22:03:01 -0500 |
commit | 3fcf346890c0437611fc18c30d58cc2d9f61fe6c (patch) | |
tree | a948999192d2089474214f4307eb454a9d0c2c59 | |
parent | 8ce9995951b14a0a4d7252cdd97597411fd5f980 (diff) | |
parent | 997de2f8e9e5453a99108bde012aa6c41d7323ec (diff) | |
download | podman-3fcf346890c0437611fc18c30d58cc2d9f61fe6c.tar.gz podman-3fcf346890c0437611fc18c30d58cc2d9f61fe6c.tar.bz2 podman-3fcf346890c0437611fc18c30d58cc2d9f61fe6c.zip |
Merge pull request #8955 from mheon/rename
Container Rename
-rw-r--r-- | README.md | 14 | ||||
-rw-r--r-- | cmd/podman/containers/rename.go | 56 | ||||
-rw-r--r-- | docs/source/Commands.rst | 2 | ||||
-rw-r--r-- | docs/source/managecontainers.rst | 2 | ||||
-rw-r--r-- | docs/source/markdown/podman-container.1.md | 3 | ||||
-rw-r--r-- | docs/source/markdown/podman-rename.1.md | 38 | ||||
-rw-r--r-- | docs/source/markdown/podman.1.md | 3 | ||||
-rw-r--r-- | libpod/runtime_ctr.go | 149 | ||||
-rw-r--r-- | pkg/api/handlers/compat/containers.go | 31 | ||||
-rw-r--r-- | pkg/api/server/register_containers.go | 62 | ||||
-rw-r--r-- | pkg/domain/entities/containers.go | 6 | ||||
-rw-r--r-- | pkg/domain/entities/engine_container.go | 1 | ||||
-rw-r--r-- | pkg/domain/infra/abi/containers.go | 16 | ||||
-rw-r--r-- | pkg/domain/infra/tunnel/containers.go | 5 | ||||
-rw-r--r-- | test/e2e/rename_test.go | 93 |
15 files changed, 459 insertions, 22 deletions
@@ -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)) + }) +}) |