mirror of https://github.com/docker/compose.git
Add --force to rm on ACI
If a container is running the user must force the removal of the container.
This commit is contained in:
parent
c049f3c7af
commit
093a69136f
|
@ -49,11 +49,9 @@ const (
|
||||||
composeContainerTag = "docker-compose-application"
|
composeContainerTag = "docker-compose-application"
|
||||||
composeContainerSeparator = "_"
|
composeContainerSeparator = "_"
|
||||||
statusUnknown = "Unknown"
|
statusUnknown = "Unknown"
|
||||||
|
statusRunning = "Running"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ErrNoSuchContainer is returned when the mentioned container does not exist
|
|
||||||
var ErrNoSuchContainer = errors.New("no such container")
|
|
||||||
|
|
||||||
// ContextParams options for creating ACI context
|
// ContextParams options for creating ACI context
|
||||||
type ContextParams struct {
|
type ContextParams struct {
|
||||||
Description string
|
Description string
|
||||||
|
@ -280,18 +278,46 @@ func (cs *aciContainerService) Logs(ctx context.Context, containerName string, r
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cs *aciContainerService) Delete(ctx context.Context, containerID string, _ bool) error {
|
func (cs *aciContainerService) Delete(ctx context.Context, containerID string, request containers.DeleteRequest) error {
|
||||||
groupName, containerName := getGroupAndContainerName(containerID)
|
groupName, containerName := getGroupAndContainerName(containerID)
|
||||||
if groupName != containerID {
|
if groupName != containerID {
|
||||||
msg := "cannot delete service %q from compose application %q, you can delete the entire compose app with docker compose down --project-name %s"
|
msg := "cannot delete service %q from compose application %q, you can delete the entire compose app with docker compose down --project-name %s"
|
||||||
return errors.New(fmt.Sprintf(msg, containerName, groupName, groupName))
|
return errors.New(fmt.Sprintf(msg, containerName, groupName, groupName))
|
||||||
}
|
}
|
||||||
cg, err := deleteACIContainerGroup(ctx, cs.ctx, groupName)
|
|
||||||
|
containerGroupsClient, err := getContainerGroupsClient(cs.ctx.SubscriptionID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !request.Force {
|
||||||
|
cg, err := containerGroupsClient.Get(ctx, cs.ctx.ResourceGroup, groupName)
|
||||||
|
if err != nil {
|
||||||
|
if cg.StatusCode == http.StatusNotFound {
|
||||||
|
return errdefs.ErrNotFound
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, container := range *cg.Containers {
|
||||||
|
status := statusUnknown
|
||||||
|
if container.InstanceView != nil && container.InstanceView.CurrentState != nil {
|
||||||
|
status = *container.InstanceView.CurrentState.State
|
||||||
|
}
|
||||||
|
|
||||||
|
if status == statusRunning {
|
||||||
|
return errdefs.ErrForbidden
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cg, err := deleteACIContainerGroup(ctx, cs.ctx, groupName)
|
||||||
|
// Delete returns `StatusNoContent` if the group is not found
|
||||||
if cg.StatusCode == http.StatusNoContent {
|
if cg.StatusCode == http.StatusNoContent {
|
||||||
return ErrNoSuchContainer
|
return errdefs.ErrNotFound
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return err
|
return err
|
||||||
|
@ -305,7 +331,7 @@ func (cs *aciContainerService) Inspect(ctx context.Context, containerID string)
|
||||||
return containers.Container{}, err
|
return containers.Container{}, err
|
||||||
}
|
}
|
||||||
if cg.StatusCode == http.StatusNoContent {
|
if cg.StatusCode == http.StatusNoContent {
|
||||||
return containers.Container{}, ErrNoSuchContainer
|
return containers.Container{}, errdefs.ErrNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
var cc containerinstance.Container
|
var cc containerinstance.Container
|
||||||
|
@ -318,7 +344,7 @@ func (cs *aciContainerService) Inspect(ctx context.Context, containerID string)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !found {
|
if !found {
|
||||||
return containers.Container{}, ErrNoSuchContainer
|
return containers.Container{}, errdefs.ErrNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
return convert.ContainerGroupToContainer(containerID, cg, cc)
|
return convert.ContainerGroupToContainer(containerID, cg, cc)
|
||||||
|
@ -362,7 +388,7 @@ func (cs *aciComposeService) Down(ctx context.Context, opts cli.ProjectOptions)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if cg.StatusCode == http.StatusNoContent {
|
if cg.StatusCode == http.StatusNoContent {
|
||||||
return ErrNoSuchContainer
|
return errdefs.ErrNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -41,7 +41,7 @@ func TestGetContainerName(t *testing.T) {
|
||||||
|
|
||||||
func TestErrorMessageDeletingContainerFromComposeApplication(t *testing.T) {
|
func TestErrorMessageDeletingContainerFromComposeApplication(t *testing.T) {
|
||||||
service := aciContainerService{}
|
service := aciContainerService{}
|
||||||
err := service.Delete(context.TODO(), "compose-app_service1", false)
|
err := service.Delete(context.TODO(), "compose-app_service1", containers.DeleteRequest{Force: false})
|
||||||
assert.Error(t, err, "cannot delete service \"service1\" from compose application \"compose-app\", you can delete the entire compose app with docker compose down --project-name compose-app")
|
assert.Error(t, err, "cannot delete service \"service1\" from compose application \"compose-app\", you can delete the entire compose app with docker compose down --project-name compose-app")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,8 @@ import (
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
"github.com/docker/api/client"
|
"github.com/docker/api/client"
|
||||||
|
"github.com/docker/api/containers"
|
||||||
|
"github.com/docker/api/errdefs"
|
||||||
"github.com/docker/api/multierror"
|
"github.com/docker/api/multierror"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -57,11 +59,20 @@ func runRm(ctx context.Context, args []string, opts rmOpts) error {
|
||||||
|
|
||||||
var errs *multierror.Error
|
var errs *multierror.Error
|
||||||
for _, id := range args {
|
for _, id := range args {
|
||||||
err := c.ContainerService().Delete(ctx, id, opts.force)
|
err := c.ContainerService().Delete(ctx, id, containers.DeleteRequest{
|
||||||
|
Force: opts.force,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if errdefs.IsForbiddenError(err) {
|
||||||
|
errs = multierror.Append(errs, fmt.Errorf("you cannot remove a running container %s. Stop the container before attempting removal or force remove", id))
|
||||||
|
} else if errdefs.IsNotFoundError(err) {
|
||||||
|
errs = multierror.Append(errs, fmt.Errorf("container %s not found", id))
|
||||||
|
} else {
|
||||||
errs = multierror.Append(errs, err)
|
errs = multierror.Append(errs, err)
|
||||||
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println(id)
|
fmt.Println(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
12
cli/main.go
12
cli/main.go
|
@ -183,6 +183,7 @@ func main() {
|
||||||
if errors.Is(ctx.Err(), context.Canceled) {
|
if errors.Is(ctx.Err(), context.Canceled) {
|
||||||
os.Exit(130)
|
os.Exit(130)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Context should always be handled by new CLI
|
// Context should always be handled by new CLI
|
||||||
requiredCmd, _, _ := root.Find(os.Args[1:])
|
requiredCmd, _, _ := root.Find(os.Args[1:])
|
||||||
if requiredCmd != nil && isOwnCommand(requiredCmd) {
|
if requiredCmd != nil && isOwnCommand(requiredCmd) {
|
||||||
|
@ -191,6 +192,7 @@ func main() {
|
||||||
mobycli.ExecIfDefaultCtxType(ctx)
|
mobycli.ExecIfDefaultCtxType(ctx)
|
||||||
|
|
||||||
checkIfUnknownCommandExistInDefaultContext(err, currentContext)
|
checkIfUnknownCommandExistInDefaultContext(err, currentContext)
|
||||||
|
|
||||||
exit(err)
|
exit(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -203,6 +205,11 @@ func exit(err error) {
|
||||||
fatal(err)
|
fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func fatal(err error) {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
func checkIfUnknownCommandExistInDefaultContext(err error, currentContext string) {
|
func checkIfUnknownCommandExistInDefaultContext(err error, currentContext string) {
|
||||||
submatch := unknownCommandRegexp.FindSubmatch([]byte(err.Error()))
|
submatch := unknownCommandRegexp.FindSubmatch([]byte(err.Error()))
|
||||||
if len(submatch) == 2 {
|
if len(submatch) == 2 {
|
||||||
|
@ -241,8 +248,3 @@ func determineCurrentContext(flag string, configDir string) string {
|
||||||
}
|
}
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
func fatal(err error) {
|
|
||||||
fmt.Fprintln(os.Stderr, err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
|
@ -105,6 +105,11 @@ type LogsRequest struct {
|
||||||
Writer io.Writer
|
Writer io.Writer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeleteRequest contains configuration about a delete request
|
||||||
|
type DeleteRequest struct {
|
||||||
|
Force bool
|
||||||
|
}
|
||||||
|
|
||||||
// Service interacts with the underlying container backend
|
// Service interacts with the underlying container backend
|
||||||
type Service interface {
|
type Service interface {
|
||||||
// List returns all the containers
|
// List returns all the containers
|
||||||
|
@ -118,7 +123,7 @@ type Service interface {
|
||||||
// Logs returns all the logs of a container
|
// Logs returns all the logs of a container
|
||||||
Logs(ctx context.Context, containerName string, request LogsRequest) error
|
Logs(ctx context.Context, containerName string, request LogsRequest) error
|
||||||
// Delete removes containers
|
// Delete removes containers
|
||||||
Delete(ctx context.Context, id string, force bool) error
|
Delete(ctx context.Context, containerID string, request DeleteRequest) error
|
||||||
// Inspect get a specific container
|
// Inspect get a specific container
|
||||||
Inspect(ctx context.Context, id string) (Container, error)
|
Inspect(ctx context.Context, id string) (Container, error)
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,15 +24,13 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/compose-spec/compose-go/cli"
|
"github.com/compose-spec/compose-go/cli"
|
||||||
|
|
||||||
"github.com/docker/api/context/cloud"
|
|
||||||
"github.com/docker/api/errdefs"
|
|
||||||
|
|
||||||
ecstypes "github.com/docker/ecs-plugin/pkg/compose"
|
ecstypes "github.com/docker/ecs-plugin/pkg/compose"
|
||||||
|
|
||||||
"github.com/docker/api/backend"
|
"github.com/docker/api/backend"
|
||||||
"github.com/docker/api/compose"
|
"github.com/docker/api/compose"
|
||||||
"github.com/docker/api/containers"
|
"github.com/docker/api/containers"
|
||||||
|
"github.com/docker/api/context/cloud"
|
||||||
|
"github.com/docker/api/errdefs"
|
||||||
)
|
)
|
||||||
|
|
||||||
type apiService struct {
|
type apiService struct {
|
||||||
|
@ -108,8 +106,8 @@ func (cs *containerService) Logs(ctx context.Context, containerName string, requ
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cs *containerService) Delete(ctx context.Context, id string, force bool) error {
|
func (cs *containerService) Delete(ctx context.Context, id string, request containers.DeleteRequest) error {
|
||||||
fmt.Printf("Deleting container %q with force = %t\n", id, force)
|
fmt.Printf("Deleting container %q with force = %t\n", id, request.Force)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -249,9 +249,9 @@ func (ms *local) Logs(ctx context.Context, containerName string, request contain
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ms *local) Delete(ctx context.Context, containerID string, force bool) error {
|
func (ms *local) Delete(ctx context.Context, containerID string, request containers.DeleteRequest) error {
|
||||||
err := ms.apiClient.ContainerRemove(ctx, containerID, types.ContainerRemoveOptions{
|
err := ms.apiClient.ContainerRemove(ctx, containerID, types.ContainerRemoveOptions{
|
||||||
Force: force,
|
Force: request.Force,
|
||||||
})
|
})
|
||||||
if client.IsErrNotFound(err) {
|
if client.IsErrNotFound(err) {
|
||||||
return errors.Wrapf(errdefs.ErrNotFound, "container %q", containerID)
|
return errors.Wrapf(errdefs.ErrNotFound, "container %q", containerID)
|
||||||
|
|
|
@ -77,7 +77,9 @@ func (p *proxy) Inspect(ctx context.Context, request *containersv1.InspectReques
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *proxy) Delete(ctx context.Context, request *containersv1.DeleteRequest) (*containersv1.DeleteResponse, error) {
|
func (p *proxy) Delete(ctx context.Context, request *containersv1.DeleteRequest) (*containersv1.DeleteResponse, error) {
|
||||||
return &containersv1.DeleteResponse{}, Client(ctx).ContainerService().Delete(ctx, request.Id, request.Force)
|
return &containersv1.DeleteResponse{}, Client(ctx).ContainerService().Delete(ctx, request.Id, containers.DeleteRequest{
|
||||||
|
Force: request.Force,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *proxy) Exec(ctx context.Context, request *containersv1.ExecRequest) (*containersv1.ExecResponse, error) {
|
func (p *proxy) Exec(ctx context.Context, request *containersv1.ExecRequest) (*containersv1.ExecResponse, error) {
|
||||||
|
|
|
@ -265,9 +265,21 @@ func TestContainerRun(t *testing.T) {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("rm", func(t *testing.T) {
|
t.Run("rm a running container", func(t *testing.T) {
|
||||||
res := c.RunDockerCmd("rm", container)
|
res := c.RunDockerCmd("rm", container)
|
||||||
res.Assert(t, icmd.Expected{Out: container})
|
res.Assert(t, icmd.Expected{
|
||||||
|
Err: fmt.Sprintf("Error: You cannot remove a running container %s. Stop the container before attempting removal or force remove\n", container),
|
||||||
|
ExitCode: 1,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("force rm", func(t *testing.T) {
|
||||||
|
res := c.RunDockerCmd("rm", "-f", container)
|
||||||
|
res.Assert(t, icmd.Expected{
|
||||||
|
Out: container,
|
||||||
|
ExitCode: 0,
|
||||||
|
})
|
||||||
|
|
||||||
checkStopped := func(t poll.LogT) poll.Result {
|
checkStopped := func(t poll.LogT) poll.Result {
|
||||||
res := c.RunDockerCmd("inspect", container)
|
res := c.RunDockerCmd("inspect", container)
|
||||||
if res.ExitCode == 1 {
|
if res.ExitCode == 1 {
|
||||||
|
@ -349,7 +361,7 @@ func TestContainerRunAttached(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("rm attached", func(t *testing.T) {
|
t.Run("rm attached", func(t *testing.T) {
|
||||||
res := c.RunDockerCmd("rm", container)
|
res := c.RunDockerCmd("rm", "-f", container)
|
||||||
res.Assert(t, icmd.Expected{Out: container})
|
res.Assert(t, icmd.Expected{Out: container})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue