From f4eac94f4daec3ff2a3ba3b468cc6e60dcd9287d Mon Sep 17 00:00:00 2001 From: Daniel J Walsh Date: Wed, 18 Nov 2020 17:13:08 -0500 Subject: Add alias for podman network rm -> remove docker network remove exists and is alias to docker network rm. Bug for bug compatible. Signed-off-by: Daniel J Walsh --- cmd/podman/networks/rm.go | 1 + 1 file changed, 1 insertion(+) (limited to 'cmd/podman') diff --git a/cmd/podman/networks/rm.go b/cmd/podman/networks/rm.go index 34e756a3a..1504d9385 100644 --- a/cmd/podman/networks/rm.go +++ b/cmd/podman/networks/rm.go @@ -18,6 +18,7 @@ var ( networkrmDescription = `Remove networks` networkrmCommand = &cobra.Command{ Use: "rm [options] NETWORK [NETWORK...]", + Aliases: []string{"remove"}, Short: "network rm", Long: networkrmDescription, RunE: networkRm, -- cgit v1.2.3-54-g00ecf From 977094781841f57c82b71e3ccc32dad952a8c0e5 Mon Sep 17 00:00:00 2001 From: Daniel J Walsh Date: Wed, 18 Nov 2020 16:51:33 -0500 Subject: Document containers.conf settings for remote connections Currently we don't document which end of the podman-remote client server operations uses the containers.conf. This PR begins documenting this and then testing to make sure the defaults follow the rules. Fixes: https://github.com/containers/podman/issues/7657 Signed-off-by: Daniel J Walsh --- cmd/podman/common/create.go | 18 ++-- cmd/podman/common/create_opts.go | 64 +++++++++++++ cmd/podman/common/volumes.go | 102 +-------------------- docs/source/markdown/podman-create.1.md | 7 ++ docs/source/markdown/podman-run.1.md | 7 ++ docs/source/markdown/podman.1.md | 8 ++ pkg/specgen/generate/container_create.go | 12 +-- pkg/specgen/generate/storage.go | 61 ++++++++++--- pkg/specgen/specgen.go | 38 +------- pkg/specgen/volumes.go | 149 +++++++++++++++++++++++++++++++ test/e2e/config/containers-remote.conf | 51 +++++++++++ test/e2e/containers_conf_test.go | 68 ++++++++++++++ 12 files changed, 421 insertions(+), 164 deletions(-) create mode 100644 pkg/specgen/volumes.go create mode 100644 test/e2e/config/containers-remote.conf (limited to 'cmd/podman') diff --git a/cmd/podman/common/create.go b/cmd/podman/common/create.go index ab3a984f0..599b430ea 100644 --- a/cmd/podman/common/create.go +++ b/cmd/podman/common/create.go @@ -84,7 +84,7 @@ func DefineCreateFlags(cmd *cobra.Command, cf *ContainerCLIOpts) { cgroupsFlagName := "cgroups" createFlags.StringVar( &cf.CGroupsMode, - cgroupsFlagName, containerConfig.Cgroups(), + cgroupsFlagName, cgroupConfig(), `control container cgroup configuration ("enabled"|"disabled"|"no-conmon"|"split")`, ) _ = cmd.RegisterFlagCompletionFunc(cgroupsFlagName, AutocompleteCgroupMode) @@ -180,7 +180,7 @@ func DefineCreateFlags(cmd *cobra.Command, cf *ContainerCLIOpts) { deviceFlagName := "device" createFlags.StringSliceVar( &cf.Devices, - deviceFlagName, containerConfig.Devices(), + deviceFlagName, devices(), fmt.Sprintf("Add a host device to the container"), ) _ = cmd.RegisterFlagCompletionFunc(deviceFlagName, completion.AutocompleteDefault) @@ -238,7 +238,7 @@ func DefineCreateFlags(cmd *cobra.Command, cf *ContainerCLIOpts) { envFlagName := "env" createFlags.StringArrayP( - envFlagName, "e", containerConfig.Env(), + envFlagName, "e", env(), "Set environment variables in container", ) _ = cmd.RegisterFlagCompletionFunc(envFlagName, completion.AutocompleteNone) @@ -357,7 +357,7 @@ func DefineCreateFlags(cmd *cobra.Command, cf *ContainerCLIOpts) { initPathFlagName := "init-path" createFlags.StringVar( &cf.InitPath, - initPathFlagName, containerConfig.InitPath(), + initPathFlagName, initPath(), // Do not use the Value field for setting the default value to determine user input (i.e., non-empty string) fmt.Sprintf("Path to the container-init binary"), ) @@ -508,7 +508,7 @@ func DefineCreateFlags(cmd *cobra.Command, cf *ContainerCLIOpts) { pidsLimitFlagName := "pids-limit" createFlags.Int64( - pidsLimitFlagName, containerConfig.PidsLimit(), + pidsLimitFlagName, pidsLimit(), "Tune container pids limit (set 0 for unlimited, -1 for server defaults)", ) _ = cmd.RegisterFlagCompletionFunc(pidsLimitFlagName, completion.AutocompleteNone) @@ -543,7 +543,7 @@ func DefineCreateFlags(cmd *cobra.Command, cf *ContainerCLIOpts) { pullFlagName := "pull" createFlags.StringVar( &cf.Pull, - pullFlagName, containerConfig.Engine.PullPolicy, + pullFlagName, policy(), `Pull image before creating ("always"|"missing"|"never")`, ) _ = cmd.RegisterFlagCompletionFunc(pullFlagName, AutocompletePullOption) @@ -606,7 +606,7 @@ func DefineCreateFlags(cmd *cobra.Command, cf *ContainerCLIOpts) { shmSizeFlagName := "shm-size" createFlags.String( - shmSizeFlagName, containerConfig.ShmSize(), + shmSizeFlagName, shmSize(), "Size of /dev/shm "+sizeWithUnitFormat, ) _ = cmd.RegisterFlagCompletionFunc(shmSizeFlagName, completion.AutocompleteNone) @@ -715,7 +715,7 @@ func DefineCreateFlags(cmd *cobra.Command, cf *ContainerCLIOpts) { ulimitFlagName := "ulimit" createFlags.StringSliceVar( &cf.Ulimit, - ulimitFlagName, containerConfig.Ulimits(), + ulimitFlagName, ulimits(), "Ulimit options", ) _ = cmd.RegisterFlagCompletionFunc(ulimitFlagName, completion.AutocompleteNone) @@ -753,7 +753,7 @@ func DefineCreateFlags(cmd *cobra.Command, cf *ContainerCLIOpts) { volumeFlagName := "volume" createFlags.StringArrayVarP( &cf.Volume, - volumeFlagName, "v", containerConfig.Volumes(), + volumeFlagName, "v", volumes(), "Bind mount a volume into the container", ) _ = cmd.RegisterFlagCompletionFunc(volumeFlagName, AutocompleteVolumeFlag) diff --git a/cmd/podman/common/create_opts.go b/cmd/podman/common/create_opts.go index 4b52663c3..f34666fff 100644 --- a/cmd/podman/common/create_opts.go +++ b/cmd/podman/common/create_opts.go @@ -6,6 +6,7 @@ import ( "strconv" "strings" + "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/pkg/api/handlers" "github.com/containers/podman/v2/pkg/cgroups" "github.com/containers/podman/v2/pkg/domain/entities" @@ -440,3 +441,66 @@ func ContainerCreateToContainerCLIOpts(cc handlers.CreateContainerConfig, cgroup cmd = append(cmd, cc.Config.Cmd...) return &cliOpts, cmd, nil } + +func ulimits() []string { + if !registry.IsRemote() { + return containerConfig.Ulimits() + } + return nil +} + +func cgroupConfig() string { + if !registry.IsRemote() { + return containerConfig.Cgroups() + } + return "" +} + +func devices() []string { + if !registry.IsRemote() { + return containerConfig.Devices() + } + return nil +} + +func env() []string { + if !registry.IsRemote() { + return containerConfig.Env() + } + return nil +} + +func initPath() string { + if !registry.IsRemote() { + return containerConfig.InitPath() + } + return "" +} + +func pidsLimit() int64 { + if !registry.IsRemote() { + return containerConfig.PidsLimit() + } + return -1 +} + +func policy() string { + if !registry.IsRemote() { + return containerConfig.Engine.PullPolicy + } + return "" +} + +func shmSize() string { + if !registry.IsRemote() { + return containerConfig.ShmSize() + } + return "" +} + +func volumes() []string { + if !registry.IsRemote() { + return containerConfig.Volumes() + } + return nil +} diff --git a/cmd/podman/common/volumes.go b/cmd/podman/common/volumes.go index b3c160ddf..0468f15e0 100644 --- a/cmd/podman/common/volumes.go +++ b/cmd/podman/common/volumes.go @@ -10,7 +10,6 @@ import ( "github.com/containers/podman/v2/pkg/util" spec "github.com/opencontainers/runtime-spec/specs-go" "github.com/pkg/errors" - "github.com/sirupsen/logrus" ) const ( @@ -45,7 +44,7 @@ func parseVolumes(volumeFlag, mountFlag, tmpfsFlag []string, addReadOnlyTmpfs bo } // Next --volumes flag. - volumeMounts, volumeVolumes, overlayVolumes, err := getVolumeMounts(volumeFlag) + volumeMounts, volumeVolumes, overlayVolumes, err := specgen.GenVolumeMounts(volumeFlag) if err != nil { return nil, nil, nil, nil, err } @@ -594,105 +593,6 @@ func getImageVolume(args []string) (*specgen.ImageVolume, error) { return newVolume, nil } -func getVolumeMounts(volumeFlag []string) (map[string]spec.Mount, map[string]*specgen.NamedVolume, map[string]*specgen.OverlayVolume, error) { - mounts := make(map[string]spec.Mount) - volumes := make(map[string]*specgen.NamedVolume) - overlayVolumes := make(map[string]*specgen.OverlayVolume) - - volumeFormatErr := errors.Errorf("incorrect volume format, should be [host-dir:]ctr-dir[:option]") - - for _, vol := range volumeFlag { - var ( - options []string - src string - dest string - err error - ) - - splitVol := strings.Split(vol, ":") - if len(splitVol) > 3 { - return nil, nil, nil, errors.Wrapf(volumeFormatErr, vol) - } - - src = splitVol[0] - if len(splitVol) == 1 { - // This is an anonymous named volume. Only thing given - // is destination. - // Name/source will be blank, and populated by libpod. - src = "" - dest = splitVol[0] - } else if len(splitVol) > 1 { - dest = splitVol[1] - } - if len(splitVol) > 2 { - if options, err = parse.ValidateVolumeOpts(strings.Split(splitVol[2], ",")); err != nil { - return nil, nil, nil, err - } - } - - // Do not check source dir for anonymous volumes - if len(splitVol) > 1 { - if err := parse.ValidateVolumeHostDir(src); err != nil { - return nil, nil, nil, err - } - } - if err := parse.ValidateVolumeCtrDir(dest); err != nil { - return nil, nil, nil, err - } - - cleanDest := filepath.Clean(dest) - - if strings.HasPrefix(src, "/") || strings.HasPrefix(src, ".") { - // This is not a named volume - overlayFlag := false - for _, o := range options { - if o == "O" { - overlayFlag = true - if len(options) > 1 { - return nil, nil, nil, errors.New("can't use 'O' with other options") - } - } - } - if overlayFlag { - // This is a overlay volume - newOverlayVol := new(specgen.OverlayVolume) - newOverlayVol.Destination = cleanDest - newOverlayVol.Source = src - if _, ok := overlayVolumes[newOverlayVol.Destination]; ok { - return nil, nil, nil, errors.Wrapf(errDuplicateDest, newOverlayVol.Destination) - } - overlayVolumes[newOverlayVol.Destination] = newOverlayVol - } else { - newMount := spec.Mount{ - Destination: cleanDest, - Type: string(TypeBind), - Source: src, - Options: options, - } - if _, ok := mounts[newMount.Destination]; ok { - return nil, nil, nil, errors.Wrapf(errDuplicateDest, newMount.Destination) - } - mounts[newMount.Destination] = newMount - } - } else { - // This is a named volume - newNamedVol := new(specgen.NamedVolume) - newNamedVol.Name = src - newNamedVol.Dest = cleanDest - newNamedVol.Options = options - - if _, ok := volumes[newNamedVol.Dest]; ok { - return nil, nil, nil, errors.Wrapf(errDuplicateDest, newNamedVol.Dest) - } - volumes[newNamedVol.Dest] = newNamedVol - } - - logrus.Debugf("User mount %s:%s options %v", src, dest, options) - } - - return mounts, volumes, overlayVolumes, nil -} - // GetTmpfsMounts creates spec.Mount structs for user-requested tmpfs mounts func getTmpfsMounts(tmpfsFlag []string) (map[string]spec.Mount, error) { m := make(map[string]spec.Mount) diff --git a/docs/source/markdown/podman-create.1.md b/docs/source/markdown/podman-create.1.md index 749af8a66..8251ba3b6 100644 --- a/docs/source/markdown/podman-create.1.md +++ b/docs/source/markdown/podman-create.1.md @@ -18,6 +18,10 @@ any point. The initial status of the container created with **podman create** is 'created'. +Default settings for flags are defined in `containers.conf`. Most settings for +remote connections use the server's containers.conf, except when documented in +man pages. + ## OPTIONS #### **--add-host**=*host* @@ -817,6 +821,7 @@ Signal to stop a container. Default is SIGTERM. #### **--stop-timeout**=*seconds* Timeout (in seconds) to stop a container. Default is 10. +Remote connections use local containers.conf for defaults #### **--subgidname**=*name* @@ -893,10 +898,12 @@ standard input. #### **--tz**=*timezone* Set timezone in container. This flag takes area-based timezones, GMT time, as well as `local`, which sets the timezone in the container to match the host machine. See `/usr/share/zoneinfo/` for valid timezones. +Remote connections use local containers.conf for defaults #### **--umask**=*umask* Set the umask inside the container. Defaults to `0022`. +Remote connections use local containers.conf for defaults #### **--uidmap**=*container_uid:host_uid:amount* diff --git a/docs/source/markdown/podman-run.1.md b/docs/source/markdown/podman-run.1.md index 5b2cdd6a5..bc3d5a8bb 100644 --- a/docs/source/markdown/podman-run.1.md +++ b/docs/source/markdown/podman-run.1.md @@ -33,6 +33,10 @@ is located at _/run/.containerenv_. When running from a user defined network namespace, the _/etc/netns/NSNAME/resolv.conf_ will be used if it exists, otherwise _/etc/resolv.conf_ will be used. +Default settings are defined in `containers.conf`. Most settings for remote +connections use the servers containers.conf, except when documented in man +pages. + ## OPTIONS #### **--add-host**=_host_:_ip_ @@ -857,6 +861,7 @@ Signal to stop a container. Default is **SIGTERM**. #### **--stop-timeout**=*seconds* Timeout to stop a container. Default is **10**. +Remote connections use local containers.conf for defaults #### **--subgidname**=*name* @@ -952,10 +957,12 @@ standard input. #### **--tz**=*timezone* Set timezone in container. This flag takes area-based timezones, GMT time, as well as `local`, which sets the timezone in the container to match the host machine. See `/usr/share/zoneinfo/` for valid timezones. +Remote connections use local containers.conf for defaults #### **--umask**=*umask* Set the umask inside the container. Defaults to `0022`. +Remote connections use local containers.conf for defaults #### **--uidmap**=*container_uid*:*host_uid*:*amount* diff --git a/docs/source/markdown/podman.1.md b/docs/source/markdown/podman.1.md index 1954ca2aa..68a17d26b 100644 --- a/docs/source/markdown/podman.1.md +++ b/docs/source/markdown/podman.1.md @@ -17,6 +17,10 @@ Podman uses Buildah(1) internally to create container images. Both tools share i (not container) storage, hence each can use or manipulate images (but not containers) created by the other. +Default settings for flags are defined in `containers.conf`. Most settings for +Remote connections use the server's containers.conf, except when documented in +man pages. + **podman [GLOBAL OPTIONS]** ## GLOBAL OPTIONS @@ -33,6 +37,7 @@ Path of the configuration directory for CNI networks. (Default: `/etc/cni/net.d #### **--connection**, **-c** Connection to use for remote podman (Default connection is configured in `containers.conf`) +Remote connections use local containers.conf for default. #### **--conmon** Path of the conmon binary (Default path is configured in `containers.conf`) @@ -71,6 +76,7 @@ Identity value resolution precedence: - command line value - environment variable `CONTAINER_SSHKEY`, if `CONTAINER_HOST` is found - `containers.conf` +Remote connections use local containers.conf for default. #### **--log-level**=*level* @@ -86,6 +92,7 @@ Path to the command binary to use for setting up a network. It is currently onl #### **--remote**, **-r** Access Podman service will be remote +Remote connections use local containers.conf for default. #### **--url**=*value* URL to access Podman service (default from `containers.conf`, rootless `unix://run/user/$UID/podman/podman.sock` or as root `unix://run/podman/podman.sock`). @@ -104,6 +111,7 @@ URL value resolution precedence: - environment variable `CONTAINER_HOST` - `containers.conf` - `unix://run/podman/podman.sock` +Remote connections use local containers.conf for default. #### **--root**=*value* diff --git a/pkg/specgen/generate/container_create.go b/pkg/specgen/generate/container_create.go index c049e64cf..45a374216 100644 --- a/pkg/specgen/generate/container_create.go +++ b/pkg/specgen/generate/container_create.go @@ -111,7 +111,7 @@ func MakeContainer(ctx context.Context, rt *libpod.Runtime, s *specgen.SpecGener return nil, errors.Wrap(err, "invalid config provided") } - finalMounts, finalVolumes, err := finalizeMounts(ctx, s, rt, rtc, newImage) + finalMounts, finalVolumes, finalOverlays, err := finalizeMounts(ctx, s, rt, rtc, newImage) if err != nil { return nil, err } @@ -121,7 +121,7 @@ func MakeContainer(ctx context.Context, rt *libpod.Runtime, s *specgen.SpecGener return nil, err } - opts, err := createContainerOptions(ctx, rt, s, pod, finalVolumes, newImage, command) + opts, err := createContainerOptions(ctx, rt, s, pod, finalVolumes, finalOverlays, newImage, command) if err != nil { return nil, err } @@ -144,7 +144,7 @@ func MakeContainer(ctx context.Context, rt *libpod.Runtime, s *specgen.SpecGener return rt.NewContainer(ctx, runtimeSpec, options...) } -func createContainerOptions(ctx context.Context, rt *libpod.Runtime, s *specgen.SpecGenerator, pod *libpod.Pod, volumes []*specgen.NamedVolume, img *image.Image, command []string) ([]libpod.CtrCreateOption, error) { +func createContainerOptions(ctx context.Context, rt *libpod.Runtime, s *specgen.SpecGenerator, pod *libpod.Pod, volumes []*specgen.NamedVolume, overlays []*specgen.OverlayVolume, img *image.Image, command []string) ([]libpod.CtrCreateOption, error) { var options []libpod.CtrCreateOption var err error @@ -224,7 +224,7 @@ func createContainerOptions(ctx context.Context, rt *libpod.Runtime, s *specgen. for _, volume := range volumes { destinations = append(destinations, volume.Dest) } - for _, overlayVolume := range s.OverlayVolumes { + for _, overlayVolume := range overlays { destinations = append(destinations, overlayVolume.Destination) } for _, imageVolume := range s.ImageVolumes { @@ -244,9 +244,9 @@ func createContainerOptions(ctx context.Context, rt *libpod.Runtime, s *specgen. options = append(options, libpod.WithNamedVolumes(vols)) } - if len(s.OverlayVolumes) != 0 { + if len(overlays) != 0 { var vols []*libpod.ContainerOverlayVolume - for _, v := range s.OverlayVolumes { + for _, v := range overlays { vols = append(vols, &libpod.ContainerOverlayVolume{ Dest: v.Destination, Source: v.Source, diff --git a/pkg/specgen/generate/storage.go b/pkg/specgen/generate/storage.go index b225f79ee..331a5c5bf 100644 --- a/pkg/specgen/generate/storage.go +++ b/pkg/specgen/generate/storage.go @@ -33,17 +33,17 @@ var ( ) // Produce final mounts and named volumes for a container -func finalizeMounts(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.Runtime, rtc *config.Config, img *image.Image) ([]spec.Mount, []*specgen.NamedVolume, error) { +func finalizeMounts(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.Runtime, rtc *config.Config, img *image.Image) ([]spec.Mount, []*specgen.NamedVolume, []*specgen.OverlayVolume, error) { // Get image volumes baseMounts, baseVolumes, err := getImageVolumes(ctx, img, s) if err != nil { - return nil, nil, err + return nil, nil, nil, err } // Get volumes-from mounts volFromMounts, volFromVolumes, err := getVolumesFrom(s.VolumesFrom, rt) if err != nil { - return nil, nil, err + return nil, nil, nil, err } // Supersede from --volumes-from. @@ -57,19 +57,53 @@ func finalizeMounts(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.Ru // Need to make map forms of specgen mounts/volumes. unifiedMounts := map[string]spec.Mount{} unifiedVolumes := map[string]*specgen.NamedVolume{} + unifiedOverlays := map[string]*specgen.OverlayVolume{} + + // Need to make map forms of specgen mounts/volumes. + commonMounts, commonVolumes, commonOverlayVolumes, err := specgen.GenVolumeMounts(rtc.Volumes()) + if err != nil { + return nil, nil, nil, err + } + for _, m := range s.Mounts { if _, ok := unifiedMounts[m.Destination]; ok { - return nil, nil, errors.Wrapf(errDuplicateDest, "conflict in specified mounts - multiple mounts at %q", m.Destination) + return nil, nil, nil, errors.Wrapf(errDuplicateDest, "conflict in specified mounts - multiple mounts at %q", m.Destination) } unifiedMounts[m.Destination] = m } + + for _, m := range commonMounts { + if _, ok := unifiedMounts[m.Destination]; !ok { + unifiedMounts[m.Destination] = m + } + } + for _, v := range s.Volumes { if _, ok := unifiedVolumes[v.Dest]; ok { - return nil, nil, errors.Wrapf(errDuplicateDest, "conflict in specified volumes - multiple volumes at %q", v.Dest) + return nil, nil, nil, errors.Wrapf(errDuplicateDest, "conflict in specified volumes - multiple volumes at %q", v.Dest) } unifiedVolumes[v.Dest] = v } + for _, v := range commonVolumes { + if _, ok := unifiedVolumes[v.Dest]; !ok { + unifiedVolumes[v.Dest] = v + } + } + + for _, v := range s.OverlayVolumes { + if _, ok := unifiedOverlays[v.Destination]; ok { + return nil, nil, nil, errors.Wrapf(errDuplicateDest, "conflict in specified volumes - multiple volumes at %q", v.Destination) + } + unifiedOverlays[v.Destination] = v + } + + for _, v := range commonOverlayVolumes { + if _, ok := unifiedOverlays[v.Destination]; ok { + unifiedOverlays[v.Destination] = v + } + } + // If requested, add container init binary if s.Init { initPath := s.InitPath @@ -78,10 +112,10 @@ func finalizeMounts(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.Ru } initMount, err := addContainerInitBinary(s, initPath) if err != nil { - return nil, nil, err + return nil, nil, nil, err } if _, ok := unifiedMounts[initMount.Destination]; ok { - return nil, nil, errors.Wrapf(errDuplicateDest, "conflict with mount added by --init to %q", initMount.Destination) + return nil, nil, nil, errors.Wrapf(errDuplicateDest, "conflict with mount added by --init to %q", initMount.Destination) } unifiedMounts[initMount.Destination] = initMount } @@ -115,12 +149,12 @@ func finalizeMounts(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.Ru // Check for conflicts between named volumes and mounts for dest := range baseMounts { if _, ok := baseVolumes[dest]; ok { - return nil, nil, errors.Wrapf(errDuplicateDest, "conflict at mount destination %v", dest) + return nil, nil, nil, errors.Wrapf(errDuplicateDest, "conflict at mount destination %v", dest) } } for dest := range baseVolumes { if _, ok := baseMounts[dest]; ok { - return nil, nil, errors.Wrapf(errDuplicateDest, "conflict at mount destination %v", dest) + return nil, nil, nil, errors.Wrapf(errDuplicateDest, "conflict at mount destination %v", dest) } } // Final step: maps to arrays @@ -129,7 +163,7 @@ func finalizeMounts(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.Ru if mount.Type == TypeBind { absSrc, err := filepath.Abs(mount.Source) if err != nil { - return nil, nil, errors.Wrapf(err, "error getting absolute path of %s", mount.Source) + return nil, nil, nil, errors.Wrapf(err, "error getting absolute path of %s", mount.Source) } mount.Source = absSrc } @@ -140,7 +174,12 @@ func finalizeMounts(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.Ru finalVolumes = append(finalVolumes, volume) } - return finalMounts, finalVolumes, nil + finalOverlays := make([]*specgen.OverlayVolume, 0, len(unifiedOverlays)) + for _, volume := range unifiedOverlays { + finalOverlays = append(finalOverlays, volume) + } + + return finalMounts, finalVolumes, finalOverlays, nil } // Get image volumes from the given image diff --git a/pkg/specgen/specgen.go b/pkg/specgen/specgen.go index 0a9a16ea7..fad2406e5 100644 --- a/pkg/specgen/specgen.go +++ b/pkg/specgen/specgen.go @@ -1,13 +1,13 @@ package specgen import ( - "errors" "net" "syscall" "github.com/containers/image/v5/manifest" "github.com/containers/storage" spec "github.com/opencontainers/runtime-spec/specs-go" + "github.com/pkg/errors" ) // LogConfig describes the logging characteristics for a container @@ -459,42 +459,6 @@ type SpecGenerator struct { ContainerHealthCheckConfig } -// NamedVolume holds information about a named volume that will be mounted into -// the container. -type NamedVolume struct { - // Name is the name of the named volume to be mounted. May be empty. - // If empty, a new named volume with a pseudorandomly generated name - // will be mounted at the given destination. - Name string - // Destination to mount the named volume within the container. Must be - // an absolute path. Path will be created if it does not exist. - Dest string - // Options are options that the named volume will be mounted with. - Options []string -} - -// OverlayVolume holds information about a overlay volume that will be mounted into -// the container. -type OverlayVolume struct { - // Destination is the absolute path where the mount will be placed in the container. - Destination string `json:"destination"` - // Source specifies the source path of the mount. - Source string `json:"source,omitempty"` -} - -// ImageVolume is a volume based on a container image. The container image is -// first mounted on the host and is then bind-mounted into the container. An -// ImageVolume is always mounted read only. -type ImageVolume struct { - // Source is the source of the image volume. The image can be referred - // to by name and by ID. - Source string - // Destination is the absolute path of the mount in the container. - Destination string - // ReadWrite sets the volume writable. - ReadWrite bool -} - // PortMapping is one or more ports that will be mapped into the container. type PortMapping struct { // HostIP is the IP that we will bind to on the host. diff --git a/pkg/specgen/volumes.go b/pkg/specgen/volumes.go new file mode 100644 index 000000000..1178f9960 --- /dev/null +++ b/pkg/specgen/volumes.go @@ -0,0 +1,149 @@ +package specgen + +import ( + "path/filepath" + "strings" + + "github.com/containers/buildah/pkg/parse" + spec "github.com/opencontainers/runtime-spec/specs-go" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" +) + +// NamedVolume holds information about a named volume that will be mounted into +// the container. +type NamedVolume struct { + // Name is the name of the named volume to be mounted. May be empty. + // If empty, a new named volume with a pseudorandomly generated name + // will be mounted at the given destination. + Name string + // Destination to mount the named volume within the container. Must be + // an absolute path. Path will be created if it does not exist. + Dest string + // Options are options that the named volume will be mounted with. + Options []string +} + +// OverlayVolume holds information about a overlay volume that will be mounted into +// the container. +type OverlayVolume struct { + // Destination is the absolute path where the mount will be placed in the container. + Destination string `json:"destination"` + // Source specifies the source path of the mount. + Source string `json:"source,omitempty"` +} + +// ImageVolume is a volume based on a container image. The container image is +// first mounted on the host and is then bind-mounted into the container. An +// ImageVolume is always mounted read only. +type ImageVolume struct { + // Source is the source of the image volume. The image can be referred + // to by name and by ID. + Source string + // Destination is the absolute path of the mount in the container. + Destination string + // ReadWrite sets the volume writable. + ReadWrite bool +} + +// GenVolumeMounts parses user input into mounts, volumes and overlay volumes +func GenVolumeMounts(volumeFlag []string) (map[string]spec.Mount, map[string]*NamedVolume, map[string]*OverlayVolume, error) { + errDuplicateDest := errors.Errorf("duplicate mount destination") + + mounts := make(map[string]spec.Mount) + volumes := make(map[string]*NamedVolume) + overlayVolumes := make(map[string]*OverlayVolume) + + volumeFormatErr := errors.Errorf("incorrect volume format, should be [host-dir:]ctr-dir[:option]") + + for _, vol := range volumeFlag { + var ( + options []string + src string + dest string + err error + ) + + splitVol := strings.Split(vol, ":") + if len(splitVol) > 3 { + return nil, nil, nil, errors.Wrapf(volumeFormatErr, vol) + } + + src = splitVol[0] + if len(splitVol) == 1 { + // This is an anonymous named volume. Only thing given + // is destination. + // Name/source will be blank, and populated by libpod. + src = "" + dest = splitVol[0] + } else if len(splitVol) > 1 { + dest = splitVol[1] + } + if len(splitVol) > 2 { + if options, err = parse.ValidateVolumeOpts(strings.Split(splitVol[2], ",")); err != nil { + return nil, nil, nil, err + } + } + + // Do not check source dir for anonymous volumes + if len(splitVol) > 1 { + if err := parse.ValidateVolumeHostDir(src); err != nil { + return nil, nil, nil, err + } + } + if err := parse.ValidateVolumeCtrDir(dest); err != nil { + return nil, nil, nil, err + } + + cleanDest := filepath.Clean(dest) + + if strings.HasPrefix(src, "/") || strings.HasPrefix(src, ".") { + // This is not a named volume + overlayFlag := false + for _, o := range options { + if o == "O" { + overlayFlag = true + if len(options) > 1 { + return nil, nil, nil, errors.New("can't use 'O' with other options") + } + } + } + if overlayFlag { + // This is a overlay volume + newOverlayVol := new(OverlayVolume) + newOverlayVol.Destination = cleanDest + newOverlayVol.Source = src + if _, ok := overlayVolumes[newOverlayVol.Destination]; ok { + return nil, nil, nil, errors.Wrapf(errDuplicateDest, newOverlayVol.Destination) + } + overlayVolumes[newOverlayVol.Destination] = newOverlayVol + } else { + newMount := spec.Mount{ + Destination: cleanDest, + Type: "bind", + Source: src, + Options: options, + } + if _, ok := mounts[newMount.Destination]; ok { + return nil, nil, nil, errors.Wrapf(errDuplicateDest, newMount.Destination) + } + mounts[newMount.Destination] = newMount + } + } else { + // This is a named volume + newNamedVol := new(NamedVolume) + newNamedVol.Name = src + newNamedVol.Dest = cleanDest + newNamedVol.Options = options + + if _, ok := volumes[newNamedVol.Dest]; ok { + return nil, nil, nil, errors.Wrapf(errDuplicateDest, newNamedVol.Dest) + } + volumes[newNamedVol.Dest] = newNamedVol + } + + logrus.Debugf("User mount %s:%s options %v", src, dest, options) + } + + return mounts, volumes, overlayVolumes, nil +} diff --git a/test/e2e/config/containers-remote.conf b/test/e2e/config/containers-remote.conf new file mode 100644 index 000000000..bc9eab951 --- /dev/null +++ b/test/e2e/config/containers-remote.conf @@ -0,0 +1,51 @@ +[containers] + +# A list of ulimits to be set in containers by default, specified as +# "=:", for example: +# "nofile=1024:2048" +# See setrlimit(2) for a list of resource names. +# Any limit not specified here will be inherited from the process launching the +# container engine. +# Ulimits has limits for non privileged container engines. +# +default_ulimits = [ + "nofile=100:100", +] + +# Environment variable list for the conmon process; used for passing necessary +# environment variables to conmon or the runtime. +# +env = [ + "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", + "foo=bar1", +] + +# container engines use container separation using MAC(SELinux) labeling. +# Flag is ignored on label disabled systems. +# +label = false + +# Size of /dev/shm. Specified as . +# Unit is optional, values: +# b (bytes), k (kilobytes), m (megabytes), or g (gigabytes). +# If the unit is omitted, the system uses bytes. +# +shm_size = "202k" + +# List of devices. Specified as +# "::", for example: +# "/dev/sdc:/dev/xvdc:rwm". +# If it is empty or commented out, only the default devices will be used +# +devices = [] + +default_sysctls = [ + "net.ipv4.ping_group_range=0 0", +] + +dns_searches=[ "barfoo.com", ] +dns_servers=[ "4.3.2.1", ] + +tz = "America/New_York" + +umask = "0022" diff --git a/test/e2e/containers_conf_test.go b/test/e2e/containers_conf_test.go index 1d5be218b..906153c0f 100644 --- a/test/e2e/containers_conf_test.go +++ b/test/e2e/containers_conf_test.go @@ -177,6 +177,9 @@ var _ = Describe("Podman run", func() { } os.Setenv("CONTAINERS_CONF", conffile) + if IsRemote() { + podmanTest.RestartRemoteService() + } result := podmanTest.Podman([]string{"run", ALPINE, "ls", tempdir}) result.WaitWithDefaultTimeout() Expect(result.ExitCode()).To(Equal(0)) @@ -224,6 +227,17 @@ var _ = Describe("Podman run", func() { Expect(session.LineInOuputStartsWith("search")).To(BeFalse()) }) + It("podman run use containers.conf search domain", func() { + session := podmanTest.Podman([]string{"run", ALPINE, "cat", "/etc/resolv.conf"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + Expect(session.LineInOuputStartsWith("search")).To(BeTrue()) + Expect(session.OutputToString()).To(ContainSubstring("foobar.com")) + + Expect(session.OutputToString()).To(ContainSubstring("1.2.3.4")) + Expect(session.OutputToString()).To(ContainSubstring("debug")) + }) + It("podman run containers.conf timezone", func() { //containers.conf timezone set to Pacific/Honolulu session := podmanTest.Podman([]string{"run", ALPINE, "date", "+'%H %Z'"}) @@ -231,6 +245,7 @@ var _ = Describe("Podman run", func() { Expect(session.ExitCode()).To(Equal(0)) Expect(session.OutputToString()).To(ContainSubstring("HST")) }) + It("podman run containers.conf umask", func() { //containers.conf umask set to 0002 if !strings.Contains(podmanTest.OCIRuntime, "crun") { @@ -243,4 +258,57 @@ var _ = Describe("Podman run", func() { Expect(session.OutputToString()).To(Equal("0002")) }) + It("podman-remote test localcontainers.conf versus remote containers.conf", func() { + if !IsRemote() { + Skip("this test is only for remote") + } + + os.Setenv("CONTAINERS_CONF", "config/containers-remote.conf") + // Configuration that comes from remote server + // env + session := podmanTest.Podman([]string{"run", ALPINE, "printenv", "foo"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + Expect(session.OutputToString()).To(Equal("bar")) + + // dns-search, server, options + session = podmanTest.Podman([]string{"run", ALPINE, "cat", "/etc/resolv.conf"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + Expect(session.LineInOuputStartsWith("search")).To(BeTrue()) + Expect(session.OutputToString()).To(ContainSubstring("foobar.com")) + Expect(session.OutputToString()).To(ContainSubstring("1.2.3.4")) + Expect(session.OutputToString()).To(ContainSubstring("debug")) + + // sysctls + session = podmanTest.Podman([]string{"run", "--rm", ALPINE, "cat", "/proc/sys/net/ipv4/ping_group_range"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + Expect(session.OutputToString()).To(ContainSubstring("1000")) + + // shm-size + session = podmanTest.Podman([]string{"run", ALPINE, "grep", "shm", "/proc/self/mounts"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + Expect(session.OutputToString()).To(ContainSubstring("size=200k")) + + // ulimits + session = podmanTest.Podman([]string{"run", "--rm", fedoraMinimal, "ulimit", "-n"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + Expect(session.OutputToString()).To(ContainSubstring("500")) + + // Configuration that comes from remote client + // Timezone + session = podmanTest.Podman([]string{"run", ALPINE, "date", "+'%H %Z'"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + Expect(session.OutputToString()).To(ContainSubstring("EST")) + + // Umask + session = podmanTest.Podman([]string{"run", "--rm", ALPINE, "sh", "-c", "umask"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + Expect(session.OutputToString()).To(Equal("0022")) + }) }) -- cgit v1.2.3-54-g00ecf From f441190d10ca3d41e01a076e4b9b3a63746888b6 Mon Sep 17 00:00:00 2001 From: Paul Holzinger Date: Thu, 19 Nov 2020 12:34:00 +0100 Subject: Allow multiple --network flags for podman run/create We allow a container to be connected to several cni networks but only if they are listed comma sperated. This is not intuitive for users especially since the flag parsing allows multiple string flags but only would take the last value. see: spf13/pflag#72 Also get rid of the extra parsing logic for pods. The invalid options are already handled by `pkg/specgen`. A test is added to prevent a future regression. Signed-off-by: Paul Holzinger --- cmd/podman/common/netflags.go | 34 ++++++++++++++++++--------------- cmd/podman/pods/create.go | 28 +-------------------------- docs/source/markdown/podman-create.1.md | 2 +- docs/source/markdown/podman-run.1.md | 2 +- pkg/specgen/namespaces.go | 6 ------ test/e2e/pod_create_test.go | 20 +++++++++++++++++++ test/e2e/run_networking_test.go | 29 ++++++++++++++++++++++++++++ 7 files changed, 71 insertions(+), 50 deletions(-) (limited to 'cmd/podman') diff --git a/cmd/podman/common/netflags.go b/cmd/podman/common/netflags.go index cae52ccaa..898d65bd0 100644 --- a/cmd/podman/common/netflags.go +++ b/cmd/podman/common/netflags.go @@ -59,8 +59,8 @@ func DefineNetFlags(cmd *cobra.Command) { _ = cmd.RegisterFlagCompletionFunc(macAddressFlagName, completion.AutocompleteNone) networkFlagName := "network" - netFlags.String( - networkFlagName, containerConfig.NetNS(), + netFlags.StringArray( + networkFlagName, []string{containerConfig.NetNS()}, "Connect a container to a network", ) _ = cmd.RegisterFlagCompletionFunc(networkFlagName, AutocompleteNetworks) @@ -194,25 +194,29 @@ func NetFlagsToNetOptions(cmd *cobra.Command) (*entities.NetOptions, error) { } if cmd.Flags().Changed("network") { - network, err := cmd.Flags().GetString("network") + networks, err := cmd.Flags().GetStringArray("network") if err != nil { return nil, err } + for i, network := range networks { + parts := strings.SplitN(network, ":", 2) - parts := strings.SplitN(network, ":", 2) - - ns, cniNets, err := specgen.ParseNetworkNamespace(network) - if err != nil { - return nil, err - } + ns, cniNets, err := specgen.ParseNetworkNamespace(network) + if err != nil { + return nil, err + } + if i > 0 && (len(cniNets) == 0 || len(opts.CNINetworks) == 0) { + return nil, errors.Errorf("network conflict between type %s and %s", opts.Network.NSMode, ns.NSMode) + } - if len(parts) > 1 { - opts.NetworkOptions = make(map[string][]string) - opts.NetworkOptions[parts[0]] = strings.Split(parts[1], ",") - cniNets = nil + if len(parts) > 1 { + opts.NetworkOptions = make(map[string][]string) + opts.NetworkOptions[parts[0]] = strings.Split(parts[1], ",") + cniNets = nil + } + opts.Network = ns + opts.CNINetworks = append(opts.CNINetworks, cniNets...) } - opts.Network = ns - opts.CNINetworks = cniNets } aliases, err := cmd.Flags().GetStringSlice("network-alias") diff --git a/cmd/podman/pods/create.go b/cmd/podman/pods/create.go index 449d60bb9..57b895e83 100644 --- a/cmd/podman/pods/create.go +++ b/cmd/podman/pods/create.go @@ -171,33 +171,7 @@ func create(cmd *cobra.Command, args []string) error { if err != nil { return err } - createOptions.Net.Network = specgen.Namespace{} - if cmd.Flag("network").Changed { - netInput, err := cmd.Flags().GetString("network") - if err != nil { - return err - } - parts := strings.SplitN(netInput, ":", 2) - - n := specgen.Namespace{} - switch { - case netInput == "bridge": - n.NSMode = specgen.Bridge - case netInput == "host": - n.NSMode = specgen.Host - case netInput == "slirp4netns", strings.HasPrefix(netInput, "slirp4netns:"): - n.NSMode = specgen.Slirp - if len(parts) > 1 { - createOptions.Net.NetworkOptions = make(map[string][]string) - createOptions.Net.NetworkOptions[parts[0]] = strings.Split(parts[1], ",") - } - default: - // Container and NS mode are presently unsupported - n.NSMode = specgen.Bridge - createOptions.Net.CNINetworks = strings.Split(netInput, ",") - } - createOptions.Net.Network = n - } + if len(createOptions.Net.PublishPorts) > 0 { if !createOptions.Infra { return errors.Errorf("you must have an infra container to publish port bindings to the host") diff --git a/docs/source/markdown/podman-create.1.md b/docs/source/markdown/podman-create.1.md index 749af8a66..2d5c1e1a0 100644 --- a/docs/source/markdown/podman-create.1.md +++ b/docs/source/markdown/podman-create.1.md @@ -584,7 +584,7 @@ Valid _mode_ values are: - **none**: no networking; - **container:**_id_: reuse another container's network stack; - **host**: use the Podman host network stack. Note: the host mode gives the container full access to local system services such as D-bus and is therefore considered insecure; -- _network-id_: connect to a user-defined network, multiple networks should be comma separated; +- **cni-network**: connect to a user-defined network, multiple networks should be comma-separated or they can be specified with multiple uses of the **--network** option; - **ns:**_path_: path to a network namespace to join; - **private**: create a new namespace for the container (default) - **slirp4netns[:OPTIONS,...]**: use **slirp4netns**(1) to create a user network stack. This is the default for rootless containers. It is possible to specify these additional options: diff --git a/docs/source/markdown/podman-run.1.md b/docs/source/markdown/podman-run.1.md index 5b2cdd6a5..45cdee8cd 100644 --- a/docs/source/markdown/podman-run.1.md +++ b/docs/source/markdown/podman-run.1.md @@ -610,7 +610,7 @@ Valid _mode_ values are: - **none**: no networking; - **container:**_id_: reuse another container's network stack; - **host**: use the Podman host network stack. Note: the host mode gives the container full access to local system services such as D-bus and is therefore considered insecure; -- _network-id_: connect to a user-defined network, multiple networks should be comma separated; +- **cni-network**: connect to a user-defined network, multiple networks should be comma-separated or they can be specified with multiple uses of the **--network** option; - **ns:**_path_: path to a network namespace to join; - **private**: create a new namespace for the container (default) - **slirp4netns[:OPTIONS,...]**: use **slirp4netns**(1) to create a user network stack. This is the default for rootless containers. It is possible to specify these additional options: diff --git a/pkg/specgen/namespaces.go b/pkg/specgen/namespaces.go index 90c56d366..11108a5c1 100644 --- a/pkg/specgen/namespaces.go +++ b/pkg/specgen/namespaces.go @@ -272,16 +272,10 @@ func ParseNetworkNamespace(ns string) (Namespace, []string, error) { toReturn.NSMode = Private case strings.HasPrefix(ns, "ns:"): split := strings.SplitN(ns, ":", 2) - if len(split) != 2 { - return toReturn, nil, errors.Errorf("must provide a path to a namespace when specifying ns:") - } toReturn.NSMode = Path toReturn.Value = split[1] case strings.HasPrefix(ns, "container:"): split := strings.SplitN(ns, ":", 2) - if len(split) != 2 { - return toReturn, nil, errors.Errorf("must provide name or ID or a container when specifying container:") - } toReturn.NSMode = FromContainer toReturn.Value = split[1] default: diff --git a/test/e2e/pod_create_test.go b/test/e2e/pod_create_test.go index be0a2f6f0..ccfbcefae 100644 --- a/test/e2e/pod_create_test.go +++ b/test/e2e/pod_create_test.go @@ -9,6 +9,7 @@ import ( "github.com/containers/podman/v2/pkg/rootless" . "github.com/containers/podman/v2/test/utils" + "github.com/containers/storage/pkg/stringid" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) @@ -476,4 +477,23 @@ entrypoint ["/fromimage"] Expect(status3.ExitCode()).To(Equal(0)) Expect(strings.Contains(status3.OutputToString(), "Degraded")).To(BeTrue()) }) + + It("podman create pod invalid network config", func() { + net1 := "n1" + stringid.GenerateNonCryptoID() + session := podmanTest.Podman([]string{"network", "create", net1}) + session.WaitWithDefaultTimeout() + defer podmanTest.removeCNINetwork(net1) + Expect(session.ExitCode()).To(BeZero()) + + session = podmanTest.Podman([]string{"pod", "create", "--network", "host", "--network", net1}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(125)) + Expect(session.ErrorToString()).To(ContainSubstring("host")) + Expect(session.ErrorToString()).To(ContainSubstring("bridge")) + + session = podmanTest.Podman([]string{"pod", "create", "--network", "container:abc"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(125)) + Expect(session.ErrorToString()).To(ContainSubstring("pods presently do not support network mode container")) + }) }) diff --git a/test/e2e/run_networking_test.go b/test/e2e/run_networking_test.go index 3e80e953e..1d416498c 100644 --- a/test/e2e/run_networking_test.go +++ b/test/e2e/run_networking_test.go @@ -665,4 +665,33 @@ var _ = Describe("Podman run networking", func() { session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(BeZero()) }) + + It("podman run with multiple networks", func() { + net1 := "n1" + stringid.GenerateNonCryptoID() + session := podmanTest.Podman([]string{"network", "create", net1}) + session.WaitWithDefaultTimeout() + defer podmanTest.removeCNINetwork(net1) + Expect(session.ExitCode()).To(BeZero()) + + net2 := "n2" + stringid.GenerateNonCryptoID() + session = podmanTest.Podman([]string{"network", "create", net2}) + session.WaitWithDefaultTimeout() + defer podmanTest.removeCNINetwork(net2) + Expect(session.ExitCode()).To(BeZero()) + + run := podmanTest.Podman([]string{"run", "--network", net1, "--network", net2, ALPINE, "ip", "-o", "-4", "addr"}) + run.WaitWithDefaultTimeout() + Expect(run.ExitCode()).To(BeZero()) + Expect(len(run.OutputToStringArray())).To(Equal(3)) + Expect(run.OutputToString()).To(ContainSubstring("lo")) + Expect(run.OutputToString()).To(ContainSubstring("eth0")) + Expect(run.OutputToString()).To(ContainSubstring("eth1")) + + //invalid config network host and cni should fail + run = podmanTest.Podman([]string{"run", "--network", "host", "--network", net2, ALPINE, "ip", "-o", "-4", "addr"}) + run.WaitWithDefaultTimeout() + Expect(run.ExitCode()).To(Equal(125)) + Expect(run.ErrorToString()).To(ContainSubstring("host")) + Expect(run.ErrorToString()).To(ContainSubstring("bridge")) + }) }) -- cgit v1.2.3-54-g00ecf From dc8996ec84ffd6f272361edbf7c19e91c52519d9 Mon Sep 17 00:00:00 2001 From: Daniel J Walsh Date: Fri, 6 Nov 2020 09:55:40 -0500 Subject: Allow containers to --restart on-failure with --rm Signed-off-by: Daniel J Walsh --- cmd/podman/common/createparse.go | 2 +- libpod/container_api.go | 14 ++++++++++++++ libpod/container_internal.go | 28 +++++++++++++++------------- pkg/api/handlers/libpod/containers.go | 24 ++++++++++++++++++++++++ pkg/bindings/containers/containers.go | 12 ++++++++++++ pkg/domain/infra/abi/containers.go | 15 +++++++++++++-- pkg/domain/infra/tunnel/containers.go | 25 +++++++++++++++++++------ test/e2e/run_test.go | 4 +--- 8 files changed, 99 insertions(+), 25 deletions(-) (limited to 'cmd/podman') diff --git a/cmd/podman/common/createparse.go b/cmd/podman/common/createparse.go index 09ee5aa0c..3a69f11b6 100644 --- a/cmd/podman/common/createparse.go +++ b/cmd/podman/common/createparse.go @@ -9,7 +9,7 @@ import ( // by validate must not need any state information on the flag (i.e. changed) func (c *ContainerCLIOpts) validate() error { var () - if c.Rm && c.Restart != "" && c.Restart != "no" { + if c.Rm && (c.Restart != "" && c.Restart != "no" && c.Restart != "on-failure") { return errors.Errorf(`the --rm option conflicts with --restart, when the restartPolicy is not "" and "no"`) } diff --git a/libpod/container_api.go b/libpod/container_api.go index a9808a30e..6a7ddc421 100644 --- a/libpod/container_api.go +++ b/libpod/container_api.go @@ -714,3 +714,17 @@ func (c *Container) Restore(ctx context.Context, options ContainerCheckpointOpti defer c.newContainerEvent(events.Restore) return c.restore(ctx, options) } + +// Indicate whether or not the container should restart +func (c *Container) ShouldRestart(ctx context.Context) bool { + logrus.Debugf("Checking if container %s should restart", c.ID()) + if !c.batched { + c.lock.Lock() + defer c.lock.Unlock() + + if err := c.syncContainer(); err != nil { + return false + } + } + return c.shouldRestart() +} diff --git a/libpod/container_internal.go b/libpod/container_internal.go index 108954bad..ffd0dc92f 100644 --- a/libpod/container_internal.go +++ b/libpod/container_internal.go @@ -206,37 +206,39 @@ func (c *Container) handleExitFile(exitFile string, fi os.FileInfo) error { return nil } -// Handle container restart policy. -// This is called when a container has exited, and was not explicitly stopped by -// an API call to stop the container or pod it is in. -func (c *Container) handleRestartPolicy(ctx context.Context) (_ bool, retErr error) { - // If we did not get a restart policy match, exit immediately. +func (c *Container) shouldRestart() bool { + // If we did not get a restart policy match, return false // Do the same if we're not a policy that restarts. if !c.state.RestartPolicyMatch || c.config.RestartPolicy == RestartPolicyNo || c.config.RestartPolicy == RestartPolicyNone { - return false, nil + return false } // If we're RestartPolicyOnFailure, we need to check retries and exit // code. if c.config.RestartPolicy == RestartPolicyOnFailure { if c.state.ExitCode == 0 { - return false, nil + return false } // If we don't have a max retries set, continue if c.config.RestartRetries > 0 { - if c.state.RestartCount < c.config.RestartRetries { - logrus.Debugf("Container %s restart policy trigger: on retry %d (of %d)", - c.ID(), c.state.RestartCount, c.config.RestartRetries) - } else { - logrus.Debugf("Container %s restart policy trigger: retries exhausted", c.ID()) - return false, nil + if c.state.RestartCount >= c.config.RestartRetries { + return false } } } + return true +} +// Handle container restart policy. +// This is called when a container has exited, and was not explicitly stopped by +// an API call to stop the container or pod it is in. +func (c *Container) handleRestartPolicy(ctx context.Context) (_ bool, retErr error) { + if !c.shouldRestart() { + return false, nil + } logrus.Debugf("Restarting container %s due to restart policy %s", c.ID(), c.config.RestartPolicy) // Need to check if dependencies are alive. diff --git a/pkg/api/handlers/libpod/containers.go b/pkg/api/handlers/libpod/containers.go index 7e6481321..14eb44831 100644 --- a/pkg/api/handlers/libpod/containers.go +++ b/pkg/api/handlers/libpod/containers.go @@ -344,3 +344,27 @@ func InitContainer(w http.ResponseWriter, r *http.Request) { } utils.WriteResponse(w, http.StatusNoContent, "") } + +func ShouldRestart(w http.ResponseWriter, r *http.Request) { + runtime := r.Context().Value("runtime").(*libpod.Runtime) + // Now use the ABI implementation to prevent us from having duplicate + // code. + containerEngine := abi.ContainerEngine{Libpod: runtime} + + name := utils.GetName(r) + report, err := containerEngine.ShouldRestart(r.Context(), name) + if err != nil { + if errors.Cause(err) == define.ErrNoSuchCtr { + utils.ContainerNotFound(w, name, err) + return + } + utils.InternalServerError(w, err) + return + + } + if report.Value { + utils.WriteResponse(w, http.StatusNoContent, "") + } else { + utils.ContainerNotFound(w, name, define.ErrNoSuchCtr) + } +} diff --git a/pkg/bindings/containers/containers.go b/pkg/bindings/containers/containers.go index b5cd2128b..4331ae6c2 100644 --- a/pkg/bindings/containers/containers.go +++ b/pkg/bindings/containers/containers.go @@ -390,3 +390,15 @@ func ContainerInit(ctx context.Context, nameOrID string) error { } return response.Process(nil) } + +func ShouldRestart(ctx context.Context, nameOrID string) (bool, error) { + conn, err := bindings.GetClient(ctx) + if err != nil { + return false, err + } + response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/shouldrestart", nil, nil, nameOrID) + if err != nil { + return false, err + } + return response.IsSuccess(), nil +} diff --git a/pkg/domain/infra/abi/containers.go b/pkg/domain/infra/abi/containers.go index 4b69ac74e..ff4277a2e 100644 --- a/pkg/domain/infra/abi/containers.go +++ b/pkg/domain/infra/abi/containers.go @@ -911,7 +911,7 @@ func (ic *ContainerEngine) ContainerRun(ctx context.Context, opts entities.Conta } else { report.ExitCode = int(ecode) } - if opts.Rm { + if opts.Rm && !ctr.ShouldRestart(ctx) { if err := ic.Libpod.RemoveContainer(ctx, ctr, false, true); err != nil { if errors.Cause(err) == define.ErrNoSuchCtr || errors.Cause(err) == define.ErrCtrRemoved { @@ -992,7 +992,7 @@ func (ic *ContainerEngine) ContainerCleanup(ctx context.Context, namesOrIds []st return []*entities.ContainerCleanupReport{}, nil } - if options.Remove { + if options.Remove && !ctr.ShouldRestart(ctx) { err = ic.Libpod.RemoveContainer(ctx, ctr, false, true) if err != nil { report.RmErr = errors.Wrapf(err, "failed to cleanup and remove container %v", ctr.ID()) @@ -1015,6 +1015,7 @@ func (ic *ContainerEngine) ContainerCleanup(ctx context.Context, namesOrIds []st _, err = ic.Libpod.RemoveImage(ctx, ctrImage, false) report.RmiErr = err } + reports = append(reports, &report) } return reports, nil @@ -1314,3 +1315,13 @@ func (ic *ContainerEngine) ContainerStats(ctx context.Context, namesOrIds []stri return statsChan, nil } + +// ShouldRestart returns whether the container should be restarted +func (ic *ContainerEngine) ShouldRestart(ctx context.Context, nameOrID string) (*entities.BoolReport, error) { + ctr, err := ic.Libpod.LookupContainer(nameOrID) + if err != nil { + return nil, err + } + + return &entities.BoolReport{Value: ctr.ShouldRestart(ctx)}, nil +} diff --git a/pkg/domain/infra/tunnel/containers.go b/pkg/domain/infra/tunnel/containers.go index 8066e1c00..1aa5afbe7 100644 --- a/pkg/domain/infra/tunnel/containers.go +++ b/pkg/domain/infra/tunnel/containers.go @@ -595,12 +595,20 @@ func (ic *ContainerEngine) ContainerRun(ctx context.Context, opts entities.Conta // Defer the removal, so we can return early if needed and // de-spaghetti the code. defer func() { - if err := containers.Remove(ic.ClientCxt, con.ID, bindings.PFalse, bindings.PTrue); err != nil { - if errorhandling.Contains(err, define.ErrNoSuchCtr) || - errorhandling.Contains(err, define.ErrCtrRemoved) { - logrus.Warnf("Container %s does not exist: %v", con.ID, err) - } else { - logrus.Errorf("Error removing container %s: %v", con.ID, err) + shouldRestart, err := containers.ShouldRestart(ic.ClientCxt, con.ID) + if err != nil { + logrus.Errorf("Failed to check if %s should restart: %v", con.ID, err) + return + } + + if !shouldRestart { + if err := containers.Remove(ic.ClientCxt, con.ID, bindings.PFalse, bindings.PTrue); err != nil { + if errorhandling.Contains(err, define.ErrNoSuchCtr) || + errorhandling.Contains(err, define.ErrCtrRemoved) { + logrus.Warnf("Container %s does not exist: %v", con.ID, err) + } else { + logrus.Errorf("Error removing container %s: %v", con.ID, err) + } } } }() @@ -737,3 +745,8 @@ func (ic *ContainerEngine) ContainerStats(ctx context.Context, namesOrIds []stri } return containers.Stats(ic.ClientCxt, namesOrIds, &options.Stream) } + +// ShouldRestart reports back whether the containre will restart +func (ic *ContainerEngine) ShouldRestart(_ context.Context, id string) (bool, error) { + return containers.ShouldRestart(ic.ClientCxt, id) +} diff --git a/test/e2e/run_test.go b/test/e2e/run_test.go index 5ee85efb9..0d65a3e59 100644 --- a/test/e2e/run_test.go +++ b/test/e2e/run_test.go @@ -75,11 +75,9 @@ var _ = Describe("Podman run", func() { session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) - // the --rm option conflicts with --restart, when the restartPolicy is not "" and "no" - // so the exitCode should not equal 0 session = podmanTest.Podman([]string{"run", "--rm", "--restart", "on-failure", ALPINE}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Not(Equal(0))) + Expect(session.ExitCode()).To(Equal(0)) session = podmanTest.Podman([]string{"run", "--rm", "--restart", "always", ALPINE}) session.WaitWithDefaultTimeout() -- cgit v1.2.3-54-g00ecf From 64dd15e53f3fd58427f6666d5de88165cf0bc634 Mon Sep 17 00:00:00 2001 From: Benjamin Porter Date: Fri, 20 Nov 2020 14:30:20 -0700 Subject: Specify what the replace flag replaces in help text The word "name" appears to have been missed in the help output for: podman pod create --help This patch fixes that Signed-off-by: Benjamin Porter --- cmd/podman/pods/create.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'cmd/podman') diff --git a/cmd/podman/pods/create.go b/cmd/podman/pods/create.go index 449d60bb9..d997ea344 100644 --- a/cmd/podman/pods/create.go +++ b/cmd/podman/pods/create.go @@ -94,7 +94,7 @@ func init() { flags.StringVar(&podIDFile, podIDFileFlagName, "", "Write the pod ID to the file") _ = createCommand.RegisterFlagCompletionFunc(podIDFileFlagName, completion.AutocompleteDefault) - flags.BoolVar(&replace, "replace", false, "If a pod with the same exists, replace it") + flags.BoolVar(&replace, "replace", false, "If a pod with the same name exists, replace it") shareFlagName := "share" flags.StringVar(&share, shareFlagName, specgen.DefaultKernelNamespaces, "A comma delimited list of kernel namespaces the pod will share") -- cgit v1.2.3-54-g00ecf From bf11bb769f7d8f38bbd0e9f40b415128399f27b3 Mon Sep 17 00:00:00 2001 From: Paul Holzinger Date: Fri, 20 Nov 2020 17:49:24 +0100 Subject: Enable remote shell completion without a running endpoint The problem is that we always unconditionally setup up the `ContainerEngine/ImageEngine`. This requires an running endpoint. Most completions (e.g. flag names) do not need them and should not fail. This commit makes sure we only setup the engines as needed in the completions. Signed-off-by: Paul Holzinger --- cmd/podman/common/completion.go | 132 ++++++++++++++++++++++++++++------------ cmd/podman/root.go | 52 +++++++--------- 2 files changed, 116 insertions(+), 68 deletions(-) (limited to 'cmd/podman') diff --git a/cmd/podman/common/completion.go b/cmd/podman/common/completion.go index db4d3d0d3..c96f7436d 100644 --- a/cmd/podman/common/completion.go +++ b/cmd/podman/common/completion.go @@ -12,6 +12,7 @@ import ( "github.com/containers/podman/v2/libpod/define" "github.com/containers/podman/v2/pkg/domain/entities" "github.com/containers/podman/v2/pkg/registries" + "github.com/containers/podman/v2/pkg/rootless" systemdGen "github.com/containers/podman/v2/pkg/systemd/generate" "github.com/spf13/cobra" ) @@ -36,7 +37,35 @@ const ( type keyValueCompletion map[string]func(s string) ([]string, cobra.ShellCompDirective) -func getContainers(toComplete string, cType completeType, statuses ...string) ([]string, cobra.ShellCompDirective) { +func setupContainerEngine(cmd *cobra.Command) (entities.ContainerEngine, error) { + containerEngine, err := registry.NewContainerEngine(cmd, []string{}) + if err != nil { + cobra.CompErrorln(err.Error()) + return nil, err + } + if !registry.IsRemote() && rootless.IsRootless() { + err := containerEngine.SetupRootless(registry.Context(), cmd) + if err != nil { + return nil, err + } + } + return containerEngine, nil +} + +func setupImageEngine(cmd *cobra.Command) (entities.ImageEngine, error) { + imageEngine, err := registry.NewImageEngine(cmd, []string{}) + if err != nil { + return nil, err + } + // we also need to set up the container engine since this + // is required to setup the rootless namespace + if _, err = setupContainerEngine(cmd); err != nil { + return nil, err + } + return imageEngine, nil +} + +func getContainers(cmd *cobra.Command, toComplete string, cType completeType, statuses ...string) ([]string, cobra.ShellCompDirective) { suggestions := []string{} listOpts := entities.ContainerListOptions{ Filters: make(map[string][]string), @@ -47,7 +76,12 @@ func getContainers(toComplete string, cType completeType, statuses ...string) ([ listOpts.Filters["status"] = statuses } - containers, err := registry.ContainerEngine().ContainerList(registry.GetContext(), listOpts) + engine, err := setupContainerEngine(cmd) + if err != nil { + cobra.CompErrorln(err.Error()) + return nil, cobra.ShellCompDirectiveNoFileComp + } + containers, err := engine.ContainerList(registry.GetContext(), listOpts) if err != nil { cobra.CompErrorln(err.Error()) return nil, cobra.ShellCompDirectiveError @@ -68,7 +102,7 @@ func getContainers(toComplete string, cType completeType, statuses ...string) ([ return suggestions, cobra.ShellCompDirectiveNoFileComp } -func getPods(toComplete string, cType completeType, statuses ...string) ([]string, cobra.ShellCompDirective) { +func getPods(cmd *cobra.Command, toComplete string, cType completeType, statuses ...string) ([]string, cobra.ShellCompDirective) { suggestions := []string{} listOpts := entities.PodPSOptions{ Filters: make(map[string][]string), @@ -77,7 +111,12 @@ func getPods(toComplete string, cType completeType, statuses ...string) ([]strin listOpts.Filters["status"] = statuses } - pods, err := registry.ContainerEngine().PodPs(registry.GetContext(), listOpts) + engine, err := setupContainerEngine(cmd) + if err != nil { + cobra.CompErrorln(err.Error()) + return nil, cobra.ShellCompDirectiveNoFileComp + } + pods, err := engine.PodPs(registry.GetContext(), listOpts) if err != nil { cobra.CompErrorln(err.Error()) return nil, cobra.ShellCompDirectiveError @@ -98,11 +137,16 @@ func getPods(toComplete string, cType completeType, statuses ...string) ([]strin return suggestions, cobra.ShellCompDirectiveNoFileComp } -func getVolumes(toComplete string) ([]string, cobra.ShellCompDirective) { +func getVolumes(cmd *cobra.Command, toComplete string) ([]string, cobra.ShellCompDirective) { suggestions := []string{} lsOpts := entities.VolumeListOptions{} - volumes, err := registry.ContainerEngine().VolumeList(registry.GetContext(), lsOpts) + engine, err := setupContainerEngine(cmd) + if err != nil { + cobra.CompErrorln(err.Error()) + return nil, cobra.ShellCompDirectiveNoFileComp + } + volumes, err := engine.VolumeList(registry.GetContext(), lsOpts) if err != nil { cobra.CompErrorln(err.Error()) return nil, cobra.ShellCompDirectiveError @@ -116,11 +160,16 @@ func getVolumes(toComplete string) ([]string, cobra.ShellCompDirective) { return suggestions, cobra.ShellCompDirectiveNoFileComp } -func getImages(toComplete string) ([]string, cobra.ShellCompDirective) { +func getImages(cmd *cobra.Command, toComplete string) ([]string, cobra.ShellCompDirective) { suggestions := []string{} listOptions := entities.ImageListOptions{} - images, err := registry.ImageEngine().List(registry.GetContext(), listOptions) + engine, err := setupImageEngine(cmd) + if err != nil { + cobra.CompErrorln(err.Error()) + return nil, cobra.ShellCompDirectiveNoFileComp + } + images, err := engine.List(registry.GetContext(), listOptions) if err != nil { cobra.CompErrorln(err.Error()) return nil, cobra.ShellCompDirectiveError @@ -171,11 +220,16 @@ func getRegistries() ([]string, cobra.ShellCompDirective) { return regs, cobra.ShellCompDirectiveNoFileComp } -func getNetworks(toComplete string) ([]string, cobra.ShellCompDirective) { +func getNetworks(cmd *cobra.Command, toComplete string) ([]string, cobra.ShellCompDirective) { suggestions := []string{} networkListOptions := entities.NetworkListOptions{} - networks, err := registry.ContainerEngine().NetworkList(registry.Context(), networkListOptions) + engine, err := setupContainerEngine(cmd) + if err != nil { + cobra.CompErrorln(err.Error()) + return nil, cobra.ShellCompDirectiveNoFileComp + } + networks, err := engine.NetworkList(registry.Context(), networkListOptions) if err != nil { cobra.CompErrorln(err.Error()) return nil, cobra.ShellCompDirectiveError @@ -266,7 +320,7 @@ func AutocompleteContainers(cmd *cobra.Command, args []string, toComplete string if !validCurrentCmdLine(cmd, args, toComplete) { return nil, cobra.ShellCompDirectiveNoFileComp } - return getContainers(toComplete, completeDefault) + return getContainers(cmd, toComplete, completeDefault) } // AutocompleteContainersCreated - Autocomplete only created container names. @@ -274,7 +328,7 @@ func AutocompleteContainersCreated(cmd *cobra.Command, args []string, toComplete if !validCurrentCmdLine(cmd, args, toComplete) { return nil, cobra.ShellCompDirectiveNoFileComp } - return getContainers(toComplete, completeDefault, "created") + return getContainers(cmd, toComplete, completeDefault, "created") } // AutocompleteContainersExited - Autocomplete only exited container names. @@ -282,7 +336,7 @@ func AutocompleteContainersExited(cmd *cobra.Command, args []string, toComplete if !validCurrentCmdLine(cmd, args, toComplete) { return nil, cobra.ShellCompDirectiveNoFileComp } - return getContainers(toComplete, completeDefault, "exited") + return getContainers(cmd, toComplete, completeDefault, "exited") } // AutocompleteContainersPaused - Autocomplete only paused container names. @@ -290,7 +344,7 @@ func AutocompleteContainersPaused(cmd *cobra.Command, args []string, toComplete if !validCurrentCmdLine(cmd, args, toComplete) { return nil, cobra.ShellCompDirectiveNoFileComp } - return getContainers(toComplete, completeDefault, "paused") + return getContainers(cmd, toComplete, completeDefault, "paused") } // AutocompleteContainersRunning - Autocomplete only running container names. @@ -298,7 +352,7 @@ func AutocompleteContainersRunning(cmd *cobra.Command, args []string, toComplete if !validCurrentCmdLine(cmd, args, toComplete) { return nil, cobra.ShellCompDirectiveNoFileComp } - return getContainers(toComplete, completeDefault, "running") + return getContainers(cmd, toComplete, completeDefault, "running") } // AutocompleteContainersStartable - Autocomplete only created and exited container names. @@ -306,7 +360,7 @@ func AutocompleteContainersStartable(cmd *cobra.Command, args []string, toComple if !validCurrentCmdLine(cmd, args, toComplete) { return nil, cobra.ShellCompDirectiveNoFileComp } - return getContainers(toComplete, completeDefault, "created", "exited") + return getContainers(cmd, toComplete, completeDefault, "created", "exited") } // AutocompletePods - Autocomplete all pod names. @@ -314,7 +368,7 @@ func AutocompletePods(cmd *cobra.Command, args []string, toComplete string) ([]s if !validCurrentCmdLine(cmd, args, toComplete) { return nil, cobra.ShellCompDirectiveNoFileComp } - return getPods(toComplete, completeDefault) + return getPods(cmd, toComplete, completeDefault) } // AutocompletePodsRunning - Autocomplete only running pod names. @@ -323,7 +377,7 @@ func AutocompletePodsRunning(cmd *cobra.Command, args []string, toComplete strin if !validCurrentCmdLine(cmd, args, toComplete) { return nil, cobra.ShellCompDirectiveNoFileComp } - return getPods(toComplete, completeDefault, "running", "degraded") + return getPods(cmd, toComplete, completeDefault, "running", "degraded") } // AutocompleteContainersAndPods - Autocomplete container names and pod names. @@ -331,8 +385,8 @@ func AutocompleteContainersAndPods(cmd *cobra.Command, args []string, toComplete if !validCurrentCmdLine(cmd, args, toComplete) { return nil, cobra.ShellCompDirectiveNoFileComp } - containers, _ := getContainers(toComplete, completeDefault) - pods, _ := getPods(toComplete, completeDefault) + containers, _ := getContainers(cmd, toComplete, completeDefault) + pods, _ := getPods(cmd, toComplete, completeDefault) return append(containers, pods...), cobra.ShellCompDirectiveNoFileComp } @@ -341,8 +395,8 @@ func AutocompleteContainersAndImages(cmd *cobra.Command, args []string, toComple if !validCurrentCmdLine(cmd, args, toComplete) { return nil, cobra.ShellCompDirectiveNoFileComp } - containers, _ := getContainers(toComplete, completeDefault) - images, _ := getImages(toComplete) + containers, _ := getContainers(cmd, toComplete, completeDefault) + images, _ := getImages(cmd, toComplete) return append(containers, images...), cobra.ShellCompDirectiveNoFileComp } @@ -351,7 +405,7 @@ func AutocompleteVolumes(cmd *cobra.Command, args []string, toComplete string) ( if !validCurrentCmdLine(cmd, args, toComplete) { return nil, cobra.ShellCompDirectiveNoFileComp } - return getVolumes(toComplete) + return getVolumes(cmd, toComplete) } // AutocompleteImages - Autocomplete images. @@ -359,7 +413,7 @@ func AutocompleteImages(cmd *cobra.Command, args []string, toComplete string) ([ if !validCurrentCmdLine(cmd, args, toComplete) { return nil, cobra.ShellCompDirectiveNoFileComp } - return getImages(toComplete) + return getImages(cmd, toComplete) } // AutocompleteCreateRun - Autocomplete only the fist argument as image and then do file completion. @@ -368,7 +422,7 @@ func AutocompleteCreateRun(cmd *cobra.Command, args []string, toComplete string) return nil, cobra.ShellCompDirectiveNoFileComp } if len(args) < 1 { - return getImages(toComplete) + return getImages(cmd, toComplete) } // TODO: add path completion for files in the image return nil, cobra.ShellCompDirectiveDefault @@ -387,7 +441,7 @@ func AutocompleteNetworks(cmd *cobra.Command, args []string, toComplete string) if !validCurrentCmdLine(cmd, args, toComplete) { return nil, cobra.ShellCompDirectiveNoFileComp } - return getNetworks(toComplete) + return getNetworks(cmd, toComplete) } // AutocompleteCpCommand - Autocomplete podman cp command args. @@ -396,7 +450,7 @@ func AutocompleteCpCommand(cmd *cobra.Command, args []string, toComplete string) return nil, cobra.ShellCompDirectiveNoFileComp } if len(args) < 2 { - containers, _ := getContainers(toComplete, completeDefault) + containers, _ := getContainers(cmd, toComplete, completeDefault) for _, container := range containers { // TODO: Add path completion for inside the container if possible if strings.HasPrefix(container, toComplete) { @@ -464,7 +518,7 @@ func AutocompleteCreateAttach(cmd *cobra.Command, args []string, toComplete stri // -> host,container:[name],ns:[path],private func AutocompleteNamespace(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { kv := keyValueCompletion{ - "container:": func(s string) ([]string, cobra.ShellCompDirective) { return getContainers(s, completeDefault) }, + "container:": func(s string) ([]string, cobra.ShellCompDirective) { return getContainers(cmd, s, completeDefault) }, "ns:": func(s string) ([]string, cobra.ShellCompDirective) { return nil, cobra.ShellCompDirectiveDefault }, "host": nil, "private": nil, @@ -623,7 +677,7 @@ func AutocompleteMountFlag(cmd *cobra.Command, args []string, toComplete string) // AutocompleteVolumeFlag - Autocomplete volume flag options. // -> volumes and paths func AutocompleteVolumeFlag(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { - volumes, _ := getVolumes(toComplete) + volumes, _ := getVolumes(cmd, toComplete) directive := cobra.ShellCompDirectiveNoSpace | cobra.ShellCompDirectiveDefault if strings.Contains(toComplete, ":") { // add space after second path @@ -753,15 +807,15 @@ var containerStatuses = []string{"created", "running", "paused", "stopped", "exi // AutocompletePsFilters - Autocomplete ps filter options. func AutocompletePsFilters(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { kv := keyValueCompletion{ - "id=": func(s string) ([]string, cobra.ShellCompDirective) { return getContainers(s, completeIDs) }, - "name=": func(s string) ([]string, cobra.ShellCompDirective) { return getContainers(s, completeNames) }, + "id=": func(s string) ([]string, cobra.ShellCompDirective) { return getContainers(cmd, s, completeIDs) }, + "name=": func(s string) ([]string, cobra.ShellCompDirective) { return getContainers(cmd, s, completeNames) }, "status=": func(_ string) ([]string, cobra.ShellCompDirective) { return containerStatuses, cobra.ShellCompDirectiveNoFileComp }, - "ancestor": func(s string) ([]string, cobra.ShellCompDirective) { return getImages(s) }, - "before=": func(s string) ([]string, cobra.ShellCompDirective) { return getContainers(s, completeDefault) }, - "since=": func(s string) ([]string, cobra.ShellCompDirective) { return getContainers(s, completeDefault) }, - "volume=": func(s string) ([]string, cobra.ShellCompDirective) { return getVolumes(s) }, + "ancestor": func(s string) ([]string, cobra.ShellCompDirective) { return getImages(cmd, s) }, + "before=": func(s string) ([]string, cobra.ShellCompDirective) { return getContainers(cmd, s, completeDefault) }, + "since=": func(s string) ([]string, cobra.ShellCompDirective) { return getContainers(cmd, s, completeDefault) }, + "volume=": func(s string) ([]string, cobra.ShellCompDirective) { return getVolumes(cmd, s) }, "health=": func(_ string) ([]string, cobra.ShellCompDirective) { return []string{define.HealthCheckHealthy, define.HealthCheckUnhealthy}, cobra.ShellCompDirectiveNoFileComp @@ -776,14 +830,14 @@ func AutocompletePsFilters(cmd *cobra.Command, args []string, toComplete string) // AutocompletePodPsFilters - Autocomplete pod ps filter options. func AutocompletePodPsFilters(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { kv := keyValueCompletion{ - "id=": func(s string) ([]string, cobra.ShellCompDirective) { return getPods(s, completeIDs) }, - "name=": func(s string) ([]string, cobra.ShellCompDirective) { return getPods(s, completeNames) }, + "id=": func(s string) ([]string, cobra.ShellCompDirective) { return getPods(cmd, s, completeIDs) }, + "name=": func(s string) ([]string, cobra.ShellCompDirective) { return getPods(cmd, s, completeNames) }, "status=": func(_ string) ([]string, cobra.ShellCompDirective) { return []string{"stopped", "running", "paused", "exited", "dead", "created", "degraded"}, cobra.ShellCompDirectiveNoFileComp }, - "ctr-ids=": func(s string) ([]string, cobra.ShellCompDirective) { return getContainers(s, completeIDs) }, - "ctr-names=": func(s string) ([]string, cobra.ShellCompDirective) { return getContainers(s, completeNames) }, + "ctr-ids=": func(s string) ([]string, cobra.ShellCompDirective) { return getContainers(cmd, s, completeIDs) }, + "ctr-names=": func(s string) ([]string, cobra.ShellCompDirective) { return getContainers(cmd, s, completeNames) }, "ctr-number=": nil, "ctr-status=": func(_ string) ([]string, cobra.ShellCompDirective) { return containerStatuses, cobra.ShellCompDirectiveNoFileComp diff --git a/cmd/podman/root.go b/cmd/podman/root.go index 34d92cd0f..7840e6100 100644 --- a/cmd/podman/root.go +++ b/cmd/podman/root.go @@ -113,33 +113,9 @@ func persistentPreRunE(cmd *cobra.Command, args []string) error { return nil } - // Special case if command is hidden completion command ("__complete","__completeNoDesc") - // Since __completeNoDesc is an alias the cm.Name is always __complete - if cmd.Name() == cobra.ShellCompRequestCmd { - // Parse the cli arguments after the the completion cmd (always called as second argument) - // This ensures that the --url, --identity and --connection flags are properly set - compCmd, _, err := cmd.Root().Traverse(os.Args[2:]) - if err != nil { - return err - } - // If we don't complete the root cmd hide all root flags - // so they won't show up in the completions on subcommands. - if compCmd != compCmd.Root() { - compCmd.Root().Flags().VisitAll(func(flag *pflag.Flag) { - flag.Hidden = true - }) - } - // No need for further setup when completing commands with subcommands. - if compCmd.HasSubCommands() { - requireCleanup = false - return nil - } - } - cfg := registry.PodmanConfig() // --connection is not as "special" as --remote so we can wait and process it here - var connErr error conn := cmd.Root().LocalFlags().Lookup("connection") if conn != nil && conn.Changed { cfg.Engine.ActiveService = conn.Value.String() @@ -147,19 +123,37 @@ func persistentPreRunE(cmd *cobra.Command, args []string) error { var err error cfg.URI, cfg.Identity, err = cfg.ActiveDestination() if err != nil { - connErr = errors.Wrap(err, "failed to resolve active destination") + return errors.Wrap(err, "failed to resolve active destination") } if err := cmd.Root().LocalFlags().Set("url", cfg.URI); err != nil { - connErr = errors.Wrap(err, "failed to override --url flag") + return errors.Wrap(err, "failed to override --url flag") } if err := cmd.Root().LocalFlags().Set("identity", cfg.Identity); err != nil { - connErr = errors.Wrap(err, "failed to override --identity flag") + return errors.Wrap(err, "failed to override --identity flag") } } - if connErr != nil { - return connErr + + // Special case if command is hidden completion command ("__complete","__completeNoDesc") + // Since __completeNoDesc is an alias the cm.Name is always __complete + if cmd.Name() == cobra.ShellCompRequestCmd { + // Parse the cli arguments after the the completion cmd (always called as second argument) + // This ensures that the --url, --identity and --connection flags are properly set + compCmd, _, err := cmd.Root().Traverse(os.Args[2:]) + if err != nil { + return err + } + // If we don't complete the root cmd hide all root flags + // so they won't show up in the completions on subcommands. + if compCmd != compCmd.Root() { + compCmd.Root().Flags().VisitAll(func(flag *pflag.Flag) { + flag.Hidden = true + }) + } + // No need for further setup the completion logic setups the engines as needed. + requireCleanup = false + return nil } // Prep the engines -- cgit v1.2.3-54-g00ecf From c989c193bc78cb62c596677869bfa89d1b349bdc Mon Sep 17 00:00:00 2001 From: Petr Sakař Date: Sun, 22 Nov 2020 14:01:52 +0100 Subject: APIv2 - create container sets wrong entrypoint MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit use nil instead of empty string as default value for entrypoint in ContainerCLIOpts - empty string signifies user wants to override image entry point value Signed-off-by: Petr Sakař --- cmd/podman/common/create_opts.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'cmd/podman') diff --git a/cmd/podman/common/create_opts.go b/cmd/podman/common/create_opts.go index f34666fff..dc3202c7f 100644 --- a/cmd/podman/common/create_opts.go +++ b/cmd/podman/common/create_opts.go @@ -137,7 +137,7 @@ func ContainerCreateToContainerCLIOpts(cc handlers.CreateContainerConfig, cgroup aliases []string capAdd []string cappDrop []string - entrypoint string + entrypoint *string init bool specPorts []specgen.PortMapping ) @@ -181,13 +181,14 @@ func ContainerCreateToContainerCLIOpts(cc handlers.CreateContainerConfig, cgroup // marshall it to json; otherwise it should just be the string // value if len(cc.Config.Entrypoint) > 0 { - entrypoint = cc.Config.Entrypoint[0] + entrypoint = &cc.Config.Entrypoint[0] if len(cc.Config.Entrypoint) > 1 { b, err := json.Marshal(cc.Config.Entrypoint) if err != nil { return nil, nil, err } - entrypoint = string(b) + var jsonString = string(b) + entrypoint = &jsonString } } @@ -322,7 +323,7 @@ func ContainerCreateToContainerCLIOpts(cc handlers.CreateContainerConfig, cgroup DeviceReadIOPs: readIops, DeviceWriteBPs: writeBps, DeviceWriteIOPs: writeIops, - Entrypoint: &entrypoint, + Entrypoint: entrypoint, Env: cc.Config.Env, Expose: expose, GroupAdd: cc.HostConfig.GroupAdd, -- cgit v1.2.3-54-g00ecf From 5d9647a12094c953436b1f9af44f88e53db4af16 Mon Sep 17 00:00:00 2001 From: Valentin Rothberg Date: Mon, 23 Nov 2020 10:35:52 +0100 Subject: clarify ps(1) fallback of `podman top` Podman top falls back to executing ps(1) inside the container in the presence of ps-specific flags. Clarify that a bit more to help users resolve issues when, for instance, ps(1) isn't installed in the container. Signed-off-by: Valentin Rothberg --- cmd/podman/containers/top.go | 8 +++----- docs/source/markdown/podman-top.1.md | 2 +- 2 files changed, 4 insertions(+), 6 deletions(-) (limited to 'cmd/podman') diff --git a/cmd/podman/containers/top.go b/cmd/podman/containers/top.go index 3eb6d2af2..f00b1dce1 100644 --- a/cmd/podman/containers/top.go +++ b/cmd/podman/containers/top.go @@ -18,12 +18,10 @@ import ( ) var ( - topDescription = `Similar to system "top" command. - - Specify format descriptors to alter the output. - - Running "podman top -l pid pcpu seccomp" will print the process ID, the CPU percentage and the seccomp mode of each process of the latest container.` + topDescription = `Display the running processes of a container. + The top command extends the ps(1) compatible AIX descriptors with container-specific ones as shown below. In the presence of ps(1) specific flags (e.g, -eo), Podman will execute ps(1) inside the container. +` topOptions = entities.TopOptions{} topCommand = &cobra.Command{ diff --git a/docs/source/markdown/podman-top.1.md b/docs/source/markdown/podman-top.1.md index f307f96da..cfb89567c 100644 --- a/docs/source/markdown/podman-top.1.md +++ b/docs/source/markdown/podman-top.1.md @@ -9,7 +9,7 @@ podman\-top - Display the running processes of a container **podman container top** [*options*] *container* [*format-descriptors*] ## DESCRIPTION -Display the running processes of the container. The *format-descriptors* are ps (1) compatible AIX format descriptors but extended to print additional information, such as the seccomp mode or the effective capabilities of a given process. The descriptors can either be passed as separated arguments or as a single comma-separated argument. Note that you can also specify options and or flags of ps(1); in this case, Podman will fallback to executing ps with the specified arguments and flags in the container. +Display the running processes of the container. The *format-descriptors* are ps (1) compatible AIX format descriptors but extended to print additional information, such as the seccomp mode or the effective capabilities of a given process. The descriptors can either be passed as separated arguments or as a single comma-separated argument. Note that you can also specify options and or flags of ps(1); in this case, Podman will fallback to executing ps with the specified arguments and flags in the container. Please use the "h*" descriptors if you want to extract host-related information. For instance, `podman top $name hpid huser` to display the PID and user of the processes in the host context. ## OPTIONS -- cgit v1.2.3-54-g00ecf From c901a766fb66564df24538f761f895212168c923 Mon Sep 17 00:00:00 2001 From: Paul Holzinger Date: Sun, 22 Nov 2020 23:55:25 +0100 Subject: Add podman container ps command This command exists in docker and is also in our documentation. Also remove mentions of `podman ls` or `podman list`. These commands do not exists in podman or docker. Signed-off-by: Paul Holzinger --- cmd/podman/containers/ps.go | 20 +++++++++++++++++++- docs/source/managecontainers.rst | 2 ++ docs/source/markdown/links/podman-list.1 | 1 - docs/source/markdown/links/podman-ls.1 | 1 - docs/source/markdown/podman-container.1.md | 1 + docs/source/markdown/podman-ps.1.md | 8 ++------ test/e2e/ps_test.go | 6 ++++++ 7 files changed, 30 insertions(+), 9 deletions(-) delete mode 100644 docs/source/markdown/links/podman-list.1 delete mode 100644 docs/source/markdown/links/podman-ls.1 (limited to 'cmd/podman') diff --git a/cmd/podman/containers/ps.go b/cmd/podman/containers/ps.go index a1a41ae08..6f84cf9b8 100644 --- a/cmd/podman/containers/ps.go +++ b/cmd/podman/containers/ps.go @@ -29,15 +29,25 @@ var ( psDescription = "Prints out information about the containers" psCommand = &cobra.Command{ Use: "ps [options]", - Args: validate.NoArgs, Short: "List containers", Long: psDescription, RunE: ps, + Args: validate.NoArgs, ValidArgsFunction: completion.AutocompleteNone, Example: `podman ps -a podman ps -a --format "{{.ID}} {{.Image}} {{.Labels}} {{.Mounts}}" podman ps --size --sort names`, } + + psContainerCommand = &cobra.Command{ + Use: psCommand.Use, + Short: psCommand.Short, + Long: psCommand.Long, + RunE: psCommand.RunE, + Args: psCommand.Args, + ValidArgsFunction: psCommand.ValidArgsFunction, + Example: strings.ReplaceAll(psCommand.Example, "podman ps", "podman container ps"), + } ) var ( listOpts = entities.ContainerListOptions{ @@ -54,6 +64,14 @@ func init() { }) listFlagSet(psCommand) validate.AddLatestFlag(psCommand, &listOpts.Latest) + + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: psContainerCommand, + Parent: containerCmd, + }) + listFlagSet(psContainerCommand) + validate.AddLatestFlag(psContainerCommand, &listOpts.Latest) } func listFlagSet(cmd *cobra.Command) { diff --git a/docs/source/managecontainers.rst b/docs/source/managecontainers.rst index 849fd1d25..9926f9996 100644 --- a/docs/source/managecontainers.rst +++ b/docs/source/managecontainers.rst @@ -39,6 +39,8 @@ Manage Containers :doc:`prune ` Remove all stopped containers +:doc:`ps ` List containers + :doc:`restart ` Restart one or more containers :doc:`restore ` Restores one or more containers from a checkpoint diff --git a/docs/source/markdown/links/podman-list.1 b/docs/source/markdown/links/podman-list.1 deleted file mode 100644 index f7f44c704..000000000 --- a/docs/source/markdown/links/podman-list.1 +++ /dev/null @@ -1 +0,0 @@ -.so man1/podman-ps.1 diff --git a/docs/source/markdown/links/podman-ls.1 b/docs/source/markdown/links/podman-ls.1 deleted file mode 100644 index f7f44c704..000000000 --- a/docs/source/markdown/links/podman-ls.1 +++ /dev/null @@ -1 +0,0 @@ -.so man1/podman-ps.1 diff --git a/docs/source/markdown/podman-container.1.md b/docs/source/markdown/podman-container.1.md index 0a6ceea33..9da5db601 100644 --- a/docs/source/markdown/podman-container.1.md +++ b/docs/source/markdown/podman-container.1.md @@ -32,6 +32,7 @@ The container command allows you to manage containers | pause | [podman-pause(1)](podman-pause.1.md) | Pause one or more containers. | | port | [podman-port(1)](podman-port.1.md) | List port mappings for the container. | | prune | [podman-container-prune(1)](podman-container-prune.1.md)| Remove all stopped containers from local storage. | +| ps | [podman-ps(1)](podman-ps.1.md) | Prints out information about containers. | | restart | [podman-restart(1)](podman-restart.1.md) | Restart one or more containers. | | restore | [podman-container-restore(1)](podman-container-restore.1.md) | Restores one or more containers from a checkpoint. | | rm | [podman-rm(1)](podman-rm.1.md) | Remove one or more containers. | diff --git a/docs/source/markdown/podman-ps.1.md b/docs/source/markdown/podman-ps.1.md index f542daf4c..b94964f6c 100644 --- a/docs/source/markdown/podman-ps.1.md +++ b/docs/source/markdown/podman-ps.1.md @@ -6,15 +6,11 @@ podman\-ps - Prints out information about containers ## SYNOPSIS **podman ps** [*options*] -**podman container list** [*options*] - -**podman container ls** [*options*] - **podman container ps** [*options*] -**podman list** [*options*] +**podman container list** [*options*] -**podman ls** [*options*] +**podman container ls** [*options*] ## DESCRIPTION **podman ps** lists the running containers on the system. Use the **--all** flag to view diff --git a/test/e2e/ps_test.go b/test/e2e/ps_test.go index fd08d4308..05571157c 100644 --- a/test/e2e/ps_test.go +++ b/test/e2e/ps_test.go @@ -44,6 +44,12 @@ var _ = Describe("Podman ps", func() { Expect(session.ExitCode()).To(Equal(0)) }) + It("podman container ps no containers", func() { + session := podmanTest.Podman([]string{"container", "ps"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + }) + It("podman ps default", func() { session := podmanTest.RunTopContainer("") session.WaitWithDefaultTimeout() -- cgit v1.2.3-54-g00ecf From 2e8ed5edf720f2ec7ed641ae92ee368d0ffe9cf2 Mon Sep 17 00:00:00 2001 From: Paul Holzinger Date: Thu, 19 Nov 2020 21:11:29 +0100 Subject: more shell completion improvements * podman image ls --filter * podman network ls --filter * podman volume ls --filter * podman network connect/disconnect * podman events --filter Signed-off-by: Paul Holzinger --- cmd/podman/common/completion.go | 74 +++++++++++++++++++++++++++++++++++++-- cmd/podman/images/list.go | 3 +- cmd/podman/networks/connect.go | 2 +- cmd/podman/networks/disconnect.go | 2 +- cmd/podman/networks/list.go | 2 +- cmd/podman/volumes/list.go | 2 +- 6 files changed, 77 insertions(+), 8 deletions(-) (limited to 'cmd/podman') diff --git a/cmd/podman/common/completion.go b/cmd/podman/common/completion.go index c96f7436d..d09c61a47 100644 --- a/cmd/podman/common/completion.go +++ b/cmd/podman/common/completion.go @@ -464,6 +464,18 @@ func AutocompleteCpCommand(cmd *cobra.Command, args []string, toComplete string) return nil, cobra.ShellCompDirectiveNoFileComp } +// AutocompleteNetworkConnectCmd - Autocomplete podman network connect/disconnect command args. +func AutocompleteNetworkConnectCmd(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + if len(args) == 0 { + return getNetworks(cmd, toComplete) + } + if len(args) == 1 { + return getContainers(cmd, toComplete, completeDefault) + } + // don't complete more than 2 args + return nil, cobra.ShellCompDirectiveNoFileComp +} + // AutocompleteSystemConnections - Autocomplete system connections. func AutocompleteSystemConnections(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { if !validCurrentCmdLine(cmd, args, toComplete) { @@ -695,8 +707,22 @@ func AutocompleteJSONFormat(cmd *cobra.Command, args []string, toComplete string // AutocompleteEventFilter - Autocomplete event filter flag options. // -> "container=", "event=", "image=", "pod=", "volume=", "type=" func AutocompleteEventFilter(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { - filters := []string{"container=", "event=", "image=", "pod=", "volume=", "type="} - return filters, cobra.ShellCompDirectiveNoSpace + eventTypes := func(_ string) ([]string, cobra.ShellCompDirective) { + return []string{"attach", "checkpoint", "cleanup", "commit", "create", "exec", + "export", "import", "init", "kill", "mount", "pause", "prune", "remove", + "restart", "restore", "start", "stop", "sync", "unmount", "unpause", + "pull", "push", "save", "tag", "untag", "refresh", "renumber", + }, cobra.ShellCompDirectiveNoFileComp + } + kv := keyValueCompletion{ + "container=": func(s string) ([]string, cobra.ShellCompDirective) { return getContainers(cmd, s, completeDefault) }, + "image=": func(s string) ([]string, cobra.ShellCompDirective) { return getImages(cmd, s) }, + "pod=": func(s string) ([]string, cobra.ShellCompDirective) { return getPods(cmd, s, completeDefault) }, + "volume=": func(s string) ([]string, cobra.ShellCompDirective) { return getVolumes(cmd, s) }, + "event=": eventTypes, + "type=": eventTypes, + } + return completeKeyValues(toComplete, kv) } // AutocompleteSystemdRestartOptions - Autocomplete systemd restart options. @@ -846,3 +872,47 @@ func AutocompletePodPsFilters(cmd *cobra.Command, args []string, toComplete stri } return completeKeyValues(toComplete, kv) } + +// AutocompleteImageFilters - Autocomplete image ls --filter options. +func AutocompleteImageFilters(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + getBool := func(_ string) ([]string, cobra.ShellCompDirective) { + return []string{"true", "false"}, cobra.ShellCompDirectiveNoFileComp + } + getImg := func(s string) ([]string, cobra.ShellCompDirective) { return getImages(cmd, s) } + kv := keyValueCompletion{ + "before=": getImg, + "since=": getImg, + "label=": nil, + "reference=": nil, + "dangling=": getBool, + "readonly=": getBool, + } + return completeKeyValues(toComplete, kv) +} + +// AutocompleteNetworkFilters - Autocomplete network ls --filter options. +func AutocompleteNetworkFilters(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + kv := keyValueCompletion{ + "name=": func(s string) ([]string, cobra.ShellCompDirective) { return getNetworks(cmd, s) }, + "plugin=": nil, + } + return completeKeyValues(toComplete, kv) +} + +// AutocompleteVolumeFilters - Autocomplete volume ls --filter options. +func AutocompleteVolumeFilters(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + local := func(_ string) ([]string, cobra.ShellCompDirective) { + return []string{"local"}, cobra.ShellCompDirectiveNoFileComp + } + kv := keyValueCompletion{ + "name=": func(s string) ([]string, cobra.ShellCompDirective) { return getVolumes(cmd, s) }, + "driver=": local, + "scope=": local, + "label=": nil, + "opt=": nil, + "dangling=": func(_ string) ([]string, cobra.ShellCompDirective) { + return []string{"true", "false"}, cobra.ShellCompDirectiveNoFileComp + }, + } + return completeKeyValues(toComplete, kv) +} diff --git a/cmd/podman/images/list.go b/cmd/podman/images/list.go index 4692699f2..bcb31e6ee 100644 --- a/cmd/podman/images/list.go +++ b/cmd/podman/images/list.go @@ -79,8 +79,7 @@ func imageListFlagSet(cmd *cobra.Command) { filterFlagName := "filter" flags.StringSliceVarP(&listOptions.Filter, filterFlagName, "f", []string{}, "Filter output based on conditions provided (default [])") - // TODO: add completion function for filters - _ = cmd.RegisterFlagCompletionFunc(filterFlagName, completion.AutocompleteNone) + _ = cmd.RegisterFlagCompletionFunc(filterFlagName, common.AutocompleteImageFilters) formatFlagName := "format" flags.StringVar(&listFlag.format, formatFlagName, "", "Change the output format to JSON or a Go template") diff --git a/cmd/podman/networks/connect.go b/cmd/podman/networks/connect.go index a7636688c..8afc0c7c0 100644 --- a/cmd/podman/networks/connect.go +++ b/cmd/podman/networks/connect.go @@ -17,7 +17,7 @@ var ( RunE: networkConnect, Example: `podman network connect web secondary`, Args: cobra.ExactArgs(2), - ValidArgsFunction: common.AutocompleteNetworks, + ValidArgsFunction: common.AutocompleteNetworkConnectCmd, } ) diff --git a/cmd/podman/networks/disconnect.go b/cmd/podman/networks/disconnect.go index 598c23a1c..a30315774 100644 --- a/cmd/podman/networks/disconnect.go +++ b/cmd/podman/networks/disconnect.go @@ -17,7 +17,7 @@ var ( RunE: networkDisconnect, Example: `podman network disconnect web secondary`, Args: cobra.ExactArgs(2), - ValidArgsFunction: common.AutocompleteNetworks, + ValidArgsFunction: common.AutocompleteNetworkConnectCmd, } ) diff --git a/cmd/podman/networks/list.go b/cmd/podman/networks/list.go index f2a5a431a..dcba3f186 100644 --- a/cmd/podman/networks/list.go +++ b/cmd/podman/networks/list.go @@ -46,7 +46,7 @@ func networkListFlags(flags *pflag.FlagSet) { filterFlagName := "filter" flags.StringVarP(&networkListOptions.Filter, filterFlagName, "", "", "Provide filter values (e.g. 'name=podman')") - _ = networklistCommand.RegisterFlagCompletionFunc(filterFlagName, completion.AutocompleteNone) + _ = networklistCommand.RegisterFlagCompletionFunc(filterFlagName, common.AutocompleteNetworkFilters) } diff --git a/cmd/podman/volumes/list.go b/cmd/podman/volumes/list.go index b0d999765..7e54de38a 100644 --- a/cmd/podman/volumes/list.go +++ b/cmd/podman/volumes/list.go @@ -56,7 +56,7 @@ func init() { filterFlagName := "filter" flags.StringSliceVarP(&cliOpts.Filter, filterFlagName, "f", []string{}, "Filter volume output") - _ = lsCommand.RegisterFlagCompletionFunc(filterFlagName, completion.AutocompleteNone) + _ = lsCommand.RegisterFlagCompletionFunc(filterFlagName, common.AutocompleteVolumeFilters) formatFlagName := "format" flags.StringVar(&cliOpts.Format, formatFlagName, "{{.Driver}}\t{{.Name}}\n", "Format volume output using Go template") -- cgit v1.2.3-54-g00ecf From 818162155a092506403d5a27ad1eb3717de37078 Mon Sep 17 00:00:00 2001 From: Paul Holzinger Date: Thu, 19 Nov 2020 23:11:47 +0100 Subject: shell completions: remove usage of ShellCompDirectiveError If we return `ShellCompDirectiveError` to the shell the shell will provide path completion. In none of that cases we want path completion so it will be better to return `ShellCompDirectiveNoFileComp` instead and log the error in case we need it. Signed-off-by: Paul Holzinger --- cmd/podman/common/completion.go | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) (limited to 'cmd/podman') diff --git a/cmd/podman/common/completion.go b/cmd/podman/common/completion.go index d09c61a47..6723cbf62 100644 --- a/cmd/podman/common/completion.go +++ b/cmd/podman/common/completion.go @@ -84,7 +84,7 @@ func getContainers(cmd *cobra.Command, toComplete string, cType completeType, st containers, err := engine.ContainerList(registry.GetContext(), listOpts) if err != nil { cobra.CompErrorln(err.Error()) - return nil, cobra.ShellCompDirectiveError + return nil, cobra.ShellCompDirectiveNoFileComp } for _, c := range containers { @@ -119,7 +119,7 @@ func getPods(cmd *cobra.Command, toComplete string, cType completeType, statuses pods, err := engine.PodPs(registry.GetContext(), listOpts) if err != nil { cobra.CompErrorln(err.Error()) - return nil, cobra.ShellCompDirectiveError + return nil, cobra.ShellCompDirectiveNoFileComp } for _, pod := range pods { @@ -149,7 +149,7 @@ func getVolumes(cmd *cobra.Command, toComplete string) ([]string, cobra.ShellCom volumes, err := engine.VolumeList(registry.GetContext(), lsOpts) if err != nil { cobra.CompErrorln(err.Error()) - return nil, cobra.ShellCompDirectiveError + return nil, cobra.ShellCompDirectiveNoFileComp } for _, v := range volumes { @@ -172,7 +172,7 @@ func getImages(cmd *cobra.Command, toComplete string) ([]string, cobra.ShellComp images, err := engine.List(registry.GetContext(), listOptions) if err != nil { cobra.CompErrorln(err.Error()) - return nil, cobra.ShellCompDirectiveError + return nil, cobra.ShellCompDirectiveNoFileComp } for _, image := range images { @@ -215,7 +215,7 @@ func getRegistries() ([]string, cobra.ShellCompDirective) { regs, err := registries.GetRegistries() if err != nil { cobra.CompErrorln(err.Error()) - return nil, cobra.ShellCompDirectiveError + return nil, cobra.ShellCompDirectiveNoFileComp } return regs, cobra.ShellCompDirectiveNoFileComp } @@ -232,7 +232,7 @@ func getNetworks(cmd *cobra.Command, toComplete string) ([]string, cobra.ShellCo networks, err := engine.NetworkList(registry.Context(), networkListOptions) if err != nil { cobra.CompErrorln(err.Error()) - return nil, cobra.ShellCompDirectiveError + return nil, cobra.ShellCompDirectiveNoFileComp } for _, n := range networks { @@ -485,7 +485,7 @@ func AutocompleteSystemConnections(cmd *cobra.Command, args []string, toComplete cfg, err := config.ReadCustomConfig() if err != nil { cobra.CompErrorln(err.Error()) - return nil, cobra.ShellCompDirectiveError + return nil, cobra.ShellCompDirectiveNoFileComp } for k, v := range cfg.Engine.ServiceDestinations { @@ -633,7 +633,8 @@ func AutocompleteUserFlag(cmd *cobra.Command, args []string, toComplete string) // but at this point we don't know the image. file, err := os.Open("/etc/group") if err != nil { - return nil, cobra.ShellCompDirectiveError + cobra.CompErrorln(err.Error()) + return nil, cobra.ShellCompDirectiveNoFileComp } defer file.Close() @@ -649,7 +650,8 @@ func AutocompleteUserFlag(cmd *cobra.Command, args []string, toComplete string) } } if err = scanner.Err(); err != nil { - return nil, cobra.ShellCompDirectiveError + cobra.CompErrorln(err.Error()) + return nil, cobra.ShellCompDirectiveNoFileComp } return groups, cobra.ShellCompDirectiveNoFileComp } @@ -658,7 +660,8 @@ func AutocompleteUserFlag(cmd *cobra.Command, args []string, toComplete string) // but at this point we don't know the image. file, err := os.Open("/etc/passwd") if err != nil { - return nil, cobra.ShellCompDirectiveError + cobra.CompErrorln(err.Error()) + return nil, cobra.ShellCompDirectiveNoFileComp } defer file.Close() @@ -673,7 +676,7 @@ func AutocompleteUserFlag(cmd *cobra.Command, args []string, toComplete string) } } if err = scanner.Err(); err != nil { - return nil, cobra.ShellCompDirectiveError + return nil, cobra.ShellCompDirectiveNoFileComp } return users, cobra.ShellCompDirectiveNoSpace } -- cgit v1.2.3-54-g00ecf From 44da01f45cd941f44d2025864c91b0d2942d2a20 Mon Sep 17 00:00:00 2001 From: Jhon Honce Date: Mon, 16 Nov 2020 16:11:31 -0700 Subject: Refactor compat container create endpoint * Make endpoint compatibile with docker-py network expectations * Update specgen helper when called from compat endpoint * Update godoc on types * Add test for network/container create using docker-py method * Add syslog logging when DEBUG=1 for tests Fixes #8361 Signed-off-by: Jhon Honce --- cmd/podman/common/create_opts.go | 80 +++++++++++++++---------- libpod/networking_linux.go | 2 +- pkg/api/handlers/compat/containers_create.go | 21 ++++--- pkg/api/handlers/types.go | 13 ++-- test/apiv2/rest_api/__init__.py | 15 ++--- test/apiv2/rest_api/test_rest_v2_0_0.py | 90 ++++++++++++++++++++++------ test/apiv2/rest_api/v1_test_rest_v1_0_0.py | 4 +- test/python/docker/__init__.py | 12 +--- test/python/docker/common.py | 4 +- test/python/docker/test_images.py | 4 +- 10 files changed, 151 insertions(+), 94 deletions(-) (limited to 'cmd/podman') diff --git a/cmd/podman/common/create_opts.go b/cmd/podman/common/create_opts.go index dc3202c7f..6dc43dbc6 100644 --- a/cmd/podman/common/create_opts.go +++ b/cmd/podman/common/create_opts.go @@ -134,7 +134,6 @@ func stringMaptoArray(m map[string]string) []string { // a specgen spec. func ContainerCreateToContainerCLIOpts(cc handlers.CreateContainerConfig, cgroupsManager string) (*ContainerCLIOpts, []string, error) { var ( - aliases []string capAdd []string cappDrop []string entrypoint *string @@ -212,7 +211,7 @@ func ContainerCreateToContainerCLIOpts(cc handlers.CreateContainerConfig, cgroup mounts = append(mounts, mount) } - //volumes + // volumes volumes := make([]string, 0, len(cc.Config.Volumes)) for v := range cc.Config.Volumes { volumes = append(volumes, v) @@ -242,16 +241,6 @@ func ContainerCreateToContainerCLIOpts(cc handlers.CreateContainerConfig, cgroup } } - // network names - endpointsConfig := cc.NetworkingConfig.EndpointsConfig - cniNetworks := make([]string, 0, len(endpointsConfig)) - for netName, endpoint := range endpointsConfig { - cniNetworks = append(cniNetworks, netName) - if len(endpoint.Aliases) > 0 { - aliases = append(aliases, endpoint.Aliases...) - } - } - // netMode nsmode, _, err := specgen.ParseNetworkNamespace(cc.HostConfig.NetworkMode.NetworkName()) if err != nil { @@ -268,8 +257,6 @@ func ContainerCreateToContainerCLIOpts(cc handlers.CreateContainerConfig, cgroup // defined when there is only one network. netInfo := entities.NetOptions{ AddHosts: cc.HostConfig.ExtraHosts, - Aliases: aliases, - CNINetworks: cniNetworks, DNSOptions: cc.HostConfig.DNSOptions, DNSSearch: cc.HostConfig.DNSSearch, DNSServers: dns, @@ -277,31 +264,58 @@ func ContainerCreateToContainerCLIOpts(cc handlers.CreateContainerConfig, cgroup PublishPorts: specPorts, } - // static IP and MAC - if len(endpointsConfig) == 1 { - for _, ep := range endpointsConfig { - // if IP address is provided - if len(ep.IPAddress) > 0 { - staticIP := net.ParseIP(ep.IPAddress) - netInfo.StaticIP = &staticIP + // network names + switch { + case len(cc.NetworkingConfig.EndpointsConfig) > 0: + var aliases []string + + endpointsConfig := cc.NetworkingConfig.EndpointsConfig + cniNetworks := make([]string, 0, len(endpointsConfig)) + for netName, endpoint := range endpointsConfig { + + cniNetworks = append(cniNetworks, netName) + + if endpoint == nil { + continue + } + if len(endpoint.Aliases) > 0 { + aliases = append(aliases, endpoint.Aliases...) } - // If MAC address is provided - if len(ep.MacAddress) > 0 { - staticMac, err := net.ParseMAC(ep.MacAddress) - if err != nil { - return nil, nil, err + } + + // static IP and MAC + if len(endpointsConfig) == 1 { + for _, ep := range endpointsConfig { + if ep == nil { + continue + } + // if IP address is provided + if len(ep.IPAddress) > 0 { + staticIP := net.ParseIP(ep.IPAddress) + netInfo.StaticIP = &staticIP + } + // If MAC address is provided + if len(ep.MacAddress) > 0 { + staticMac, err := net.ParseMAC(ep.MacAddress) + if err != nil { + return nil, nil, err + } + netInfo.StaticMAC = &staticMac } - netInfo.StaticMAC = &staticMac + break } - break } + netInfo.Aliases = aliases + netInfo.CNINetworks = cniNetworks + case len(cc.HostConfig.NetworkMode) > 0: + netInfo.CNINetworks = []string{string(cc.HostConfig.NetworkMode)} } // Note: several options here are marked as "don't need". this is based // on speculation by Matt and I. We think that these come into play later // like with start. We believe this is just a difference in podman/compat cliOpts := ContainerCLIOpts{ - //Attach: nil, // dont need? + // Attach: nil, // dont need? Authfile: "", CapAdd: append(capAdd, cc.HostConfig.CapAdd...), CapDrop: append(cappDrop, cc.HostConfig.CapDrop...), @@ -312,11 +326,11 @@ func ContainerCreateToContainerCLIOpts(cc handlers.CreateContainerConfig, cgroup CPURTPeriod: uint64(cc.HostConfig.CPURealtimePeriod), CPURTRuntime: cc.HostConfig.CPURealtimeRuntime, CPUShares: uint64(cc.HostConfig.CPUShares), - //CPUS: 0, // dont need? + // CPUS: 0, // dont need? CPUSetCPUs: cc.HostConfig.CpusetCpus, CPUSetMems: cc.HostConfig.CpusetMems, - //Detach: false, // dont need - //DetachKeys: "", // dont need + // Detach: false, // dont need + // DetachKeys: "", // dont need Devices: devices, DeviceCGroupRule: nil, DeviceReadBPs: readBps, @@ -438,7 +452,7 @@ func ContainerCreateToContainerCLIOpts(cc handlers.CreateContainerConfig, cgroup } // specgen assumes the image name is arg[0] - cmd := []string{cc.Image} + cmd := []string{cc.Config.Image} cmd = append(cmd, cc.Config.Cmd...) return &cliOpts, cmd, nil } diff --git a/libpod/networking_linux.go b/libpod/networking_linux.go index 7a0bebd95..baa11935d 100644 --- a/libpod/networking_linux.go +++ b/libpod/networking_linux.go @@ -900,7 +900,7 @@ func (c *Container) getContainerNetworkInfo() (*define.InspectNetworkSettings, e // If we have CNI networks - handle that here if len(networks) > 0 && !isDefault { if len(networks) != len(c.state.NetworkStatus) { - return nil, errors.Wrapf(define.ErrInternal, "network inspection mismatch: asked to join %d CNI networks but have information on %d networks", len(networks), len(c.state.NetworkStatus)) + return nil, errors.Wrapf(define.ErrInternal, "network inspection mismatch: asked to join %d CNI network(s) %v, but have information on %d network(s)", len(networks), networks, len(c.state.NetworkStatus)) } settings.Networks = make(map[string]*define.InspectAdditionalNetwork) diff --git a/pkg/api/handlers/compat/containers_create.go b/pkg/api/handlers/compat/containers_create.go index 4efe770b3..729639928 100644 --- a/pkg/api/handlers/compat/containers_create.go +++ b/pkg/api/handlers/compat/containers_create.go @@ -19,7 +19,6 @@ import ( func CreateContainer(w http.ResponseWriter, r *http.Request) { runtime := r.Context().Value("runtime").(*libpod.Runtime) decoder := r.Context().Value("decoder").(*schema.Decoder) - input := handlers.CreateContainerConfig{} query := struct { Name string `schema:"name"` }{ @@ -30,11 +29,15 @@ func CreateContainer(w http.ResponseWriter, r *http.Request) { errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) return } - if err := json.NewDecoder(r.Body).Decode(&input); err != nil { + + // compatible configuration + body := handlers.CreateContainerConfig{} + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Decode()")) return } - if len(input.HostConfig.Links) > 0 { + + if len(body.HostConfig.Links) > 0 { utils.Error(w, utils.ErrLinkNotSupport.Error(), http.StatusBadRequest, errors.Wrapf(utils.ErrLinkNotSupport, "bad parameter")) return } @@ -43,7 +46,7 @@ func CreateContainer(w http.ResponseWriter, r *http.Request) { utils.Error(w, "unable to obtain runtime config", http.StatusInternalServerError, errors.Wrap(err, "unable to get runtime config")) } - newImage, err := runtime.ImageRuntime().NewFromLocal(input.Image) + newImage, err := runtime.ImageRuntime().NewFromLocal(body.Config.Image) if err != nil { if errors.Cause(err) == define.ErrNoSuchImage { utils.Error(w, "No such image", http.StatusNotFound, err) @@ -54,11 +57,8 @@ func CreateContainer(w http.ResponseWriter, r *http.Request) { return } - // Add the container name to the input struct - input.Name = query.Name - - // Take input structure and convert to cliopts - cliOpts, args, err := common.ContainerCreateToContainerCLIOpts(input, rtc.Engine.CgroupManager) + // Take body structure and convert to cliopts + cliOpts, args, err := common.ContainerCreateToContainerCLIOpts(body, rtc.Engine.CgroupManager) if err != nil { utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "make cli opts()")) return @@ -69,6 +69,9 @@ func CreateContainer(w http.ResponseWriter, r *http.Request) { return } + // Override the container name in the body struct + body.Name = query.Name + ic := abi.ContainerEngine{Libpod: runtime} report, err := ic.ContainerCreate(r.Context(), sg) if err != nil { diff --git a/pkg/api/handlers/types.go b/pkg/api/handlers/types.go index 6bb5f5101..40cf16807 100644 --- a/pkg/api/handlers/types.go +++ b/pkg/api/handlers/types.go @@ -110,11 +110,12 @@ type ContainerWaitOKBody struct { } } +// CreateContainerConfig used when compatible endpoint creates a container type CreateContainerConfig struct { - Name string - dockerContainer.Config - HostConfig dockerContainer.HostConfig - NetworkingConfig dockerNetwork.NetworkingConfig + Name string // container name + dockerContainer.Config // desired container configuration + HostConfig dockerContainer.HostConfig // host dependent configuration for container + NetworkingConfig dockerNetwork.NetworkingConfig // network configuration for container } // swagger:model IDResponse @@ -253,7 +254,7 @@ func ImageDataToImageInspect(ctx context.Context, l *libpodImage.Image) (*ImageI // StdinOnce: false, Env: info.Config.Env, Cmd: info.Config.Cmd, - //Healthcheck: l.ImageData.HealthCheck, + // Healthcheck: l.ImageData.HealthCheck, // ArgsEscaped: false, // Image: "", Volumes: info.Config.Volumes, @@ -261,7 +262,7 @@ func ImageDataToImageInspect(ctx context.Context, l *libpodImage.Image) (*ImageI Entrypoint: info.Config.Entrypoint, // NetworkDisabled: false, // MacAddress: "", - //OnBuild: info.Config.OnBuild, + // OnBuild: info.Config.OnBuild, Labels: info.Labels, StopSignal: info.Config.StopSignal, // StopTimeout: nil, diff --git a/test/apiv2/rest_api/__init__.py b/test/apiv2/rest_api/__init__.py index 8100a4df5..db0257f03 100644 --- a/test/apiv2/rest_api/__init__.py +++ b/test/apiv2/rest_api/__init__.py @@ -16,19 +16,18 @@ class Podman(object): binary = os.getenv("PODMAN", "bin/podman") self.cmd = [binary, "--storage-driver=vfs"] - cgroupfs = os.getenv("CGROUP_MANAGER", "cgroupfs") + cgroupfs = os.getenv("CGROUP_MANAGER", "systemd") self.cmd.append(f"--cgroup-manager={cgroupfs}") if os.getenv("DEBUG"): self.cmd.append("--log-level=debug") + self.cmd.append("--syslog=true") self.anchor_directory = tempfile.mkdtemp(prefix="podman_restapi_") self.cmd.append("--root=" + os.path.join(self.anchor_directory, "crio")) self.cmd.append("--runroot=" + os.path.join(self.anchor_directory, "crio-run")) - os.environ["REGISTRIES_CONFIG_PATH"] = os.path.join( - self.anchor_directory, "registry.conf" - ) + os.environ["REGISTRIES_CONFIG_PATH"] = os.path.join(self.anchor_directory, "registry.conf") p = configparser.ConfigParser() p.read_dict( { @@ -40,14 +39,10 @@ class Podman(object): with open(os.environ["REGISTRIES_CONFIG_PATH"], "w") as w: p.write(w) - os.environ["CNI_CONFIG_PATH"] = os.path.join( - self.anchor_directory, "cni", "net.d" - ) + os.environ["CNI_CONFIG_PATH"] = os.path.join(self.anchor_directory, "cni", "net.d") os.makedirs(os.environ["CNI_CONFIG_PATH"], exist_ok=True) self.cmd.append("--cni-config-dir=" + os.environ["CNI_CONFIG_PATH"]) - cni_cfg = os.path.join( - os.environ["CNI_CONFIG_PATH"], "87-podman-bridge.conflist" - ) + cni_cfg = os.path.join(os.environ["CNI_CONFIG_PATH"], "87-podman-bridge.conflist") # json decoded and encoded to ensure legal json buf = json.loads( """ 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 49e18f063..52348d4f4 100644 --- a/test/apiv2/rest_api/test_rest_v2_0_0.py +++ b/test/apiv2/rest_api/test_rest_v2_0_0.py @@ -61,9 +61,7 @@ class TestApi(unittest.TestCase): super().setUpClass() TestApi.podman = Podman() - TestApi.service = TestApi.podman.open( - "system", "service", "tcp:localhost:8080", "--time=0" - ) + TestApi.service = TestApi.podman.open("system", "service", "tcp:localhost:8080", "--time=0") # give the service some time to be ready... time.sleep(2) @@ -165,11 +163,71 @@ class TestApi(unittest.TestCase): r = requests.get(_url(ctnr("/containers/{}/logs?stdout=true"))) self.assertEqual(r.status_code, 200, r.text) - def test_post_create_compat(self): + # TODO Need to support Docker-py order of network/container creates + def test_post_create_compat_connect(self): """Create network and container then connect to network""" - net = requests.post( - PODMAN_URL + "/v1.40/networks/create", json={"Name": "TestNetwork"} + net_default = requests.post( + PODMAN_URL + "/v1.40/networks/create", json={"Name": "TestDefaultNetwork"} + ) + self.assertEqual(net_default.status_code, 201, net_default.text) + + create = requests.post( + PODMAN_URL + "/v1.40/containers/create?name=postCreate", + json={ + "Cmd": ["top"], + "Image": "alpine:latest", + "NetworkDisabled": False, + # FIXME adding these 2 lines cause: (This is sampled from docker-py) + # "network already exists","message":"container + # 01306e499df5441560d70071a54342611e422a94de20865add50a9565fd79fb9 is already connected to CNI network \"TestDefaultNetwork\": network already exists" + # "HostConfig": {"NetworkMode": "TestDefaultNetwork"}, + # "NetworkingConfig": {"EndpointsConfig": {"TestDefaultNetwork": None}}, + # FIXME These two lines cause: + # CNI network \"TestNetwork\" not found","message":"error configuring network namespace for container 369ddfa7d3211ebf1fbd5ddbff91bd33fa948858cea2985c133d6b6507546dff: CNI network \"TestNetwork\" not found" + # "HostConfig": {"NetworkMode": "TestNetwork"}, + # "NetworkingConfig": {"EndpointsConfig": {"TestNetwork": None}}, + # FIXME no networking defined cause: (note this error is from the container inspect below) + # "internal libpod error","message":"network inspection mismatch: asked to join 2 CNI network(s) [TestDefaultNetwork podman], but have information on 1 network(s): internal libpod error" + }, + ) + self.assertEqual(create.status_code, 201, create.text) + payload = json.loads(create.text) + self.assertIsNotNone(payload["Id"]) + + start = requests.post(PODMAN_URL + f"/v1.40/containers/{payload['Id']}/start") + self.assertEqual(start.status_code, 204, start.text) + + connect = requests.post( + PODMAN_URL + "/v1.40/networks/TestDefaultNetwork/connect", + json={"Container": payload["Id"]}, + ) + self.assertEqual(connect.status_code, 200, connect.text) + self.assertEqual(connect.text, "OK\n") + + inspect = requests.get(f"{PODMAN_URL}/v1.40/containers/{payload['Id']}/json") + self.assertEqual(inspect.status_code, 200, inspect.text) + + payload = json.loads(inspect.text) + self.assertFalse(payload["Config"].get("NetworkDisabled", False)) + + self.assertEqual( + "TestDefaultNetwork", + payload["NetworkSettings"]["Networks"]["TestDefaultNetwork"]["NetworkID"], ) + # TODO restore this to test, when joining multiple networks possible + # self.assertEqual( + # "TestNetwork", + # payload["NetworkSettings"]["Networks"]["TestNetwork"]["NetworkID"], + # ) + # TODO Need to support network aliases + # self.assertIn( + # "test_post_create", + # payload["NetworkSettings"]["Networks"]["TestNetwork"]["Aliases"], + # ) + + def test_post_create_compat(self): + """Create network and connect container during create""" + net = requests.post(PODMAN_URL + "/v1.40/networks/create", json={"Name": "TestNetwork"}) self.assertEqual(net.status_code, 201, net.text) create = requests.post( @@ -178,23 +236,21 @@ class TestApi(unittest.TestCase): "Cmd": ["date"], "Image": "alpine:latest", "NetworkDisabled": False, - "NetworkConfig": { - "EndpointConfig": {"TestNetwork": {"Aliases": ["test_post_create"]}} - }, + "HostConfig": {"NetworkMode": "TestNetwork"}, }, ) self.assertEqual(create.status_code, 201, create.text) payload = json.loads(create.text) self.assertIsNotNone(payload["Id"]) - # This cannot be done until full completion of the network connect - # stack and network disconnect stack are complete - # connect = requests.post( - # PODMAN_URL + "/v1.40/networks/TestNetwork/connect", - # json={"Container": payload["Id"]}, - # ) - # self.assertEqual(connect.status_code, 200, connect.text) - # self.assertEqual(connect.text, "OK\n") + inspect = requests.get(f"{PODMAN_URL}/v1.40/containers/{payload['Id']}/json") + self.assertEqual(inspect.status_code, 200, inspect.text) + payload = json.loads(inspect.text) + self.assertFalse(payload["Config"].get("NetworkDisabled", False)) + self.assertEqual( + "TestNetwork", + payload["NetworkSettings"]["Networks"]["TestNetwork"]["NetworkID"], + ) def test_commit(self): r = requests.post(_url(ctnr("/commit?container={}"))) diff --git a/test/apiv2/rest_api/v1_test_rest_v1_0_0.py b/test/apiv2/rest_api/v1_test_rest_v1_0_0.py index acd6273ef..23528a246 100644 --- a/test/apiv2/rest_api/v1_test_rest_v1_0_0.py +++ b/test/apiv2/rest_api/v1_test_rest_v1_0_0.py @@ -84,9 +84,7 @@ class TestApi(unittest.TestCase): print("\nService Stderr:\n" + stderr.decode("utf-8")) if TestApi.podman.returncode > 0: - sys.stderr.write( - "podman exited with error code {}\n".format(TestApi.podman.returncode) - ) + sys.stderr.write("podman exited with error code {}\n".format(TestApi.podman.returncode)) sys.exit(2) return super().tearDownClass() diff --git a/test/python/docker/__init__.py b/test/python/docker/__init__.py index 316b102f4..a52f0742c 100644 --- a/test/python/docker/__init__.py +++ b/test/python/docker/__init__.py @@ -39,9 +39,7 @@ class Podman(object): self.cmd.append("--root=" + os.path.join(self.anchor_directory, "crio")) self.cmd.append("--runroot=" + os.path.join(self.anchor_directory, "crio-run")) - os.environ["REGISTRIES_CONFIG_PATH"] = os.path.join( - self.anchor_directory, "registry.conf" - ) + os.environ["REGISTRIES_CONFIG_PATH"] = os.path.join(self.anchor_directory, "registry.conf") p = configparser.ConfigParser() p.read_dict( { @@ -53,14 +51,10 @@ class Podman(object): with open(os.environ["REGISTRIES_CONFIG_PATH"], "w") as w: p.write(w) - os.environ["CNI_CONFIG_PATH"] = os.path.join( - self.anchor_directory, "cni", "net.d" - ) + os.environ["CNI_CONFIG_PATH"] = os.path.join(self.anchor_directory, "cni", "net.d") os.makedirs(os.environ["CNI_CONFIG_PATH"], exist_ok=True) self.cmd.append("--cni-config-dir=" + os.environ["CNI_CONFIG_PATH"]) - cni_cfg = os.path.join( - os.environ["CNI_CONFIG_PATH"], "87-podman-bridge.conflist" - ) + cni_cfg = os.path.join(os.environ["CNI_CONFIG_PATH"], "87-podman-bridge.conflist") # json decoded and encoded to ensure legal json buf = json.loads( """ diff --git a/test/python/docker/common.py b/test/python/docker/common.py index e79d64a9b..11f512495 100644 --- a/test/python/docker/common.py +++ b/test/python/docker/common.py @@ -4,9 +4,7 @@ from test.python.docker import constant def run_top_container(client: DockerClient): - c = client.containers.create( - constant.ALPINE, command="top", detach=True, tty=True, name="top" - ) + c = client.containers.create(constant.ALPINE, command="top", detach=True, tty=True, name="top") c.start() return c.id diff --git a/test/python/docker/test_images.py b/test/python/docker/test_images.py index 7ef3d708b..1fa4aade9 100644 --- a/test/python/docker/test_images.py +++ b/test/python/docker/test_images.py @@ -78,9 +78,7 @@ class TestImages(unittest.TestCase): self.assertEqual(len(self.client.images.list()), 2) # List images with filter - self.assertEqual( - len(self.client.images.list(filters={"reference": "alpine"})), 1 - ) + self.assertEqual(len(self.client.images.list(filters={"reference": "alpine"})), 1) def test_search_image(self): """Search for image""" -- cgit v1.2.3-54-g00ecf From f38e18959974901eec4b69bc904de910a4459da9 Mon Sep 17 00:00:00 2001 From: Paul Holzinger Date: Mon, 23 Nov 2020 23:32:48 +0100 Subject: Implement shell completion for podman top Signed-off-by: Paul Holzinger --- cmd/podman/common/completion.go | 20 ++++++++++++++++++++ cmd/podman/containers/top.go | 2 +- cmd/podman/pods/top.go | 2 +- 3 files changed, 22 insertions(+), 2 deletions(-) (limited to 'cmd/podman') diff --git a/cmd/podman/common/completion.go b/cmd/podman/common/completion.go index 6723cbf62..9856e46ef 100644 --- a/cmd/podman/common/completion.go +++ b/cmd/podman/common/completion.go @@ -14,6 +14,7 @@ import ( "github.com/containers/podman/v2/pkg/registries" "github.com/containers/podman/v2/pkg/rootless" systemdGen "github.com/containers/podman/v2/pkg/systemd/generate" + "github.com/containers/podman/v2/pkg/util" "github.com/spf13/cobra" ) @@ -476,6 +477,25 @@ func AutocompleteNetworkConnectCmd(cmd *cobra.Command, args []string, toComplete return nil, cobra.ShellCompDirectiveNoFileComp } +// AutocompleteTopCmd - Autocomplete podman top/pod top command args. +func AutocompleteTopCmd(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + latest := cmd.Flags().Lookup("latest") + // only complete containers/pods as first arg if latest is not set + if len(args) == 0 && (latest == nil || !latest.Changed) { + if cmd.Parent().Name() == "pod" { + // need to complete pods since we are using pod top + return getPods(cmd, toComplete, completeDefault) + } + return getContainers(cmd, toComplete, completeDefault) + } + descriptors, err := util.GetContainerPidInformationDescriptors() + if err != nil { + cobra.CompErrorln(err.Error()) + return nil, cobra.ShellCompDirectiveNoFileComp + } + return descriptors, cobra.ShellCompDirectiveNoFileComp +} + // AutocompleteSystemConnections - Autocomplete system connections. func AutocompleteSystemConnections(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { if !validCurrentCmdLine(cmd, args, toComplete) { diff --git a/cmd/podman/containers/top.go b/cmd/podman/containers/top.go index f00b1dce1..e691f527a 100644 --- a/cmd/podman/containers/top.go +++ b/cmd/podman/containers/top.go @@ -30,7 +30,7 @@ var ( Long: topDescription, RunE: top, Args: cobra.ArbitraryArgs, - ValidArgsFunction: common.AutocompleteContainersRunning, + ValidArgsFunction: common.AutocompleteTopCmd, Example: `podman top ctrID podman top --latest podman top ctrID pid seccomp args %C diff --git a/cmd/podman/pods/top.go b/cmd/podman/pods/top.go index 45ef1e7c2..829882080 100644 --- a/cmd/podman/pods/top.go +++ b/cmd/podman/pods/top.go @@ -29,7 +29,7 @@ var ( Long: topDescription, RunE: top, Args: cobra.ArbitraryArgs, - ValidArgsFunction: common.AutocompletePodsRunning, + ValidArgsFunction: common.AutocompleteTopCmd, Example: `podman pod top podID podman pod top --latest podman pod top podID pid seccomp args %C -- cgit v1.2.3-54-g00ecf From 05f7c95568853bb76b7c74899ceff4d0a871062c Mon Sep 17 00:00:00 2001 From: Qi Wang Date: Mon, 23 Nov 2020 16:27:21 -0500 Subject: [CI:DOCS] fix misleading save/load usage Fix the container archive description in podman save/load docs that may lead to misusing the save/load instead of import/export for containers. Signed-off-by: Qi Wang --- cmd/podman/images/load.go | 2 +- docs/source/markdown/podman-export.1.md | 2 ++ docs/source/markdown/podman-import.1.md | 1 + docs/source/markdown/podman-load.1.md | 3 ++- docs/source/markdown/podman-save.1.md | 4 +++- docs/source/markdown/podman.1.md | 4 ++-- 6 files changed, 11 insertions(+), 5 deletions(-) (limited to 'cmd/podman') diff --git a/cmd/podman/images/load.go b/cmd/podman/images/load.go index a7884f4c5..a24f46781 100644 --- a/cmd/podman/images/load.go +++ b/cmd/podman/images/load.go @@ -23,7 +23,7 @@ var ( loadDescription = "Loads an image from a locally stored archive (tar file) into container storage." loadCommand = &cobra.Command{ Use: "load [options] [NAME[:TAG]]", - Short: "Load an image from container archive", + Short: "Load image(s) from a tar archive", Long: loadDescription, RunE: load, Args: cobra.MaximumNArgs(1), diff --git a/docs/source/markdown/podman-export.1.md b/docs/source/markdown/podman-export.1.md index 647c25770..63989a4db 100644 --- a/docs/source/markdown/podman-export.1.md +++ b/docs/source/markdown/podman-export.1.md @@ -12,6 +12,8 @@ podman\-export - Export a container's filesystem contents as a tar archive **podman export** exports the filesystem of a container and saves it as a tarball on the local machine. **podman export** writes to STDOUT by default and can be redirected to a file using the `--output` flag. +The image of the container exported by **podman export** can be imported by **podman import**. +To export image(s) with parent layers, use **podman save**. Note: `:` is a restricted character and cannot be part of the file name. **podman [GLOBAL OPTIONS]** diff --git a/docs/source/markdown/podman-import.1.md b/docs/source/markdown/podman-import.1.md index ebedf90b4..427c67da4 100644 --- a/docs/source/markdown/podman-import.1.md +++ b/docs/source/markdown/podman-import.1.md @@ -14,6 +14,7 @@ and saves it as a filesystem image. Remote tarballs can be specified using a URL Various image instructions can be configured with the **--change** flag and a commit message can be set using the **--message** flag. **reference**, if present, is a tag to assign to the image. +**podman import** is used for importing from the archive generated by **podman export**, that includes the container's filesystem. To import the archive of image layers created by **podman save**, use **podman load**. Note: `:` is a restricted character and cannot be part of the file name. ## OPTIONS diff --git a/docs/source/markdown/podman-load.1.md b/docs/source/markdown/podman-load.1.md index cdc99fbf2..177709a43 100644 --- a/docs/source/markdown/podman-load.1.md +++ b/docs/source/markdown/podman-load.1.md @@ -1,7 +1,7 @@ % podman-load(1) ## NAME -podman\-load - Load an image from a container image archive into container storage +podman\-load - Load image(s) from a tar archive into container storage ## SYNOPSIS **podman load** [*options*] [*name*[:*tag*]] @@ -11,6 +11,7 @@ podman\-load - Load an image from a container image archive into container stora ## DESCRIPTION **podman load** loads an image from either an **oci-archive** or a **docker-archive** stored on the local machine into container storage. **podman load** reads from stdin by default or a file if the **input** option is set. You can also specify a name for the image if the archive does not contain a named reference, of if you want an additional name for the local image. +**podman load** is used for loading from the archive generated by **podman save**, that includes the image parent layers. To load the archive of container's filesystem created by **podman export**, use **podman import**. The local client further supports loading an **oci-dir** or a **docker-dir** as created with **podman save** (1). diff --git a/docs/source/markdown/podman-save.1.md b/docs/source/markdown/podman-save.1.md index bfb52e7f8..fb79cfd2a 100644 --- a/docs/source/markdown/podman-save.1.md +++ b/docs/source/markdown/podman-save.1.md @@ -1,7 +1,7 @@ % podman-save(1) ## NAME -podman\-save - Save an image to a container archive +podman\-save - Save image(s) to an archive ## SYNOPSIS **podman save** [*options*] *name*[:*tag*] @@ -12,6 +12,8 @@ podman\-save - Save an image to a container archive **podman save** saves an image to either **docker-archive**, **oci-archive**, **oci-dir** (directory with oci manifest type), or **docker-dir** (directory with v2s2 manifest type) on the local machine, default is **docker-archive**. **podman save** writes to STDOUT by default and can be redirected to a file using the **output** flag. The **quiet** flag suppresses the output when set. +**podman save** will save parent layers of the image(s) and the image(s) can be loaded using **podman load**. +To export the containers, use the **podman export**. Note: `:` is a restricted character and cannot be part of the file name. **podman [GLOBAL OPTIONS]** diff --git a/docs/source/markdown/podman.1.md b/docs/source/markdown/podman.1.md index 68a17d26b..7da01d389 100644 --- a/docs/source/markdown/podman.1.md +++ b/docs/source/markdown/podman.1.md @@ -231,7 +231,7 @@ the exit codes follow the `chroot` standard, see below: | [podman-init(1)](podman-init.1.md) | Initialize one or more containers | | [podman-inspect(1)](podman-inspect.1.md) | Display a container, image, volume, network, or pod's configuration. | | [podman-kill(1)](podman-kill.1.md) | Kill the main process in one or more containers. | -| [podman-load(1)](podman-load.1.md) | Load an image from a container image archive into container storage. | +| [podman-load(1)](podman-load.1.md) | Load image(s) from a tar archive into container storage. | | [podman-login(1)](podman-login.1.md) | Login to a container registry. | | [podman-logout(1)](podman-logout.1.md) | Logout of a container registry. | | [podman-logs(1)](podman-logs.1.md) | Display the logs of one or more containers. | @@ -249,7 +249,7 @@ the exit codes follow the `chroot` standard, see below: | [podman-rm(1)](podman-rm.1.md) | Remove one or more containers. | | [podman-rmi(1)](podman-rmi.1.md) | Removes one or more locally stored images. | | [podman-run(1)](podman-run.1.md) | Run a command in a new container. | -| [podman-save(1)](podman-save.1.md) | Save an image to a container archive. | +| [podman-save(1)](podman-save.1.md) | Save image(s) to an archive. | | [podman-search(1)](podman-search.1.md) | Search a registry for an image. | | [podman-start(1)](podman-start.1.md) | Start one or more containers. | | [podman-stats(1)](podman-stats.1.md) | Display a live stream of one or more container's resource usage statistics. | -- cgit v1.2.3-54-g00ecf From f7f85fef7c7086a39e57eb36e875209bb9553f4e Mon Sep 17 00:00:00 2001 From: Paul Holzinger Date: Wed, 25 Nov 2020 16:43:53 +0100 Subject: Do not validate the volume source path in specgen The volume src path should not be validated in specgen since the remote client also uses that part and the path must only exists on the server. This now fails later and only on the server and not the client. I don't think I can add a test for this because the CI runs server and client always on the same vm. Fixes #8473 Signed-off-by: Paul Holzinger --- cmd/podman/common/volumes.go | 4 ++-- pkg/specgen/volumes.go | 5 ++--- 2 files changed, 4 insertions(+), 5 deletions(-) (limited to 'cmd/podman') diff --git a/cmd/podman/common/volumes.go b/cmd/podman/common/volumes.go index 0468f15e0..dfbb7b1b2 100644 --- a/cmd/podman/common/volumes.go +++ b/cmd/podman/common/volumes.go @@ -323,8 +323,8 @@ func getBindMount(args []string) (spec.Mount, error) { if len(kv) == 1 { return newMount, errors.Wrapf(optionArgError, kv[0]) } - if err := parse.ValidateVolumeHostDir(kv[1]); err != nil { - return newMount, err + if len(kv[1]) == 0 { + return newMount, errors.Wrapf(optionArgError, "host directory cannot be empty") } newMount.Source = kv[1] setSource = true diff --git a/pkg/specgen/volumes.go b/pkg/specgen/volumes.go index 1178f9960..41efee3b3 100644 --- a/pkg/specgen/volumes.go +++ b/pkg/specgen/volumes.go @@ -84,11 +84,10 @@ func GenVolumeMounts(volumeFlag []string) (map[string]spec.Mount, map[string]*Na return nil, nil, nil, err } } - // Do not check source dir for anonymous volumes if len(splitVol) > 1 { - if err := parse.ValidateVolumeHostDir(src); err != nil { - return nil, nil, nil, err + if len(src) == 0 { + return nil, nil, nil, errors.New("host directory cannot be empty") } } if err := parse.ValidateVolumeCtrDir(dest); err != nil { -- cgit v1.2.3-54-g00ecf