inclusion of podman network

adding podman network and the subcommands inspect, list, and rm.  the
inspect subcommand displays the raw cni network configuration.  the list
subcommand displays a summary of the cni networks ala ps.  and the rm
subcommand removes a cni network.

Signed-off-by: baude <bbaude@redhat.com>
This commit is contained in:
baude
2019-07-03 19:21:38 -05:00
parent 3f1657d729
commit 6220ef1488
16 changed files with 745 additions and 0 deletions

View File

@ -258,6 +258,20 @@ type MountValues struct {
Latest bool
}
type NetworkListValues struct {
PodmanCommand
Filter []string
Quiet bool
}
type NetworkRmValues struct {
PodmanCommand
}
type NetworkInspectValues struct {
PodmanCommand
}
type PauseValues struct {
PodmanCommand
All bool

31
cmd/podman/network.go Normal file
View File

@ -0,0 +1,31 @@
//+build !remoteclient
package main
import (
"github.com/containers/libpod/cmd/podman/cliconfig"
"github.com/spf13/cobra"
)
var networkcheckDescription = "Manage networks"
var networkcheckCommand = cliconfig.PodmanCommand{
Command: &cobra.Command{
Use: "network",
Short: "Manage Networks",
Long: networkcheckDescription,
RunE: commandRunE(),
},
}
// Commands that are universally implemented
var networkcheckCommands = []*cobra.Command{
_networkinspectCommand,
_networklistCommand,
_networkrmCommand,
}
func init() {
networkcheckCommand.AddCommand(networkcheckCommands...)
networkcheckCommand.SetUsageTemplate(UsageTemplate())
rootCmd.AddCommand(networkcheckCommand.Command)
}

View File

@ -0,0 +1,48 @@
// +build !remoteclient
package main
import (
"github.com/containers/libpod/cmd/podman/cliconfig"
"github.com/containers/libpod/pkg/adapter"
"github.com/containers/libpod/pkg/rootless"
"github.com/pkg/errors"
"github.com/spf13/cobra"
)
var (
networkinspectCommand cliconfig.NetworkInspectValues
networkinspectDescription = `Inspect network`
_networkinspectCommand = &cobra.Command{
Use: "inspect NETWORK [NETWORK...] [flags] ",
Short: "network inspect",
Long: networkinspectDescription,
RunE: func(cmd *cobra.Command, args []string) error {
networkinspectCommand.InputArgs = args
networkinspectCommand.GlobalFlags = MainGlobalOpts
networkinspectCommand.Remote = remoteclient
return networkinspectCmd(&networkinspectCommand)
},
Example: `podman network inspect podman`,
}
)
func init() {
networkinspectCommand.Command = _networkinspectCommand
networkinspectCommand.SetHelpTemplate(HelpTemplate())
networkinspectCommand.SetUsageTemplate(UsageTemplate())
}
func networkinspectCmd(c *cliconfig.NetworkInspectValues) error {
if rootless.IsRootless() && !remoteclient {
return errors.New("network inspect is not supported for rootless mode")
}
if len(c.InputArgs) < 1 {
return errors.Errorf("at least one network name is required")
}
runtime, err := adapter.GetRuntimeNoStore(getContext(), &c.PodmanCommand)
if err != nil {
return err
}
return runtime.NetworkInspect(c)
}

View File

@ -0,0 +1,53 @@
// +build !remoteclient
package main
import (
"errors"
"github.com/containers/libpod/cmd/podman/cliconfig"
"github.com/containers/libpod/pkg/adapter"
"github.com/containers/libpod/pkg/rootless"
"github.com/spf13/cobra"
)
var (
networklistCommand cliconfig.NetworkListValues
networklistDescription = `List networks`
_networklistCommand = &cobra.Command{
Use: "ls",
Args: noSubArgs,
Short: "network list",
Long: networklistDescription,
RunE: func(cmd *cobra.Command, args []string) error {
networklistCommand.InputArgs = args
networklistCommand.GlobalFlags = MainGlobalOpts
networklistCommand.Remote = remoteclient
return networklistCmd(&networklistCommand)
},
Example: `podman network list`,
}
)
func init() {
networklistCommand.Command = _networklistCommand
networklistCommand.SetHelpTemplate(HelpTemplate())
networklistCommand.SetUsageTemplate(UsageTemplate())
flags := networklistCommand.Flags()
// TODO enable filters based on something
//flags.StringSliceVarP(&networklistCommand.Filter, "filter", "f", []string{}, "Pause all running containers")
flags.BoolVarP(&networklistCommand.Quiet, "quiet", "q", false, "display only names")
}
func networklistCmd(c *cliconfig.NetworkListValues) error {
if rootless.IsRootless() && !remoteclient {
return errors.New("network list is not supported for rootless mode")
}
if len(c.InputArgs) > 0 {
return errors.New("network list takes no arguments")
}
runtime, err := adapter.GetRuntimeNoStore(getContext(), &c.PodmanCommand)
if err != nil {
return err
}
return runtime.NetworkList(c)
}

48
cmd/podman/network_rm.go Normal file
View File

@ -0,0 +1,48 @@
// +build !remoteclient
package main
import (
"github.com/containers/libpod/cmd/podman/cliconfig"
"github.com/containers/libpod/pkg/adapter"
"github.com/containers/libpod/pkg/rootless"
"github.com/pkg/errors"
"github.com/spf13/cobra"
)
var (
networkrmCommand cliconfig.NetworkRmValues
networkrmDescription = `Remove networks`
_networkrmCommand = &cobra.Command{
Use: "rm [flags] NETWORK [NETWORK...]",
Short: "network rm",
Long: networkrmDescription,
RunE: func(cmd *cobra.Command, args []string) error {
networkrmCommand.InputArgs = args
networkrmCommand.GlobalFlags = MainGlobalOpts
networkrmCommand.Remote = remoteclient
return networkrmCmd(&networkrmCommand)
},
Example: `podman network rm podman`,
}
)
func init() {
networkrmCommand.Command = _networkrmCommand
networkrmCommand.SetHelpTemplate(HelpTemplate())
networkrmCommand.SetUsageTemplate(UsageTemplate())
}
func networkrmCmd(c *cliconfig.NetworkRmValues) error {
if rootless.IsRootless() && !remoteclient {
return errors.New("network rm is not supported for rootless mode")
}
if len(c.InputArgs) < 1 {
return errors.Errorf("at least one network name is required")
}
runtime, err := adapter.GetRuntimeNoStore(getContext(), &c.PodmanCommand)
if err != nil {
return err
}
return runtime.NetworkRemove(c)
}

View File

@ -44,6 +44,10 @@
| [podman-logout(1)](/docs/podman-logout.1.md) | Logout of a container registry |
| [podman-logs(1)](/docs/podman-logs.1.md) | Display the logs of a container |
| [podman-mount(1)](/docs/podman-mount.1.md) | Mount a working container's root filesystem |
| [podman-network(1)](/docs/podman-network.1.md) | Manage Podman CNI networks |
| [podman-network-inspect(1)](/docs/podman-network-inspect.1.md) | Inspect one or more Podman networks |
| [podman-network-ls(1)](/docs/podman-network-ls.1.md) | Display a summary of Podman networks |
| [podman-network-rm(1)](/docs/podman-network-rm.1.md) | Remove one or more Podman networks |
| [podman-pause(1)](/docs/podman-pause.1.md) | Pause one or more running containers | [![...](/docs/play.png)](https://podman.io/asciinema/podman/pause_unpause/) | [Here](https://github.com/containers/Demos/blob/master/podman_cli/podman_pause_unpause.sh) |
| [podman-play(1)](/docs/podman-play.1.md) | Play pods and containers based on a structured input file |
| [podman-pod(1)](/docs/podman-pod.1.md) | Simple management tool for groups of containers, called pods |

View File

@ -946,6 +946,78 @@ _podman_healthcheck() {
esac
}
_podman_network() {
local boolean_options="
--help
-h
"
subcommands="
inspect
ls
rm
"
__podman_subcommands "$subcommands $aliases" && return
case "$cur" in
-*)
COMPREPLY=( $( compgen -W "--help" -- "$cur" ) )
;;
*)
COMPREPLY=( $( compgen -W "$subcommands" -- "$cur" ) )
;;
esac
}
_podman_network_inspect() {
local options_with_args="
"
local boolean_options="
--help
-h
"
_complete_ "$options_with_args" "$boolean_options"
case "$cur" in
-*)
COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur"))
;;
esac
}
_podman_network_ls() {
local options_with_args="
"
local boolean_options="
--help
-h
--quiet
-q
"
_complete_ "$options_with_args" "$boolean_options"
case "$cur" in
-*)
COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur"))
;;
esac
}
_podman_network_ls() {
local options_with_args="
"
local boolean_options="
--help
-h
"
_complete_ "$options_with_args" "$boolean_options"
case "$cur" in
-*)
COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur"))
;;
esac
}
_podman_generate() {
local boolean_options="
--help

View File

@ -0,0 +1,50 @@
% podman-network-inspect(1)
## NAME
podman\-network-inspect- Inspect one or more Podman networks
## SYNOPSIS
**podman network inspect** [*network* ...]
## DESCRIPTION
Display the raw (JSON format) network configuration. This command is not available for rootless users.
## EXAMPLE
Inspect the default podman network
```
# podman network inspect podman
[{
"cniVersion": "0.3.0",
"name": "podman",
"plugins": [
{
"type": "bridge",
"bridge": "cni0",
"isGateway": true,
"ipMasq": true,
"ipam": {
"type": "host-local",
"subnet": "10.88.1.0/24",
"routes": [
{ "dst": "0.0.0.0/0" }
]
}
},
{
"type": "portmap",
"capabilities": {
"portMappings": true
}
}
]
}
]
```
## SEE ALSO
podman(1), podman-network(1), podman-network-ls(1)
## HISTORY
August 2019, Originally compiled by Brent Baude <bbaude@redhat.com>

View File

@ -0,0 +1,43 @@
% podman-network-ls(1)
## NAME
podman\-network-ls- Display a summary of CNI networks
## SYNOPSIS
**podman network ls** [*options*]
## DESCRIPTION
Displays a list of existing podman networks. This command is not available for rootless users.
## OPTIONS
**--quiet**, **-q**
The `quiet` options will restrict the output to only the network names
## EXAMPLE
Display networks
```
# podman network ls
NAME VERSION PLUGINS
podman 0.3.0 bridge,portmap
podman2 0.3.0 bridge,portmap
outside 0.3.0 bridge
podman9 0.3.0 bridge,portmap
```
Display only network names
```
# podman network ls -q
podman
podman2
outside
podman9
```
## SEE ALSO
podman(1), podman-network(1), podman-network-inspect(1)
## HISTORY
August 2019, Originally compiled by Brent Baude <bbaude@redhat.com>

View File

@ -0,0 +1,25 @@
% podman-network-rm(1)
## NAME
podman\-network-rm- Delete a Podman CNI network
## SYNOPSIS
**podman network rm** [*network...*]
## DESCRIPTION
Delete one or more Podman networks.
## EXAMPLE
Delete the `podman9` network
```
# podman network rm podman
Deleted: podman9
```
## SEE ALSO
podman(1), podman-network(1), podman-network-inspect(1)
## HISTORY
August 2019, Originally compiled by Brent Baude <bbaude@redhat.com>

21
docs/podman-network.1.md Normal file
View File

@ -0,0 +1,21 @@
% podman-network(1)
## NAME
podman\-network- Manage podman CNI networks
## SYNOPSIS
**podman network** *subcommand*
## DESCRIPTION
The network command manages CNI networks for Podman. It is not supported for rootless users.
## COMMANDS
| Command | Man Page | Description |
| ------- | --------------------------------------------------- | ---------------------------------------------------------------------------- |
| 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 |
| rm | [podman-network-rm(1)](podman-network-rm.1.md)| Remove one or more CNI networks |
## SEE ALSO
podman(1)

View File

@ -161,6 +161,7 @@ the exit codes follow the `chroot` standard, see below:
| [podman-logout(1)](podman-logout.1.md) | Logout of a container registry. |
| [podman-logs(1)](podman-logs.1.md) | Display the logs of a container. |
| [podman-mount(1)](podman-mount.1.md) | Mount a working container's root filesystem. |
| [podman-network(1)](podman-network.1.md) | Manage Podman CNI networks. |
| [podman-pause(1)](podman-pause.1.md) | Pause one or more containers. |
| [podman-play(1)](podman-play.1.md) | Play pods and containers based on a structured input file. |
| [podman-pod(1)](podman-pod.1.md) | Management tool for groups of containers, called pods. |

147
pkg/adapter/network.go Normal file
View File

@ -0,0 +1,147 @@
// +build !remoteclient
package adapter
import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
"strings"
"text/tabwriter"
"github.com/containernetworking/cni/libcni"
"github.com/containers/libpod/cmd/podman/cliconfig"
"github.com/containers/libpod/pkg/network"
"github.com/pkg/errors"
)
func getCNIConfDir(r *LocalRuntime) (string, error) {
config, err := r.GetConfig()
if err != nil {
return "", err
}
configPath := config.CNIConfigDir
if len(config.CNIConfigDir) < 1 {
configPath = network.CNIConfigDir
}
return configPath, nil
}
// NetworkList displays summary information about CNI networks
func (r *LocalRuntime) NetworkList(cli *cliconfig.NetworkListValues) error {
cniConfigPath, err := getCNIConfDir(r)
if err != nil {
return err
}
networks, err := network.LoadCNIConfsFromDir(cniConfigPath)
if err != nil {
return err
}
// quiet means we only print the network names
if cli.Quiet {
for _, cniNetwork := range networks {
fmt.Println(cniNetwork.Name)
}
return nil
}
w := tabwriter.NewWriter(os.Stdout, 0, 0, 3, ' ', 0)
if _, err := fmt.Fprintln(w, "NAME\tVERSION\tPLUGINS"); err != nil {
return err
}
for _, cniNetwork := range networks {
if _, err := fmt.Fprintf(w, "%s\t%s\t%s\n", cniNetwork.Name, cniNetwork.CNIVersion, getCNIPlugins(cniNetwork)); err != nil {
return err
}
}
return w.Flush()
}
// NetworkInspect displays the raw CNI configuration for one
// or more CNI networks
func (r *LocalRuntime) NetworkInspect(cli *cliconfig.NetworkInspectValues) error {
var (
rawCNINetworks []map[string]interface{}
)
cniConfigPath, err := getCNIConfDir(r)
if err != nil {
return err
}
for _, name := range cli.InputArgs {
b, err := readRawCNIConfByName(name, cniConfigPath)
if err != nil {
return err
}
rawList := make(map[string]interface{})
if err := json.Unmarshal(b, &rawList); err != nil {
return fmt.Errorf("error parsing configuration list: %s", err)
}
rawCNINetworks = append(rawCNINetworks, rawList)
}
out, err := json.MarshalIndent(rawCNINetworks, "", "\t")
if err != nil {
return err
}
fmt.Printf("%s\n", out)
return nil
}
// NetworkRemove deletes one or more CNI networks
func (r *LocalRuntime) NetworkRemove(cli *cliconfig.NetworkRmValues) error {
cniConfigPath, err := getCNIConfDir(r)
if err != nil {
return err
}
for _, name := range cli.InputArgs {
cniPath, err := getCNIConfigPathByName(name, cniConfigPath)
if err != nil {
return err
}
if err := os.Remove(cniPath); err != nil {
return err
}
fmt.Printf("Deleted: %s\n", name)
}
return nil
}
// getCNIConfigPathByName finds a CNI network by name and
// returns its configuration file path
func getCNIConfigPathByName(name, cniConfigPath string) (string, error) {
files, err := libcni.ConfFiles(cniConfigPath, []string{".conflist"})
if err != nil {
return "", err
}
for _, confFile := range files {
conf, err := libcni.ConfListFromFile(confFile)
if err != nil {
return "", err
}
if conf.Name == name {
return confFile, nil
}
}
return "", errors.Errorf("unable to find network configuration for %s", name)
}
// readRawCNIConfByName reads the raw CNI configuration for a CNI
// network by name
func readRawCNIConfByName(name, cniConfigPath string) ([]byte, error) {
confFile, err := getCNIConfigPathByName(name, cniConfigPath)
if err != nil {
return nil, err
}
b, err := ioutil.ReadFile(confFile)
return b, err
}
// getCNIPlugins returns a list of plugins that a given network
// has in the form of a string
func getCNIPlugins(list *libcni.NetworkConfigList) string {
var plugins []string
for _, plug := range list.Plugins {
plugins = append(plugins, plug.Network.Type)
}
return strings.Join(plugins, ",")
}

