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 --- test/e2e/network_test.go | 55 ++++++++++++++++++++++++++---------------------- 1 file changed, 30 insertions(+), 25 deletions(-) (limited to 'test') diff --git a/test/e2e/network_test.go b/test/e2e/network_test.go index b010010f0..9f2fb4459 100644 --- a/test/e2e/network_test.go +++ b/test/e2e/network_test.go @@ -76,31 +76,36 @@ var _ = Describe("Podman network", func() { Expect(session.LineInOutputContains(name)).To(BeFalse()) }) - It("podman network rm no args", func() { - session := podmanTest.Podman([]string{"network", "rm"}) - session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).ToNot(BeZero()) - }) - - It("podman network rm", func() { - SkipIfRootless("FIXME: This one is definitely broken in rootless mode") - name, path := generateNetworkConfig(podmanTest) - defer removeConf(path) - - session := podmanTest.Podman([]string{"network", "ls", "--quiet"}) - session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Equal(0)) - Expect(session.LineInOutputContains(name)).To(BeTrue()) - - rm := podmanTest.Podman([]string{"network", "rm", name}) - rm.WaitWithDefaultTimeout() - Expect(rm.ExitCode()).To(BeZero()) - - results := podmanTest.Podman([]string{"network", "ls", "--quiet"}) - results.WaitWithDefaultTimeout() - Expect(results.ExitCode()).To(Equal(0)) - Expect(results.LineInOutputContains(name)).To(BeFalse()) - }) + rm_func := func(rm string) { + It(fmt.Sprintf("podman network %s no args", rm), func() { + session := podmanTest.Podman([]string{"network", rm}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).ToNot(BeZero()) + + }) + + It(fmt.Sprintf("podman network %s", rm), func() { + name, path := generateNetworkConfig(podmanTest) + defer removeConf(path) + + session := podmanTest.Podman([]string{"network", "ls", "--quiet"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + Expect(session.LineInOutputContains(name)).To(BeTrue()) + + rm := podmanTest.Podman([]string{"network", rm, name}) + rm.WaitWithDefaultTimeout() + Expect(rm.ExitCode()).To(BeZero()) + + results := podmanTest.Podman([]string{"network", "ls", "--quiet"}) + results.WaitWithDefaultTimeout() + Expect(results.ExitCode()).To(Equal(0)) + Expect(results.LineInOutputContains(name)).To(BeFalse()) + }) + } + + rm_func("rm") + rm_func("remove") It("podman network inspect no args", func() { session := podmanTest.Podman([]string{"network", "inspect"}) -- 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 'test') 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 'test') 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 'test') 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 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 'test') 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 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 'test') 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 1ddb19bc8e5ba036a3539fe63c6b9fb241eea099 Mon Sep 17 00:00:00 2001 From: baude Date: Mon, 23 Nov 2020 12:25:29 -0600 Subject: update container status with new results a bug was being caused by the fact that the container network results were not being updated properly. given that jhon is on PTO, this PR will replace #8362 Signed-off-by: baude --- libpod/networking_linux.go | 5 +++-- test/python/docker/__init__.py | 2 +- test/python/docker/test_containers.py | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) (limited to 'test') diff --git a/libpod/networking_linux.go b/libpod/networking_linux.go index baa11935d..4e7ffaf81 100644 --- a/libpod/networking_linux.go +++ b/libpod/networking_linux.go @@ -1144,8 +1144,8 @@ func (c *Container) NetworkConnect(nameOrID, netName string, aliases []string) e // build a list of network names so we can sort and // get the new name's index var networkNames []string - for netName := range networks { - networkNames = append(networkNames, netName) + for name := range networks { + networkNames = append(networkNames, name) } networkNames = append(networkNames, netName) // sort @@ -1157,6 +1157,7 @@ func (c *Container) NetworkConnect(nameOrID, netName string, aliases []string) e // populate network status copy(networkStatus[index+1:], networkStatus[index:]) networkStatus[index] = networkResults[0] + c.state.NetworkStatus = networkStatus } c.newNetworkEvent(events.NetworkConnect, netName) return c.save() diff --git a/test/python/docker/__init__.py b/test/python/docker/__init__.py index a52f0742c..351834316 100644 --- a/test/python/docker/__init__.py +++ b/test/python/docker/__init__.py @@ -60,7 +60,7 @@ class Podman(object): """ { "cniVersion": "0.3.0", - "name": "podman", + "name": "default", "plugins": [{ "type": "bridge", "bridge": "cni0", diff --git a/test/python/docker/test_containers.py b/test/python/docker/test_containers.py index 0fd419d9d..20d8417c3 100644 --- a/test/python/docker/test_containers.py +++ b/test/python/docker/test_containers.py @@ -87,7 +87,7 @@ class TestContainers(unittest.TestCase): self.assertEqual(len(containers), 2) def test_stop_container(self): - top = self.client.containers.get("top") + top = self.client.containers.get(TestContainers.topContainerId) self.assertEqual(top.status, "running") # Stop a running container and validate the state -- cgit v1.2.3-54-g00ecf From 9602e290de75bdd7fb5b42f6d36069dd19271735 Mon Sep 17 00:00:00 2001 From: Paul Holzinger Date: Tue, 24 Nov 2020 15:09:20 +0100 Subject: Fix custom mac address with a custom cni network The cni plugin `tuning` is required to set a custom mac address. This plugin is configured in the default cni config file which is packaged with podman but was not included the generated config form `podman network create`. Fixes #8385 Signed-off-by: Paul Holzinger --- libpod/network/config.go | 10 ++++++++++ libpod/network/create.go | 1 + libpod/network/netconflist.go | 7 +++++++ test/e2e/create_staticmac_test.go | 18 ++++++++++++++++++ 4 files changed, 36 insertions(+) (limited to 'test') diff --git a/libpod/network/config.go b/libpod/network/config.go index ce8a4446c..ce351129e 100644 --- a/libpod/network/config.go +++ b/libpod/network/config.go @@ -129,6 +129,16 @@ func (f FirewallConfig) Bytes() ([]byte, error) { return json.MarshalIndent(f, "", "\t") } +// TuningConfig describes the tuning plugin +type TuningConfig struct { + PluginType string `json:"type"` +} + +// Bytes outputs the configuration as []byte +func (f TuningConfig) Bytes() ([]byte, error) { + return json.MarshalIndent(f, "", "\t") +} + // DNSNameConfig describes the dns container name resolution plugin config type DNSNameConfig struct { PluginType string `json:"type"` diff --git a/libpod/network/create.go b/libpod/network/create.go index 387f4fcd3..7e4fc574a 100644 --- a/libpod/network/create.go +++ b/libpod/network/create.go @@ -176,6 +176,7 @@ func createBridge(name string, options entities.NetworkCreateOptions, runtimeCon plugins = append(plugins, bridge) plugins = append(plugins, NewPortMapPlugin()) plugins = append(plugins, NewFirewallPlugin()) + plugins = append(plugins, NewTuningPlugin()) // if we find the dnsname plugin or are rootless, we add configuration for it // the rootless-cni-infra container has the dnsname plugin always installed if (HasDNSNamePlugin(runtimeConfig.Network.CNIPluginDirs) || rootless.IsRootless()) && !options.DisableDNS { diff --git a/libpod/network/netconflist.go b/libpod/network/netconflist.go index 111f1715c..ee9adce14 100644 --- a/libpod/network/netconflist.go +++ b/libpod/network/netconflist.go @@ -119,6 +119,13 @@ func NewFirewallPlugin() FirewallConfig { } } +// NewTuningPlugin creates a generic tuning section +func NewTuningPlugin() TuningConfig { + return TuningConfig{ + PluginType: "tuning", + } +} + // NewDNSNamePlugin creates the dnsname config with a given // domainname func NewDNSNamePlugin(domainName string) DNSNameConfig { diff --git a/test/e2e/create_staticmac_test.go b/test/e2e/create_staticmac_test.go index adffdc1ca..1ac431da2 100644 --- a/test/e2e/create_staticmac_test.go +++ b/test/e2e/create_staticmac_test.go @@ -5,6 +5,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" ) @@ -45,4 +46,21 @@ var _ = Describe("Podman run with --mac-address flag", func() { Expect(result.OutputToString()).To(ContainSubstring("92:d0:c6:0a:29:34")) } }) + + It("Podman run --mac-address with custom network", func() { + net := "n1" + stringid.GenerateNonCryptoID() + session := podmanTest.Podman([]string{"network", "create", net}) + session.WaitWithDefaultTimeout() + defer podmanTest.removeCNINetwork(net) + Expect(session.ExitCode()).To(BeZero()) + + result := podmanTest.Podman([]string{"run", "--network", net, "--mac-address", "92:d0:c6:00:29:34", ALPINE, "ip", "addr"}) + result.WaitWithDefaultTimeout() + if rootless.IsRootless() { + Expect(result.ExitCode()).To(Equal(125)) + } else { + Expect(result.ExitCode()).To(Equal(0)) + Expect(result.OutputToString()).To(ContainSubstring("92:d0:c6:00:29:34")) + } + }) }) -- cgit v1.2.3-54-g00ecf From 2f7bca0685c611d118db98f4c1a5f06370a9e286 Mon Sep 17 00:00:00 2001 From: Paul Holzinger Date: Thu, 26 Nov 2020 17:15:06 +0100 Subject: Fix problems with network remove First, make sure we are only trying to remove the network interface if we are root. Second, if we cannot get the interface name (e.g macvlan config) then we should not fail. Just remove the config file. Signed-off-by: Paul Holzinger --- libpod/network/files.go | 5 ++++- libpod/network/network.go | 28 +++++++++++++++++----------- test/e2e/network_test.go | 11 +++++++++++ 3 files changed, 32 insertions(+), 12 deletions(-) (limited to 'test') diff --git a/libpod/network/files.go b/libpod/network/files.go index 846e5c62d..7f1e3ee18 100644 --- a/libpod/network/files.go +++ b/libpod/network/files.go @@ -14,6 +14,9 @@ import ( "github.com/pkg/errors" ) +// ErrNoSuchNetworkInterface indicates that no network interface exists +var ErrNoSuchNetworkInterface = errors.New("unable to find interface name for network") + // GetCNIConfDir get CNI configuration directory func GetCNIConfDir(configArg *config.Config) string { if len(configArg.Network.NetworkConfigDir) < 1 { @@ -142,7 +145,7 @@ func GetInterfaceNameFromConfig(path string) (string, error) { } } if len(name) == 0 { - return "", errors.New("unable to find interface name for network") + return "", ErrNoSuchNetworkInterface } return name, nil } diff --git a/libpod/network/network.go b/libpod/network/network.go index 7327a1a7d..0febb52f6 100644 --- a/libpod/network/network.go +++ b/libpod/network/network.go @@ -10,6 +10,7 @@ import ( "github.com/containernetworking/plugins/plugins/ipam/host-local/backend/allocator" "github.com/containers/common/pkg/config" "github.com/containers/podman/v2/libpod/define" + "github.com/containers/podman/v2/pkg/rootless" "github.com/containers/podman/v2/pkg/util" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -181,21 +182,26 @@ func RemoveNetwork(config *config.Config, name string) error { // Before we delete the configuration file, we need to make sure we can read and parse // it to get the network interface name so we can remove that too interfaceName, err := GetInterfaceNameFromConfig(cniPath) - if err != nil { - return errors.Wrapf(err, "failed to find network interface name in %q", cniPath) - } - liveNetworkNames, err := GetLiveNetworkNames() - if err != nil { - return errors.Wrapf(err, "failed to get live network names") - } - if util.StringInSlice(interfaceName, liveNetworkNames) { - if err := RemoveInterface(interfaceName); err != nil { - return errors.Wrapf(err, "failed to delete the network interface %q", interfaceName) + if err == nil { + // Don't try to remove the network interface if we are not root + if !rootless.IsRootless() { + liveNetworkNames, err := GetLiveNetworkNames() + if err != nil { + return errors.Wrapf(err, "failed to get live network names") + } + if util.StringInSlice(interfaceName, liveNetworkNames) { + if err := RemoveInterface(interfaceName); err != nil { + return errors.Wrapf(err, "failed to delete the network interface %q", interfaceName) + } + } } + } else if err != ErrNoSuchNetworkInterface { + // Don't error if we couldn't find the network interface name + return err } // Remove the configuration file if err := os.Remove(cniPath); err != nil { - return errors.Wrapf(err, "failed to remove network configuration file %q", cniPath) + return errors.Wrap(err, "failed to remove network configuration") } return nil } diff --git a/test/e2e/network_test.go b/test/e2e/network_test.go index 139a90ac7..adcf74f7e 100644 --- a/test/e2e/network_test.go +++ b/test/e2e/network_test.go @@ -499,4 +499,15 @@ var _ = Describe("Podman network", func() { exec.WaitWithDefaultTimeout() Expect(exec.ExitCode()).To(BeZero()) }) + + It("podman network create/remove macvlan", func() { + net := "macvlan" + stringid.GenerateNonCryptoID() + nc := podmanTest.Podman([]string{"network", "create", "--macvlan", "lo", net}) + nc.WaitWithDefaultTimeout() + Expect(nc.ExitCode()).To(Equal(0)) + + nc = podmanTest.Podman([]string{"network", "rm", net}) + nc.WaitWithDefaultTimeout() + Expect(nc.ExitCode()).To(Equal(0)) + }) }) -- cgit v1.2.3-54-g00ecf From b84304da5e720df11ebb2093c68a6f7e8a684b34 Mon Sep 17 00:00:00 2001 From: Alban Bedel Date: Wed, 18 Nov 2020 20:42:37 +0100 Subject: Prepare support in kube play for other volume types than hostPath Replace the simple map of names to paths with a map of names to a struct to allow passing more parameters. Also move the code to parse the volumes to its own file to avoid making the playKubePod() function overly complex. Finally rework the kube volumes test to also be ready to support more volume types. Signed-off-by: Alban Bedel --- pkg/domain/infra/abi/play.go | 67 +-------------------- pkg/specgen/generate/kube/kube.go | 31 +++++----- pkg/specgen/generate/kube/volume.go | 113 ++++++++++++++++++++++++++++++++++++ test/e2e/play_kube_test.go | 48 +++++++++------ 4 files changed, 163 insertions(+), 96 deletions(-) create mode 100644 pkg/specgen/generate/kube/volume.go (limited to 'test') diff --git a/pkg/domain/infra/abi/play.go b/pkg/domain/infra/abi/play.go index 4bcc6469c..3aeb6a2ee 100644 --- a/pkg/domain/infra/abi/play.go +++ b/pkg/domain/infra/abi/play.go @@ -8,7 +8,6 @@ import ( "os" "strings" - "github.com/containers/buildah/pkg/parse" "github.com/containers/image/v5/types" "github.com/containers/podman/v2/libpod" "github.com/containers/podman/v2/libpod/image" @@ -24,13 +23,6 @@ import ( v1 "k8s.io/api/core/v1" ) -const ( - // https://kubernetes.io/docs/concepts/storage/volumes/#hostpath - kubeDirectoryPermission = 0755 - // https://kubernetes.io/docs/concepts/storage/volumes/#hostpath - kubeFilePermission = 0644 -) - func (ic *ContainerEngine) PlayKube(ctx context.Context, path string, options entities.PlayKubeOptions) (*entities.PlayKubeReport, error) { var ( kubeObject v1.ObjectReference @@ -168,62 +160,9 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY DockerInsecureSkipTLSVerify: options.SkipTLSVerify, } - // map from name to mount point - volumes := make(map[string]string) - for _, volume := range podYAML.Spec.Volumes { - hostPath := volume.VolumeSource.HostPath - if hostPath == nil { - return nil, errors.Errorf("HostPath is currently the only supported VolumeSource") - } - if hostPath.Type != nil { - switch *hostPath.Type { - case v1.HostPathDirectoryOrCreate: - if _, err := os.Stat(hostPath.Path); os.IsNotExist(err) { - if err := os.Mkdir(hostPath.Path, kubeDirectoryPermission); err != nil { - return nil, err - } - } - // Label a newly created volume - if err := libpod.LabelVolumePath(hostPath.Path); err != nil { - return nil, errors.Wrapf(err, "error giving %s a label", hostPath.Path) - } - case v1.HostPathFileOrCreate: - if _, err := os.Stat(hostPath.Path); os.IsNotExist(err) { - f, err := os.OpenFile(hostPath.Path, os.O_RDONLY|os.O_CREATE, kubeFilePermission) - if err != nil { - return nil, errors.Wrap(err, "error creating HostPath") - } - if err := f.Close(); err != nil { - logrus.Warnf("Error in closing newly created HostPath file: %v", err) - } - } - // unconditionally label a newly created volume - if err := libpod.LabelVolumePath(hostPath.Path); err != nil { - return nil, errors.Wrapf(err, "error giving %s a label", hostPath.Path) - } - case v1.HostPathSocket: - st, err := os.Stat(hostPath.Path) - if err != nil { - return nil, errors.Wrap(err, "error checking HostPathSocket") - } - if st.Mode()&os.ModeSocket != os.ModeSocket { - return nil, errors.Errorf("error checking HostPathSocket: path %s is not a socket", hostPath.Path) - } - - case v1.HostPathDirectory: - case v1.HostPathFile: - case v1.HostPathUnset: - // do nothing here because we will verify the path exists in validateVolumeHostDir - break - default: - return nil, errors.Errorf("Invalid HostPath type %v", hostPath.Type) - } - } - - if err := parse.ValidateVolumeHostDir(hostPath.Path); err != nil { - return nil, errors.Wrapf(err, "error in parsing HostPath in YAML") - } - volumes[volume.Name] = hostPath.Path + volumes, err := kube.InitializeVolumes(podYAML.Spec.Volumes) + if err != nil { + return nil, err } seccompPaths, err := kube.InitializeSeccompPaths(podYAML.ObjectMeta.Annotations, options.SeccompProfileRoot) diff --git a/pkg/specgen/generate/kube/kube.go b/pkg/specgen/generate/kube/kube.go index e1202956c..aee30b652 100644 --- a/pkg/specgen/generate/kube/kube.go +++ b/pkg/specgen/generate/kube/kube.go @@ -47,7 +47,7 @@ func ToPodGen(ctx context.Context, podName string, podYAML *v1.PodTemplateSpec) return p, nil } -func ToSpecGen(ctx context.Context, containerYAML v1.Container, iid string, newImage *image.Image, volumes map[string]string, podID, podName, infraID string, configMaps []v1.ConfigMap, seccompPaths *KubeSeccompPaths, restartPolicy string) (*specgen.SpecGenerator, error) { +func ToSpecGen(ctx context.Context, containerYAML v1.Container, iid string, newImage *image.Image, volumes map[string]*KubeVolume, podID, podName, infraID string, configMaps []v1.ConfigMap, seccompPaths *KubeSeccompPaths, restartPolicy string) (*specgen.SpecGenerator, error) { s := specgen.NewSpecGenerator(iid, false) // podName should be non-empty for Deployment objects to be able to create @@ -163,22 +163,27 @@ func ToSpecGen(ctx context.Context, containerYAML v1.Container, iid string, newI s.Env = envs for _, volume := range containerYAML.VolumeMounts { - hostPath, exists := volumes[volume.Name] + volumeSource, exists := volumes[volume.Name] if !exists { return nil, errors.Errorf("Volume mount %s specified for container but not configured in volumes", volume.Name) } - if err := parse.ValidateVolumeCtrDir(volume.MountPath); err != nil { - return nil, errors.Wrapf(err, "error in parsing MountPath") - } - mount := spec.Mount{ - Destination: volume.MountPath, - Source: hostPath, - Type: "bind", - } - if volume.ReadOnly { - mount.Options = []string{"ro"} + switch volumeSource.Type { + case KubeVolumeTypeBindMount: + if err := parse.ValidateVolumeCtrDir(volume.MountPath); err != nil { + return nil, errors.Wrapf(err, "error in parsing MountPath") + } + mount := spec.Mount{ + Destination: volume.MountPath, + Source: volumeSource.Source, + Type: "bind", + } + if volume.ReadOnly { + mount.Options = []string{"ro"} + } + s.Mounts = append(s.Mounts, mount) + default: + return nil, errors.Errorf("Unsupported volume source type") } - s.Mounts = append(s.Mounts, mount) } s.RestartPolicy = restartPolicy diff --git a/pkg/specgen/generate/kube/volume.go b/pkg/specgen/generate/kube/volume.go new file mode 100644 index 000000000..b24583e8e --- /dev/null +++ b/pkg/specgen/generate/kube/volume.go @@ -0,0 +1,113 @@ +package kube + +import ( + "os" + + "github.com/containers/buildah/pkg/parse" + "github.com/containers/podman/v2/libpod" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" + v1 "k8s.io/api/core/v1" +) + +const ( + // https://kubernetes.io/docs/concepts/storage/volumes/#hostpath + kubeDirectoryPermission = 0755 + // https://kubernetes.io/docs/concepts/storage/volumes/#hostpath + kubeFilePermission = 0644 +) + +type KubeVolumeType int + +const ( + KubeVolumeTypeBindMount KubeVolumeType = iota +) + +type KubeVolume struct { + // Type of volume to create + Type KubeVolumeType + // Path for bind mount + Source string +} + +// Create a KubeVolume from an HostPathVolumeSource +func VolumeFromHostPath(hostPath *v1.HostPathVolumeSource) (*KubeVolume, error) { + if hostPath.Type != nil { + switch *hostPath.Type { + case v1.HostPathDirectoryOrCreate: + if _, err := os.Stat(hostPath.Path); os.IsNotExist(err) { + if err := os.Mkdir(hostPath.Path, kubeDirectoryPermission); err != nil { + return nil, err + } + } + // Label a newly created volume + if err := libpod.LabelVolumePath(hostPath.Path); err != nil { + return nil, errors.Wrapf(err, "error giving %s a label", hostPath.Path) + } + case v1.HostPathFileOrCreate: + if _, err := os.Stat(hostPath.Path); os.IsNotExist(err) { + f, err := os.OpenFile(hostPath.Path, os.O_RDONLY|os.O_CREATE, kubeFilePermission) + if err != nil { + return nil, errors.Wrap(err, "error creating HostPath") + } + if err := f.Close(); err != nil { + logrus.Warnf("Error in closing newly created HostPath file: %v", err) + } + } + // unconditionally label a newly created volume + if err := libpod.LabelVolumePath(hostPath.Path); err != nil { + return nil, errors.Wrapf(err, "error giving %s a label", hostPath.Path) + } + case v1.HostPathSocket: + st, err := os.Stat(hostPath.Path) + if err != nil { + return nil, errors.Wrap(err, "error checking HostPathSocket") + } + if st.Mode()&os.ModeSocket != os.ModeSocket { + return nil, errors.Errorf("error checking HostPathSocket: path %s is not a socket", hostPath.Path) + } + + case v1.HostPathDirectory: + case v1.HostPathFile: + case v1.HostPathUnset: + // do nothing here because we will verify the path exists in validateVolumeHostDir + break + default: + return nil, errors.Errorf("Invalid HostPath type %v", hostPath.Type) + } + } + + if err := parse.ValidateVolumeHostDir(hostPath.Path); err != nil { + return nil, errors.Wrapf(err, "error in parsing HostPath in YAML") + } + + return &KubeVolume{ + Type: KubeVolumeTypeBindMount, + Source: hostPath.Path, + }, nil +} + +// Create a KubeVolume from one of the supported VolumeSource +func VolumeFromSource(volumeSource v1.VolumeSource) (*KubeVolume, error) { + if volumeSource.HostPath != nil { + return VolumeFromHostPath(volumeSource.HostPath) + } else { + return nil, errors.Errorf("HostPath is currently the only supported VolumeSource") + } +} + +// Create a map of volume name to KubeVolume +func InitializeVolumes(specVolumes []v1.Volume) (map[string]*KubeVolume, error) { + volumes := make(map[string]*KubeVolume) + + for _, specVolume := range specVolumes { + volume, err := VolumeFromSource(specVolume.VolumeSource) + if err != nil { + return nil, err + } + + volumes[specVolume.Name] = volume + } + + return volumes, nil +} diff --git a/test/e2e/play_kube_test.go b/test/e2e/play_kube_test.go index 92e4544f9..eb82b3862 100644 --- a/test/e2e/play_kube_test.go +++ b/test/e2e/play_kube_test.go @@ -164,9 +164,11 @@ spec: volumes: {{ range . }} - name: {{ .Name }} + {{- if (eq .VolumeType "HostPath") }} hostPath: - path: {{ .Path }} - type: {{ .Type }} + path: {{ .HostPath.Path }} + type: {{ .HostPath.Type }} + {{- end }} {{ end }} {{ end }} status: {} @@ -692,19 +694,27 @@ func getCtrNameInPod(pod *Pod) string { return fmt.Sprintf("%s-%s", pod.Name, defaultCtrName) } -type Volume struct { - Name string +type HostPath struct { Path string Type string } -// getVolume takes a type and a location for a volume -// giving it a default name of volName -func getVolume(vType, vPath string) *Volume { +type Volume struct { + VolumeType string + Name string + HostPath +} + +// getHostPathVolume takes a type and a location for a HostPath +// volume giving it a default name of volName +func getHostPathVolume(vType, vPath string) *Volume { return &Volume{ - Name: defaultVolName, - Path: vPath, - Type: vType, + VolumeType: "HostPath", + Name: defaultVolName, + HostPath: HostPath{ + Path: vPath, + Type: vType, + }, } } @@ -1257,7 +1267,7 @@ spec: It("podman play kube test with non-existent empty HostPath type volume", func() { hostPathLocation := filepath.Join(tempdir, "file") - pod := getPod(withVolume(getVolume(`""`, hostPathLocation))) + pod := getPod(withVolume(getHostPathVolume(`""`, hostPathLocation))) err := generateKubeYaml("pod", pod, kubeYaml) Expect(err).To(BeNil()) @@ -1272,7 +1282,7 @@ spec: Expect(err).To(BeNil()) f.Close() - pod := getPod(withVolume(getVolume(`""`, hostPathLocation))) + pod := getPod(withVolume(getHostPathVolume(`""`, hostPathLocation))) err = generateKubeYaml("pod", pod, kubeYaml) Expect(err).To(BeNil()) @@ -1284,7 +1294,7 @@ spec: It("podman play kube test with non-existent File HostPath type volume", func() { hostPathLocation := filepath.Join(tempdir, "file") - pod := getPod(withVolume(getVolume("File", hostPathLocation))) + pod := getPod(withVolume(getHostPathVolume("File", hostPathLocation))) err := generateKubeYaml("pod", pod, kubeYaml) Expect(err).To(BeNil()) @@ -1299,7 +1309,7 @@ spec: Expect(err).To(BeNil()) f.Close() - pod := getPod(withVolume(getVolume("File", hostPathLocation))) + pod := getPod(withVolume(getHostPathVolume("File", hostPathLocation))) err = generateKubeYaml("pod", pod, kubeYaml) Expect(err).To(BeNil()) @@ -1311,7 +1321,7 @@ spec: It("podman play kube test with FileOrCreate HostPath type volume", func() { hostPathLocation := filepath.Join(tempdir, "file") - pod := getPod(withVolume(getVolume("FileOrCreate", hostPathLocation))) + pod := getPod(withVolume(getHostPathVolume("FileOrCreate", hostPathLocation))) err := generateKubeYaml("pod", pod, kubeYaml) Expect(err).To(BeNil()) @@ -1327,7 +1337,7 @@ spec: It("podman play kube test with DirectoryOrCreate HostPath type volume", func() { hostPathLocation := filepath.Join(tempdir, "file") - pod := getPod(withVolume(getVolume("DirectoryOrCreate", hostPathLocation))) + pod := getPod(withVolume(getHostPathVolume("DirectoryOrCreate", hostPathLocation))) err := generateKubeYaml("pod", pod, kubeYaml) Expect(err).To(BeNil()) @@ -1347,7 +1357,7 @@ spec: Expect(err).To(BeNil()) f.Close() - pod := getPod(withVolume(getVolume("Socket", hostPathLocation))) + pod := getPod(withVolume(getHostPathVolume("Socket", hostPathLocation))) err = generateKubeYaml("pod", pod, kubeYaml) Expect(err).To(BeNil()) @@ -1356,14 +1366,14 @@ spec: Expect(kube.ExitCode()).NotTo(Equal(0)) }) - It("podman play kube test with read only volume", func() { + It("podman play kube test with read only HostPath volume", func() { hostPathLocation := filepath.Join(tempdir, "file") f, err := os.Create(hostPathLocation) Expect(err).To(BeNil()) f.Close() ctr := getCtr(withVolumeMount(hostPathLocation, true), withImage(BB)) - pod := getPod(withVolume(getVolume("File", hostPathLocation)), withCtr(ctr)) + pod := getPod(withVolume(getHostPathVolume("File", hostPathLocation)), withCtr(ctr)) err = generateKubeYaml("pod", pod, kubeYaml) Expect(err).To(BeNil()) -- cgit v1.2.3-54-g00ecf From 66944baad657aded4aba07fac28ea590e120afac Mon Sep 17 00:00:00 2001 From: Alban Bedel Date: Wed, 18 Nov 2020 20:51:59 +0100 Subject: Add support for persistent volume claims in kube files In k8s a persistent volume claim (PVC) allow pods to define a volume by referencing the name of a PVC. The PVC basically contains criterias that k8s then use to select which storage source it will use for the volume. Podman only provide one abtracted storage, the named volumes, and create them if they don't exists yet. So this patch simply use a volume with the name of the PVC. Signed-off-by: Alban Bedel --- pkg/specgen/generate/kube/kube.go | 9 ++++++++ pkg/specgen/generate/kube/volume.go | 15 ++++++++++++-- test/e2e/play_kube_test.go | 41 +++++++++++++++++++++++++++++++++++++ 3 files changed, 63 insertions(+), 2 deletions(-) (limited to 'test') diff --git a/pkg/specgen/generate/kube/kube.go b/pkg/specgen/generate/kube/kube.go index aee30b652..5f72d28bb 100644 --- a/pkg/specgen/generate/kube/kube.go +++ b/pkg/specgen/generate/kube/kube.go @@ -181,6 +181,15 @@ func ToSpecGen(ctx context.Context, containerYAML v1.Container, iid string, newI mount.Options = []string{"ro"} } s.Mounts = append(s.Mounts, mount) + case KubeVolumeTypeNamed: + namedVolume := specgen.NamedVolume{ + Dest: volume.MountPath, + Name: volumeSource.Source, + } + if volume.ReadOnly { + namedVolume.Options = []string{"ro"} + } + s.Volumes = append(s.Volumes, &namedVolume) default: return nil, errors.Errorf("Unsupported volume source type") } diff --git a/pkg/specgen/generate/kube/volume.go b/pkg/specgen/generate/kube/volume.go index b24583e8e..2ef0f4c23 100644 --- a/pkg/specgen/generate/kube/volume.go +++ b/pkg/specgen/generate/kube/volume.go @@ -21,12 +21,13 @@ type KubeVolumeType int const ( KubeVolumeTypeBindMount KubeVolumeType = iota + KubeVolumeTypeNamed KubeVolumeType = iota ) type KubeVolume struct { // Type of volume to create Type KubeVolumeType - // Path for bind mount + // Path for bind mount or volume name for named volume Source string } @@ -87,12 +88,22 @@ func VolumeFromHostPath(hostPath *v1.HostPathVolumeSource) (*KubeVolume, error) }, nil } +// Create a KubeVolume from a PersistentVolumeClaimVolumeSource +func VolumeFromPersistentVolumeClaim(claim *v1.PersistentVolumeClaimVolumeSource) (*KubeVolume, error) { + return &KubeVolume{ + Type: KubeVolumeTypeNamed, + Source: claim.ClaimName, + }, nil +} + // Create a KubeVolume from one of the supported VolumeSource func VolumeFromSource(volumeSource v1.VolumeSource) (*KubeVolume, error) { if volumeSource.HostPath != nil { return VolumeFromHostPath(volumeSource.HostPath) + } else if volumeSource.PersistentVolumeClaim != nil { + return VolumeFromPersistentVolumeClaim(volumeSource.PersistentVolumeClaim) } else { - return nil, errors.Errorf("HostPath is currently the only supported VolumeSource") + return nil, errors.Errorf("HostPath and PersistentVolumeClaim are currently the conly supported VolumeSource") } } diff --git a/test/e2e/play_kube_test.go b/test/e2e/play_kube_test.go index eb82b3862..5ecfdd6b5 100644 --- a/test/e2e/play_kube_test.go +++ b/test/e2e/play_kube_test.go @@ -169,6 +169,10 @@ spec: path: {{ .HostPath.Path }} type: {{ .HostPath.Type }} {{- end }} + {{- if (eq .VolumeType "PersistentVolumeClaim") }} + persistentVolumeClaim: + claimName: {{ .PersistentVolumeClaim.ClaimName }} + {{- end }} {{ end }} {{ end }} status: {} @@ -699,10 +703,15 @@ type HostPath struct { Type string } +type PersistentVolumeClaim struct { + ClaimName string +} + type Volume struct { VolumeType string Name string HostPath + PersistentVolumeClaim } // getHostPathVolume takes a type and a location for a HostPath @@ -718,6 +727,18 @@ func getHostPathVolume(vType, vPath string) *Volume { } } +// getHostPathVolume takes a name for a Persistentvolumeclaim +// volume giving it a default name of volName +func getPersistentVolumeClaimVolume(vName string) *Volume { + return &Volume{ + VolumeType: "PersistentVolumeClaim", + Name: defaultVolName, + PersistentVolumeClaim: PersistentVolumeClaim{ + ClaimName: vName, + }, + } +} + type Env struct { Name string Value string @@ -1389,6 +1410,26 @@ spec: Expect(inspect.OutputToString()).To(ContainSubstring(correct)) }) + It("podman play kube test with PersistentVolumeClaim volume", func() { + volumeName := "namedVolume" + + ctr := getCtr(withVolumeMount("/test", false), withImage(BB)) + pod := getPod(withVolume(getPersistentVolumeClaimVolume(volumeName)), withCtr(ctr)) + err = generateKubeYaml("pod", pod, kubeYaml) + Expect(err).To(BeNil()) + + kube := podmanTest.Podman([]string{"play", "kube", kubeYaml}) + kube.WaitWithDefaultTimeout() + Expect(kube.ExitCode()).To(Equal(0)) + + inspect := podmanTest.Podman([]string{"inspect", getCtrNameInPod(pod), "--format", "{{ (index .Mounts 0).Type }}:{{ (index .Mounts 0).Name }}"}) + inspect.WaitWithDefaultTimeout() + Expect(inspect.ExitCode()).To(Equal(0)) + + correct := fmt.Sprintf("volume:%s", volumeName) + Expect(inspect.OutputToString()).To(Equal(correct)) + }) + It("podman play kube applies labels to pods", func() { var numReplicas int32 = 5 expectedLabelKey := "key1" -- cgit v1.2.3-54-g00ecf From 70fa48837285c8cadc7d5915935259c4cdf5fe1c Mon Sep 17 00:00:00 2001 From: Edward Shen Date: Mon, 30 Nov 2020 01:52:34 -0500 Subject: Add APIv2 tests for kube generate Signed-off-by: Edward Shen --- test/apiv2/25-containersMore.at | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) (limited to 'test') diff --git a/test/apiv2/25-containersMore.at b/test/apiv2/25-containersMore.at index e0e6f7222..4f6b80a5f 100644 --- a/test/apiv2/25-containersMore.at +++ b/test/apiv2/25-containersMore.at @@ -52,4 +52,31 @@ t POST libpod/containers/foo/unmount '' 204 t DELETE libpod/containers/foo?force=true 204 +podman run $IMAGE true + +t GET libpod/containers/json?last=1 200 \ + length=1 \ + .[0].Id~[0-9a-f]\\{64\\} \ + .[0].Image=$IMAGE \ + .[0].Command[0]="true" \ + .[0].State~\\\(exited\\\|stopped\\\) \ + .[0].ExitCode=0 \ + .[0].IsInfra=false + +cid=$(jq -r '.[0].Id' <<<"$output") + +t GET libpod/generate/$cid/kube 200 +like "$output" ".*apiVersion:.*" "Check generated kube yaml - apiVersion" +like "$output" ".*kind:\\sPod.*" "Check generated kube yaml - kind: Pod" +like "$output" ".*metadata:.*" "Check generated kube yaml - metadata" +like "$output" ".*spec:.*" "Check generated kube yaml - spec" + +t GET libpod/generate/$cid/kube?service=true 200 +like "$output" ".*apiVersion:.*" "Check generated kube yaml(service=true) - apiVersion" +like "$output" ".*kind:\\sPod.*" "Check generated kube yaml(service=true) - kind: Pod" +like "$output" ".*metadata:.*" "Check generated kube yaml(service=true) - metadata" +like "$output" ".*spec:.*" "Check generated kube yaml(service=true) - spec" +like "$output" ".*kind:\\sService.*" "Check generated kube yaml(service=true) - kind: Service" + +t DELETE libpod/containers/$cid 204 # vim: filetype=sh -- cgit v1.2.3-54-g00ecf