Skip to content

Commit

Permalink
Added fix for container prune and volume prune(issue containerd#648) …
Browse files Browse the repository at this point in the history
…and added changes to README.md

Signed-off-by: Raymond Mathew <[email protected]>
  • Loading branch information
click2cloud-lamda committed May 6, 2022
1 parent 0274bda commit cffa9e4
Show file tree
Hide file tree
Showing 7 changed files with 470 additions and 1 deletion.
22 changes: 21 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,7 @@ It does not necessarily mean that the corresponding features are missing in cont
- [:whale: nerdctl cp](#whale-nerdctl-cp)
- [:whale: :blue_square: nerdctl ps](#whale-blue_square-nerdctl-ps)
- [:whale: :blue_square: nerdctl inspect](#whale-blue_square-nerdctl-inspect)
- [:whale: :blue_square: nerdctl container prune](#whale-blue_square-nerdctl-container-prune)
- [:whale: nerdctl logs](#whale-nerdctl-logs)
- [:whale: nerdctl port](#whale-nerdctl-port)
- [:whale: nerdctl rm](#whale-nerdctl-rm)
Expand Down Expand Up @@ -297,6 +298,7 @@ It does not necessarily mean that the corresponding features are missing in cont
- [:whale: nerdctl volume ls](#whale-nerdctl-volume-ls)
- [:whale: nerdctl volume inspect](#whale-nerdctl-volume-inspect)
- [:whale: nerdctl volume rm](#whale-nerdctl-volume-rm)
- [:whale: nerdctl volume prune](#whale-nerdctl-volume-prune)
- [Namespace management](#namespace-management)
- [:nerd_face: :blue_square: nerdctl namespace create](#nerd_face-blue_square-nerdctl-namespace-create)
- [:nerd_face: :blue_square: nerdctl namespace inspect](#nerd_face-blue_square-nerdctl-namespace-inspect)
Expand Down Expand Up @@ -686,6 +688,15 @@ Flags:

Unimplemented `docker inspect` flags: `--size`

### :whale: :blue_square: nerdctl container prune
Removes all stopped containers

Usage: `nerdctl container prune [OPTIONS] NAME|ID [NAME|ID...]`

Flags:
- :whale: `-f, --force`: Do not prompt for confirmation
- :whale: `--filter`: Provide filter values (e.g. 'until=<timestamp>')

### :whale: nerdctl logs
Fetch the logs of a container.

Expand Down Expand Up @@ -1133,6 +1144,16 @@ Usage: `nerdctl volume rm [OPTIONS] VOLUME [VOLUME...]`
- :whale: `-f, --force`: Force the removal of one or more volumes
- :warning: WIP: currently, volumes are always forcibly removed, even when `--force` is not specified.

### :whale: nerdctl volume prune
Remove all unused local volumes. Unused local volumes are those which are not referenced by any containers

Usage: `nerdctl volume prune [OPTIONS]`

Flags:
- :whale: `-f, --force`: Do not prompt for confirmation
- :whale: `--filter`: Provide filter values (e.g. 'label=<label>')


## Namespace management

### :nerd_face: :blue_square: nerdctl namespace create
Expand Down Expand Up @@ -1457,7 +1478,6 @@ Container management:
- `docker attach`
- `docker diff`

- `docker container prune`

- `docker checkpoint *`

Expand Down
1 change: 1 addition & 0 deletions cmd/nerdctl/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ func newContainerCommand() *cobra.Command {
newWaitCommand(),
newUnpauseCommand(),
newCommitCommand(),
newContainerPruneCommand(),
newRenameCommand(),
)
addCpCommand(containerCommand)
Expand Down
256 changes: 256 additions & 0 deletions cmd/nerdctl/container_prune.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,256 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package main

import (
"context"
"fmt"
"math"
"strconv"
"strings"
"time"

"github.com/containerd/containerd"
"github.com/containerd/nerdctl/pkg/containerinspector"
"github.com/containerd/nerdctl/pkg/namestore"
"github.com/containerd/nerdctl/pkg/strutil"
"github.com/spf13/cobra"
)

func newContainerPruneCommand() *cobra.Command {
containerPruneCommand := &cobra.Command{
Use: "prune [flags]",
Short: "Removes all stopped containers",
RunE: containerPruneAction,
SilenceUsage: true,
SilenceErrors: true,
}
containerPruneCommand.Flags().BoolP("force", "f", false, "Do not prompt for confirmation")
containerPruneCommand.Flags().StringArray("filter", nil, "Provide filter values (e.g. 'until=<timestamp>')")
return containerPruneCommand
}

func containerPruneAction(cmd *cobra.Command, _ []string) error {
client, ctx, cancel, err := newClient(cmd)
if err != nil {
return err
}
defer cancel()
dataStore, err := getDataStore(cmd)
if err != nil {
return err
}
force, err := cmd.Flags().GetBool("force")
if err != nil {
return err
}
filterMaps, err := checkFilter(cmd)
if err != nil {
return err
}

if !force {
var confirm string
fmt.Fprintf(cmd.OutOrStdout(), "%s", "WARNING! This will remove all stopped containers.\nAre you sure you want to continue? [y/N] ")
fmt.Fscanf(cmd.InOrStdin(), "%s", &confirm)

if strings.ToLower(confirm) != "y" {
return nil
}
}
ns, err := cmd.Flags().GetString("namespace")
if err != nil {
return err
}

var deletedContainers []string
containers, err := client.Containers(ctx)
if err != nil {
return err
}
for _, container := range containers {
if filterMaps != nil {
ok, err := getFilteredContainers(container, filterMaps, ctx)
if err != nil {
return err
}
if !ok {
continue
}
}
cont, err := containerinspector.Inspect(ctx, container)
if err != nil {
return err
}
if cont.Process == nil || (cont.Process != nil && cont.Process.Status.Status != containerd.Running) {
containerNameStore, err := namestore.New(dataStore, ns)
if err != nil {
return err
}
stateDir, err := getContainerStateDirPath(cmd, dataStore, container.ID())
if err != nil {
return err
}
err = removeContainer(cmd, ctx, container, ns, "", force, dataStore, stateDir, containerNameStore, true)
if err != nil {
return err
}
deletedContainers = append(deletedContainers, container.ID())
}
}
if deletedContainers != nil {
fmt.Fprintf(cmd.OutOrStdout(), "Deleted Containers: \n")
for _, elem := range deletedContainers {
fmt.Fprintln(cmd.OutOrStdout(), elem)
}
}

return nil
}
func checkFilter(cmd *cobra.Command) (map[string]map[string]string, error) {
filters, err := cmd.Flags().GetStringArray("filter")
if err != nil {
return nil, err
}
filters = strutil.DedupeStrSlice(filters)
filtersMap, err := convToMap(filters)
if err != nil {
return nil, err
}
return filtersMap, nil
}

func getFilteredContainers(container containerd.Container, filterMaps map[string]map[string]string, ctx context.Context) (bool, error) {
for label, filterKey := range filterMaps {
if label == "until" {
if ok, err := untilCheck(container, filterKey, ctx); ok {
continue
} else {
if err != nil {
return false, err
}
return false, nil
}

} else if strings.Contains(label, "label") {
if ok, err := labelCheck(container, filterKey, ctx, label); ok {
continue
} else {
if err != nil {
return false, err
}
return false, nil
}
}
}
return true, nil
}

func convToMap(values []string) (map[string]map[string]string, error) {
labelMap := make(map[string]map[string]string, len(values))
var once bool
for i, value := range values {
kv := strings.Split(value, "=")
if kv[0] == "label" || kv[0] == "label!" {
if len(kv) == 3 {
labels := kv[1] + "=" + kv[2]
labelMap[kv[0]+strconv.Itoa(i)] = strutil.ConvertKVStringsToMap([]string{labels})
} else {
labelMap[kv[0]+strconv.Itoa(i)] = strutil.ConvertKVStringsToMap([]string{kv[1]})
}
} else if kv[0] == "until" {
if !once {
labelMap[kv[0]] = strutil.ConvertKVStringsToMap([]string{kv[1]})
once = true
} else {
err := fmt.Errorf("more than one until filter specified")
return nil, err
}
} else {
err := fmt.Errorf("invalid filter %q", kv[0])
return nil, err
}
}
return labelMap, nil
}

func untilCheck(container containerd.Container, filterMap map[string]string, ctx context.Context) (bool, error) {
for filterValue, _ := range filterMap {
infoCont, _ := container.Info(ctx)
dur, err := time.ParseDuration(filterValue)
if math.Abs(time.Until(infoCont.CreatedAt).Seconds()) >= dur.Seconds() {
return true, nil
}
if dur == 0 {
checkT, err := time.Parse("2006-01-02T15:04:05Z02:00", filterValue)
if err == nil {
goto check
}
checkT, err = time.Parse("2006-01-02T15:04:05", filterValue)
if err == nil {
goto check
}
checkT, err = time.Parse("2006-01-02", filterValue)
if err == nil {
goto check
}
checkT, err = time.Parse("2006-01-02Z02:00", filterValue)
if err == nil {
goto check
}
checkT, err = time.Parse("2006-01-02T15:04:05.999999999Z02:00", filterValue)
if err != nil {
return false, err
}
check:
if checkT.After(infoCont.CreatedAt) {
return true, nil
}
return false, nil
}
if err != nil {
return false, err
}
}
return false, nil
}
func labelCheck(container containerd.Container, filterMap map[string]string, ctx context.Context, filter string) (bool, error) {
for key, filterValue := range filterMap {
contLabels, err := container.Labels(ctx)
if err != nil {
return false, err
}
if strings.Contains(filter, "label!") {
if fVal, ok := contLabels[key]; ok {
if filterValue != "" && fVal != filterValue {
return true, nil
}
return false, nil
}
return true, nil
}
if fVal, ok := contLabels[key]; ok {
if filterValue != "" && fVal == filterValue {
return true, nil
} else if filterValue != "" && fVal != filterValue {
return false, nil
}
return true, nil
}
}
return false, nil
}
30 changes: 30 additions & 0 deletions cmd/nerdctl/container_prune_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package main

import (
"github.com/containerd/nerdctl/pkg/testutil"
"testing"
)

func TestContainerPrune(t *testing.T) {
tests := []struct {
name string
command string
}{
{
name: "testContainer1",
command: "create",
},
{
name: "testContainer2",
command: "run",
},
}
t.Parallel()
base := testutil.NewBase(t)
tID := testutil.Identifier(t)
for _, test := range tests {
base.Cmd(test.command, "--name", test.name+tID, testutil.CommonImage).AssertOK()

}
defer base.Cmd("container", "prune", "-f").Run()
}
1 change: 1 addition & 0 deletions cmd/nerdctl/volume.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ func newVolumeCommand() *cobra.Command {
newVolumeInspectCommand(),
newVolumeCreateCommand(),
newVolumeRmCommand(),
newVolumePruneCommand(),
)
return volumeCommand
}
Expand Down
Loading

0 comments on commit cffa9e4

Please sign in to comment.