Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix handling of container remove #9014

Merged
merged 1 commit into from
Jan 20, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions cmd/podman/containers/rm.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package containers
import (
"context"
"fmt"
"io/ioutil"
"strings"

"github.com/containers/common/pkg/completion"
Expand Down Expand Up @@ -54,6 +55,7 @@ var (

var (
rmOptions = entities.RmOptions{}
cidFiles = []string{}
)

func rmFlags(cmd *cobra.Command) {
Expand All @@ -65,7 +67,7 @@ func rmFlags(cmd *cobra.Command) {
flags.BoolVarP(&rmOptions.Volumes, "volumes", "v", false, "Remove anonymous volumes associated with the container")

cidfileFlagName := "cidfile"
flags.StringArrayVarP(&rmOptions.CIDFiles, cidfileFlagName, "", nil, "Read the container ID from the file")
flags.StringArrayVar(&cidFiles, cidfileFlagName, nil, "Read the container ID from the file")
_ = cmd.RegisterFlagCompletionFunc(cidfileFlagName, completion.AutocompleteDefault)

if !registry.IsRemote() {
Expand All @@ -92,7 +94,16 @@ func init() {
validate.AddLatestFlag(containerRmCommand, &rmOptions.Latest)
}

func rm(_ *cobra.Command, args []string) error {
func rm(cmd *cobra.Command, args []string) error {
for _, cidFile := range cidFiles {
content, err := ioutil.ReadFile(string(cidFile))
if err != nil {
return errors.Wrap(err, "error reading CIDFile")
}
id := strings.Split(string(content), "\n")[0]
args = append(args, id)
}

return removeContainers(args, rmOptions, true)
}

Expand Down
45 changes: 23 additions & 22 deletions pkg/api/handlers/compat/containers.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ import (
"github.com/containers/podman/v2/libpod/define"
"github.com/containers/podman/v2/pkg/api/handlers"
"github.com/containers/podman/v2/pkg/api/handlers/utils"
"github.com/containers/podman/v2/pkg/domain/entities"
"github.com/containers/podman/v2/pkg/domain/filters"
"github.com/containers/podman/v2/pkg/domain/infra/abi"
"github.com/containers/podman/v2/pkg/ps"
"github.com/containers/podman/v2/pkg/signal"
"github.com/docker/docker/api/types"
Expand All @@ -29,9 +31,11 @@ import (
func RemoveContainer(w http.ResponseWriter, r *http.Request) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the compat endpoint. We shouldn't be modifying its parameters.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the only endpoint we have for removing containers. All of the Docker parameters still work, is it invalid to use additional libpod params on a compat endpoint?

@baude @jwhonce WDYT?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can make it conditional which is something other endpoints are doing. utils.IsLibpodRequest(r) returns a boolean that indicates if the request was made against a libpod/... endpoint.

decoder := r.Context().Value("decoder").(*schema.Decoder)
query := struct {
Force bool `schema:"force"`
Vols bool `schema:"v"`
Link bool `schema:"link"`
All bool `schema:"all"`
Force bool `schema:"force"`
Ignore bool `schema:"ignore"`
Link bool `schema:"link"`
Volumes bool `schema:"v"`
}{
// override any golang type defaults
}
Expand All @@ -49,34 +53,31 @@ func RemoveContainer(w http.ResponseWriter, r *http.Request) {
}

runtime := r.Context().Value("runtime").(*libpod.Runtime)
// Now use the ABI implementation to prevent us from having duplicate
// code.
containerEngine := abi.ContainerEngine{Libpod: runtime}
name := utils.GetName(r)
con, err := runtime.LookupContainer(name)
if err != nil && errors.Cause(err) == define.ErrNoSuchCtr {
// Failed to get container. If force is specified, get the container's ID
// and evict it
if !query.Force {
options := entities.RmOptions{
All: query.All,
Force: query.Force,
Volumes: query.Volumes,
Ignore: query.Ignore,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit, alpha order this plz.

}
report, err := containerEngine.ContainerRm(r.Context(), []string{name}, options)
if err != nil {
if errors.Cause(err) == define.ErrNoSuchCtr {
utils.ContainerNotFound(w, name, err)
return
}

if _, err := runtime.EvictContainer(r.Context(), name, query.Vols); err != nil {
if errors.Cause(err) == define.ErrNoSuchCtr {
logrus.Debugf("Ignoring error (--allow-missing): %q", err)
w.WriteHeader(http.StatusNoContent)
return
}
logrus.Warn(errors.Wrapf(err, "failed to evict container: %q", name))
utils.InternalServerError(w, err)
return
}
w.WriteHeader(http.StatusNoContent)
utils.InternalServerError(w, err)
return
}

if err := runtime.RemoveContainer(r.Context(), con, query.Force, query.Vols); err != nil {
utils.InternalServerError(w, err)
if report[0].Err != nil {
utils.InternalServerError(w, report[0].Err)
return
}

utils.WriteResponse(w, http.StatusNoContent, nil)
}

Expand Down
12 changes: 10 additions & 2 deletions pkg/bindings/containers/containers.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,10 @@ func Prune(ctx context.Context, options *PruneOptions) ([]*reports.PruneReport,
}

// Remove removes a container from local storage. The force bool designates
// that the container should be removed forcibly (example, even it is running). The volumes
// bool dictates that a container's volumes should also be removed.
// that the container should be removed forcibly (example, even it is running).
// The volumes bool dictates that a container's volumes should also be removed.
// The All option indicates that all containers should be removed
// The Ignore option indicates that if a container did not exist, ignore the error
func Remove(ctx context.Context, nameOrID string, options *RemoveOptions) error {
if options == nil {
options = new(RemoveOptions)
Expand All @@ -85,9 +87,15 @@ func Remove(ctx context.Context, nameOrID string, options *RemoveOptions) error
if v := options.GetVolumes(); options.Changed("Volumes") {
params.Set("v", strconv.FormatBool(v))
}
if all := options.GetAll(); options.Changed("All") {
params.Set("all", strconv.FormatBool(all))
}
if force := options.GetForce(); options.Changed("Force") {
params.Set("force", strconv.FormatBool(force))
}
if ignore := options.GetIgnore(); options.Changed("Ignore") {
params.Set("ignore", strconv.FormatBool(ignore))
}
response, err := conn.DoRequest(nil, http.MethodDelete, "/containers/%s", params, nil, nameOrID)
if err != nil {
return err
Expand Down
2 changes: 2 additions & 0 deletions pkg/bindings/containers/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,8 @@ type PruneOptions struct {
//go:generate go run ../generator/generator.go RemoveOptions
// RemoveOptions are optional options for removing containers
type RemoveOptions struct {
All *bool
Ignore *bool
Force *bool
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm going to assume that Tom will ask you to sort those

Volumes *bool
}
Expand Down
32 changes: 32 additions & 0 deletions pkg/bindings/containers/types_remove_options.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,38 @@ func (o *RemoveOptions) ToParams() (url.Values, error) {
return params, nil
}

// WithAll
func (o *RemoveOptions) WithAll(value bool) *RemoveOptions {
v := &value
o.All = v
return o
}

// GetAll
func (o *RemoveOptions) GetAll() bool {
var all bool
if o.All == nil {
return all
}
return *o.All
}

// WithIgnore
func (o *RemoveOptions) WithIgnore(value bool) *RemoveOptions {
v := &value
o.Ignore = v
return o
}

// GetIgnore
func (o *RemoveOptions) GetIgnore() bool {
var ignore bool
if o.Ignore == nil {
return ignore
}
return *o.Ignore
}

// WithForce
func (o *RemoveOptions) WithForce(value bool) *RemoveOptions {
v := &value
Expand Down
11 changes: 5 additions & 6 deletions pkg/domain/entities/containers.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,12 +128,11 @@ type RestartReport struct {
}

type RmOptions struct {
All bool
CIDFiles []string
Force bool
Ignore bool
Latest bool
Volumes bool
All bool
Force bool
Ignore bool
Latest bool
Volumes bool
}

type RmReport struct {
Expand Down
30 changes: 15 additions & 15 deletions pkg/domain/infra/abi/containers.go
Original file line number Diff line number Diff line change
Expand Up @@ -264,30 +264,30 @@ func (ic *ContainerEngine) ContainerRm(ctx context.Context, namesOrIds []string,
reports := []*entities.RmReport{}

names := namesOrIds
for _, cidFile := range options.CIDFiles {
content, err := ioutil.ReadFile(cidFile)
if err != nil {
return nil, errors.Wrap(err, "error reading CIDFile")
}
id := strings.Split(string(content), "\n")[0]
names = append(names, id)
}

// Attempt to remove named containers directly from storage, if container is defined in libpod
// this will fail and code will fall through to removing the container from libpod.`
tmpNames := []string{}
for _, ctr := range names {
report := entities.RmReport{Id: ctr}
if err := ic.Libpod.RemoveStorageContainer(ctr, options.Force); err != nil {
report.Err = ic.Libpod.RemoveStorageContainer(ctr, options.Force)
switch errors.Cause(report.Err) {
case nil:
// remove container names that we successfully deleted
tmpNames = append(tmpNames, ctr)
} else {
reports = append(reports, &report)
case define.ErrNoSuchCtr:
// There is still a potential this is a libpod container
tmpNames = append(tmpNames, ctr)
mheon marked this conversation as resolved.
Show resolved Hide resolved
default:
if _, err := ic.Libpod.LookupContainer(ctr); errors.Cause(err) == define.ErrNoSuchCtr {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This bit seems unnecessary - I don't see any way we fail in a fashion that's not ErrNoSuchCtr before RemoveStorageContainer checks Libpod to see if the container exists.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The use case we are trying to fix #8735, is the case where we created a buildah container that is in use, and fails. This would end up in this block, and now we check to see if it is a libpod container. Since it is not, we return the error.
Thus we end up with an error telling us the container is in use, and the user has to add --force.
In the current code we fail over to trying to remove a libpod container, and we report
container does not exist.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure you're right here - the check for whether a container is a Libpod container in RemoveStorageContainer happens before we try to remove the container, so we should already have gotten an ErrCtrExists

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you run @edsantiago test you will see this happen.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then we need to catch this inside RemoveStorageContainer - why work around broken behavior when we can just fix the broken API

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RemoveStorageContainer is not broken, it is returning a valid error, that we are returning to the user. The problem is we are ignoring the error and then falling through to the libpod remove, which finds that there is no Container libpod container, and reports container does not exists.

// remove container failed, but not a libpod container
reports = append(reports, &report)
continue
}
// attempt to remove as a libpod container
tmpNames = append(tmpNames, ctr)
}
}
if len(tmpNames) < len(names) {
names = tmpNames
}
names = tmpNames

ctrs, err := getContainersByContext(options.All, options.Latest, names, ic.Libpod)
if err != nil && !(options.Ignore && errors.Cause(err) == define.ErrNoSuchCtr) {
Expand Down
8 changes: 0 additions & 8 deletions pkg/domain/infra/tunnel/containers.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,14 +173,6 @@ func (ic *ContainerEngine) ContainerRestart(ctx context.Context, namesOrIds []st
}

func (ic *ContainerEngine) ContainerRm(ctx context.Context, namesOrIds []string, opts entities.RmOptions) ([]*entities.RmReport, error) {
for _, cidFile := range opts.CIDFiles {
content, err := ioutil.ReadFile(cidFile)
if err != nil {
return nil, errors.Wrap(err, "error reading CIDFile")
}
id := strings.Split(string(content), "\n")[0]
namesOrIds = append(namesOrIds, id)
}
ctrs, err := getContainersByContext(ic.ClientCtx, opts.All, opts.Ignore, namesOrIds)
if err != nil {
return nil, err
Expand Down
34 changes: 34 additions & 0 deletions test/e2e/rm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,40 @@ var _ = Describe("Podman rm", func() {
Expect(result.ExitCode()).To(Equal(125))
})

It("podman rm --all", func() {
session := podmanTest.Podman([]string{"create", ALPINE, "ls"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
Expect(podmanTest.NumberOfContainers()).To(Equal(1))

session = podmanTest.Podman([]string{"create", ALPINE, "ls"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
Expect(podmanTest.NumberOfContainers()).To(Equal(2))

session = podmanTest.Podman([]string{"rm", "--all"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
Expect(podmanTest.NumberOfContainers()).To(Equal(0))
})

It("podman rm --ignore", func() {
session := podmanTest.Podman([]string{"create", ALPINE, "ls"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
cid := session.OutputToStringArray()[0]
Expect(podmanTest.NumberOfContainers()).To(Equal(1))

session = podmanTest.Podman([]string{"rm", "bogus", cid})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(1))

session = podmanTest.Podman([]string{"rm", "--ignore", "bogus", cid})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
Expect(podmanTest.NumberOfContainers()).To(Equal(0))
})

It("podman rm bogus container", func() {
session := podmanTest.Podman([]string{"rm", "bogus"})
session.WaitWithDefaultTimeout()
Expand Down
7 changes: 5 additions & 2 deletions test/system/040-ps.bats
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,11 @@ EOF
run_podman ps --storage -a
is "${#lines[@]}" "2" "podman ps -a --storage sees buildah container"

# This is what deletes the container
# FIXME: why doesn't "podman rm --storage $cid" do anything?
# We can't rm it without -f, but podman should issue a helpful message
run_podman 2 rm "$cid"
is "$output" "Error: container .* is mounted and cannot be removed without using force: container state improper" "podman rm <buildah container> without -f"

# With -f, we can remove it.
run_podman rm -f "$cid"

run_podman ps --storage -a
Expand Down