diff options
44 files changed, 893 insertions, 116 deletions
diff --git a/.cirrus.yml b/.cirrus.yml index 5898fa160..2aae343e8 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -39,7 +39,7 @@ env: UBUNTU_NAME: "ubuntu-19" PRIOR_UBUNTU_NAME: "ubuntu-18" - _BUILT_IMAGE_SUFFIX: "libpod-6220812239765504" + _BUILT_IMAGE_SUFFIX: "libpod-6224667180531712" # From the packer output of 'build_vm_images_script' FEDORA_CACHE_IMAGE_NAME: "${FEDORA_NAME}-${_BUILT_IMAGE_SUFFIX}" PRIOR_FEDORA_CACHE_IMAGE_NAME: "${PRIOR_FEDORA_NAME}-${_BUILT_IMAGE_SUFFIX}" UBUNTU_CACHE_IMAGE_NAME: "${UBUNTU_NAME}-${_BUILT_IMAGE_SUFFIX}" diff --git a/cmd/podman/common/create.go b/cmd/podman/common/create.go index a0aed984c..0f9476754 100644 --- a/cmd/podman/common/create.go +++ b/cmd/podman/common/create.go @@ -49,9 +49,7 @@ func GetCreateFlags(cf *ContainerCLIOpts) *pflag.FlagSet { "cap-drop", []string{}, "Drop capabilities from the container", ) - cgroupNS := "" - createFlags.StringVar( - &cgroupNS, + createFlags.String( "cgroupns", containerConfig.CgroupNS(), "cgroup namespace to use", ) @@ -155,9 +153,7 @@ func GetCreateFlags(cf *ContainerCLIOpts) *pflag.FlagSet { "device-write-iops", []string{}, "Limit write rate (IO per second) to a device (e.g. --device-write-iops=/dev/sda:1000)", ) - createFlags.StringVar( - &cf.Entrypoint, - "entrypoint", "", + createFlags.String("entrypoint", "", "Overwrite the default ENTRYPOINT of the image", ) createFlags.StringArrayVarP( @@ -248,9 +244,7 @@ func GetCreateFlags(cf *ContainerCLIOpts) *pflag.FlagSet { "interactive", "i", false, "Keep STDIN open even if not attached", ) - ipcNS := "" - createFlags.StringVar( - &ipcNS, + createFlags.String( "ipc", containerConfig.IPCNS(), "IPC namespace to use", ) @@ -331,9 +325,7 @@ func GetCreateFlags(cf *ContainerCLIOpts) *pflag.FlagSet { "use `OS` instead of the running OS for choosing images", ) // markFlagHidden(createFlags, "override-os") - pid := "" - createFlags.StringVar( - &pid, + createFlags.String( "pid", containerConfig.PidNS(), "PID namespace to use", ) @@ -397,9 +389,7 @@ func GetCreateFlags(cf *ContainerCLIOpts) *pflag.FlagSet { "security-opt", containerConfig.SecurityOptions(), "Security Options", ) - shmSize := "" - createFlags.StringVar( - &shmSize, + createFlags.String( "shm-size", containerConfig.ShmSize(), "Size of /dev/shm "+sizeWithUnitFormat, ) @@ -464,15 +454,11 @@ func GetCreateFlags(cf *ContainerCLIOpts) *pflag.FlagSet { "user", "u", "", "Username or UID (format: <name|uid>[:<group|gid>])", ) - userNS := "" - createFlags.StringVar( - &userNS, + createFlags.String( "userns", containerConfig.Containers.UserNS, "User namespace to use", ) - utsNS := "" - createFlags.StringVar( - &utsNS, + createFlags.String( "uts", containerConfig.Containers.UTSNS, "UTS namespace to use", ) diff --git a/cmd/podman/common/create_opts.go b/cmd/podman/common/create_opts.go index 2f08bb6a6..c275b1674 100644 --- a/cmd/podman/common/create_opts.go +++ b/cmd/podman/common/create_opts.go @@ -31,7 +31,7 @@ type ContainerCLIOpts struct { DeviceReadIOPs []string DeviceWriteBPs []string DeviceWriteIOPs []string - Entrypoint string + Entrypoint *string env []string EnvHost bool EnvFile []string diff --git a/cmd/podman/common/specgen.go b/cmd/podman/common/specgen.go index 96cd630a3..33cba30cd 100644 --- a/cmd/podman/common/specgen.go +++ b/cmd/podman/common/specgen.go @@ -364,20 +364,20 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string s.WorkDir = workDir entrypoint := []string{} userCommand := []string{} - if ep := c.Entrypoint; len(ep) > 0 { - // Check if entrypoint specified is json - if err := json.Unmarshal([]byte(c.Entrypoint), &entrypoint); err != nil { - entrypoint = append(entrypoint, ep) + if c.Entrypoint != nil { + if ep := *c.Entrypoint; len(ep) > 0 { + // Check if entrypoint specified is json + if err := json.Unmarshal([]byte(*c.Entrypoint), &entrypoint); err != nil { + entrypoint = append(entrypoint, ep) + } } + s.Entrypoint = entrypoint } - var command []string - s.Entrypoint = entrypoint - // Build the command // If we have an entry point, it goes first - if len(entrypoint) > 0 { + if c.Entrypoint != nil { command = entrypoint } if len(inputCommand) > 0 { @@ -386,9 +386,12 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string userCommand = append(userCommand, inputCommand...) } - if len(inputCommand) > 0 { + switch { + case len(inputCommand) > 0: s.Command = userCommand - } else { + case c.Entrypoint != nil: + s.Command = []string{} + default: s.Command = command } @@ -400,6 +403,7 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string } s.ShmSize = &shmSize } + s.CNINetworks = c.Net.CNINetworks s.HostAdd = c.Net.AddHosts s.UseImageResolvConf = c.Net.UseImageResolvConf s.DNSServers = c.Net.DNSServers diff --git a/cmd/podman/containers/attach.go b/cmd/podman/containers/attach.go index ee4d811d7..119b47d3f 100644 --- a/cmd/podman/containers/attach.go +++ b/cmd/podman/containers/attach.go @@ -4,6 +4,7 @@ import ( "os" "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/cmd/podman/validate" "github.com/containers/libpod/pkg/domain/entities" "github.com/pkg/errors" "github.com/spf13/cobra" @@ -17,12 +18,7 @@ var ( Short: "Attach to a running container", Long: attachDescription, RunE: attach, - Args: func(cmd *cobra.Command, args []string) error { - if len(args) > 1 || (len(args) == 0 && !cmd.Flag("latest").Changed) { - return errors.Errorf("attach requires the name or id of one running container or the latest flag") - } - return nil - }, + Args: validate.IdOrLatestArgs, Example: `podman attach ctrID podman attach 1234 podman attach --no-stdin foobar`, @@ -33,6 +29,7 @@ var ( Short: attachCommand.Short, Long: attachCommand.Long, RunE: attachCommand.RunE, + Args: validate.IdOrLatestArgs, Example: `podman container attach ctrID podman container attach 1234 podman container attach --no-stdin foobar`, diff --git a/cmd/podman/containers/commit.go b/cmd/podman/containers/commit.go index 137e486eb..b3c3d7626 100644 --- a/cmd/podman/containers/commit.go +++ b/cmd/podman/containers/commit.go @@ -30,6 +30,7 @@ var ( } containerCommitCommand = &cobra.Command{ + Args: cobra.MinimumNArgs(1), Use: commitCommand.Use, Short: commitCommand.Short, Long: commitCommand.Long, diff --git a/cmd/podman/containers/create.go b/cmd/podman/containers/create.go index 3e47a8b4f..7927da04d 100644 --- a/cmd/podman/containers/create.go +++ b/cmd/podman/containers/create.go @@ -1,8 +1,10 @@ package containers import ( + "context" "fmt" "os" + "strings" "github.com/containers/common/pkg/config" "github.com/containers/libpod/cmd/podman/common" @@ -33,6 +35,7 @@ var ( } containerCreateCommand = &cobra.Command{ + Args: cobra.MinimumNArgs(1), Use: createCommand.Use, Short: createCommand.Short, Long: createCommand.Long, @@ -105,6 +108,10 @@ func create(cmd *cobra.Command, args []string) error { return err } + if _, err := createPodIfNecessary(s); err != nil { + return err + } + report, err := registry.ContainerEngine().ContainerCreate(registry.GetContext(), s) if err != nil { return err @@ -159,6 +166,10 @@ func createInit(c *cobra.Command) error { if c.Flag("cgroupns").Changed { cliVals.CGroupsNS = c.Flag("cgroupns").Value.String() } + if c.Flag("entrypoint").Changed { + val := c.Flag("entrypoint").Value.String() + cliVals.Entrypoint = &val + } // Docker-compatibility: the "-h" flag for run/create is reserved for // the hostname (see https://github.com/containers/libpod/issues/1367). @@ -180,8 +191,10 @@ func pullImage(imageName string) error { return errors.New("unable to find a name and tag match for busybox in repotags: no such image") } _, pullErr := registry.ImageEngine().Pull(registry.GetContext(), imageName, entities.ImagePullOptions{ - Authfile: cliVals.Authfile, - Quiet: cliVals.Quiet, + Authfile: cliVals.Authfile, + Quiet: cliVals.Quiet, + OverrideArch: cliVals.OverrideArch, + OverrideOS: cliVals.OverrideOS, }) if pullErr != nil { return pullErr @@ -203,3 +216,25 @@ func openCidFile(cidfile string) (*os.File, error) { } return cidFile, nil } + +// createPodIfNecessary automatically creates a pod when requested. if the pod name +// has the form new:ID, the pod ID is created and the name in the spec generator is replaced +// with ID. +func createPodIfNecessary(s *specgen.SpecGenerator) (*entities.PodCreateReport, error) { + if !strings.HasPrefix(s.Pod, "new:") { + return nil, nil + } + podName := strings.Replace(s.Pod, "new:", "", 1) + if len(podName) < 1 { + return nil, errors.Errorf("new pod name must be at least one character") + } + createOptions := entities.PodCreateOptions{ + Name: podName, + Infra: true, + Net: &entities.NetOptions{ + PublishPorts: s.PortMappings, + }, + } + s.Pod = podName + return registry.ContainerEngine().PodCreate(context.Background(), createOptions) +} diff --git a/cmd/podman/containers/exec.go b/cmd/podman/containers/exec.go index 2bff8ae33..0992b3862 100644 --- a/cmd/podman/containers/exec.go +++ b/cmd/podman/containers/exec.go @@ -16,20 +16,22 @@ var ( execDescription = `Execute the specified command inside a running container. ` execCommand = &cobra.Command{ - Use: "exec [flags] CONTAINER [COMMAND [ARG...]]", - Short: "Run a process in a running container", - Long: execDescription, - RunE: exec, + Use: "exec [flags] CONTAINER [COMMAND [ARG...]]", + Short: "Run a process in a running container", + Long: execDescription, + RunE: exec, + DisableFlagsInUseLine: true, Example: `podman exec -it ctrID ls podman exec -it -w /tmp myCtr pwd podman exec --user root ctrID ls`, } containerExecCommand = &cobra.Command{ - Use: execCommand.Use, - Short: execCommand.Short, - Long: execCommand.Long, - RunE: execCommand.RunE, + Use: execCommand.Use, + Short: execCommand.Short, + Long: execCommand.Long, + RunE: execCommand.RunE, + DisableFlagsInUseLine: true, Example: `podman container exec -it ctrID ls podman container exec -it -w /tmp myCtr pwd podman container exec --user root ctrID ls`, @@ -79,6 +81,10 @@ func init() { func exec(cmd *cobra.Command, args []string) error { var nameOrId string + + if len(args) == 0 && !execOpts.Latest { + return errors.New("exec requires the name or ID of a container or the --latest flag") + } execOpts.Cmd = args if !execOpts.Latest { execOpts.Cmd = args[1:] diff --git a/cmd/podman/containers/export.go b/cmd/podman/containers/export.go index fb5bd468f..bbb6a6bc9 100644 --- a/cmd/podman/containers/export.go +++ b/cmd/podman/containers/export.go @@ -28,6 +28,7 @@ var ( } containerExportCommand = &cobra.Command{ + Args: cobra.MinimumNArgs(1), Use: exportCommand.Use, Short: exportCommand.Short, Long: exportCommand.Long, diff --git a/cmd/podman/containers/kill.go b/cmd/podman/containers/kill.go index 8b4a384fe..ef85aad7d 100644 --- a/cmd/podman/containers/kill.go +++ b/cmd/podman/containers/kill.go @@ -30,6 +30,9 @@ var ( } containerKillCommand = &cobra.Command{ + Args: func(cmd *cobra.Command, args []string) error { + return parse.CheckAllLatestAndCIDFile(cmd, args, false, false) + }, Use: killCommand.Use, Short: killCommand.Short, Long: killCommand.Long, diff --git a/cmd/podman/containers/rm.go b/cmd/podman/containers/rm.go index 3021853a9..96549cead 100644 --- a/cmd/podman/containers/rm.go +++ b/cmd/podman/containers/rm.go @@ -38,6 +38,9 @@ var ( Short: rmCommand.Use, Long: rmCommand.Long, RunE: rmCommand.RunE, + Args: func(cmd *cobra.Command, args []string) error { + return parse.CheckAllLatestAndCIDFile(cmd, args, false, true) + }, Example: `podman container rm imageID podman container rm mywebserver myflaskserver 860a4b23 podman container rm --force --all diff --git a/cmd/podman/containers/run.go b/cmd/podman/containers/run.go index e3fe4cd0b..b13983e37 100644 --- a/cmd/podman/containers/run.go +++ b/cmd/podman/containers/run.go @@ -20,6 +20,7 @@ import ( var ( runDescription = "Runs a command in a new container from the given image" runCommand = &cobra.Command{ + Args: cobra.MinimumNArgs(1), Use: "run [flags] IMAGE [COMMAND [ARG...]]", Short: "Run a command in a new container", Long: runDescription, @@ -30,6 +31,7 @@ var ( } containerRunCommand = &cobra.Command{ + Args: cobra.MinimumNArgs(1), Use: runCommand.Use, Short: runCommand.Short, Long: runCommand.Long, @@ -144,6 +146,10 @@ func run(cmd *cobra.Command, args []string) error { } runOpts.Spec = s + if _, err := createPodIfNecessary(s); err != nil { + return err + } + report, err := registry.ContainerEngine().ContainerRun(registry.GetContext(), runOpts) // report.ExitCode is set by ContainerRun even it it returns an error if report != nil { diff --git a/cmd/podman/containers/start.go b/cmd/podman/containers/start.go index 381bf8e26..ce78d24ed 100644 --- a/cmd/podman/containers/start.go +++ b/cmd/podman/containers/start.go @@ -99,7 +99,7 @@ func start(cmd *cobra.Command, args []string) error { for _, r := range responses { if r.Err == nil { - fmt.Println(r.Id) + fmt.Println(r.RawInput) } else { errs = append(errs, r.Err) } diff --git a/cmd/podman/containers/stop.go b/cmd/podman/containers/stop.go index 4a451134a..22c487961 100644 --- a/cmd/podman/containers/stop.go +++ b/cmd/podman/containers/stop.go @@ -34,6 +34,9 @@ var ( Short: stopCommand.Short, Long: stopCommand.Long, RunE: stopCommand.RunE, + Args: func(cmd *cobra.Command, args []string) error { + return parse.CheckAllLatestAndCIDFile(cmd, args, false, true) + }, Example: `podman container stop ctrID podman container stop --latest podman container stop --time 2 mywebserver 6e534f14da9d`, diff --git a/cmd/podman/containers/unmount.go b/cmd/podman/containers/unmount.go index 7b6eb5553..d0ca202fe 100644 --- a/cmd/podman/containers/unmount.go +++ b/cmd/podman/containers/unmount.go @@ -40,6 +40,9 @@ var ( Short: umountCommand.Short, Long: umountCommand.Long, RunE: umountCommand.RunE, + Args: func(cmd *cobra.Command, args []string) error { + return parse.CheckAllLatestAndCIDFile(cmd, args, false, false) + }, Example: `podman container umount ctrID podman container umount ctrID1 ctrID2 ctrID3 podman container umount --all`, diff --git a/cmd/podman/containers/wait.go b/cmd/podman/containers/wait.go index eac1e2956..1f4d4159b 100644 --- a/cmd/podman/containers/wait.go +++ b/cmd/podman/containers/wait.go @@ -34,6 +34,7 @@ var ( Short: waitCommand.Short, Long: waitCommand.Long, RunE: waitCommand.RunE, + Args: validate.IdOrLatestArgs, Example: `podman container wait --latest podman container wait --interval 5000 ctrID podman container wait ctrID1 ctrID2`, diff --git a/cmd/podman/images/pull.go b/cmd/podman/images/pull.go index fead5f7ed..9f4cbc50e 100644 --- a/cmd/podman/images/pull.go +++ b/cmd/podman/images/pull.go @@ -45,6 +45,7 @@ var ( Short: pullCmd.Short, Long: pullCmd.Long, RunE: pullCmd.RunE, + Args: cobra.ExactArgs(1), Example: `podman image pull imageName podman image pull fedora:latest`, } diff --git a/cmd/podman/login.go b/cmd/podman/login.go index 1843a764d..9de805d15 100644 --- a/cmd/podman/login.go +++ b/cmd/podman/login.go @@ -19,7 +19,7 @@ type loginOptionsWrapper struct { var ( loginOptions = loginOptionsWrapper{} loginCommand = &cobra.Command{ - Use: "login [flags] REGISTRY", + Use: "login [flags] [REGISTRY]", Short: "Login to a container registry", Long: "Login to a container registry on a specified server.", RunE: login, diff --git a/cmd/podman/logout.go b/cmd/podman/logout.go index 77bdc92b4..c21711fc0 100644 --- a/cmd/podman/logout.go +++ b/cmd/podman/logout.go @@ -14,7 +14,7 @@ import ( var ( logoutOptions = auth.LogoutOptions{} logoutCommand = &cobra.Command{ - Use: "logout [flags] REGISTRY", + Use: "logout [flags] [REGISTRY]", Short: "Logout of a container registry", Long: "Remove the cached username and password for the registry.", RunE: logout, diff --git a/cmd/podman/main.go b/cmd/podman/main.go index 3a8958b6d..422dee90b 100644 --- a/cmd/podman/main.go +++ b/cmd/podman/main.go @@ -9,6 +9,7 @@ import ( _ "github.com/containers/libpod/cmd/podman/healthcheck" _ "github.com/containers/libpod/cmd/podman/images" _ "github.com/containers/libpod/cmd/podman/manifest" + _ "github.com/containers/libpod/cmd/podman/networks" _ "github.com/containers/libpod/cmd/podman/pods" "github.com/containers/libpod/cmd/podman/registry" _ "github.com/containers/libpod/cmd/podman/system" diff --git a/cmd/podman/networks/create.go b/cmd/podman/networks/create.go new file mode 100644 index 000000000..2bb75ea9e --- /dev/null +++ b/cmd/podman/networks/create.go @@ -0,0 +1,81 @@ +package network + +import ( + "fmt" + "net" + + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/libpod" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/containers/libpod/pkg/network" + "github.com/pkg/errors" + "github.com/spf13/cobra" + "github.com/spf13/pflag" +) + +var ( + networkCreateDescription = `create CNI networks for containers and pods` + networkCreateCommand = &cobra.Command{ + Use: "create [flags] [NETWORK]", + Short: "network create", + Long: networkCreateDescription, + RunE: networkCreate, + Example: `podman network create podman1`, + Annotations: map[string]string{ + registry.ParentNSRequired: "", + }, + } +) + +var ( + networkCreateOptions entities.NetworkCreateOptions +) + +func networkCreateFlags(flags *pflag.FlagSet) { + flags.StringVarP(&networkCreateOptions.Driver, "driver", "d", "bridge", "driver to manage the network") + flags.IPVar(&networkCreateOptions.Gateway, "gateway", nil, "IPv4 or IPv6 gateway for the subnet") + flags.BoolVar(&networkCreateOptions.Internal, "internal", false, "restrict external access from this network") + flags.IPNetVar(&networkCreateOptions.Range, "ip-range", net.IPNet{}, "allocate container IP from range") + flags.StringVar(&networkCreateOptions.MacVLAN, "macvlan", "", "create a Macvlan connection based on this device") + // TODO not supported yet + //flags.StringVar(&networkCreateOptions.IPamDriver, "ipam-driver", "", "IP Address Management Driver") + // TODO enable when IPv6 is working + //flags.BoolVar(&networkCreateOptions.IPV6, "IPv6", false, "enable IPv6 networking") + flags.IPNetVar(&networkCreateOptions.Subnet, "subnet", net.IPNet{}, "subnet in CIDR format") + flags.BoolVar(&networkCreateOptions.DisableDNS, "disable-dns", false, "disable dns plugin") +} +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode}, + Command: networkCreateCommand, + Parent: networkCmd, + }) + flags := networkCreateCommand.Flags() + networkCreateFlags(flags) + +} + +func networkCreate(cmd *cobra.Command, args []string) error { + var ( + name string + ) + if err := network.IsSupportedDriver(networkCreateOptions.Driver); err != nil { + return err + } + if len(args) > 1 { + return errors.Errorf("only one network can be created at a time") + } + if len(args) > 0 && !libpod.NameRegex.MatchString(args[0]) { + return libpod.RegexError + } + + if len(args) > 0 { + name = args[0] + } + response, err := registry.ContainerEngine().NetworkCreate(registry.Context(), name, networkCreateOptions) + if err != nil { + return err + } + fmt.Println(response.Filename) + return nil +} diff --git a/cmd/podman/networks/inspect.go b/cmd/podman/networks/inspect.go new file mode 100644 index 000000000..0bc73579a --- /dev/null +++ b/cmd/podman/networks/inspect.go @@ -0,0 +1,46 @@ +package network + +import ( + "encoding/json" + "fmt" + + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/spf13/cobra" +) + +var ( + networkinspectDescription = `Inspect network` + networkinspectCommand = &cobra.Command{ + Use: "inspect NETWORK [NETWORK...] [flags] ", + Short: "network inspect", + Long: networkinspectDescription, + RunE: networkInspect, + Example: `podman network inspect podman`, + Args: cobra.MinimumNArgs(1), + Annotations: map[string]string{ + registry.ParentNSRequired: "", + }, + } +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode}, + Command: networkinspectCommand, + Parent: networkCmd, + }) +} + +func networkInspect(cmd *cobra.Command, args []string) error { + responses, err := registry.ContainerEngine().NetworkInspect(registry.Context(), args, entities.NetworkInspectOptions{}) + if err != nil { + return err + } + b, err := json.MarshalIndent(responses, "", " ") + if err != nil { + return err + } + fmt.Println(string(b)) + return nil +} diff --git a/cmd/podman/networks/list.go b/cmd/podman/networks/list.go new file mode 100644 index 000000000..e27062255 --- /dev/null +++ b/cmd/podman/networks/list.go @@ -0,0 +1,133 @@ +package network + +import ( + "encoding/json" + "fmt" + "html/template" + "io" + "os" + "strings" + + "github.com/containers/libpod/cmd/podman/validate" + + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/containers/libpod/pkg/network" + "github.com/spf13/cobra" + "github.com/spf13/pflag" +) + +var ( + networklistDescription = `List networks` + networklistCommand = &cobra.Command{ + Use: "ls", + Args: validate.NoArgs, + Short: "network list", + Long: networklistDescription, + RunE: networkList, + Example: `podman network list`, + Annotations: map[string]string{ + registry.ParentNSRequired: "", + }, + } +) + +var ( + networkListOptions entities.NetworkListOptions + headers string = "NAME\tVERSION\tPLUGINS\n" + defaultListRow string = "{{.Name}}\t{{.Version}}\t{{.Plugins}}\n" +) + +func networkListFlags(flags *pflag.FlagSet) { + // TODO enable filters based on something + //flags.StringSliceVarP(&networklistCommand.Filter, "filter", "f", []string{}, "Pause all running containers") + flags.StringVarP(&networkListOptions.Format, "format", "f", "", "Pretty-print containers to JSON or using a Go template") + flags.BoolVarP(&networkListOptions.Quiet, "quiet", "q", false, "display only names") +} + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode}, + Command: networklistCommand, + Parent: networkCmd, + }) + flags := networklistCommand.Flags() + networkListFlags(flags) +} + +func networkList(cmd *cobra.Command, args []string) error { + var ( + w io.Writer = os.Stdout + nlprs []NetworkListPrintReports + ) + + responses, err := registry.ContainerEngine().NetworkList(registry.Context(), networkListOptions) + if err != nil { + return err + } + + // quiet means we only print the network names + if networkListOptions.Quiet { + return quietOut(responses) + } + + if networkListOptions.Format == "json" { + return jsonOut(responses) + } + + for _, r := range responses { + nlprs = append(nlprs, NetworkListPrintReports{r}) + } + + row := networkListOptions.Format + if len(row) < 1 { + row = defaultListRow + } + if !strings.HasSuffix(row, "\n") { + row += "\n" + } + + format := "{{range . }}" + row + "{{end}}" + if !cmd.Flag("format").Changed { + format = headers + format + } + tmpl, err := template.New("listNetworks").Parse(format) + if err != nil { + return err + } + if err := tmpl.Execute(w, nlprs); err != nil { + return err + } + if flusher, ok := w.(interface{ Flush() error }); ok { + return flusher.Flush() + } + return nil +} + +func quietOut(responses []*entities.NetworkListReport) error { + for _, r := range responses { + fmt.Println(r.Name) + } + return nil +} + +func jsonOut(responses []*entities.NetworkListReport) error { + b, err := json.MarshalIndent(responses, "", " ") + if err != nil { + return err + } + fmt.Println(string(b)) + return nil +} + +type NetworkListPrintReports struct { + *entities.NetworkListReport +} + +func (n NetworkListPrintReports) Version() string { + return n.CNIVersion +} + +func (n NetworkListPrintReports) Plugins() string { + return network.GetCNIPlugins(n.NetworkConfigList) +} diff --git a/cmd/podman/networks/network.go b/cmd/podman/networks/network.go index e2a928312..56dd390ea 100644 --- a/cmd/podman/networks/network.go +++ b/cmd/podman/networks/network.go @@ -1,4 +1,4 @@ -package images +package network import ( "github.com/containers/libpod/cmd/podman/registry" @@ -9,7 +9,7 @@ import ( var ( // Command: podman _network_ - cmd = &cobra.Command{ + networkCmd = &cobra.Command{ Use: "network", Short: "Manage networks", Long: "Manage networks", @@ -18,12 +18,9 @@ var ( } ) -// TODO add the following to main.go to get networks back onto the -// command list. -// _ "github.com/containers/libpod/cmd/podman/networks" func init() { registry.Commands = append(registry.Commands, registry.CliCommand{ Mode: []entities.EngineMode{entities.ABIMode}, - Command: cmd, + Command: networkCmd, }) } diff --git a/cmd/podman/networks/rm.go b/cmd/podman/networks/rm.go new file mode 100644 index 000000000..dc1eb9909 --- /dev/null +++ b/cmd/podman/networks/rm.go @@ -0,0 +1,63 @@ +package network + +import ( + "fmt" + + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/cmd/podman/utils" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/spf13/cobra" + "github.com/spf13/pflag" +) + +var ( + networkrmDescription = `Remove networks` + networkrmCommand = &cobra.Command{ + Use: "rm [flags] NETWORK [NETWORK...]", + Short: "network rm", + Long: networkrmDescription, + RunE: networkRm, + Example: `podman network rm podman`, + Args: cobra.MinimumNArgs(1), + Annotations: map[string]string{ + registry.ParentNSRequired: "", + }, + } +) + +var ( + networkRmOptions entities.NetworkRmOptions +) + +func networkRmFlags(flags *pflag.FlagSet) { + flags.BoolVarP(&networkRmOptions.Force, "force", "f", false, "remove any containers using network") +} + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode}, + Command: networkrmCommand, + Parent: networkCmd, + }) + flags := networkrmCommand.Flags() + networkRmFlags(flags) +} + +func networkRm(cmd *cobra.Command, args []string) error { + var ( + errs utils.OutputErrors + ) + + responses, err := registry.ContainerEngine().NetworkRm(registry.Context(), args, networkRmOptions) + if err != nil { + return err + } + for _, r := range responses { + if r.Err == nil { + fmt.Println(r.Name) + } else { + errs = append(errs, r.Err) + } + } + return errs.PrintErrors() +} diff --git a/contrib/cirrus/lib.sh b/contrib/cirrus/lib.sh index dd4f66f56..750aec3b6 100644 --- a/contrib/cirrus/lib.sh +++ b/contrib/cirrus/lib.sh @@ -69,8 +69,8 @@ export PACKER_BUILDS="${PACKER_BUILDS:-ubuntu-18,ubuntu-19,fedora-32,fedora-31}" export UBUNTU_BASE_IMAGE="ubuntu-1910-eoan-v20200211" export PRIOR_UBUNTU_BASE_IMAGE="ubuntu-1804-bionic-v20200218" # Manually produced base-image names (see $SCRIPT_BASE/README.md) -export FEDORA_BASE_IMAGE="fedora-cloud-base-32-n-0-1586202964" -export PRIOR_FEDORA_BASE_IMAGE="fedora-cloud-base-31-1-9-1586202964" +export FEDORA_BASE_IMAGE="fedora-cloud-base-32-1-6-1588257430" +export PRIOR_FEDORA_BASE_IMAGE="fedora-cloud-base-31-1-9-1588257430" export BUILT_IMAGE_SUFFIX="${BUILT_IMAGE_SUFFIX:--$CIRRUS_REPO_NAME-${CIRRUS_BUILD_ID}}" # IN_PODMAN container image IN_PODMAN_IMAGE="quay.io/libpod/in_podman:$DEST_BRANCH" diff --git a/contrib/cirrus/packer/fedora_base-setup.sh b/contrib/cirrus/packer/fedora_base-setup.sh index 29c23117f..f271abee0 100644 --- a/contrib/cirrus/packer/fedora_base-setup.sh +++ b/contrib/cirrus/packer/fedora_base-setup.sh @@ -8,16 +8,14 @@ set -e # Load in library (copied by packer, before this script was run) source $GOSRC/$SCRIPT_BASE/lib.sh -install_ooe - echo "Updating packages" -ooe.sh dnf -y update +dnf -y update echo "Installing necessary packages and google services" -ooe.sh dnf -y install rng-tools google-compute-engine-tools google-compute-engine-oslogin ethtool +dnf -y install rng-tools google-compute-engine-tools google-compute-engine-oslogin ethtool echo "Enabling services" -ooe.sh systemctl enable rngd +systemctl enable rngd # There is a race that can happen on boot between the GCE services configuring # the VM, and cloud-init trying to do similar activities. Use a customized @@ -25,6 +23,19 @@ ooe.sh systemctl enable rngd echo "Setting cloud-init service to start after google-network-daemon.service" cp -v $GOSRC/$PACKER_BASE/cloud-init/fedora/cloud-init.service /etc/systemd/system/ +# ref: https://cloud.google.com/compute/docs/startupscript +# The mechanism used by Cirrus-CI to execute tasks on the system is through an +# "agent" process launched as a GCP startup-script (from the metadata service). +# This agent is responsible for cloning the repository and executing all task +# scripts and other operations. Therefor, on SELinux-enforcing systems, the +# service must be labeled properly to ensure it's child processes can +# run with the proper contexts. +METADATA_SERVICE_CTX=unconfined_u:unconfined_r:unconfined_t:s0 +METADATA_SERVICE_PATH=systemd/system/google-startup-scripts.service +sed -r -e \ + "s/Type=oneshot/Type=oneshot\nSELinuxContext=$METADATA_SERVICE_CTX/" \ + /lib/$METADATA_SERVICE_PATH > /etc/$METADATA_SERVICE_PATH + # Ensure there are no disruptive periodic services enabled by default in image systemd_banish diff --git a/contrib/cirrus/packer/libpod_base_images.yml b/contrib/cirrus/packer/libpod_base_images.yml index a66fac31c..f53bfafc5 100644 --- a/contrib/cirrus/packer/libpod_base_images.yml +++ b/contrib/cirrus/packer/libpod_base_images.yml @@ -17,9 +17,9 @@ variables: PRIOR_UBUNTU_BASE_IMAGE: # Latest Fedora release - FEDORA_IMAGE_URL: "https://dl.fedoraproject.org/pub/fedora/linux/development/32/Cloud/x86_64/images/Fedora-Cloud-Base-32-20200406.n.0.x86_64.qcow2" - FEDORA_CSUM_URL: "https://dl.fedoraproject.org/pub/fedora/linux/development/32/Cloud/x86_64/images/Fedora-Cloud-32-x86_64-20200406.n.0-CHECKSUM" - FEDORA_BASE_IMAGE_NAME: 'fedora-cloud-base-32-n-0' + FEDORA_IMAGE_URL: "https://dl.fedoraproject.org/pub/fedora/linux/releases/32/Cloud/x86_64/images/Fedora-Cloud-Base-32-1.6.x86_64.qcow2" + FEDORA_CSUM_URL: "https://dl.fedoraproject.org/pub/fedora/linux/releases/32/Cloud/x86_64/images/Fedora-Cloud-32-1.6-x86_64-CHECKSUM" + FEDORA_BASE_IMAGE_NAME: 'fedora-cloud-base-32-1-6' # Prior Fedora release PRIOR_FEDORA_IMAGE_URL: "https://dl.fedoraproject.org/pub/fedora/linux/releases/31/Cloud/x86_64/images/Fedora-Cloud-Base-31-1.9.x86_64.qcow2" diff --git a/pkg/domain/entities/containers.go b/pkg/domain/entities/containers.go index e58258b75..622e8eb5b 100644 --- a/pkg/domain/entities/containers.go +++ b/pkg/domain/entities/containers.go @@ -227,6 +227,7 @@ type ContainerStartOptions struct { // containers from the cli type ContainerStartReport struct { Id string + RawInput string Err error ExitCode int } diff --git a/pkg/domain/entities/engine_container.go b/pkg/domain/entities/engine_container.go index eebf4c033..b11932de6 100644 --- a/pkg/domain/entities/engine_container.go +++ b/pkg/domain/entities/engine_container.go @@ -44,6 +44,10 @@ type ContainerEngine interface { GenerateSystemd(ctx context.Context, nameOrID string, opts GenerateSystemdOptions) (*GenerateSystemdReport, error) HealthCheckRun(ctx context.Context, nameOrId string, options HealthCheckOptions) (*define.HealthCheckResults, error) Info(ctx context.Context) (*define.Info, error) + NetworkCreate(ctx context.Context, name string, options NetworkCreateOptions) (*NetworkCreateReport, error) + NetworkInspect(ctx context.Context, namesOrIds []string, options NetworkInspectOptions) ([]NetworkInspectReport, error) + NetworkList(ctx context.Context, options NetworkListOptions) ([]*NetworkListReport, error) + NetworkRm(ctx context.Context, namesOrIds []string, options NetworkRmOptions) ([]*NetworkRmReport, error) PodCreate(ctx context.Context, opts PodCreateOptions) (*PodCreateReport, error) PodExists(ctx context.Context, nameOrId string) (*BoolReport, error) PodInspect(ctx context.Context, options PodInspectOptions) (*PodInspectReport, error) diff --git a/pkg/domain/entities/network.go b/pkg/domain/entities/network.go new file mode 100644 index 000000000..cffd40899 --- /dev/null +++ b/pkg/domain/entities/network.go @@ -0,0 +1,52 @@ +package entities + +import ( + "net" + + "github.com/containernetworking/cni/libcni" +) + +// NetworkListOptions describes options for listing networks in cli +type NetworkListOptions struct { + Format string + Quiet bool +} + +// NetworkListReport describes the results from listing networks +type NetworkListReport struct { + *libcni.NetworkConfigList +} + +// NetworkInspectOptions describes options for inspect networks +type NetworkInspectOptions struct { +} + +// NetworkInspectReport describes the results from inspect networks +type NetworkInspectReport map[string]interface{} + +// NetworkRmOptions describes options for removing networks +type NetworkRmOptions struct { + Force bool +} + +//NetworkRmReport describes the results of network removal +type NetworkRmReport struct { + Name string + Err error +} + +// NetworkCreateOptions describes options to create a network +type NetworkCreateOptions struct { + DisableDNS bool + Driver string + Gateway net.IP + Internal bool + MacVLAN string + Range net.IPNet + Subnet net.IPNet +} + +// NetworkCreateReport describes a created network for the cli +type NetworkCreateReport struct { + Filename string +} diff --git a/pkg/domain/infra/abi/containers.go b/pkg/domain/infra/abi/containers.go index 82bf82bf0..f4996583a 100644 --- a/pkg/domain/infra/abi/containers.go +++ b/pkg/domain/infra/abi/containers.go @@ -32,9 +32,9 @@ import ( "github.com/sirupsen/logrus" ) -// getContainersByContext gets pods whether all, latest, or a slice of names/ids -// is specified. -func getContainersByContext(all, latest bool, names []string, runtime *libpod.Runtime) (ctrs []*libpod.Container, err error) { +// getContainersAndInputByContext gets containers whether all, latest, or a slice of names/ids +// is specified. It also returns a list of the corresponding input name used to lookup each container. +func getContainersAndInputByContext(all, latest bool, names []string, runtime *libpod.Runtime) (ctrs []*libpod.Container, rawInput []string, err error) { var ctr *libpod.Container ctrs = []*libpod.Container{} @@ -43,6 +43,7 @@ func getContainersByContext(all, latest bool, names []string, runtime *libpod.Ru ctrs, err = runtime.GetAllContainers() case latest: ctr, err = runtime.GetLatestContainer() + rawInput = append(rawInput, ctr.ID()) ctrs = append(ctrs, ctr) default: for _, n := range names { @@ -54,6 +55,7 @@ func getContainersByContext(all, latest bool, names []string, runtime *libpod.Ru err = e } } else { + rawInput = append(rawInput, n) ctrs = append(ctrs, ctr) } } @@ -61,6 +63,13 @@ func getContainersByContext(all, latest bool, names []string, runtime *libpod.Ru return } +// getContainersByContext gets containers whether all, latest, or a slice of names/ids +// is specified. +func getContainersByContext(all, latest bool, names []string, runtime *libpod.Runtime) (ctrs []*libpod.Container, err error) { + ctrs, _, err = getContainersAndInputByContext(all, latest, names, runtime) + return +} + // TODO: Should return *entities.ContainerExistsReport, error func (ic *ContainerEngine) ContainerExists(ctx context.Context, nameOrId string) (*entities.BoolReport, error) { _, err := ic.Libpod.LookupContainer(nameOrId) @@ -555,12 +564,14 @@ func (ic *ContainerEngine) ContainerExec(ctx context.Context, nameOrId string, o func (ic *ContainerEngine) ContainerStart(ctx context.Context, namesOrIds []string, options entities.ContainerStartOptions) ([]*entities.ContainerStartReport, error) { var reports []*entities.ContainerStartReport var exitCode = define.ExecErrorCodeGeneric - ctrs, err := getContainersByContext(false, options.Latest, namesOrIds, ic.Libpod) + ctrs, rawInputs, err := getContainersAndInputByContext(false, options.Latest, namesOrIds, ic.Libpod) if err != nil { return nil, err } // There can only be one container if attach was used - for _, ctr := range ctrs { + for i := range ctrs { + ctr := ctrs[i] + rawInput := rawInputs[i] ctrState, err := ctr.State() if err != nil { return nil, err @@ -574,6 +585,7 @@ func (ic *ContainerEngine) ContainerStart(ctx context.Context, namesOrIds []stri // Exit cleanly immediately reports = append(reports, &entities.ContainerStartReport{ Id: ctr.ID(), + RawInput: rawInput, Err: nil, ExitCode: 0, }) @@ -584,6 +596,7 @@ func (ic *ContainerEngine) ContainerStart(ctx context.Context, namesOrIds []stri logrus.Debugf("Deadlock error: %v", err) reports = append(reports, &entities.ContainerStartReport{ Id: ctr.ID(), + RawInput: rawInput, Err: err, ExitCode: define.ExitCode(err), }) @@ -593,6 +606,7 @@ func (ic *ContainerEngine) ContainerStart(ctx context.Context, namesOrIds []stri if ctrRunning { reports = append(reports, &entities.ContainerStartReport{ Id: ctr.ID(), + RawInput: rawInput, Err: nil, ExitCode: 0, }) @@ -602,6 +616,7 @@ func (ic *ContainerEngine) ContainerStart(ctx context.Context, namesOrIds []stri if err != nil { reports = append(reports, &entities.ContainerStartReport{ Id: ctr.ID(), + RawInput: rawInput, Err: err, ExitCode: exitCode, }) @@ -624,6 +639,7 @@ func (ic *ContainerEngine) ContainerStart(ctx context.Context, namesOrIds []stri } reports = append(reports, &entities.ContainerStartReport{ Id: ctr.ID(), + RawInput: rawInput, Err: err, ExitCode: exitCode, }) @@ -636,6 +652,7 @@ func (ic *ContainerEngine) ContainerStart(ctx context.Context, namesOrIds []stri // If the container is in a pod, also set to recursively start dependencies report := &entities.ContainerStartReport{ Id: ctr.ID(), + RawInput: rawInput, ExitCode: 125, } if err := ctr.Start(ctx, ctr.PodID() != ""); err != nil { diff --git a/pkg/domain/infra/abi/network.go b/pkg/domain/infra/abi/network.go new file mode 100644 index 000000000..5c39b5374 --- /dev/null +++ b/pkg/domain/infra/abi/network.go @@ -0,0 +1,258 @@ +package abi + +import ( + "context" + "encoding/json" + "fmt" + "io/ioutil" + "path/filepath" + + cniversion "github.com/containernetworking/cni/pkg/version" + "github.com/containers/libpod/libpod" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/containers/libpod/pkg/network" + "github.com/containers/libpod/pkg/util" + "github.com/pkg/errors" +) + +func getCNIConfDir(r *libpod.Runtime) (string, error) { + config, err := r.GetConfig() + if err != nil { + return "", err + } + configPath := config.Network.NetworkConfigDir + + if len(config.Network.NetworkConfigDir) < 1 { + configPath = network.CNIConfigDir + } + return configPath, nil +} + +func (ic *ContainerEngine) NetworkList(ctx context.Context, options entities.NetworkListOptions) ([]*entities.NetworkListReport, error) { + var reports []*entities.NetworkListReport + cniConfigPath, err := getCNIConfDir(ic.Libpod) + if err != nil { + return nil, err + } + networks, err := network.LoadCNIConfsFromDir(cniConfigPath) + if err != nil { + return nil, err + } + + for _, n := range networks { + reports = append(reports, &entities.NetworkListReport{NetworkConfigList: n}) + } + return reports, nil +} + +func (ic *ContainerEngine) NetworkInspect(ctx context.Context, namesOrIds []string, options entities.NetworkInspectOptions) ([]entities.NetworkInspectReport, error) { + var ( + rawCNINetworks []entities.NetworkInspectReport + ) + for _, name := range namesOrIds { + rawList, err := network.InspectNetwork(name) + if err != nil { + return nil, err + } + rawCNINetworks = append(rawCNINetworks, rawList) + } + return rawCNINetworks, nil +} + +func (ic *ContainerEngine) NetworkRm(ctx context.Context, namesOrIds []string, options entities.NetworkRmOptions) ([]*entities.NetworkRmReport, error) { + var reports []*entities.NetworkRmReport + for _, name := range namesOrIds { + report := entities.NetworkRmReport{Name: name} + containers, err := ic.Libpod.GetAllContainers() + if err != nil { + return reports, err + } + // We need to iterate containers looking to see if they belong to the given network + for _, c := range containers { + if util.StringInSlice(name, c.Config().Networks) { + // if user passes force, we nuke containers + if !options.Force { + // Without the force option, we return an error + return reports, errors.Errorf("%q has associated containers with it. Use -f to forcibly delete containers", name) + } + if err := ic.Libpod.RemoveContainer(ctx, c, true, true); err != nil { + return reports, err + } + } + } + if err := network.RemoveNetwork(name); err != nil { + report.Err = err + } + reports = append(reports, &report) + } + return reports, nil +} + +func (ic *ContainerEngine) NetworkCreate(ctx context.Context, name string, options entities.NetworkCreateOptions) (*entities.NetworkCreateReport, error) { + var ( + err error + fileName string + ) + if len(options.MacVLAN) > 0 { + fileName, err = createMacVLAN(ic.Libpod, name, options) + } else { + fileName, err = createBridge(ic.Libpod, name, options) + } + if err != nil { + return nil, err + } + return &entities.NetworkCreateReport{Filename: fileName}, nil +} + +// createBridge creates a CNI network +func createBridge(r *libpod.Runtime, name string, options entities.NetworkCreateOptions) (string, error) { + isGateway := true + ipMasq := true + subnet := &options.Subnet + ipRange := options.Range + runtimeConfig, err := r.GetConfig() + if err != nil { + return "", err + } + // if range is provided, make sure it is "in" network + if subnet.IP != nil { + // if network is provided, does it conflict with existing CNI or live networks + err = network.ValidateUserNetworkIsAvailable(subnet) + } else { + // if no network is provided, figure out network + subnet, err = network.GetFreeNetwork() + } + if err != nil { + return "", err + } + gateway := options.Gateway + if gateway == nil { + // if no gateway is provided, provide it as first ip of network + gateway = network.CalcGatewayIP(subnet) + } + // if network is provided and if gateway is provided, make sure it is "in" network + if options.Subnet.IP != nil && options.Gateway != nil { + if !subnet.Contains(gateway) { + return "", errors.Errorf("gateway %s is not in valid for subnet %s", gateway.String(), subnet.String()) + } + } + if options.Internal { + isGateway = false + ipMasq = false + } + + // if a range is given, we need to ensure it is "in" the network range. + if options.Range.IP != nil { + if options.Subnet.IP == nil { + return "", errors.New("you must define a subnet range to define an ip-range") + } + firstIP, err := network.FirstIPInSubnet(&options.Range) + if err != nil { + return "", err + } + lastIP, err := network.LastIPInSubnet(&options.Range) + if err != nil { + return "", err + } + if !subnet.Contains(firstIP) || !subnet.Contains(lastIP) { + return "", errors.Errorf("the ip range %s does not fall within the subnet range %s", options.Range.String(), subnet.String()) + } + } + bridgeDeviceName, err := network.GetFreeDeviceName() + if err != nil { + return "", err + } + + if len(name) > 0 { + netNames, err := network.GetNetworkNamesFromFileSystem() + if err != nil { + return "", err + } + if util.StringInSlice(name, netNames) { + return "", errors.Errorf("the network name %s is already used", name) + } + } else { + // If no name is given, we give the name of the bridge device + name = bridgeDeviceName + } + + ncList := network.NewNcList(name, cniversion.Current()) + var plugins []network.CNIPlugins + var routes []network.IPAMRoute + + defaultRoute, err := network.NewIPAMDefaultRoute() + if err != nil { + return "", err + } + routes = append(routes, defaultRoute) + ipamConfig, err := network.NewIPAMHostLocalConf(subnet, routes, ipRange, gateway) + if err != nil { + return "", err + } + + // TODO need to iron out the role of isDefaultGW and IPMasq + bridge := network.NewHostLocalBridge(bridgeDeviceName, isGateway, false, ipMasq, ipamConfig) + plugins = append(plugins, bridge) + plugins = append(plugins, network.NewPortMapPlugin()) + plugins = append(plugins, network.NewFirewallPlugin()) + // if we find the dnsname plugin, we add configuration for it + if network.HasDNSNamePlugin(runtimeConfig.Network.CNIPluginDirs) && !options.DisableDNS { + // Note: in the future we might like to allow for dynamic domain names + plugins = append(plugins, network.NewDNSNamePlugin(network.DefaultPodmanDomainName)) + } + ncList["plugins"] = plugins + b, err := json.MarshalIndent(ncList, "", " ") + if err != nil { + return "", err + } + cniConfigPath, err := getCNIConfDir(r) + if err != nil { + return "", err + } + cniPathName := filepath.Join(cniConfigPath, fmt.Sprintf("%s.conflist", name)) + err = ioutil.WriteFile(cniPathName, b, 0644) + return cniPathName, err +} + +func createMacVLAN(r *libpod.Runtime, name string, options entities.NetworkCreateOptions) (string, error) { + var ( + plugins []network.CNIPlugins + ) + liveNetNames, err := network.GetLiveNetworkNames() + if err != nil { + return "", err + } + // Make sure the host-device exists + if !util.StringInSlice(options.MacVLAN, liveNetNames) { + return "", errors.Errorf("failed to find network interface %q", options.MacVLAN) + } + if len(name) > 0 { + netNames, err := network.GetNetworkNamesFromFileSystem() + if err != nil { + return "", err + } + if util.StringInSlice(name, netNames) { + return "", errors.Errorf("the network name %s is already used", name) + } + } else { + name, err = network.GetFreeDeviceName() + if err != nil { + return "", err + } + } + ncList := network.NewNcList(name, cniversion.Current()) + macvlan := network.NewMacVLANPlugin(options.MacVLAN) + plugins = append(plugins, macvlan) + ncList["plugins"] = plugins + b, err := json.MarshalIndent(ncList, "", " ") + if err != nil { + return "", err + } + cniConfigPath, err := getCNIConfDir(r) + if err != nil { + return "", err + } + cniPathName := filepath.Join(cniConfigPath, fmt.Sprintf("%s.conflist", name)) + err = ioutil.WriteFile(cniPathName, b, 0644) + return cniPathName, err +} diff --git a/pkg/domain/infra/tunnel/network.go b/pkg/domain/infra/tunnel/network.go new file mode 100644 index 000000000..4ff72dcfc --- /dev/null +++ b/pkg/domain/infra/tunnel/network.go @@ -0,0 +1,23 @@ +package tunnel + +import ( + "context" + "errors" + + "github.com/containers/libpod/pkg/domain/entities" +) + +func (ic *ContainerEngine) NetworkList(ctx context.Context, options entities.NetworkListOptions) ([]*entities.NetworkListReport, error) { + return nil, errors.New("not implemented") +} + +func (ic *ContainerEngine) NetworkInspect(ctx context.Context, namesOrIds []string, options entities.NetworkInspectOptions) ([]entities.NetworkInspectReport, error) { + return nil, errors.New("not implemented") +} +func (ic *ContainerEngine) NetworkRm(ctx context.Context, namesOrIds []string, options entities.NetworkRmOptions) ([]*entities.NetworkRmReport, error) { + return nil, errors.New("not implemented") +} + +func (ic *ContainerEngine) NetworkCreate(ctx context.Context, name string, options entities.NetworkCreateOptions) (*entities.NetworkCreateReport, error) { + return nil, errors.New("not implemented") +} diff --git a/pkg/spec/spec.go b/pkg/spec/spec.go index a62344640..41ed5f1f0 100644 --- a/pkg/spec/spec.go +++ b/pkg/spec/spec.go @@ -16,6 +16,8 @@ import ( spec "github.com/opencontainers/runtime-spec/specs-go" "github.com/opencontainers/runtime-tools/generate" "github.com/pkg/errors" + "github.com/sirupsen/logrus" + "golang.org/x/sys/unix" ) const CpuPeriod = 100000 @@ -534,11 +536,31 @@ func addRlimits(config *CreateConfig, g *generate.Generator) error { // If not explicitly overridden by the user, default number of open // files and number of processes to the maximum they can be set to // (without overriding a sysctl) - if !nofileSet && !isRootless { - g.AddProcessRlimits("RLIMIT_NOFILE", kernelMax, kernelMax) - } - if !nprocSet && !isRootless { - g.AddProcessRlimits("RLIMIT_NPROC", kernelMax, kernelMax) + if !nofileSet { + max := kernelMax + current := kernelMax + if isRootless { + var rlimit unix.Rlimit + if err := unix.Getrlimit(unix.RLIMIT_NOFILE, &rlimit); err != nil { + logrus.Warnf("failed to return RLIMIT_NOFILE ulimit %q", err) + } + current = rlimit.Cur + max = rlimit.Max + } + g.AddProcessRlimits("RLIMIT_NOFILE", current, max) + } + if !nprocSet { + max := kernelMax + current := kernelMax + if isRootless { + var rlimit unix.Rlimit + if err := unix.Getrlimit(unix.RLIMIT_NPROC, &rlimit); err != nil { + logrus.Warnf("failed to return RLIMIT_NPROC ulimit %q", err) + } + current = rlimit.Cur + max = rlimit.Max + } + g.AddProcessRlimits("RLIMIT_NPROC", current, max) } return nil diff --git a/pkg/specgen/generate/container_create.go b/pkg/specgen/generate/container_create.go index 01ddcf9c8..14836035d 100644 --- a/pkg/specgen/generate/container_create.go +++ b/pkg/specgen/generate/container_create.go @@ -24,11 +24,10 @@ func MakeContainer(ctx context.Context, rt *libpod.Runtime, s *specgen.SpecGener // If joining a pod, retrieve the pod for use. var pod *libpod.Pod if s.Pod != "" { - foundPod, err := rt.LookupPod(s.Pod) + pod, err = rt.LookupPod(s.Pod) if err != nil { return nil, errors.Wrapf(err, "error retrieving pod %s", s.Pod) } - pod = foundPod } // Set defaults for unset namespaces @@ -130,12 +129,8 @@ func createContainerOptions(rt *libpod.Runtime, s *specgen.SpecGenerator, pod *l logrus.Debugf("setting container name %s", s.Name) options = append(options, libpod.WithName(s.Name)) } - if s.Pod != "" { - pod, err := rt.LookupPod(s.Pod) - if err != nil { - return nil, err - } - logrus.Debugf("adding container to pod %s", s.Pod) + if pod != nil { + logrus.Debugf("adding container to pod %s", pod.Name()) options = append(options, rt.WithPod(pod)) } destinations := []string{} @@ -160,11 +155,12 @@ func createContainerOptions(rt *libpod.Runtime, s *specgen.SpecGenerator, pod *l options = append(options, libpod.WithNamedVolumes(vols)) } - if len(s.Command) != 0 { + if s.Command != nil { options = append(options, libpod.WithCommand(s.Command)) } - - options = append(options, libpod.WithEntrypoint(s.Entrypoint)) + if s.Entrypoint != nil { + options = append(options, libpod.WithEntrypoint(s.Entrypoint)) + } if s.StopSignal != nil { options = append(options, libpod.WithStopSignal(*s.StopSignal)) } diff --git a/pkg/specgen/generate/oci.go b/pkg/specgen/generate/oci.go index 87262684e..7993777fb 100644 --- a/pkg/specgen/generate/oci.go +++ b/pkg/specgen/generate/oci.go @@ -13,6 +13,8 @@ import ( spec "github.com/opencontainers/runtime-spec/specs-go" "github.com/opencontainers/runtime-tools/generate" "github.com/pkg/errors" + "github.com/sirupsen/logrus" + "golang.org/x/sys/unix" ) func addRlimits(s *specgen.SpecGenerator, g *generate.Generator) error { @@ -41,11 +43,31 @@ func addRlimits(s *specgen.SpecGenerator, g *generate.Generator) error { // If not explicitly overridden by the user, default number of open // files and number of processes to the maximum they can be set to // (without overriding a sysctl) - if !nofileSet && !isRootless { - g.AddProcessRlimits("RLIMIT_NOFILE", kernelMax, kernelMax) - } - if !nprocSet && !isRootless { - g.AddProcessRlimits("RLIMIT_NPROC", kernelMax, kernelMax) + if !nofileSet { + max := kernelMax + current := kernelMax + if isRootless { + var rlimit unix.Rlimit + if err := unix.Getrlimit(unix.RLIMIT_NOFILE, &rlimit); err != nil { + logrus.Warnf("failed to return RLIMIT_NOFILE ulimit %q", err) + } + current = rlimit.Cur + max = rlimit.Max + } + g.AddProcessRlimits("RLIMIT_NOFILE", current, max) + } + if !nprocSet { + max := kernelMax + current := kernelMax + if isRootless { + var rlimit unix.Rlimit + if err := unix.Getrlimit(unix.RLIMIT_NPROC, &rlimit); err != nil { + logrus.Warnf("failed to return RLIMIT_NPROC ulimit %q", err) + } + current = rlimit.Cur + max = rlimit.Max + } + g.AddProcessRlimits("RLIMIT_NPROC", current, max) } return nil @@ -67,7 +89,7 @@ func makeCommand(ctx context.Context, s *specgen.SpecGenerator, img *image.Image finalCommand = append(finalCommand, entrypoint...) command := s.Command - if len(command) == 0 && img != nil { + if command == nil && img != nil { newCmd, err := img.Cmd(ctx) if err != nil { return nil, err diff --git a/test/e2e/create_test.go b/test/e2e/create_test.go index 82346823a..10742a0e8 100644 --- a/test/e2e/create_test.go +++ b/test/e2e/create_test.go @@ -18,7 +18,6 @@ var _ = Describe("Podman create", func() { ) BeforeEach(func() { - Skip(v2fail) tempdir, err = CreateTempDirInTempDir() if err != nil { os.Exit(1) diff --git a/test/e2e/network_create_test.go b/test/e2e/network_create_test.go index 19dabced7..7eccaa9ab 100644 --- a/test/e2e/network_create_test.go +++ b/test/e2e/network_create_test.go @@ -76,7 +76,6 @@ var _ = Describe("Podman network create", func() { ) BeforeEach(func() { - Skip(v2fail) SkipIfRootless() tempdir, err = CreateTempDirInTempDir() if err != nil { diff --git a/test/e2e/network_test.go b/test/e2e/network_test.go index 2cb7eb144..8d575d7f9 100644 --- a/test/e2e/network_test.go +++ b/test/e2e/network_test.go @@ -34,7 +34,7 @@ var _ = Describe("Podman network", func() { ) BeforeEach(func() { - Skip(v2fail) + SkipIfRootless() tempdir, err = CreateTempDirInTempDir() if err != nil { os.Exit(1) @@ -80,7 +80,6 @@ var _ = Describe("Podman network", func() { ) 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)) @@ -94,7 +93,6 @@ var _ = Describe("Podman network", func() { }) 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)) @@ -108,14 +106,12 @@ var _ = Describe("Podman network", func() { }) 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)) @@ -138,14 +134,12 @@ var _ = Describe("Podman network", func() { }) 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)) @@ -159,7 +153,6 @@ var _ = Describe("Podman network", func() { }) It("podman inspect container single CNI network", func() { - SkipIfRootless() netName := "testNetSingleCNI" network := podmanTest.Podman([]string{"network", "create", "--subnet", "10.50.50.0/24", netName}) network.WaitWithDefaultTimeout() @@ -190,7 +183,6 @@ var _ = Describe("Podman network", func() { }) It("podman inspect container two CNI networks", func() { - SkipIfRootless() netName1 := "testNetTwoCNI1" network1 := podmanTest.Podman([]string{"network", "create", "--subnet", "10.50.51.0/25", netName1}) network1.WaitWithDefaultTimeout() diff --git a/test/e2e/run_entrypoint_test.go b/test/e2e/run_entrypoint_test.go index ebc06b36c..b1344a371 100644 --- a/test/e2e/run_entrypoint_test.go +++ b/test/e2e/run_entrypoint_test.go @@ -18,7 +18,6 @@ var _ = Describe("Podman run entrypoint", func() { ) BeforeEach(func() { - Skip(v2fail) tempdir, err = CreateTempDirInTempDir() if err != nil { os.Exit(1) diff --git a/test/e2e/run_test.go b/test/e2e/run_test.go index d94c6c169..59215c7e5 100644 --- a/test/e2e/run_test.go +++ b/test/e2e/run_test.go @@ -634,7 +634,6 @@ USER mail` }) It("podman run --volumes-from flag with built-in volumes", func() { - Skip(v2fail) session := podmanTest.Podman([]string{"create", redis, "sh"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) @@ -729,7 +728,6 @@ USER mail` }) It("podman run --pod automatically", func() { - Skip(v2fail) session := podmanTest.Podman([]string{"run", "--pod", "new:foobar", ALPINE, "ls"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) diff --git a/test/e2e/start_test.go b/test/e2e/start_test.go index 5f6f5a8cf..6af0b7068 100644 --- a/test/e2e/start_test.go +++ b/test/e2e/start_test.go @@ -17,7 +17,6 @@ var _ = Describe("Podman start", func() { ) BeforeEach(func() { - Skip(v2fail) tempdir, err = CreateTempDirInTempDir() if err != nil { os.Exit(1) @@ -66,10 +65,11 @@ var _ = Describe("Podman start", func() { session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) cid := session.OutputToString() - session = podmanTest.Podman([]string{"container", "start", cid[0:10]}) + shortID := cid[0:10] + session = podmanTest.Podman([]string{"container", "start", shortID}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) - Expect(session.OutputToString()).To(Equal(cid)) + Expect(session.OutputToString()).To(Equal(shortID)) }) It("podman start single container by name", func() { diff --git a/test/system/015-help.bats b/test/system/015-help.bats index fd4be87b2..6c3d617dc 100644 --- a/test/system/015-help.bats +++ b/test/system/015-help.bats @@ -55,11 +55,24 @@ function check_help() { # If usage has required arguments, try running without them if expr "$usage" : '.*\[flags\] [A-Z]' >/dev/null; then - if [ "$cmd" != "stats"]; then - dprint "podman $@ $cmd (without required args)" - run_podman 125 "$@" $cmd - is "$output" "Error:" + # Exceptions: these commands don't work rootless + if is_rootless; then + # "pause is not supported for rootless containers" + if [ "$cmd" = "pause" -o "$cmd" = "unpause" ]; then + continue + fi + # "network rm" too + if [ "$@" = "network" -a "$cmd" = "rm" ]; then + continue + fi fi + + # The </dev/null protects us from 'podman login' which will + # try to read username/password from stdin. + dprint "podman $@ $cmd (without required args)" + run_podman 125 "$@" $cmd </dev/null + is "$output" "Error:.* \(require\|specif\|must\|provide\|need\|choose\|accepts\)" \ + "'podman $@ $cmd' without required arg" fi count=$(expr $count + 1) |