From 91ea3fabd625a891487cd0d9b130ac71366ecb74 Mon Sep 17 00:00:00 2001 From: baude Date: Thu, 4 Feb 2021 12:58:55 -0600 Subject: add network prune add the ability to prune unused cni networks. filters are not implemented but included both compat and podman api endpoints. Fixes :#8673 Signed-off-by: baude --- cmd/podman/networks/prune.go | 82 ++++++++++++++++++++++++++ docs/source/markdown/podman-network-prune.1.md | 31 ++++++++++ docs/source/markdown/podman-network.1.md | 1 + docs/source/network.rst | 2 + libpod/network/network.go | 50 +++++++++++++--- pkg/api/handlers/compat/networks.go | 22 +++++++ pkg/api/handlers/compat/swagger.go | 7 +++ pkg/api/handlers/libpod/networks.go | 14 +++++ pkg/api/server/register_networks.go | 66 +++++++++++++++++---- pkg/bindings/network/network.go | 18 ++++++ pkg/bindings/network/types.go | 6 ++ pkg/bindings/network/types_prune_options.go | 75 +++++++++++++++++++++++ pkg/domain/entities/engine_container.go | 1 + pkg/domain/entities/network.go | 12 ++++ pkg/domain/infra/abi/network.go | 25 ++++++++ pkg/domain/infra/tunnel/network.go | 5 ++ test/apiv2/rest_api/test_rest_v2_0_0.py | 2 +- test/e2e/network_test.go | 50 ++++++++++++++++ 18 files changed, 447 insertions(+), 22 deletions(-) create mode 100644 cmd/podman/networks/prune.go create mode 100644 docs/source/markdown/podman-network-prune.1.md create mode 100644 pkg/bindings/network/types_prune_options.go 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=')") + 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 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 ` network list +:doc:`prune ` network prune + :doc:`reload ` network reload :doc:`rm ` network rm 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= Prune networks created before this timestamp. The 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=, label==, label!=, or label!==) 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= Prune networks created before this timestamp. The 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=, label==, label!=, or label!==) 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/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()) + } + + }) }) -- cgit v1.2.3-54-g00ecf