4
pkg/network/config.go Normal file
View File

@ -0,0 +1,4 @@
package network
// CNIConfigDir is the path where CNI config files exist
const CNIConfigDir = "/etc/cni/net.d"

26
pkg/network/network.go Normal file
View File

@ -0,0 +1,26 @@
package network
import (
"sort"
"github.com/containernetworking/cni/libcni"
)
// LoadCNIConfsFromDir loads all the CNI configurations from a dir
func LoadCNIConfsFromDir(dir string) ([]*libcni.NetworkConfigList, error) {
var configs []*libcni.NetworkConfigList
files, err := libcni.ConfFiles(dir, []string{".conflist"})
if err != nil {
return nil, err
}
sort.Strings(files)
for _, confFile := range files {
conf, err := libcni.ConfListFromFile(confFile)
if err != nil {
return nil, err
}
configs = append(configs, conf)
}
return configs, nil
}

158
test/e2e/network_test.go Normal file
View File

@ -0,0 +1,158 @@
// +build !remoteclient
package integration
import (
"fmt"
. "github.com/containers/libpod/test/utils"
"github.com/containers/storage/pkg/stringid"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"io/ioutil"
"os"
"path/filepath"
)
func writeConf(conf []byte, confPath string) {
if err := ioutil.WriteFile(confPath, conf, 777); err != nil {
fmt.Println(err)
}
}
func removeConf(confPath string) {
if err := os.Remove(confPath); err != nil {
fmt.Println(err)
}
}
var _ = Describe("Podman network", func() {
var (
tempdir string
err error
podmanTest *PodmanTestIntegration
)
BeforeEach(func() {
tempdir, err = CreateTempDirInTempDir()
if err != nil {
os.Exit(1)
}
podmanTest = PodmanTestCreate(tempdir)
podmanTest.Setup()
})
AfterEach(func() {
podmanTest.Cleanup()
f := CurrentGinkgoTestDescription()
processTestResult(f)
})
var (
secondConf = `{
"cniVersion": "0.3.0",
"name": "podman-integrationtest",
"plugins": [
{
"type": "bridge",
"bridge": "cni1",
"isGateway": true,
"ipMasq": true,
"ipam": {
"type": "host-local",
"subnet": "10.99.0.0/16",
"routes": [
{ "dst": "0.0.0.0/0" }
]
}
},
{
"type": "portmap",
"capabilities": {
"portMappings": true
}
}
]
}`
cniPath = "/etc/cni/net.d"
)
It("podman network list", func() {
SkipIfRootless()
// Setup, use uuid to prevent conflict with other tests
uuid := stringid.GenerateNonCryptoID()
secondPath := filepath.Join(cniPath, fmt.Sprintf("%s.conflist", uuid))
writeConf([]byte(secondConf), secondPath)
defer removeConf(secondPath)
session := podmanTest.Podman([]string{"network", "ls"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
Expect(session.LineInOutputContains("podman-integrationtest")).To(BeTrue())
})
It("podman network list -q", func() {
SkipIfRootless()
// Setup, use uuid to prevent conflict with other tests
uuid := stringid.GenerateNonCryptoID()
secondPath := filepath.Join(cniPath, fmt.Sprintf("%s.conflist", uuid))
writeConf([]byte(secondConf), secondPath)
defer removeConf(secondPath)
session := podmanTest.Podman([]string{"network", "ls", "--quiet"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
Expect(session.LineInOutputContains("podman-integrationtest")).To(BeTrue())
})
It("podman network rm no args", func() {
SkipIfRootless()
session := podmanTest.Podman([]string{"network", "rm"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).ToNot(BeZero())
})
It("podman network rm", func() {
SkipIfRootless()
// Setup, use uuid to prevent conflict with other tests
uuid := stringid.GenerateNonCryptoID()
secondPath := filepath.Join(cniPath, fmt.Sprintf("%s.conflist", uuid))
writeConf([]byte(secondConf), secondPath)
defer removeConf(secondPath)
session := podmanTest.Podman([]string{"network", "ls", "--quiet"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
Expect(session.LineInOutputContains("podman-integrationtest")).To(BeTrue())
rm := podmanTest.Podman([]string{"network", "rm", "podman-integrationtest"})
rm.WaitWithDefaultTimeout()
Expect(rm.ExitCode()).To(BeZero())
results := podmanTest.Podman([]string{"network", "ls", "--quiet"})
results.WaitWithDefaultTimeout()
Expect(results.ExitCode()).To(Equal(0))
Expect(results.LineInOutputContains("podman-integrationtest")).To(BeFalse())
})
It("podman network inspect no args", func() {
SkipIfRootless()
session := podmanTest.Podman([]string{"network", "inspect"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).ToNot(BeZero())
})
It("podman network inspect", func() {
SkipIfRootless()
// Setup, use uuid to prevent conflict with other tests
uuid := stringid.GenerateNonCryptoID()
secondPath := filepath.Join(cniPath, fmt.Sprintf("%s.conflist", uuid))
writeConf([]byte(secondConf), secondPath)
defer removeConf(secondPath)
session := podmanTest.Podman([]string{"network", "inspect", "podman-integrationtest", "podman"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
Expect(session.IsJSONOutputValid()).To(BeTrue())
})
})