diff options
35 files changed, 627 insertions, 185 deletions
diff --git a/cmd/podman/networks/prune.go b/cmd/podman/networks/prune.go new file mode 100644 index 000000000..d6c7d3a7f --- /dev/null +++ b/cmd/podman/networks/prune.go @@ -0,0 +1,82 @@ +package network + +import ( + "bufio" + "fmt" + "os" + "strings" + + "github.com/containers/podman/v2/cmd/podman/common" + "github.com/containers/podman/v2/cmd/podman/registry" + "github.com/containers/podman/v2/cmd/podman/utils" + "github.com/containers/podman/v2/cmd/podman/validate" + "github.com/containers/podman/v2/pkg/domain/entities" + "github.com/spf13/cobra" + "github.com/spf13/pflag" +) + +var ( + networkPruneDescription = `Prune unused networks` + networkPruneCommand = &cobra.Command{ + Use: "prune [options]", + Short: "network prune", + Long: networkPruneDescription, + RunE: networkPrune, + Example: `podman network prune`, + Args: validate.NoArgs, + ValidArgsFunction: common.AutocompleteNetworks, + } +) + +var ( + networkPruneOptions entities.NetworkPruneOptions + force bool +) + +func networkPruneFlags(flags *pflag.FlagSet) { + //TODO: Not implemented but for future reference + //flags.StringSliceVar(&networkPruneOptions.Filters,"filters", []string{}, "provide filter values (e.g. 'until=<timestamp>')") + flags.BoolVarP(&force, "force", "f", false, "do not prompt for confirmation") +} + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: networkPruneCommand, + Parent: networkCmd, + }) + flags := networkPruneCommand.Flags() + networkPruneFlags(flags) +} + +func networkPrune(cmd *cobra.Command, _ []string) error { + var ( + errs utils.OutputErrors + ) + if !force { + reader := bufio.NewReader(os.Stdin) + fmt.Println("WARNING! This will remove all networks not used by at least one container.") + fmt.Print("Are you sure you want to continue? [y/N] ") + answer, err := reader.ReadString('\n') + if err != nil { + return err + } + if strings.ToLower(answer)[0] != 'y' { + return nil + } + } + responses, err := registry.ContainerEngine().NetworkPrune(registry.Context(), networkPruneOptions) + if err != nil { + setExitCode(err) + return err + } + for _, r := range responses { + if r.Error == nil { + fmt.Println(r.Name) + } else { + setExitCode(r.Error) + errs = append(errs, r.Error) + } + } + return errs.PrintErrors() +} diff --git a/docs/source/markdown/podman-network-prune.1.md b/docs/source/markdown/podman-network-prune.1.md new file mode 100644 index 000000000..af0a7295d --- /dev/null +++ b/docs/source/markdown/podman-network-prune.1.md @@ -0,0 +1,31 @@ +% podman-network-prune(1) + +## NAME +podman\-network\-prune - Remove all unused networks + +## SYNOPSIS +**podman network prune** [*options*] + +## DESCRIPTION +Remove all unused networks. An unused network is defined by a network which +has no containers connected or configured to connect to it. It will not remove +the so-called default network which goes by the name of *podman*. + +## OPTIONS +#### **--force**, **-f** + +Do not prompt for confirmation + +## EXAMPLE +Prune networks + +``` +podman network prune +``` + + +## SEE ALSO +podman(1), podman-network(1), podman-network-remove(1) + +## HISTORY +February 2021, Originally compiled by Brent Baude <bbaude@redhat.com> diff --git a/docs/source/markdown/podman-network.1.md b/docs/source/markdown/podman-network.1.md index 3ad37b8bf..885c957b6 100644 --- a/docs/source/markdown/podman-network.1.md +++ b/docs/source/markdown/podman-network.1.md @@ -19,6 +19,7 @@ The network command manages CNI networks for Podman. | exists | [podman-network-exists(1)](podman-network-exists.1.md) | Check if the given network exists | | inspect | [podman-network-inspect(1)](podman-network-inspect.1.md) | Displays the raw CNI network configuration for one or more networks | | ls | [podman-network-ls(1)](podman-network-ls.1.md) | Display a summary of CNI networks | +| prune | [podman-network-prune(1)](podman-network-prune.1.md) | Remove all unused networks | | reload | [podman-network-reload(1)](podman-network-reload.1.md) | Reload network configuration for containers | | rm | [podman-network-rm(1)](podman-network-rm.1.md) | Remove one or more CNI networks | diff --git a/docs/source/network.rst b/docs/source/network.rst index b5829876e..eb0c2c7f9 100644 --- a/docs/source/network.rst +++ b/docs/source/network.rst @@ -13,6 +13,8 @@ Network :doc:`ls <markdown/podman-network-ls.1>` network list +:doc:`prune <markdown/podman-network-prune.1>` network prune + :doc:`reload <markdown/podman-network-reload.1>` network reload :doc:`rm <markdown/podman-network-rm.1>` network rm @@ -11,7 +11,7 @@ require ( github.com/containernetworking/cni v0.8.1 github.com/containernetworking/plugins v0.9.0 github.com/containers/buildah v1.19.3 - github.com/containers/common v0.33.1 + github.com/containers/common v0.34.3-0.20210208115708-8668c76dd577 github.com/containers/conmon v2.0.20+incompatible github.com/containers/image/v5 v5.10.1 github.com/containers/ocicrypt v1.0.3 @@ -101,6 +101,8 @@ github.com/containers/buildah v1.19.3 h1:U0E1UKzqW5C11W7giHhLZI06xkZiV40ZKDK/c1j github.com/containers/buildah v1.19.3/go.mod h1:uZb6GuE36tmRSOcIXGfiYqdpr+GPXWmlUIJSk5sn19w= github.com/containers/common v0.33.1 h1:XpDiq8Cta8+u1s4kpYSEWdB140ZmqgyIXfWkLqKx3z0= github.com/containers/common v0.33.1/go.mod h1:mjDo/NKeweL/onaspLhZ38WnHXaYmrELHclIdvSnYpY= +github.com/containers/common v0.34.3-0.20210208115708-8668c76dd577 h1:tUJcLouJ1bC3w9gdqgKqZBsj2uCuM8D8jSR592lxbhE= +github.com/containers/common v0.34.3-0.20210208115708-8668c76dd577/go.mod h1:mwZ9H8sK4+dtWxsnVLyWcjxK/gEQClrLsXsqLvbEKbI= github.com/containers/conmon v2.0.20+incompatible h1:YbCVSFSCqFjjVwHTPINGdMX1F6JXHGTUje2ZYobNrkg= github.com/containers/conmon v2.0.20+incompatible/go.mod h1:hgwZ2mtuDrppv78a/cOBNiCm6O0UMWGx1mu7P00nu5I= github.com/containers/image/v5 v5.9.0 h1:dRmUtcluQcmasNo3DpnRoZjfU0rOu1qZeL6wlDJr10Q= diff --git a/libpod/network/network.go b/libpod/network/network.go index 0ff14c1f7..cdaef6c13 100644 --- a/libpod/network/network.go +++ b/libpod/network/network.go @@ -11,6 +11,7 @@ import ( "github.com/containernetworking/plugins/plugins/ipam/host-local/backend/allocator" "github.com/containers/common/pkg/config" "github.com/containers/podman/v2/libpod/define" + "github.com/containers/podman/v2/pkg/domain/entities" "github.com/containers/podman/v2/pkg/rootless" "github.com/containers/podman/v2/pkg/util" "github.com/pkg/errors" @@ -174,14 +175,9 @@ func ValidateUserNetworkIsAvailable(config *config.Config, userNet *net.IPNet) e return nil } -// RemoveNetwork removes a given network by name. If the network has container associated with it, that -// must be handled outside the context of this. -func RemoveNetwork(config *config.Config, name string) error { - l, err := acquireCNILock(config) - if err != nil { - return err - } - defer l.releaseCNILock() +// removeNetwork is removes a cni network without a lock and should only be called +// when a lock was otherwise acquired. +func removeNetwork(config *config.Config, name string) error { cniPath, err := GetCNIConfigPathByNameOrID(config, name) if err != nil { return err @@ -213,6 +209,17 @@ func RemoveNetwork(config *config.Config, name string) error { return nil } +// RemoveNetwork removes a given network by name. If the network has container associated with it, that +// must be handled outside the context of this. +func RemoveNetwork(config *config.Config, name string) error { + l, err := acquireCNILock(config) + if err != nil { + return err + } + defer l.releaseCNILock() + return removeNetwork(config, name) +} + // InspectNetwork reads a CNI config and returns its configuration func InspectNetwork(config *config.Config, name string) (map[string]interface{}, error) { b, err := ReadRawCNIConfByName(config, name) @@ -243,3 +250,30 @@ func GetNetworkID(name string) string { hash := sha256.Sum256([]byte(name)) return hex.EncodeToString(hash[:]) } + +// PruneNetworks removes networks that are not being used and that is not the default +// network. To keep proper fencing for imports, you must provide the used networks +// to this function as a map. the key is meaningful in the map, the book is a no-op +func PruneNetworks(rtc *config.Config, usedNetworks map[string]bool) ([]*entities.NetworkPruneReport, error) { + var reports []*entities.NetworkPruneReport + lock, err := acquireCNILock(rtc) + if err != nil { + return nil, err + } + defer lock.releaseCNILock() + nets, err := GetNetworkNamesFromFileSystem(rtc) + if err != nil { + return nil, err + } + for _, n := range nets { + _, found := usedNetworks[n] + // Remove is not default network and not found in the used list + if n != rtc.Network.DefaultNetwork && !found { + reports = append(reports, &entities.NetworkPruneReport{ + Name: n, + Error: removeNetwork(rtc, n), + }) + } + } + return reports, nil +} diff --git a/pkg/api/handlers/compat/networks.go b/pkg/api/handlers/compat/networks.go index f0b922885..f7a70816f 100644 --- a/pkg/api/handlers/compat/networks.go +++ b/pkg/api/handlers/compat/networks.go @@ -388,3 +388,25 @@ func Disconnect(w http.ResponseWriter, r *http.Request) { } utils.WriteResponse(w, http.StatusOK, "OK") } + +// Prune removes unused networks +func Prune(w http.ResponseWriter, r *http.Request) { + // TODO Filters are not implemented + runtime := r.Context().Value("runtime").(*libpod.Runtime) + ic := abi.ContainerEngine{Libpod: runtime} + pruneOptions := entities.NetworkPruneOptions{} + pruneReports, err := ic.NetworkPrune(r.Context(), pruneOptions) + if err != nil { + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err) + return + } + var prunedNetworks []string //nolint + for _, pr := range pruneReports { + if pr.Error != nil { + logrus.Error(pr.Error) + continue + } + prunedNetworks = append(prunedNetworks, pr.Name) + } + utils.WriteResponse(w, http.StatusOK, prunedNetworks) +} diff --git a/pkg/api/handlers/compat/swagger.go b/pkg/api/handlers/compat/swagger.go index 0a514822b..1d1f1ecf2 100644 --- a/pkg/api/handlers/compat/swagger.go +++ b/pkg/api/handlers/compat/swagger.go @@ -77,3 +77,10 @@ type swagCompatNetworkDisconnectRequest struct { // in:body Body struct{ types.NetworkDisconnect } } + +// Network prune +// swagger:response NetworkPruneResponse +type swagCompatNetworkPruneResponse struct { + // in:body + Body []string +} diff --git a/pkg/api/handlers/libpod/networks.go b/pkg/api/handlers/libpod/networks.go index d3bf06988..998f89d96 100644 --- a/pkg/api/handlers/libpod/networks.go +++ b/pkg/api/handlers/libpod/networks.go @@ -175,3 +175,17 @@ func ExistsNetwork(w http.ResponseWriter, r *http.Request) { } utils.WriteResponse(w, http.StatusNoContent, "") } + +// Prune removes unused networks +func Prune(w http.ResponseWriter, r *http.Request) { + // TODO Filters are not implemented + runtime := r.Context().Value("runtime").(*libpod.Runtime) + ic := abi.ContainerEngine{Libpod: runtime} + pruneOptions := entities.NetworkPruneOptions{} + pruneReports, err := ic.NetworkPrune(r.Context(), pruneOptions) + if err != nil { + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err) + return + } + utils.WriteResponse(w, http.StatusOK, pruneReports) +} diff --git a/pkg/api/server/register_networks.go b/pkg/api/server/register_networks.go index 3d9e7fb89..d3345d8da 100644 --- a/pkg/api/server/register_networks.go +++ b/pkg/api/server/register_networks.go @@ -9,19 +9,6 @@ import ( ) func (s *APIServer) registerNetworkHandlers(r *mux.Router) error { - // swagger:operation POST /networks/prune compat compatPruneNetwork - // --- - // tags: - // - networks (compat) - // Summary: Delete unused networks - // description: Not supported - // produces: - // - application/json - // responses: - // 404: - // $ref: "#/responses/NoSuchNetwork" - r.HandleFunc(VersionedPath("/networks/prune"), compat.UnsupportedHandler).Methods(http.MethodPost) - r.HandleFunc("/networks/prune", compat.UnsupportedHandler).Methods(http.MethodPost) // swagger:operation DELETE /networks/{name} compat compatRemoveNetwork // --- // tags: @@ -172,6 +159,35 @@ func (s *APIServer) registerNetworkHandlers(r *mux.Router) error { // $ref: "#/responses/InternalError" r.HandleFunc(VersionedPath("/networks/{name}/disconnect"), s.APIHandler(compat.Disconnect)).Methods(http.MethodPost) r.HandleFunc("/networks/{name}/disconnect", s.APIHandler(compat.Disconnect)).Methods(http.MethodPost) + // swagger:operation POST /networks/prune compat compatPruneNetwork + // --- + // tags: + // - networks (compat) + // summary: Delete unused networks + // description: Remove CNI networks that do not have containers + // produces: + // - application/json + // parameters: + // - in: query + // name: filters + // type: string + // description: | + // NOT IMPLEMENTED + // Filters to process on the prune list, encoded as JSON (a map[string][]string). + // Available filters: + // - until=<timestamp> Prune networks created before this timestamp. The <timestamp> can be Unix timestamps, date formatted timestamps, or Go duration strings (e.g. 10m, 1h30m) computed relative to the daemon machine’s time. + // - label (label=<key>, label=<key>=<value>, label!=<key>, or label!=<key>=<value>) Prune networks with (or without, in case label!=... is used) the specified labels. + // responses: + // 200: + // description: OK + // schema: + // type: array + // items: + // type: string + // 500: + // $ref: "#/responses/InternalError" + r.HandleFunc(VersionedPath("/networks/prune"), s.APIHandler(compat.Prune)).Methods(http.MethodPost) + r.HandleFunc("/networks/prune", s.APIHandler(compat.Prune)).Methods(http.MethodPost) // swagger:operation DELETE /libpod/networks/{name} libpod libpodRemoveNetwork // --- @@ -353,5 +369,29 @@ func (s *APIServer) registerNetworkHandlers(r *mux.Router) error { // 500: // $ref: "#/responses/InternalError" r.HandleFunc(VersionedPath("/libpod/networks/{name}/disconnect"), s.APIHandler(compat.Disconnect)).Methods(http.MethodPost) + // swagger:operation POST /libpod/networks/prune libpod libpodPruneNetwork + // --- + // tags: + // - networks + // summary: Delete unused networks + // description: Remove CNI networks that do not have containers + // produces: + // - application/json + // parameters: + // - in: query + // name: filters + // type: string + // description: | + // NOT IMPLEMENTED + // Filters to process on the prune list, encoded as JSON (a map[string][]string). + // Available filters: + // - until=<timestamp> Prune networks created before this timestamp. The <timestamp> can be Unix timestamps, date formatted timestamps, or Go duration strings (e.g. 10m, 1h30m) computed relative to the daemon machine’s time. + // - label (label=<key>, label=<key>=<value>, label!=<key>, or label!=<key>=<value>) Prune networks with (or without, in case label!=... is used) the specified labels. + // responses: + // 200: + // $ref: "#/responses/NetworkPruneResponse" + // 500: + // $ref: "#/responses/InternalError" + r.HandleFunc(VersionedPath("/libpod/networks/prune"), s.APIHandler(libpod.Prune)).Methods(http.MethodPost) return nil } diff --git a/pkg/bindings/network/network.go b/pkg/bindings/network/network.go index 8debeee84..428e60cf2 100644 --- a/pkg/bindings/network/network.go +++ b/pkg/bindings/network/network.go @@ -180,3 +180,21 @@ func Exists(ctx context.Context, nameOrID string, options *ExistsOptions) (bool, } return response.IsSuccess(), nil } + +// Prune removes unused CNI networks +func Prune(ctx context.Context, options *PruneOptions) ([]*entities.NetworkPruneReport, error) { + // TODO Filters is not implemented + var ( + prunedNetworks []*entities.NetworkPruneReport + ) + conn, err := bindings.GetClient(ctx) + if err != nil { + return nil, err + } + + response, err := conn.DoRequest(nil, http.MethodPost, "/networks/prune", nil, nil) + if err != nil { + return nil, err + } + return prunedNetworks, response.Process(&prunedNetworks) +} diff --git a/pkg/bindings/network/types.go b/pkg/bindings/network/types.go index 91cbcf044..47dce67c7 100644 --- a/pkg/bindings/network/types.go +++ b/pkg/bindings/network/types.go @@ -74,3 +74,9 @@ type ConnectOptions struct { // if a network exists type ExistsOptions struct { } + +//go:generate go run ../generator/generator.go PruneOptions +// PruneOptions are optional options for removing unused +// CNI networks +type PruneOptions struct { +} diff --git a/pkg/bindings/network/types_prune_options.go b/pkg/bindings/network/types_prune_options.go new file mode 100644 index 000000000..c56dcd0d3 --- /dev/null +++ b/pkg/bindings/network/types_prune_options.go @@ -0,0 +1,75 @@ +package network + +import ( + "net/url" + "reflect" + "strings" + + "github.com/containers/podman/v2/pkg/bindings/util" + jsoniter "github.com/json-iterator/go" + "github.com/pkg/errors" +) + +/* +This file is generated automatically by go generate. Do not edit. +*/ + +// Changed +func (o *PruneOptions) Changed(fieldName string) bool { + r := reflect.ValueOf(o) + value := reflect.Indirect(r).FieldByName(fieldName) + return !value.IsNil() +} + +// ToParams +func (o *PruneOptions) ToParams() (url.Values, error) { + params := url.Values{} + if o == nil { + return params, nil + } + json := jsoniter.ConfigCompatibleWithStandardLibrary + s := reflect.ValueOf(o) + if reflect.Ptr == s.Kind() { + s = s.Elem() + } + sType := s.Type() + for i := 0; i < s.NumField(); i++ { + fieldName := sType.Field(i).Name + if !o.Changed(fieldName) { + continue + } + fieldName = strings.ToLower(fieldName) + f := s.Field(i) + if reflect.Ptr == f.Kind() { + f = f.Elem() + } + switch { + case util.IsSimpleType(f): + params.Set(fieldName, util.SimpleTypeToParam(f)) + case f.Kind() == reflect.Slice: + for i := 0; i < f.Len(); i++ { + elem := f.Index(i) + if util.IsSimpleType(elem) { + params.Add(fieldName, util.SimpleTypeToParam(elem)) + } else { + return nil, errors.New("slices must contain only simple types") + } + } + case f.Kind() == reflect.Map: + lowerCaseKeys := make(map[string][]string) + iter := f.MapRange() + for iter.Next() { + lowerCaseKeys[iter.Key().Interface().(string)] = iter.Value().Interface().([]string) + + } + s, err := json.MarshalToString(lowerCaseKeys) + if err != nil { + return nil, err + } + + params.Set(fieldName, s) + } + + } + return params, nil +} diff --git a/pkg/domain/entities/engine_container.go b/pkg/domain/entities/engine_container.go index 39bda1d72..2c97d7baf 100644 --- a/pkg/domain/entities/engine_container.go +++ b/pkg/domain/entities/engine_container.go @@ -63,6 +63,7 @@ type ContainerEngine interface { NetworkExists(ctx context.Context, networkname string) (*BoolReport, error) NetworkInspect(ctx context.Context, namesOrIds []string, options InspectOptions) ([]NetworkInspectReport, []error, error) NetworkList(ctx context.Context, options NetworkListOptions) ([]*NetworkListReport, error) + NetworkPrune(ctx context.Context, options NetworkPruneOptions) ([]*NetworkPruneReport, error) NetworkReload(ctx context.Context, names []string, options NetworkReloadOptions) ([]*NetworkReloadReport, error) NetworkRm(ctx context.Context, namesOrIds []string, options NetworkRmOptions) ([]*NetworkRmReport, error) PlayKube(ctx context.Context, path string, opts PlayKubeOptions) (*PlayKubeReport, error) diff --git a/pkg/domain/entities/network.go b/pkg/domain/entities/network.go index b76bfcac7..1859f920e 100644 --- a/pkg/domain/entities/network.go +++ b/pkg/domain/entities/network.go @@ -80,3 +80,15 @@ type NetworkConnectOptions struct { Aliases []string Container string } + +// NetworkPruneReport containers the name of network and an error +// associated in its pruning (removal) +// swagger:model NetworkPruneReport +type NetworkPruneReport struct { + Name string + Error error +} + +// NetworkPruneOptions describes options for pruning +// unused cni networks +type NetworkPruneOptions struct{} diff --git a/pkg/domain/infra/abi/images.go b/pkg/domain/infra/abi/images.go index 8ca93e770..f2d0f2c39 100644 --- a/pkg/domain/infra/abi/images.go +++ b/pkg/domain/infra/abi/images.go @@ -580,12 +580,21 @@ func (ir *ImageEngine) Remove(ctx context.Context, images []string, opts entitie // without having to pass all local data around. deleteImage := func(img *image.Image) error { results, err := ir.Libpod.RemoveImage(ctx, img, opts.Force) - if err != nil { + switch errors.Cause(err) { + case nil: + // Removal worked, so let's report it. + report.Deleted = append(report.Deleted, results.Deleted) + report.Untagged = append(report.Untagged, results.Untagged...) + return nil + case storage.ErrImageUnknown: + // The image must have been removed already (see #6510). + report.Deleted = append(report.Deleted, img.ID()) + report.Untagged = append(report.Untagged, img.ID()) + return nil + default: + // Fatal error. return err } - report.Deleted = append(report.Deleted, results.Deleted) - report.Untagged = append(report.Untagged, results.Untagged...) - return nil } // Delete all images from the local storage. diff --git a/pkg/domain/infra/abi/network.go b/pkg/domain/infra/abi/network.go index bc4328fcd..13fabe89d 100644 --- a/pkg/domain/infra/abi/network.go +++ b/pkg/domain/infra/abi/network.go @@ -155,3 +155,28 @@ func (ic *ContainerEngine) NetworkExists(ctx context.Context, networkname string Value: exists, }, nil } + +// Network prune removes unused cni networks +func (ic *ContainerEngine) NetworkPrune(ctx context.Context, options entities.NetworkPruneOptions) ([]*entities.NetworkPruneReport, error) { + runtimeConfig, err := ic.Libpod.GetConfig() + if err != nil { + return nil, err + } + cons, err := ic.Libpod.GetAllContainers() + if err != nil { + return nil, err + } + // Gather up all the non-default networks that the + // containers want + usedNetworks := make(map[string]bool) + for _, c := range cons { + nets, _, err := c.Networks() + if err != nil { + return nil, err + } + for _, n := range nets { + usedNetworks[n] = true + } + } + return network.PruneNetworks(runtimeConfig, usedNetworks) +} diff --git a/pkg/domain/infra/tunnel/network.go b/pkg/domain/infra/tunnel/network.go index bdb1beb03..990bfa880 100644 --- a/pkg/domain/infra/tunnel/network.go +++ b/pkg/domain/infra/tunnel/network.go @@ -89,3 +89,8 @@ func (ic *ContainerEngine) NetworkExists(ctx context.Context, networkname string Value: exists, }, nil } + +// Network prune removes unused cni networks +func (ic *ContainerEngine) NetworkPrune(ctx context.Context, options entities.NetworkPruneOptions) ([]*entities.NetworkPruneReport, error) { + return network.Prune(ic.ClientCtx, nil) +} diff --git a/test/apiv2/rest_api/test_rest_v2_0_0.py b/test/apiv2/rest_api/test_rest_v2_0_0.py index 9ce0803fb..73db35cc1 100644 --- a/test/apiv2/rest_api/test_rest_v2_0_0.py +++ b/test/apiv2/rest_api/test_rest_v2_0_0.py @@ -484,7 +484,7 @@ class TestApi(unittest.TestCase): self.assertEqual(inspect.status_code, 404, inspect.content) prune = requests.post(PODMAN_URL + "/v1.40/networks/prune") - self.assertEqual(prune.status_code, 404, prune.content) + self.assertEqual(prune.status_code, 200, prune.content) def test_volumes_compat(self): name = "Volume_" + "".join(random.choice(string.ascii_letters) for i in range(10)) diff --git a/test/e2e/network_test.go b/test/e2e/network_test.go index c6010ba43..d4e1a3698 100644 --- a/test/e2e/network_test.go +++ b/test/e2e/network_test.go @@ -540,4 +540,54 @@ var _ = Describe("Podman network", func() { nc.WaitWithDefaultTimeout() Expect(nc.ExitCode()).To(Equal(0)) }) + + It("podman network prune", func() { + // Create two networks + // Check they are there + // Run a container on one of them + // Network Prune + // Check that one has been pruned, other remains + net := "macvlan" + stringid.GenerateNonCryptoID() + net1 := net + "1" + net2 := net + "2" + nc := podmanTest.Podman([]string{"network", "create", net1}) + nc.WaitWithDefaultTimeout() + defer podmanTest.removeCNINetwork(net1) + Expect(nc.ExitCode()).To(Equal(0)) + + nc2 := podmanTest.Podman([]string{"network", "create", net2}) + nc2.WaitWithDefaultTimeout() + defer podmanTest.removeCNINetwork(net2) + Expect(nc2.ExitCode()).To(Equal(0)) + + list := podmanTest.Podman([]string{"network", "ls", "--format", "{{.Name}}"}) + list.WaitWithDefaultTimeout() + Expect(list.ExitCode()).To(BeZero()) + + Expect(StringInSlice(net1, list.OutputToStringArray())).To(BeTrue()) + Expect(StringInSlice(net2, list.OutputToStringArray())).To(BeTrue()) + if !isRootless() { + Expect(StringInSlice("podman", list.OutputToStringArray())).To(BeTrue()) + } + + session := podmanTest.Podman([]string{"run", "-dt", "--net", net2, ALPINE, "top"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(BeZero()) + + prune := podmanTest.Podman([]string{"network", "prune", "-f"}) + prune.WaitWithDefaultTimeout() + Expect(prune.ExitCode()).To(BeZero()) + + listAgain := podmanTest.Podman([]string{"network", "ls", "--format", "{{.Name}}"}) + listAgain.WaitWithDefaultTimeout() + Expect(listAgain.ExitCode()).To(BeZero()) + + Expect(StringInSlice(net1, listAgain.OutputToStringArray())).To(BeFalse()) + Expect(StringInSlice(net2, listAgain.OutputToStringArray())).To(BeTrue()) + // Make sure default network 'podman' still exists + if !isRootless() { + Expect(StringInSlice("podman", list.OutputToStringArray())).To(BeTrue()) + } + + }) }) diff --git a/test/e2e/rmi_test.go b/test/e2e/rmi_test.go index 4833a282e..257570ea7 100644 --- a/test/e2e/rmi_test.go +++ b/test/e2e/rmi_test.go @@ -1,7 +1,9 @@ package integration import ( + "fmt" "os" + "sync" . "github.com/containers/podman/v2/test/utils" . "github.com/onsi/ginkgo" @@ -275,4 +277,32 @@ RUN find $LOCAL match, _ := session.ErrorGrepString("image name or ID must be specified") Expect(match).To(BeTrue()) }) + + It("podman image rm - concurrent with shared layers", func() { + // #6510 has shown a fairly simple reproducer to force storage + // errors during parallel image removal. Since it's subject to + // a race, we may not hit the condition a 100 percent of times + // but ocal reproducers hit it all the time. + + var wg sync.WaitGroup + + buildAndRemove := func(i int) { + defer GinkgoRecover() + defer wg.Done() + imageName := fmt.Sprintf("rmtest:%d", i) + containerfile := `FROM quay.io/libpod/cirros:latest +RUN ` + fmt.Sprintf("touch %s", imageName) + + podmanTest.BuildImage(containerfile, imageName, "false") + session := podmanTest.Podman([]string{"rmi", "-f", imageName}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + } + + wg.Add(10) + for i := 0; i < 10; i++ { + go buildAndRemove(i) + } + wg.Wait() + }) }) diff --git a/vendor/github.com/containers/common/pkg/cgroupv2/cgroups_linux.go b/vendor/github.com/containers/common/pkg/cgroupv2/cgroups_linux.go index b11eafebb..749c89932 100644 --- a/vendor/github.com/containers/common/pkg/cgroupv2/cgroups_linux.go +++ b/vendor/github.com/containers/common/pkg/cgroupv2/cgroups_linux.go @@ -13,7 +13,7 @@ var ( isCgroupV2Err error ) -// Enabled returns whether we are running in cgroup 2 cgroup2 mode. +// Enabled returns whether we are running on cgroup v2 func Enabled() (bool, error) { isCgroupV2Once.Do(func() { var st syscall.Statfs_t diff --git a/vendor/github.com/containers/common/pkg/cgroupv2/cgroups_unsupported.go b/vendor/github.com/containers/common/pkg/cgroupv2/cgroups_unsupported.go index cda68b405..61b3653e5 100644 --- a/vendor/github.com/containers/common/pkg/cgroupv2/cgroups_unsupported.go +++ b/vendor/github.com/containers/common/pkg/cgroupv2/cgroups_unsupported.go @@ -2,7 +2,7 @@ package cgroupv2 -// Enabled returns whether we are running in cgroup 2 cgroup2 mode. +// Enabled returns whether we are running on cgroup v2 func Enabled() (bool, error) { return false, nil } diff --git a/vendor/github.com/containers/common/pkg/config/config.go b/vendor/github.com/containers/common/pkg/config/config.go index 3b8baf87a..4a98c7e92 100644 --- a/vendor/github.com/containers/common/pkg/config/config.go +++ b/vendor/github.com/containers/common/pkg/config/config.go @@ -187,10 +187,6 @@ type ContainersConfig struct { // EngineConfig contains configuration options used to set up a engine runtime type EngineConfig struct { - // ImageBuildFormat indicates the default image format to building - // container images. Valid values are "oci" (default) or "docker". - ImageBuildFormat string `toml:"image_build_format,omitempty"` - // CgroupCheck indicates the configuration has been rewritten after an // upgrade to Fedora 31 to change the default OCI runtime for cgroupv2v2. CgroupCheck bool `toml:"cgroup_check,omitempty"` @@ -235,10 +231,25 @@ type EngineConfig struct { // this slice takes precedence. HooksDir []string `toml:"hooks_dir,omitempty"` + // ImageBuildFormat (DEPRECATED) indicates the default image format to + // building container images. Should use ImageDefaultFormat + ImageBuildFormat string `toml:"image_build_format,omitempty"` + // ImageDefaultTransport is the default transport method used to fetch // images. ImageDefaultTransport string `toml:"image_default_transport,omitempty"` + // ImageParallelCopies indicates the maximum number of image layers + // to be copied simultaneously. If this is zero, container engines + // will fall back to containers/image defaults. + ImageParallelCopies uint `toml:"image_parallel_copies,omitempty"` + + // ImageDefaultFormat sepecified the manifest Type (oci, v2s2, or v2s1) + // to use when pulling, pushing, building container images. By default + // image pulled and pushed match the format of the source image. + // Building/committing defaults to OCI. + ImageDefaultFormat string `toml:"image_default_format,omitempty"` + // InfraCommand is the command run to start up a pod infra container. InfraCommand string `toml:"infra_command,omitempty"` diff --git a/vendor/github.com/containers/common/pkg/config/containers.conf b/vendor/github.com/containers/common/pkg/config/containers.conf index 0587469b2..18243f296 100644 --- a/vendor/github.com/containers/common/pkg/config/containers.conf +++ b/vendor/github.com/containers/common/pkg/config/containers.conf @@ -246,9 +246,14 @@ default_sysctls = [ # network_config_dir = "/etc/cni/net.d/" [engine] -# ImageBuildFormat indicates the default image format to building -# container images. Valid values are "oci" (default) or "docker". -# image_build_format = "oci" +# Maximum number of image layers to be copied (pulled/pushed) simultaneously. +# Not setting this field, or setting it to zero, will fall back to containers/image defaults. +# image_parallel_copies=0 + +# Manifest Type (oci, v2s2, or v2s1) to use when pulling, pushing, building +# container images. By default image pulled and pushed match the format of the +# source image. Building/commiting defaults to OCI. +# image_default_format = "" # Cgroup management implementation used for the runtime. # Valid options "systemd" or "cgroupfs" diff --git a/vendor/github.com/containers/common/pkg/config/default.go b/vendor/github.com/containers/common/pkg/config/default.go index 2e26fb7b8..918ce93e5 100644 --- a/vendor/github.com/containers/common/pkg/config/default.go +++ b/vendor/github.com/containers/common/pkg/config/default.go @@ -518,3 +518,9 @@ func (c *Config) TZ() string { func (c *Config) Umask() string { return c.Containers.Umask } + +// LogDriver returns the logging driver to be used +// currently k8s-file or journald +func (c *Config) LogDriver() string { + return c.Containers.LogDriver +} diff --git a/vendor/github.com/containers/common/pkg/config/util_supported.go b/vendor/github.com/containers/common/pkg/config/util_supported.go index 4595716d1..326e7973a 100644 --- a/vendor/github.com/containers/common/pkg/config/util_supported.go +++ b/vendor/github.com/containers/common/pkg/config/util_supported.go @@ -25,6 +25,17 @@ func getRuntimeDir() (string, error) { rootlessRuntimeDirOnce.Do(func() { runtimeDir := os.Getenv("XDG_RUNTIME_DIR") + if runtimeDir != "" { + st, err := os.Stat(runtimeDir) + if err != nil { + rootlessRuntimeDirError = err + return + } + if int(st.Sys().(*syscall.Stat_t).Uid) != os.Geteuid() { + rootlessRuntimeDirError = fmt.Errorf("XDG_RUNTIME_DIR directory %q is not owned by the current user", runtimeDir) + return + } + } uid := fmt.Sprintf("%d", unshare.GetRootlessUID()) if runtimeDir == "" { tmpDir := filepath.Join("/run", "user", uid) diff --git a/vendor/github.com/containers/common/pkg/parse/parse.go b/vendor/github.com/containers/common/pkg/parse/parse.go index 611b2e84b..882953309 100644 --- a/vendor/github.com/containers/common/pkg/parse/parse.go +++ b/vendor/github.com/containers/common/pkg/parse/parse.go @@ -13,7 +13,7 @@ import ( // ValidateVolumeOpts validates a volume's options func ValidateVolumeOpts(options []string) ([]string, error) { - var foundRootPropagation, foundRWRO, foundLabelChange, bindType, foundExec, foundDev, foundSuid int + var foundRootPropagation, foundRWRO, foundLabelChange, bindType, foundExec, foundDev, foundSuid, foundChown int finalOpts := make([]string, 0, len(options)) for _, opt := range options { switch opt { @@ -42,6 +42,11 @@ func ValidateVolumeOpts(options []string) ([]string, error) { if foundLabelChange > 1 { return nil, errors.Errorf("invalid options %q, can only specify 1 'z', 'Z', or 'O' option", strings.Join(options, ", ")) } + case "U": + foundChown++ + if foundChown > 1 { + return nil, errors.Errorf("invalid options %q, can only specify 1 'U' option", strings.Join(options, ", ")) + } case "private", "rprivate", "shared", "rshared", "slave", "rslave", "unbindable", "runbindable": foundRootPropagation++ if foundRootPropagation > 1 { diff --git a/vendor/github.com/containers/common/pkg/report/doc.go b/vendor/github.com/containers/common/pkg/report/doc.go index 60d954d7e..326b315f2 100644 --- a/vendor/github.com/containers/common/pkg/report/doc.go +++ b/vendor/github.com/containers/common/pkg/report/doc.go @@ -38,7 +38,17 @@ Helpers: ... process JSON and output } -and +Template Functions: + +The following template functions are added to the template when parsed: + - join strings.Join, {{join .Field separator}} + - lower strings.ToLower {{ .Field | lower }} + - split strings.Split {{ .Field | split }} + - title strings.Title {{ .Field | title }} + - upper strings.ToUpper {{ .Field | upper }} + +report.Funcs() may be used to add additional template functions. +Adding an existing function will replace that function for the life of that template. Note: Your code should not ignore errors diff --git a/vendor/github.com/containers/common/pkg/report/template.go b/vendor/github.com/containers/common/pkg/report/template.go index 551fbb3cf..559c1625b 100644 --- a/vendor/github.com/containers/common/pkg/report/template.go +++ b/vendor/github.com/containers/common/pkg/report/template.go @@ -1,6 +1,8 @@ package report import ( + "bytes" + "encoding/json" "reflect" "strings" "text/template" @@ -21,16 +23,32 @@ type FuncMap template.FuncMap var tableReplacer = strings.NewReplacer( "table ", "", `\t`, "\t", - `\n`, "\n", " ", "\t", ) // escapedReplacer will clean up escaped characters from CLI var escapedReplacer = strings.NewReplacer( `\t`, "\t", - `\n`, "\n", ) +var DefaultFuncs = FuncMap{ + "join": strings.Join, + "json": func(v interface{}) string { + buf := &bytes.Buffer{} + enc := json.NewEncoder(buf) + enc.SetEscapeHTML(false) + enc.Encode(v) + // Remove the trailing new line added by the encoder + return strings.TrimSpace(buf.String()) + }, + "lower": strings.ToLower, + "pad": padWithSpace, + "split": strings.Split, + "title": strings.Title, + "truncate": truncateWithLength, + "upper": strings.ToUpper, +} + // NormalizeFormat reads given go template format provided by CLI and munges it into what we need func NormalizeFormat(format string) string { var f string @@ -47,6 +65,22 @@ func NormalizeFormat(format string) string { return f } +// padWithSpace adds spaces*prefix and spaces*suffix to the input when it is non-empty +func padWithSpace(source string, prefix, suffix int) string { + if source == "" { + return source + } + return strings.Repeat(" ", prefix) + source + strings.Repeat(" ", suffix) +} + +// truncateWithLength truncates the source string up to the length provided by the input +func truncateWithLength(source string, length int) string { + if len(source) < length { + return source + } + return source[:length] +} + // Headers queries the interface for field names. // Array of map is returned to support range templates // Note: unexported fields can be supported by adding field to overrides @@ -88,7 +122,7 @@ func Headers(object interface{}, overrides map[string]string) []map[string]strin // NewTemplate creates a new template object func NewTemplate(name string) *Template { - return &Template{template.New(name), false} + return &Template{Template: template.New(name).Funcs(template.FuncMap(DefaultFuncs))} } // Parse parses text as a template body for t @@ -100,13 +134,21 @@ func (t *Template) Parse(text string) (*Template, error) { text = NormalizeFormat(text) } - tt, err := t.Template.Parse(text) + tt, err := t.Template.Funcs(template.FuncMap(DefaultFuncs)).Parse(text) return &Template{tt, t.isTable}, err } -// Funcs adds the elements of the argument map to the template's function map +// Funcs adds the elements of the argument map to the template's function map. +// A default template function will be replace if there is a key collision. func (t *Template) Funcs(funcMap FuncMap) *Template { - return &Template{t.Template.Funcs(template.FuncMap(funcMap)), t.isTable} + m := make(FuncMap) + for k, v := range DefaultFuncs { + m[k] = v + } + for k, v := range funcMap { + m[k] = v + } + return &Template{Template: t.Template.Funcs(template.FuncMap(m)), isTable: t.isTable} } // IsTable returns true if format string defines a "table" diff --git a/vendor/github.com/containers/common/pkg/seccomp/default_linux.go b/vendor/github.com/containers/common/pkg/seccomp/default_linux.go index 5c4427318..24077778e 100644 --- a/vendor/github.com/containers/common/pkg/seccomp/default_linux.go +++ b/vendor/github.com/containers/common/pkg/seccomp/default_linux.go @@ -5,8 +5,6 @@ package seccomp import ( - "syscall" - "golang.org/x/sys/unix" ) @@ -45,7 +43,7 @@ func arches() []Architecture { // DefaultProfile defines the allowlist for the default seccomp profile. func DefaultProfile() *Seccomp { - einval := uint(syscall.EINVAL) + einval := uint(unix.EINVAL) syscalls := []*Syscall{ { @@ -87,6 +85,7 @@ func DefaultProfile() *Seccomp { "epoll_ctl", "epoll_ctl_old", "epoll_pwait", + "epoll_pwait2", "epoll_wait", "epoll_wait_old", "eventfd", @@ -115,7 +114,11 @@ func DefaultProfile() *Seccomp { "flock", "fork", "fremovexattr", + "fsconfig", "fsetxattr", + "fsmount", + "fsopen", + "fspick", "fstat", "fstat64", "fstatat64", @@ -203,6 +206,7 @@ func DefaultProfile() *Seccomp { "mmap", "mmap2", "mount", + "move_mount", "mprotect", "mq_getsetattr", "mq_notify", @@ -225,6 +229,7 @@ func DefaultProfile() *Seccomp { "open", "openat", "openat2", + "open_tree", "pause", "pidfd_getfd", "pidfd_open", @@ -331,7 +336,6 @@ func DefaultProfile() *Seccomp { "signalfd", "signalfd4", "sigreturn", - "socket", "socketcall", "socketpair", "splice", @@ -512,19 +516,13 @@ func DefaultProfile() *Seccomp { { Names: []string{ "bpf", - "clone", "fanotify_init", "lookup_dcookie", - "mount", - "name_to_handle_at", "perf_event_open", "quotactl", "setdomainname", "sethostname", "setns", - "umount", - "umount2", - "unshare", }, Action: ActAllow, Args: []*Arg{}, @@ -534,55 +532,6 @@ func DefaultProfile() *Seccomp { }, { Names: []string{ - "clone", - }, - Action: ActAllow, - Args: []*Arg{ - { - Index: 0, - Value: unix.CLONE_NEWNS | unix.CLONE_NEWUTS | unix.CLONE_NEWIPC | unix.CLONE_NEWUSER | unix.CLONE_NEWPID | unix.CLONE_NEWNET, - ValueTwo: 0, - Op: OpMaskedEqual, - }, - }, - Excludes: Filter{ - Caps: []string{"CAP_SYS_ADMIN"}, - Arches: []string{"s390", "s390x"}, - }, - }, - { - Names: []string{ - "clone", - }, - Action: ActAllow, - Args: []*Arg{ - { - Index: 1, - Value: unix.CLONE_NEWNS | unix.CLONE_NEWUTS | unix.CLONE_NEWIPC | unix.CLONE_NEWUSER | unix.CLONE_NEWPID | unix.CLONE_NEWNET, - ValueTwo: 0, - Op: OpMaskedEqual, - }, - }, - Comment: "s390 parameter ordering for clone is different", - Includes: Filter{ - Arches: []string{"s390", "s390x"}, - }, - Excludes: Filter{ - Caps: []string{"CAP_SYS_ADMIN"}, - }, - }, - { - Names: []string{ - "reboot", - }, - Action: ActAllow, - Args: []*Arg{}, - Includes: Filter{ - Caps: []string{"CAP_SYS_BOOT"}, - }, - }, - { - Names: []string{ "chroot", }, Action: ActAllow, @@ -608,7 +557,6 @@ func DefaultProfile() *Seccomp { Names: []string{ "get_mempolicy", "mbind", - "name_to_handle_at", "set_mempolicy", }, Action: ActAllow, @@ -630,6 +578,7 @@ func DefaultProfile() *Seccomp { { Names: []string{ "kcmp", + "process_madvise", "process_vm_readv", "process_vm_writev", "ptrace", @@ -683,12 +632,12 @@ func DefaultProfile() *Seccomp { Args: []*Arg{ { Index: 0, - Value: syscall.AF_NETLINK, + Value: unix.AF_NETLINK, Op: OpEqualTo, }, { Index: 2, - Value: syscall.NETLINK_AUDIT, + Value: unix.NETLINK_AUDIT, Op: OpEqualTo, }, }, @@ -704,7 +653,7 @@ func DefaultProfile() *Seccomp { Args: []*Arg{ { Index: 2, - Value: syscall.NETLINK_AUDIT, + Value: unix.NETLINK_AUDIT, Op: OpNotEqual, }, }, @@ -720,7 +669,7 @@ func DefaultProfile() *Seccomp { Args: []*Arg{ { Index: 0, - Value: syscall.AF_NETLINK, + Value: unix.AF_NETLINK, Op: OpNotEqual, }, }, @@ -736,7 +685,7 @@ func DefaultProfile() *Seccomp { Args: []*Arg{ { Index: 2, - Value: syscall.NETLINK_AUDIT, + Value: unix.NETLINK_AUDIT, Op: OpNotEqual, }, }, diff --git a/vendor/github.com/containers/common/pkg/seccomp/seccomp.json b/vendor/github.com/containers/common/pkg/seccomp/seccomp.json index d6f3f4938..48420905c 100644 --- a/vendor/github.com/containers/common/pkg/seccomp/seccomp.json +++ b/vendor/github.com/containers/common/pkg/seccomp/seccomp.json @@ -89,6 +89,7 @@ "epoll_ctl", "epoll_ctl_old", "epoll_pwait", + "epoll_pwait2", "epoll_wait", "epoll_wait_old", "eventfd", @@ -117,7 +118,11 @@ "flock", "fork", "fremovexattr", + "fsconfig", "fsetxattr", + "fsmount", + "fsopen", + "fspick", "fstat", "fstat64", "fstatat64", @@ -177,6 +182,7 @@ "ioprio_get", "ioprio_set", "ipc", + "keyctl", "kill", "lchown", "lchown32", @@ -204,6 +210,7 @@ "mmap", "mmap2", "mount", + "move_mount", "mprotect", "mq_getsetattr", "mq_notify", @@ -226,6 +233,7 @@ "open", "openat", "openat2", + "open_tree", "pause", "pidfd_getfd", "pidfd_open", @@ -574,19 +582,13 @@ { "names": [ "bpf", - "clone", "fanotify_init", "lookup_dcookie", - "mount", - "name_to_handle_at", "perf_event_open", "quotactl", "setdomainname", "sethostname", - "setns", - "umount", - "umount2", - "unshare" + "setns" ], "action": "SCMP_ACT_ALLOW", "args": [], @@ -600,71 +602,6 @@ }, { "names": [ - "clone" - ], - "action": "SCMP_ACT_ALLOW", - "args": [ - { - "index": 0, - "value": 2080505856, - "valueTwo": 0, - "op": "SCMP_CMP_MASKED_EQ" - } - ], - "comment": "", - "includes": {}, - "excludes": { - "caps": [ - "CAP_SYS_ADMIN" - ], - "arches": [ - "s390", - "s390x" - ] - } - }, - { - "names": [ - "clone" - ], - "action": "SCMP_ACT_ALLOW", - "args": [ - { - "index": 1, - "value": 2080505856, - "valueTwo": 0, - "op": "SCMP_CMP_MASKED_EQ" - } - ], - "comment": "s390 parameter ordering for clone is different", - "includes": { - "arches": [ - "s390", - "s390x" - ] - }, - "excludes": { - "caps": [ - "CAP_SYS_ADMIN" - ] - } - }, - { - "names": [ - "reboot" - ], - "action": "SCMP_ACT_ALLOW", - "args": [], - "comment": "", - "includes": { - "caps": [ - "CAP_SYS_BOOT" - ] - }, - "excludes": {} - }, - { - "names": [ "chroot" ], "action": "SCMP_ACT_ALLOW", @@ -698,7 +635,6 @@ "names": [ "get_mempolicy", "mbind", - "name_to_handle_at", "set_mempolicy" ], "action": "SCMP_ACT_ALLOW", @@ -728,6 +664,7 @@ { "names": [ "kcmp", + "process_madvise", "process_vm_readv", "process_vm_writev", "ptrace" @@ -894,4 +831,4 @@ "excludes": {} } ] -} +}
\ No newline at end of file diff --git a/vendor/github.com/containers/common/version/version.go b/vendor/github.com/containers/common/version/version.go index 7d7cf59f1..8efc8b8a2 100644 --- a/vendor/github.com/containers/common/version/version.go +++ b/vendor/github.com/containers/common/version/version.go @@ -1,4 +1,4 @@ package version // Version is the version of the build. -const Version = "0.33.1" +const Version = "0.34.3-dev" diff --git a/vendor/modules.txt b/vendor/modules.txt index e8604daab..719285112 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -89,7 +89,7 @@ github.com/containers/buildah/pkg/parse github.com/containers/buildah/pkg/rusage github.com/containers/buildah/pkg/supplemented github.com/containers/buildah/util -# github.com/containers/common v0.33.1 +# github.com/containers/common v0.34.3-0.20210208115708-8668c76dd577 github.com/containers/common/pkg/apparmor github.com/containers/common/pkg/apparmor/internal/supported github.com/containers/common/pkg/auth |